Magnett.Automation.Core 1.0.0

dotnet add package Magnett.Automation.Core --version 1.0.0
                    
NuGet\Install-Package Magnett.Automation.Core -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Magnett.Automation.Core" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Magnett.Automation.Core" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Magnett.Automation.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Magnett.Automation.Core --version 1.0.0
                    
#r "nuget: Magnett.Automation.Core, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Magnett.Automation.Core@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Magnett.Automation.Core&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Magnett.Automation.Core&version=1.0.0
                    
Install as a Cake Tool

magnett automation

Library designed for create custom workflow, orchestration between components, microservices and other automation utilities, created on dotnet version 6

Introduction

This library has been designed to have an ecosystem of entities oriented to the orchestration of any type of process. From the simplest processes such as calls between several of our classes to more complex processes such as calls to microservices, etc. Always, however, declaratively and looking to save code in terms of tedious and complex nesting of “If” “else”... etc. Structure

In this repository, there will be the core classes of this ecosystem, grouped under the namespace magnett.automation.core. Some of these classes can be used outside the scope of creating a workflow, as they are generic enough to be useful independently.

  • Common
  • Context.
  • StateMachine.
  • Workflows.

Common

In this namespace we found utilities' class used inside the library classes

  • Enumeration
  • CommonNamedKey
  • DictionaryWrapper

Context

The concept context is very generic and is used in several fields. For us, a context will be a common space where values are stored to be shared between several components, being that they are values of the heterogeneous type.

We are, therefore, in front of a key/value system, where the values will be of any type.

Structure

The structure of the context is straightforward, it is formed only by the Context class, which will be our input and retrieval of values class, and the IContextVault interface which will be the definition of the vault where the values are stored.

By default, we will have an implementation of the IContextVault, where it will store the values in memory, but will be open for any other implementation that stores these values in any other way.

We will use the class ContextField to Get and Set values in a context, with this class we can define the type and the name of the class.

Example of how to Get / Set value in a context

var context = Context.Create();
var field   = ContextField<int>.Create("FieldName");

//Set Value
context.Store(field, random.Next(1000));

//Get Value
var value  = context.Value(field);

StateMachine

We have at our disposal several interfaces for the definition of a state machine as well as its execution. The definition of the machine will be separated from the execution of the machine itself, to avoid couplings and clear separation of responsibilities.

Structure

The main interface is IMachine. With this interface, we will have access to the current state, IState interface, and the possibility of transitioning to another state using action codes that generate a transition, ITransaction entity, to another state.

It is not possible to go from one state to another directly, only through a transition, so that we have a model to which states we can go from one in particular.

A state without defined transitions can be given, and this means that the state is terminal. In this way, we can define finite or non-finite machines.

Regarding the runtime part, the definition of a machine will be done from the IMachineDefinition interface, which will be generated from the MachineDefinitionBuilder class.

Example of machine definition code

//Helper class with states enumeration
public class State : Enumeration
{
    public static readonly State Init     = new State(1, nameof(Init));
    public static readonly State Working  = new State(2, nameof(Working));
    public static readonly State Paused   = new State(3, nameof(Paused));
    public static readonly State Finished = new State(4, nameof(Finished));

    private State(int id, string name) : base(id, name)
    {

    }
}

//Helper class with action enumerations
public class Action : Enumeration
{
    public static readonly Action Start    = new Action(1, nameof(Start));
    public static readonly Action Pause    = new Action(2, nameof(Pause));
    public static readonly Action Continue = new Action(3, nameof(Continue));
    public static readonly Action Finish   = new Action(4, nameof(Finish));

    private Action(int id, string name) : base(id, name)
    {

    }
}

//Now we can create a definition
_definition = MachineDefinitionBuilder.Create()

    .InitialState(State.Init)
        .OnAction(Action.Start).ToState(State.Working)
        .Build()

    .AddState(State.Working)
        .OnAction(Action.Pause).ToState(State.Paused)
        .OnAction(Action.Finish).ToState(State.Finished)
        .Build()

    .AddState(State.Paused)
        .OnAction(Action.Continue).ToState(State.Working)
        .Build()

    .AddState(State.Finished)
        .Build()

    .BuildDefinition();

Example of machine creation and usage code.

var machine = Machine
    .Create(SimpleMachineDefinition.GetDefinition());

machine.Dispatch(Action.Start);

var currentState = machine.State;

Workflows

Under this namespace, we will have the necessary classes to define a workflow and execute it. As in the previous section, we will keep the workflow definition separate from the runtime.

Structure

This separation will be done using the IWorkflowDefinition and IWorkflowRunner interfaces.

To encapsulate the definition and execution we have the IFlow interface, this interface also will allow us in the future to build sub-flows, create flows that are encapsulated as a service within more complex applications... etc.

If we think of a basic flow,just and initial node to reset field values, the next node just to calculate to random numbers, and a final node to sum both values, the definition should be something like that.

Example workflow definition code.

  var contextDefinition = ContextDefinition.Create();

  var definition = FlowDefinitionBuilder.Create()
     .WithInitialNode<ResetValue>(NodeName.Reset)
     .OnExitCode(ResetValue.ExitCode.Ok).GoTo(NodeName.SetValue)
     .Build()
         
     .WithNode<SetValue>(NodeName.SetValue)
     .OnExitCode(SetValue.ExitCode.Assigned).GoTo(NodeName.SumValue)
     .Build()
         
     .WithNode<SumValue>(NodeName.SumValue)
     .Build()
         
     .BuildDefinition();

Previously, you have defined some helper classes like ContextDefinition it's just a static class to contain Context field and to avoid duplication and mistakes with name definitions.

internal static class ContextDefinition
{
    public static ContextField<int> FirstDigit  => ContextField<int>.Create("FieldOne");
    public static ContextField<int> SecondDigit => ContextField<int>.Create("FieldTwo");     
    public static ContextField<int> Result => ContextField<int>.Create("FieldResult");
}

We have two node types sync and async, under the INode and INodeAsync interfaces, so we can use nodes as a wrapper of both types of process.

The library provides a base class for each type of node, Node and NodeAsync respectively, so we can implement our custom nodes.

In this example, we have only the sync implementation.

Example Node

internal class ResetValue : Node
{
    #region ExitCodes

    public class ExitCode : Enumeration
    {
        public static readonly ExitCode Ok  = new ExitCode(1, "Ok"); 

        private ExitCode(int id, string name) : base(id, name)
        {
        }
    }
        
    #endregion

    public ResetValue(CommonNamedKey key) : base(key)
    {
            
    }

    public override NodeExit Execute(Context context)
    {
        context.Store(ContextDefinition.FirstDigit, 0);
        context.Store(ContextDefinition.SecondDigit, 0);
        context.Store(ContextDefinition.Result, 0);
            
        return NodeExit.Create(ExitCode.Ok.Name);
    }
}

The inner class ExitCodes is just another helper class, build over Enumeration class with the definition of available exit codes for this node, we use something similar

A runner, to instantiate itself, will need to receive the workflow definition and a context instance that will be used to share information between nodes. Once the runner has been executed, we can retrieve return values from context if there are any.

We have the abstract class FlowRunnerBase so we can implement our custom runners, step to step, distributed, etc.

Example Flow runner

var flowRunner = FlowRunner.Create(definition, Context.Create());

var exit = await flowRunner.Start();

The class flow, as we said before, it's a wrapper for all this process, now has basic functionalities but in future versions will be used as the main class for workflow management.

var definition = SimpleFlowDefinition.GetDefinition();
var context    = Context.Create();

var flow = Flow.Create(FlowRunner.Create(definition, context));

var exit = await flow.Run();

Thanks for reading, and I hope you find this library useful. Feedback is always welcome.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 163 2/2/2025
0.5.1 908 2/4/2022
0.5.0 850 12/31/2021

v.0.5.1
- Initial serious release

v.0.6.0
- Split Runtime from Definition in Workflow declaration
- Add support for net Core 8.

v.1.0.0
- Definition of Workflow does not require an instance, just using generic type
- Total separation between Definition and Runtime
- Minor changes in runtime to increase performance
- Add support for net Core 9.0