1

I recently used the Stateless state machine. I can define the rules for transitions etc. like this:

stateMachine.Configure(State.Unknown)
    .Permit(Trigger.StartApplication, State.Initialized)
    .OnEntry(this.DoBeforeTransition)
    .OnExit(this.DoAfterTransition);

stateMachine.Configure(State.Initialized)
    .Permit(Trigger.CheckSomething, State.SomethingChecked)
    .OnEntry(this.DoBeforeTransition)
    .OnExit(this.DoAfterTransition);

and then you are able to fire a trigger to change the state. However, you need to know the current state and what will be the next state if you want to go to a particular state. Thus the "client" of the statemachine needs knowledge how to reach a certain state if there is no direct transition defined. Is there a possibility to call something like "goto " and the machine fires all the required triggers its own?

marco.m
  • 4,573
  • 2
  • 26
  • 41
Beachwalker
  • 7,685
  • 6
  • 52
  • 94

1 Answers1

1

You can do this is there is only ONE "Permit" per State. If you have more than one "Permit", then you cannot auto-move through the workflow (there has to be some reason why you would pick one Permit/Trigger over the other). When I say you "cannot", that's not technically, its practically.

Below is an example of auto-moving through the workflow.

using Stateless;
using System;
using System.Runtime.CompilerServices;

namespace MyExample.BAL.WorkFlows
{
    public class TelephoneCallWorkFlow
    {

        private static volatile StateMachine<TelephoneCallStateEnum, TelephoneCallTriggerEnum> SingletonInstance;

        public StateMachine<TelephoneCallStateEnum, TelephoneCallTriggerEnum> Instance
        {
            [MethodImpl(MethodImplOptions.Synchronized)]
            get
            {
                if (SingletonInstance == null)
                {
                    SingletonInstance = new StateMachine<TelephoneCallStateEnum, TelephoneCallTriggerEnum>(TelephoneCallStateEnum.OffHook);

                    SingletonInstance.Configure(TelephoneCallStateEnum.OffHook)
                        .Permit(TelephoneCallTriggerEnum.CallDialed, TelephoneCallStateEnum.Ringing);

                    SingletonInstance.Configure(TelephoneCallStateEnum.Ringing)
                        //removing so there is only one valid path workflow//.Permit(TelephoneCallTriggerEnum.HungUp, TelephoneCallStateEnum.OffHook)
                        .Permit(TelephoneCallTriggerEnum.CallConnected, TelephoneCallStateEnum.Connected);

                    SingletonInstance.Configure(TelephoneCallStateEnum.Connected)
                        //.OnEntry(t => StartCallTimer())
                        //.OnExit(t => StopCallTimer())
                        //removing so there is only one valid path workflow//.Permit(TelephoneCallTriggerEnum.LeftMessage, TelephoneCallStateEnum.OffHook)
                        //removing so there is only one valid path workflow//.Permit(TelephoneCallTriggerEnum.HungUp, TelephoneCallStateEnum.OffHook)
                        .Permit(TelephoneCallTriggerEnum.PlacedOnHold, TelephoneCallStateEnum.OnHold)
                        ;

                    SingletonInstance.Configure(TelephoneCallStateEnum.OnHold)
                        //removing so there is only one valid path workflow//.SubstateOf(TelephoneCallStateEnum.Connected)
                        //removing so there is only one valid path workflow//.Permit(TelephoneCallTriggerEnum.TakenOffHold, TelephoneCallStateEnum.Connected)
                        //removing so there is only one valid path workflow//.Permit(TelephoneCallTriggerEnum.HungUp, TelephoneCallStateEnum.OffHook)
                        .Permit(TelephoneCallTriggerEnum.PhoneHurledAgainstWall, TelephoneCallStateEnum.PhoneDestroyed)
                        ;
                }

                return SingletonInstance;
            }
        }

        public void Fire(TelephoneCallTriggerEnum trigger)
        {
            Console.WriteLine("............[Firing:] {0}", trigger);
            this.Instance.Fire(trigger);
        }
    }
}

public enum TelephoneCallStateEnum
{
    OffHook,
    Ringing,
    Connected,
    OnHold,
    PhoneDestroyed
}

public enum TelephoneCallTriggerEnum
{
    CallDialed,
    HungUp,
    CallConnected,
    LeftMessage,
    PlacedOnHold,
    TakenOffHold,
    PhoneHurledAgainstWall
}

and now the "auto-move" trick.

            TelephoneCallWorkFlow tcwf1 = new TelephoneCallWorkFlow();
            IEnumerable<TelephoneCallTriggerEnum> myPermittedTriggers = tcwf1.Instance.PermittedTriggers;
            while (null != myPermittedTriggers && myPermittedTriggers.Count() > 0)
            {
                if (myPermittedTriggers.Count() > 1)
                {
                    throw new ArgumentOutOfRangeException("You cannot auto-move the workflow when there's more than one trigger");
                }
                TelephoneCallTriggerEnum nextTrigger = myPermittedTriggers.FirstOrDefault();
                Console.WriteLine("About to call the 'next' trigger: --> {0}", nextTrigger);
                tcwf1.Fire(nextTrigger);
                Console.WriteLine("CurrentState: --> {0}", tcwf1.Instance.State);
                myPermittedTriggers = tcwf1.Instance.PermittedTriggers;
            }

You basically get the PermittedTriggers, and get the first-one (and for auto-move to work there should only be one Permitted-Trigger per State).....and then invoke that trigger.

Again, practically (not technically) you would only do this if there was one Permit/Trigger per State. Thus why I have an exception if there is more than 1. You could "get the first" if there were more than 1, it just wouldn't make any sense.

granadaCoder
  • 26,328
  • 10
  • 113
  • 146