Cgxarrie.FluStMac 1.0.3

dotnet add package Cgxarrie.FluStMac --version 1.0.3
NuGet\Install-Package Cgxarrie.FluStMac -Version 1.0.3
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="Cgxarrie.FluStMac" Version="1.0.3" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Cgxarrie.FluStMac --version 1.0.3
#r "nuget: Cgxarrie.FluStMac, 1.0.3"
#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.
// Install Cgxarrie.FluStMac as a Cake Addin
#addin nuget:?package=Cgxarrie.FluStMac&version=1.0.3

// Install Cgxarrie.FluStMac as a Cake Tool
#tool nuget:?package=Cgxarrie.FluStMac&version=1.0.3

FluStMac - Fluent State Machine

A Fluent finite state machine which provides an easy to configure and use state mahine base class

Features

  • Fluent addition of transitions
  • Transitions based on current status and requested action
  • Conditional transitions based on current status, requested action, and a condition to be met by the element handled by the state machine.

How to use

  • Declare the collection of possible status to be handled by the machine, of type TStatus
  • Declare the element to be handled by the machine of type T, inheriting StateMachineElement< TStatus>
  • Declare the machine inheriting FluentStateMachine<T, TStatus>
  • Declare transitions in constructor of the machine
  • Transitions are evaluated in the reverse order they are added. The last transition found matching the current request will provide the new status (add first the less restrictive transitions)
  • If the requested action is not permitted in the current Status a ActionNotPermittedException is thrown
  • If no condition can be met to get the next status a TransitionNotFoundException is thorwn

Declaration of transitions

Valid transitions are added to the state machine on constructor via the following fluent command

        WithTransition()
            .From(Initial status) // Starting in this status
            .On(action) // On executing this action
            .When(condition) // and this condition is met (optional)
            .To(target status); // This will be the next satus

Should there be a base state machine and derived, consider declaring the base transitions in a protected method, then call it after child transitions in constructor

public abstract class BaseStateMachine
{
	public BaseStateMachine()
	{
		AddBaseTransition01();
		AddBaseTransition02();
		AddBaseTransition03();
	}
}

public class DerivedStateMachine01()
{
	public DerivedStateMachine01() 
	{
		AddDerivedTransition0101();
		AddDerivedTransition0102();
	}
}

public class DerivedStateMachine02()
{
	public DerivedStateMachine02() 
	{
		AddDerivedTransition0201();
		AddDerivedTransition0202();
	}
}

Example 1 : State-Machine

We will simulate an Invoice workflow, declaring the following statuses

  • Created
  • Waiting for approval
  • Approved
  • Rejected
  • Waiting for signature

the folloing actions

  • Send For Approval
  • Approve
  • Reject
  • Receive signature

and the following use cases

  • In satus Created, On sent for approval, change to Waiting for approval
  • In status Waiting for approval, On Receive signature, stay in Waiting for approval
  • In status Waiting for approval, On Reject, change to Rejected
  • In status Waiting for approval, On Approve,
    • When signature is needed and not received, change to Waiting for signature
    • Else, change to Approved.

Declare Invoice statuses

public enum InvoiceStatus
{
	Created = 0,
    WaitingForApproval = 1,
    Approved = 2,
    Rejected = 3,
    WaitingForSignature = 4
}

Declare Invoice class

public enum Invoice : StateMachineElement<InvoiceStatus>
{
	public Guid Id {get; set;} = Guid.NewGuid();
	public bool NeedsSignature { get; set; } = false;
	
	// flags to change the state of the instance
	public bool HasBeenApproved { get; private set; } = false;
    public bool HasBeenRejected { get; private set; } = false;
    public bool HasBeenSentForApproval { get; private set; } = false;
    public bool HasReceivedSignature { get; private set; } = false;
    
	// Actions
	public void Approve()
    {
	    HasBeenApproved = true;
	}

    public void ReceiveSignature()
    {
	    HasReceivedSignature = true;
	}

    public void Reject()
    {
	    HasBeenRejected = true;
	}

    public void SendForApproval()
    {
	    HasBeenSentForApproval = true;
	}
}

Declare the state machine

public class InvoiceStateMachine : FluentStateMachine<Invoice, InvoiceStatus>
{
    public InvoiceStateMachine(Invoice invoice) : base(invoice, InvoiceStatus.Created)
    {
        WithTransition()
            .From(InvoiceStatus.Created)
            .On(x => x.SendForApproval())
            .To(InvoiceStatus.WaitingForApproval);

        WithTransition()
            .From(InvoiceStatus.WaitingForApproval)
            .On(x => x.ReceiveSignature())
            .To(InvoiceStatus.WaitingForApproval);

        WithTransition()
            .From(InvoiceStatus.WaitingForApproval)
            .On(x => x.Approve())
            .When(x => x.NeedsSignature && x.HasReceivedSignature)
            .To(InvoiceStatus.Approved);

        WithTransition()
            .From(InvoiceStatus.WaitingForApproval)
            .On(x => x.Approve())
            .When(x => x.NeedsSignature && !x.HasReceivedSignature)
            .To(InvoiceStatus.WaitingForSignature);

        WithTransition()
            .From(InvoiceStatus.WaitingForApproval)
            .On(x => x.Approve())
            .When(x => !x.NeedsSignature)
            .To(InvoiceStatus.Approved);

        WithTransition()
            .From(InvoiceStatus.WaitingForApproval)
            .On(x => x.Reject())
            .To(InvoiceStatus.Rejected);

        WithTransition()
            .From(InvoiceStatus.WaitingForSignature)
            .On(x => x.ReceiveSignature())
            .To(InvoiceStatus.Approved);
    }
}

Using the state machine

public class InvoiceService
{
	public async Task Approve(Guid id)
	{
		var invoice = await GetInvoiceFromDataSource(id);
		var sm = new InvoiceStateMachine(invoice);
		sm.Do(x => x.Approve);
	}

	public async Task Reject(Guid id)
	{
		var invoice = await GetInvoiceFromDataSource(id);
		var sm = new InvoiceStateMachine(invoice);
		sm.Do(x => x.Reject);
	}

}

Example 2: Inherited State-Machine

The following example ilustrates how to use different state-machines for different user roles.

Imagine the same Invoice Statuses List, same Invoice class, and same allowed transitions. But with the folloging restrictions:

  • Only SalesAgent can Send For approval
  • Only SalesManager can Approve invoice
  • Only SalesManager can Reject invoice
  • All can Receive Signature

First, we create the base state machine, with the transitions permitted for all

public abstract class InvoiceStateMachine : FluentStateMachine<Invoice, InvoiceStatus>
{
    public InvoiceStateMachine(Invoice invoice) : base(invoice, InvoiceStatus.Created)
    {
        WithTransition()
            .From(InvoiceStatus.WaitingForApproval)
            .On(x => x.ReceiveSignature())
            .To(InvoiceStatus.WaitingForApproval);

       WithTransition()
            .From(InvoiceStatus.WaitingForSignature)
            .On(x => x.ReceiveSignature())
            .To(InvoiceStatus.Approved);
    }
}

Then the specific state-machines

public class SalesAgentInvoiceStateMachine : InvoiceStateMachine
{
    public SalesAgentInvoiceStateMachine(Invoice invoice) : base(invoice, InvoiceStatus.Created)
    {
        WithTransition()
            .From(InvoiceStatus.Created)
            .On(x => x.SendForApproval())
            .To(InvoiceStatus.WaitingForApproval);
    }
}

public class SalesManagerInvoiceStateMachine : InvoiceStateMachine
{
    public SalesManagerInvoiceStateMachine (Invoice invoice) : base(invoice, InvoiceStatus.Created)
    {
        WithTransition()
            .From(InvoiceStatus.WaitingForApproval)
            .On(x => x.Approve())
            .When(x => x.NeedsSignature && x.HasReceivedSignature)
            .To(InvoiceStatus.Approved);

        WithTransition()
            .From(InvoiceStatus.WaitingForApproval)
            .On(x => x.Approve())
            .When(x => x.NeedsSignature && !x.HasReceivedSignature)
            .To(InvoiceStatus.WaitingForSignature);

        WithTransition()
            .From(InvoiceStatus.WaitingForApproval)
            .On(x => x.Approve())
            .When(x => !x.NeedsSignature)
            .To(InvoiceStatus.Approved);

        WithTransition()
            .From(InvoiceStatus.WaitingForApproval)
            .On(x => x.Reject())
            .To(InvoiceStatus.Rejected);
    }
}
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 was computed.  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 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0

    • No dependencies.

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.3 413 3/14/2022