SkillAgentSearch skills...

MicroFlow

Lightweight workflow engine

Install / Use

/learn @akarpov89/MicroFlow
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

MicroFlow

Build status

Getting Started

What is MicroFlow?

MicroFlow is a lightweight workflow engine. It allows to build workflows as flowcharts. Every flow is constructed from a limited number of connected nodes.

Features:

  • Code-centric, no XAML
  • Data flow friendly: easy to pass data from one activity to another
  • Integrated dependency injection
  • Flow validation
  • Visualization support

Available node types:

  • activity node represents user-defined action;
  • condition node represents decision (like if-else statement);
  • switch node represents multiway branch (like switch statement);
  • fork-join node represents concurrent activities;
  • block node groups several nodes (like blocks in programming languages).

NuGet Package

MicroFlow is available on NuGet and MyGet.

Target frameworks:

  • .NET 4.0+
  • .NET Core
  • Portable Libraries (net45+win8+wpa81+wp8)

Activities

The user-defined activities should inherit from one of the following base classes

Activity<TResult>

The most generic activity returning the result of type TResult. An implementation must override the method

public abstract Task<Result> Execute();
Activity

The most generic activity without returning value. An implementation must override the method

protected abstract Task ExecuteCore();
SyncActivity<TResult>

The base class of the synchronous activities returning the value of type TResult. An implemenation must override the method:

protected abstract TResult ExecuteActivity();
SyncActivity

The base class of the synchronous activities without returning value. An implemenation must override the method:

protected abstract void ExecuteActivity();
BackgroundActivity<TResult>

Provides the way to execute a function as a separate background task. An implemenation must override the method:

protected abstract TResult ExecuteCore();

BackgroundActivity<TResult> exposes the following properties:

  • CancellationToken - allows the work to be cancelled;
  • Scheduler - schedules the worker task;
  • IsLongRunning - allows to hint TaskScheduler that task will be a long-running operation.
BackgroundActivity

Provides the way to execute a function as a separate background task. An implemenation must override the method:

protected abstract void ExecuteCore();

BackgroundActivity exposes the same properties as BackgroundActivity<TResult>.

IFaultHandlerActivity

The interface of all fault handlers. Every fault handler must provide the following property:

Exception Exception { get; set; }

Nodes

The FlowBuilder class provides the way to create nodes of the flow.

ActivityNode<TActivity>
var node = builder.Activity<SomeActivity>("Node name");

node.ConnectTo(anotherNode)
    .ConnectFaultTo(faultHandler)
    .ConnnectCancellationTo(cancellationHandler);
ConditionNode
var node = builder.Condition("Node name");
node.WithCondition(() => boolExpr);

node.ConnectFalseTo(falseBranchNode)
    .ConnectTrueTo(trueBranchNode);

There is also an alternative syntax that allows to create if-then-else constructs:

var node = builder
    .If("Condition1", () => boolExpr1).Then(node1)
    .ElseIf("Condition2", () => boolExpr2).Then(node2)
    .Else(node3);    

Notice that in this case node is initial ConditionNode (the one with condition description "Condition description").

SwitchNode
var node = builder.SwitchOf<int>("Node name");
node.WithChoice(() => someIntExpression);

node.ConnectCase(0).To(caseHandler1)
    .ConnectCase(1).To(caseHandler2)
    .ConnectCase(42).To(caseHandler3)
    .ConnectDefault(caseHandler4).
ForkJoinNode
var node = builder.ForkJoin("Node name");

var fork1 = node.Fork<SomeForkActivity>("Fork 1 name");
var fork2 = node.Fork<SomeAnotherForkActivity>("Fork 2 name");
var fork3 = node.Fork<SomeActivity>("Fork 3 name");
BlockNode
var node = builder.Block("Optional node name", (block, blockBuilder) =>
{
    var activity1 = blockBuilder.Activity<SomeActivity>();
    var activity2 = blockBuilder.Activity<SomeAnotherActivity>();
    
    activity1.ConnectTo(activity2);
});
Default fault handler

Every activity node should be connected with some specific or default fault handler

builder.WithDefaultFaultHandler<MyFaultHandler>(); 
Default cancellation handler

Every activity node should be connected with some specific or default cancellation handler

builder.WithDefaultCancellationHandler<MyCancellationHandler>();

Data flow

As flow executes data transfers from one activity to another. The MicroFlow has two mechanisms to define the data flow: bindings and variables.

In the examples below we will use the following activities:

public class ReadIntActivity : SyncActivity<int>
{
    protected override int ExecuteActivity()
    {
        return int.Parse(Console.ReadLine());
    }
}

public class SumActivity : SyncActivity<int>
{
    [Required] public int FirstNumber { get; set; }
    [Required] public int SecondNumber { get; set; }
    
    protected override int ExecuteActivity()
    {
        return FirstNumber + SecondNumber;
    }
}
Binding to activity result

In this example we bind properties FirstNumber and SecondNumber to the results of readFirstNumber and readSecondNumber:

var readFirstNumber = builder.Activity<ReadIntActivity>();
var readSecondNumber = builder.Activity<ReadIntActivity>();

var sumTwoNumbers = builder.Activity<SumActivity>();

sumTwoNumbers.Bind(a => a.FirstNumber).ToResultOf(readFirstNumber);
sumTwoNumbers.Bind(a => a.SecondNumber).ToResultOf(readSecondNumber);
Binding to value

In this example we bind FirstNumber to the value 42 and SecondNumber to 5:

var sumTwoNumbers = builder.Activity<SumActivity>();

sumTwoNumbers.Bind(a => a.FirstNumber).To(42);
sumTwoNumbers.Bind(a => a.SecondNumber).To(5);
Binding to expression

In this example we bind FirstNumber to expression using the result of the readFirstNumber and SecondNumber to function call Factorial(5) expression:

var readFirstNumber = builder.Activity<ReadIntActivity>();

var firstNumber = Result<int>.Of(readFirstNumber); // Create result thunk

var sumTwoNumbers = builder.Activity<SumActivity>();

sumTwoNumbers.Bind(a => a.FirstNumber).To(() => firstNumber.Get() + 1);
sumTwoNumbers.Bind(a => a.SecondNumber).To(() => Factorial(5)); 
Using flow variables

The MicroFlow allows to create and use variables scoped to the whole flow or to some specific block:

var globalVariable = builder.Variable<int>();

var block = builder.Block("my block", (thisBlock, blockBuilder) =>
{
    var localVariable = thisBlock.Variable<string>("initial value");
});

It's possible to change the variable value after completion of some activity:

Assign activity result:

 var myVar = builder.Variable<int>();
 
 var readFirstNumber = builder.Activity<ReadIntActivity>();

 myVar.BindToResultOf(readFirstNumber);

Assign some constant value:

var myVar = builder.Variable<bool>();
 
var readFirstNumber = builder.Activity<ReadIntActivity>();

readFirstNumber.OnCompletionAssign(myVar, true);

Update value without using activity result:

var myVar = builder.Variable<int>(42);
 
var readFirstNumber = builder.Activity<ReadIntActivity>();
 
readFirstNumber.OnCompletionUpdate(
    myVar, 
    oldValue => oldValue + 1
);

Update value using activity result:

var myVar = builder.Variable<int>(42);
 
var readFirstNumber = builder.Activity<ReadIntActivity>();
 
readFirstNumber.OnCompletionUpdate(
    myVar, 
    (int oldValue, int result) => oldValue + result
);

Later the current value of a variable can be retrieved via property CurrentValue:

var myVar = builder.Variable<int>();
...
var sumTwoNumbers = builder.Activity<SumActivity>();

sumTwoNumbers.Bind(a => a.FirstNumber)
             .To(() => myVar.CurrentValue);
...

Flow creation

Every flow is a subclass of the Flow abstract class. The Flow base class provides the way to validate and run the the constructed flow:

public ValidationResult Validate();
public Task Run();

In order to create new flow definition it's required to describe the flow structure and give the flow a name:

public class MyFlow : Flow
{
    public override string Name => "My brand new flow";
    
    protected override void Build(FlowBuilder builder)
    {
        // Create and connect nodes
    }
}

The Flow сlass also has several configuration extension points:

  • ConfigureServices method allows to register services required for activities (dependency injection mechanism);
  • ConfigureValidation method allows to add custom flow validators;
  • CreateFlowExecutionLogger method allows to setup execution logging.
Services registration

Configuring services is possible via overriding the method ConfigureServices

protected virtual void ConfigureServices(
    [NotNull]IServiceCollection services
);

Let's say our ReadIntActivity uses IReader service:

public int

Related Skills

View on GitHub
GitHub Stars161
CategoryDevelopment
Updated2mo ago
Forks46

Languages

C#

Security Score

100/100

Audited on Jan 7, 2026

No findings