Git Product home page Git Product logo

stateless's Introduction

Stateless Build status NuGet Pre Release Join the chat at https://gitter.im/dotnet-state-machine/stateless Stack Overflow

Create state machines and lightweight state machine-based workflows directly in .NET code:

var phoneCall = new StateMachine<State, Trigger>(State.OffHook);

phoneCall.Configure(State.OffHook)
    .Permit(Trigger.CallDialled, State.Ringing);
	
phoneCall.Configure(State.Ringing)
    .Permit(Trigger.CallConnected, State.Connected);
 
phoneCall.Configure(State.Connected)
    .OnEntry(() => StartCallTimer())
    .OnExit(() => StopCallTimer())
    .Permit(Trigger.LeftMessage, State.OffHook)
    .Permit(Trigger.PlacedOnHold, State.OnHold);

// ...

phoneCall.Fire(Trigger.CallDialled);
Assert.AreEqual(State.Ringing, phoneCall.State);

This project, as well as the example above, was inspired by Simple State Machine.

Features

Most standard state machine constructs are supported:

  • Generic support for states and triggers of any .NET type (numbers, strings, enums, etc.)
  • Hierarchical states
  • Entry/exit events for states
  • Guard clauses to support conditional transitions
  • Introspection

Some useful extensions are also provided:

  • Ability to store state externally (for example, in a property tracked by an ORM)
  • Parameterised triggers
  • Reentrant states
  • Export to DOT graph

Hierarchical States

In the example below, the OnHold state is a substate of the Connected state. This means that an OnHold call is still connected.

phoneCall.Configure(State.OnHold)
    .SubstateOf(State.Connected)
    .Permit(Trigger.TakenOffHold, State.Connected)
    .Permit(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed);

In addition to the StateMachine.State property, which will report the precise current state, an IsInState(State) method is provided. IsInState(State) will take substates into account, so that if the example above was in the OnHold state, IsInState(State.Connected) would also evaluate to true.

Entry/Exit Events

In the example, the StartCallTimer() method will be executed when a call is connected. The StopCallTimer() will be executed when call completes (by either hanging up or hurling the phone against the wall.)

The call can move between the Connected and OnHold states without the StartCallTimer() and StopCallTimer() methods being called repeatedly because the OnHold state is a substate of the Connected state.

Entry/Exit event handlers can be supplied with a parameter of type Transition that describes the trigger, source and destination states.

External State Storage

Stateless is designed to be embedded in various application models. For example, some ORMs place requirements upon where mapped data may be stored, and UI frameworks often require state to be stored in special "bindable" properties. To this end, the StateMachine constructor can accept function arguments that will be used to read and write the state values:

var stateMachine = new StateMachine<State, Trigger>(
    () => myState.Value,
    s => myState.Value = s);

In this example the state machine will use the myState object for state storage.

Another example can be found in the JsonExample solution, located in the example folder.

Introspection

The state machine can provide a list of the triggers than can be successfully fired within the current state via the StateMachine.PermittedTriggers property. Use StateMachine.GetInfo() to retreive information about the state configuration.

Guard Clauses

The state machine will choose between multiple transitions based on guard clauses, e.g.:

phoneCall.Configure(State.OffHook)
    .PermitIf(Trigger.CallDialled, State.Ringing, () => IsValidNumber)
    .PermitIf(Trigger.CallDialled, State.Beeping, () => !IsValidNumber);

Guard clauses within a state must be mutually exclusive (multiple guard clauses cannot be valid at the same time.) Substates can override transitions by respecifying them, however substates cannot disallow transitions that are allowed by the superstate.

The guard clauses will be evaluated whenever a trigger is fired. Guards should therefor be made side effect free.

Parameterised Triggers

Strongly-typed parameters can be assigned to triggers:

var assignTrigger = stateMachine.SetTriggerParameters<string>(Trigger.Assign);

stateMachine.Configure(State.Assigned)
    .OnEntryFrom(assignTrigger, email => OnAssigned(email));

stateMachine.Fire(assignTrigger, "[email protected]");

Trigger parameters can be used to dynamically select the destination state using the PermitDynamic() configuration method.

Ignored Transitions and Reentrant States

Firing a trigger that does not have an allowed transition associated with it will cause an exception to be thrown.

To ignore triggers within certain states, use the Ignore(TTrigger) directive:

phoneCall.Configure(State.Connected)
    .Ignore(Trigger.CallDialled);

Alternatively, a state can be marked reentrant so its entry and exit events will fire even when transitioning from/to itself:

stateMachine.Configure(State.Assigned)
    .PermitReentry(Trigger.Assigned)
    .OnEntry(() => SendEmailToAssignee());

By default, triggers must be ignored explicitly. To override Stateless's default behaviour of throwing an exception when an unhandled trigger is fired, configure the state machine using the OnUnhandledTrigger method:

stateMachine.OnUnhandledTrigger((state, trigger) => { });

Export to DOT graph

It can be useful to visualize state machines on runtime. With this approach the code is the authoritative source and state diagrams are by-products which are always up to date.

phoneCall.Configure(State.OffHook)
    .PermitIf(Trigger.CallDialled, State.Ringing, IsValidNumber);
    
string graph = UmlDotGraph.Format(phoneCall.GetInfo());

The UmlDotGraph.Format() method returns a string representation of the state machine in the DOT graph language, e.g.:

digraph {
  OffHook -> Ringing [label="CallDialled [IsValidNumber]"];
}

This can then be rendered by tools that support the DOT graph language, such as the dot command line tool from graphviz.org or viz.js. See http://www.webgraphviz.com for instant gratification. Command line example: dot -T pdf -o phoneCall.pdf phoneCall.dot to generate a PDF file.

Async triggers

On platforms that provide Task<T>, the StateMachine supports async entry/exit actions and so-on:

stateMachine.Configure(State.Assigned)
    .OnEntryAsync(async () => await SendEmailToAssignee());

Asynchronous handlers must be registered using the *Async() methods in these cases.

To fire a trigger that invokes asynchronous actions, the FireAsync() method must be used:

await stateMachine.FireAsync(Trigger.Assigned);

Note: while StateMachine may be used asynchronously, it remains single-threaded and may not be used concurrently by multiple threads.

Building

Stateless runs on .NET 4.0+ and practically all modern .NET platforms by targeting .NET Standard 1.0. Visual Studio 2017 is required to build the solution.

Project Goals

This page is an almost-complete description of Stateless, and its explicit aim is to remain minimal.

Please use the issue tracker or the if you'd like to report problems or discuss features.

(Why the name? Stateless implements the set of rules regarding state transitions, but, at least when the delegate version of the constructor is used, doesn't maintain any internal state itself.)

stateless's People

Contributors

nblumhardt avatar henningnt avatar trgrote avatar edgars-pivovarenoks avatar arturkarbone avatar angularsen avatar tlk avatar cmendible avatar pdinnissen avatar pardahlman avatar yevhen avatar surajgupta avatar pete-craig avatar lakritzator avatar mike706574 avatar matthiaslischka avatar brianwhited avatar doan-van-tuan avatar beachwalker avatar almazdeldiablo avatar shanselman avatar p-m-j avatar ianwall avatar anheledir avatar fantasyteddy avatar betty-crokker avatar chetanbhat avatar braincrumbz avatar abalfour avatar

Watchers

James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.