GotchaCode

Simple State Machine Framework in c#

26 Feb 2014

This blog post covers a very simple, light weight, yet flexible state machine framework in C# .Net.

Souce code on Github

Download NuGet package

Concept

State : The state of a stateful entity. Represented by IState

Stateful entity : An entity that has a defined state. Represented by IStatefulEntity

Context : A context in which a state machine executes a transition. It is simply a set of configuration and data that governs the execution of a state machine. Represented by IStateMachineContext

Transition handler : A component that handles a transition of a stateful entity between a source state to target state. Represented by ITransitionHandler

Usage

Start by creating an entity that represents a state.


    public class ServiceTicketState : IState
    {
        public string Code { get; set; }
        public string Name { get; set; }
        // ...
    }
    

Now, create a stateful entity by implementing IStatefulEntity interface. For exmaple:

    public class ServiceTicket : IStatefulEntity<ServiceTicketState>
    {
        public ServiceTicketState State { get; set; }
        public string Name { get; set; }
        // ...
    }

Next comes transition handlers. A transition hadler is created by implementing ITransitionHandler interface. Below is an example of a generic transition handler that can be used to abstract common functionalities in our Service Ticket example.


    public class GenericTransitionHandler : ITransitionHandler<ServiceTicket, ServiceTicketState>
    {
        public virtual string TransitionKey { get { return ""; } }

        public virtual ServiceTicket Execute(ServiceTicket entity, ServiceTicketState nextState, IDictionary<string, object> argumentsMap = null)
        {
            entity.State = nextState;
            return entity;
        }

        public virtual ServiceTicket ValidateTransition(ServiceTicket entity, ServiceTicketState nextState, IDictionary<string, object> argumentsMap = null)
        {
            return entity;
        }

        public virtual void BeforeTransition(ServiceTicket entity, IDictionary<string, object> argumentsMap = null)
        {

        }

        public virtual void AfterTransition(ServiceTicket entity, IDictionary<string, object> argumentsMap = null)
        {

        }
    }

Next and final step is to create a ‘context’ by implementing IStateMachineContext interface. This interface declares following methods that need to be implemented to create a context.

ICollection<StateTransition> GetTransitions();
IDictionary<string, ITransitionHandler<T,M>> GetTransitionHandlersMap();
ICollection<M> AllStates();

Implementation approach is left to the user. It could from just hard coding for simple cases to database backed storage and retrieval for more advanced cases.

Here is an example implementation (hard coded for simplicity)

        public ICollection<StateTransition> GetTransitions()
        {
            ICollection<StateTransition> map = new List<StateTransition>();
            // Define all possible transitions, and its source and target states
            map.Add(new StateTransition("?", "Transition_New", "New")); // ? means null or undefined state
            map.Add(new StateTransition("New", "Transition_Open", "Open"));
            map.Add(new StateTransition("Open", "Transition_Close", "Closed"));
            map.Add(new StateTransition("Closed", "Transition_ReOpen", "Open"));
            map.Add(new StateTransition("?", "Transition_Cancel", "Cancelled"));

            return map;
        }

        public IDictionary<string, ITransitionHandler<ServiceTicket, ServiceTicketState>> GetTransitionHandlersMap()
        {
            IDictionary<string, ITransitionHandler<ServiceTicket, ServiceTicketState>> map = new Dictionary<string, ITransitionHandler<ServiceTicket, ServiceTicketState>>();
            // create a map of all available transitions and their respective transition handlers
            map.Add("Transition_New", new NewTransitionHandler());
            map.Add("Transition_Open", new OpenTransitionHandler());
            map.Add("Transition_Close", new CloseTransitionHandler());
            map.Add("Transition_ReOpen", new OpenTransitionHandler());

            return map;
        }

        public ICollection<ServiceTicketState> AllStates()
        {
            List<ServiceTicketState> states = new List<ServiceTicketState>();
            // create a list of all possible states
            states.Add(new ServiceTicketState() { Code = "New", Name = "New Ticket" });
            states.Add(new ServiceTicketState() { Code = "Open", Name = "Ticket Open" });
            states.Add(new ServiceTicketState() { Code = "Closed", Name = "Ticket Closed" });
            states.Add(new ServiceTicketState() { Code = "Cancelled", Name = "Ticket Cancelled" });

            return states;
        }

Now ServiceTicket entity can be transitioned through state machine. Create an instance of StateMachine as follows.

            IStateMachineContext<ServiceTicket, ServiceTicketState> context = new ExampleStateMachineContext();
            StateMachine<ServiceTicket, ServiceTicketState> StateMachine = new StateMachine<ServiceTicket, ServiceTicketState>(context);

And request a transition as shown below.

            ServiceTicket serviceTicket = new ServiceTicket()
            {
                 Name = "An example service ticket"
            };

            StateMachine.RequestTransition(serviceTicket, "Transition_New");

And thats it. Your state machine is ready to handle transitions on the Service Ticket example entity.

Tweet