Udostępnij za pośrednictwem


A practical State Machine example

Now that the State Machine Activity Pack has been released on Codeplex (see https://wf.codeplex.com for details) I thought I should resurrect one of the samples I regularly used in the 3.x days to show a state machine example. The point of this example is to show how the state machine activities could be used to control access to a building as it’s a simple example and showcases the main features available in the state machine activity pack.

My example models access control to a door – the host is a WPF application (which could do with some better styling!). Imagine if you could ‘boot’ up a building. In that case each door would for sake of argument be closed and locked – this forms the initial state of the state machine. Thereafter we’re awaiting an event, which is the user unlocking the door. This might be by using a PIN pad or a smart card – the details of this are beyond the scope of the post. An overview of the states and transitions is shown in the image below.

StateMachine

When the user presents their credentials the Unlocked transition occurs – here you could optionally pass the PIN number (or card entry code) into the transition to lookup the code in a database and verify that it is correct. In my example I just transition to the ClosedUnlocked state without any verification.

From this state there are 3 possible things that could happen – the user could open the door (the most likely outcome), they could choose to re-lock the door for some reason, or they might not open the door within a set period in which case you would want the door to automatically re-lock itself and transition back to the ClosedLocked state. So at this point there are 3 transitions executing, two are waiting on an event (more on this in a moment) and the third (Timeout) is waiting for a Delay which in my example is 5 seconds. I’ll explain a little more about the State and Transition classes as they are critical to understanding how the state machine works.

States & Transitions

A state machine comprises a collection of states and a collection of transitions between states. There’s always one initial state, and you can optionally have a final state (which completes the execution of the workflow instance). When you enter a state you can optionally run some activities, and similarly when you exit a state you can also execute some activities. To define the entry and exit activities, double click on the state in the designer which will show something like the following (I chose the ClosedLocked state from the above diagram)…

State

When a state activity executes it calls the Entry activity (which can of course be a Sequence so that you can run multiple activities) and we then find all outgoing transitions. We then execute each transition, which in the case of my state machine example executes a set of activities that create a bookmark and then wait for this bookmark to be resumed. This may seem a bit odd – if you have 3 transitions we schedule the Trigger activity (shown in the image below) for each of the 3 branches. When the state machine engine then does is waits for one of these activities to complete, and whichever gets in first will ‘win’ and that transition will complete.

 

Transition

 

Possibly the easiest way to view transitions is as a Pick activity. When you are in a given state, we effectively execute each transition branch in a similar manner to that of the Pick activity, and the first activity to complete will complete the transition. You can optionally include a Condition on the transition which is evaluated when the trigger event occurs and the transition is only completed if the condition returns true. In the ClosedLocked state we’re effectively waiting as follows…

PickExample I’ve omitted any Actions in the above as the transition branch works in a slightly different manner to Pick. When the transitions Trigger activity completes (and assuming the condition evaluates to true) we’ll also execute the Action activity if you’ve created one.

So, in a state we’re waiting for an activity to complete, and in the above image I have a custom activity shown as the DoorUnlocked and DoorLocked activities. These are instances of the DoorEvent activity which uses a bookmark that can be resumed by the client. The code for this activity is fairly trivial and is shown below…

 public class DoorEvent : NativeActivity
{
    protected override bool CanInduceIdle
    {
        get { return true; }
    }

    [RequiredArgument]
    public InArgument<string> BookmarkName { get; set; }

    protected override void Execute(NativeActivityContext context)
    {
        context.CreateBookmark(BookmarkName.Get(context), new BookmarkCallback(OnBookmarkResumed));
    }

    private void OnBookmarkResumed(NativeActivityContext context, Bookmark bookmark, object state)
    {
    }
}

Not a great deal going on there really – it has a mandatory BookmarkName property which defines the name of the bookmark we’re using. In the example, the name of the bookmark matches the transition label.

Hosting in WPF

In the example I’m using a WPF host, and using the MVVM pattern (here with only a View Model) to maintain the user interface. The UI needs to know what state the state machine is in in order to resume appropriate bookmarks, so in each state activity I pass the state out to the View Model using another custom activity. This updates a property on the view model which in turn enables/disables parts of the UI.

There are a couple of classes used in the example for view models – the MainViewModel class is the root of the application, and it contains a set of DoorModel instances, one for each door shown on the UI. When a DoorModel is created it creates a WorkflowApplication instance and adds an extension to the instance (IDoor) that the door model implements. It is this extension that the workflow calls back to the door model on when notifying state changes. The pseudo code for the system is shown below…

  • The DoorModel instance is created. I set its name (shown on screen).
  • An instance of the state machine is created inside a WorkflowApplication instance. This is passed an interface back to the door model that owns it (IDoor).
  • The state machine starts up and immediately executes the first state activity ‘ClosedLocked’. This executes the SetDoorState activity which calls IDoor.SetState() and passes an enum value to the method to indicate what state the machine is in.
  • The UI is updated to enable/disable commands as appropriate based on the state of the workflow.

Thereafter the UI is in charge of the state machine. When a command such as ‘Unlock’ is executed, it simply resumes the appropriate bookmark on the state machine…

 _unlockCommand = new DelegateCommand((unused) =>
    {
        _stateMachine.ResumeBookmark("Unlocked", null);
    },
    (unused) =>
    {
        return this.DoorState == DoorState.ClosedLocked;
    });

Here I’m using a DelegateCommand class so that I can use lambda functions to respond to the command (and also to define whether the command is valid or not based on the current state).

That’s pretty much all there is to it – as the user clicks around on the UI it executes the appropriate command, this resumes a bookmark which will then force a transition in the workflow, and when a new state is entered this calls back to the view model to indicate the new state. Simple.

There’s one other part of the application I wanted to call out and that’s the use of a custom PlaySound action on each transition. This simply plays a sound to indicate the state of the door (well, sort of!) when the transition completes. As an example there’s one sound used when the timeout happens but another when the door is manually locked. I initially defined these sounds in the ‘Entry’ element of each State activity, but this didn’t indicate the particular transition that had just occurred – i.e. there was no way to distinguish how the state machine had transitioned to the ClosedLocked state to allow me to play a different sound on entry to that activity.

When you run the application you’ll see the following wonderful UI…

DoorLocked If you click on the Unlock button the workflow executes and changes the state to ClosedUnlocked. You’ll also see a couple more buttons show up that correspond to the now valid states (Lock and Open). If you unlock a door and then leave it for around 5 seconds the door will automatically lock again. For all transitions you’ll also be rewarded with some sounds – if these don’t work check the PlaySound activities in each transition and choose your own files to play (I’m on Windows 7 so the directory and files might be different from your machine).

The Code

Click here to download the source code and VS project. Feel free to tart up the user interface!

Comments

  • Anonymous
    August 30, 2010
    Great post! Thanks for all the information given. I will bookmark this site for my research.