WF How-To

Building State Machines with Windows Workflow Foundation

Keith Pijanowski

This article discusses:

  • Sequential workflows vs. state machine workflows
  • When to use state machine workflows
  • Building and designing state machine workflows
  • Raising events to running workflows
  • Passing data to running workflows
This article uses the following technologies:
Windows Workflow Foundation, Visual Studio

Code download available at:WFStateMachines2008_02.exe(177 KB)

ContentsState Machine Theory
State Machines and Windows WF
Communicating with State Machine Workflows
Events Required for Data Exchange and State Transitions
Defining an External Data Exchange Interface
Adding EventDriven Activities
Referencing the External Data Exchange Interface
Implementing an External Data Exchange Service
Add an External Data Exchange Service to the Workflow Runtime
Using the External Data Exchange
Useful State Machine APIs
Tying Up Loose Ends

The Microsoft® .NET Framework 3.0 introduced new capabilities for visually and declaratively building workflows that can be hosted by managed code. Out of the box, the .NET Framework 3.0 allows developers to build both sequential workflows and state machine workflows. Much has been written about sequential workflows because they closely resemble conventional programming techniques.

State machine workflows, on the other hand, represent a different way of thinking about program logic. When designed and implemented correctly, they are just as valuable as sequential workflows. Furthermore, this style of workflow is a good starting point for building human workflows. Consequently, state machine workflows should be a part of every architect's and developer's skill set.

State Machine Theory

Additional Resources

The code download for this article contains all the workflows, code figures, and code snippets shown in this article. It is designed as a sandbox or test arena for state machine workflows; consequently it will only run one instance of a workflow at a time.

This is a Visual Studio 2005 solution that contains the following projects:
StateMachineLibrary contains the two workflows presented here. These workflows are the SimpleStateMachine and the SupplyFulfillment workflows.
ConsoleHost is a console application used to host the simple state machine workflow presented at the beginning of this article.
WinFormHost is a Windows Forms application used to host the SupplyFulfillment workflow. It is in this project that the ExternalDataExchange is used to raise events to an instance of the SupplyFulfillment workflow.
ExternalDataExchange is a class library that contains the IEventService interface, the implementation of this interface—EventService, and the SupplyFulfillmentArgs class. It is this service that allows external events (and data) to be sent to the current state of a state machine workflow.

If you would like to know more, the following resources will be useful to you:

Business logic that is implemented as a state machine workflow follows a very different path than business logic implemented as a sequential workflow. Rather than flowing from activity to activity like a sequential workflow, state machines transition from state to state. Think of a state as a well-known step, stage, or even a status within a business process. States are useful because they indicate how much of a workflow has completed and how much must still occur. This lends itself very well to workflows that are long running.

A good indication that a business process is suited for a state machine workflow is that the requirements describe data or logic as moving in discreet steps (or states) from some starting point to some ending point. Take a look below at the description of a supply fulfillment business process that could be implemented as a state machine.

  1. Employees fulfilling customer orders may need additional supplies to fulfill an order. Requests for additional supplies should be submitted to a central system for processing.
  2. When a request is submitted, it will be assigned to a specific user's work queue for review.
  3. Once the request has been assigned, it will be either approved or rejected based on criteria external to the business process.
  4. A request that is rejected will be either reassigned or canceled based on criteria external to the business process. Reassigned orders will be processed the same as assigned orders. Canceled orders will be considered completed.
  5. If the employee request is approved, the resource that is required will be ordered.
  6. Once the order is received, the business process surrounding that order is considered complete.

Key steps of this business process that can be considered states are shown in Figure 1.

Figure 1 Logic Design of the Supply Fulfillment Workflow

Figure 1** Logic Design of the Supply Fulfillment Workflow **

There are a few key aspects that are worth noting before building this workflow using Windows® Workflow Foundation (Windows WF). First, the workflow has exactly one initial state and exactly one completed state: submitted and completed, respectively. Every instance of a state machine workflow begins in the state that has been designated as the initial state and ends when the workflow reaches the state designated as the completed state.

Second, state transitions are predefined. In other words, part of the definition of each state is a list of allowable next states. Figure 2 lists the states and the allowed transitions.

Figure 2 Allowable State Transitions for the Supply Fulfillment

State Allowed Transitions
Submitted Assigned
Assigned Approved or Rejected
Approved Ordered
Rejected Assigned or Completed
Ordered Completed
Completed (None)

Third, backtracking is allowed. Notice that our requirements specify that rejected requests can be reassigned. A reassigned state could have been placed into the logical design, but this would not have been an efficient design. The requirements specify that reassigned requests are to be processed in the same manner as newly assigned requests, so there is no need to create a separate branch off of the rejected state for handling reassigned requests. In other words, transitioning a workflow back to a previous state allows for better reuse within the workflow.

Fourth, state machine workflows are suited for long-running processes. If a state machine workflow flows from its initial state to its completed state in a matter of milliseconds and never pauses for external input, then the workflow could be more efficiently implemented using a sequential approach.

State Machines and Windows WF

Before implementing the logical design discussed earlier, let's explore a simple Windows WF state machine to get a feel for all the tools WF provides. I'll build a simple state machine workflow as an introduction to the design-time experience of Visual Studio® .NET and the tools in the .NET Framework 3.0. Note that all the workflows and code snippets shown here come from a working Visual Studio solution that is packaged as a code download for this article. For more information, see the "Additional Resources" sidebar.

When you install the Visual Studio 2005 extensions for .NET Framework 3.0 (Windows WF) from go.microsoft.com/fwlink/?LinkId=105219, several new project templates are added to the Visual Studio .NET 2005 environment (see Figure 3). Note that if you are using Visual Studio 2008, all of these project templates have been included as a part of the core product. Once you select one of the state machine project templates, a Visual Studio .NET project will be set up with the correct references and a workflow file that can be used as a starting point for the development of a state machine workflow.

Figure 3 Workflow Foundation Project Templates

Figure 3** Workflow Foundation Project Templates **(Click the image for a larger view)

The designer for a state machine workflow is much more restrictive than the designer for a sequential workflow. Most state machines will only require the State activity to be dragged onto the designer. (It is also possible to add an EventDriven activity to the designer for handling events that are not associated with a specific state.) The State activity represents a step, stage, or status within a state machine workflow. This activity is used to represent the initial state, the completed state, and all the potential states that may occur in between.

The first step in building a state machine is to place all the needed states on the design surface by dragging the state activity from the workflow toolbox and assigning each state a meaningful name. Workflow states are named using the properties dialog in the same way that Windows controls are named in a Windows Forms project. Figure 4 is an example of a simple state machine workflow containing three states: Submitted, Ordered, and Completed.

Figure 4 Simple State Machine Workflow

Figure 4** Simple State Machine Workflow **(Click the image for a larger view)

Workflow Foundation needs to know the initial state and the completed state for the purposes of proper run-time management. Therefore, the next step is to specify them via the property dialog of the Workflow itself. To view this dialog make sure that the property dialog is visible within Visual Studio .NET and then click on the design surface of your workflow, or just right-click the workflow designer and select properties. The lower right-hand corner of Figure 4 shows the property dialog for a state machine workflow. The InitialStateName and CompletedStateName properties are used to specify the initial state and the completed state respectively. Once these properties are specified, the designer will modify the graphical depiction of the specified states. Look closely at Figure 4 and you'll see the green light icon on the initial state and the red light icon on the completed state.

The State activity is a composite activity, meaning that it can contain other activities. Once again there are restrictions to consider. A state activity may contain only one StateInitialization activity, only one StateFinalization activity, one or more EventDriven activities, and one or more other State activities for nested states. These are the only four out-of-the-box activities that can be placed inside a state activity. Nested states are beyond the scope of this article and the use of the EventDriven activity will be described in the next section. Our simple state machine example will only utilize the StateInitialization activity and the StateFinalization activity.

Experimenting with the StateInitialization and StateFinalization activities is a good way to get a feel for how to work with the state machine designer. Playing around with these two activities will also provide a deeper understanding of how to control the flow of a state machine workflow.

All of the states in the simple example in Figure 4 have been equipped with a StateInitialization activity and a StateFinalization activity—with the exception of the completed state. It turns out that once a state is specified as completed, no activities can be added to it. This is because the act of transitioning to the completed state signifies the end of a workflow's lifecycle.

When program control is passed to a state activity and it begins execution, the state activity will check to see whether a StateInitialization activity is present. If one is present, it will be executed. Similarly, when a state activity is about to pass control to another state, it will check to see if a StateFinalization activity is present. If so, it will be executed. It is important to note that both the StateInitialization activity and the StateFinalization activity are also composite activities. Unlike the state activity, there are very few restrictions on what can be placed inside these activities. In other words, all the activities that can be used in a sequential workflow can be placed inside these activities. Visual Studio will provide a different design view for placing logic into these composite activities. Figure 5 shows the design view that is displayed after double-clicking on the StateInitialization activity of the Submitted state shown in Figure 4. Here the Submitted state has been equipped with a StateInitialization activity named SubmittedInitialization. The purpose of this design view is to allow additional activities to be placed inside the StateInitialization activity. In the example in Figure 5, a Code activity and a SetState activity have been placed within the SubmittedInitialization activity. The following code has been placed into the execute event of the code activity:

Figure 5 Design View for a StateInitialization Activity

Figure 5** Design View for a StateInitialization Activity **

private void codeSubmittedInitialization_ExecuteCode(object sender, 
  EventArgs e) {
    Console.WriteLine("The Submitted state has been initialized at " + 
      DateTime.Now.ToString() + ".");
}

The SetState activity (setStateOrdered in Figure 5) is used to transition control to another state in the StateMachine workflow. No code is necessary to utilize this activity. Figure 6 shows the properties dialog for setStateToOrdered. The TargetStateName property must be set to a valid state in the StateMachine workflow. Immediately after this activity executes, the current state's StateFinalization activity will execute and control will pass to the Ordered state. No additional activities will execute within the SubmittedInitialization activity. Therefore, care must be taken to insure that it is the last activity. The StateFinalization activity for our Submitted State has been configured with a single code activity with the following code in its execute event:

Figure 6 Properties for a SetState Activity

Figure 6** Properties for a SetState Activity **(Click the image for a larger view)

private void codeSubmittedFinalization_ ExecuteCode( object sender, EventArgs e) { 
  Console.WriteLine("The Submitted " + "state has been finalized at " + 
    DateTime.Now.ToString() + "."); 
}

It is important to note that a SetState activity cannot be placed within a StateFinalization activity. This wouldn't make sense anyway since the only way a StateFinalization activity can execute is if a SetState has already executed. A SetState activity can only reside within a StateInitialization or an EventDriven activity.

To return the designer to the original design view, which shows the entire workflow, just click the workflow name that appears in the upper left corner of the designer shown in Figure 5. This area of the designer is used for navigation. Clicking on "SimpleStateMachine" returns the design view to the original view that shows all the states in the workflow. If your state machine is very complicated and contains many levels of nested states, this navigational area of the designer will show you exactly where you are in the workflow.

At this point, the state machine designer will detect any state transitions specified via the setState activities and draw the appropriate transition arrows on the design surface. The TargetStateName property for SetState activities can be modified in two ways. The first, which was just described, is to use the property dialog. The TargetStateName property can also be set directly from the workflow's design surface by dragging and dropping the arrow end of a transition arrow to the desired State activity.

The ordered state in our simple state machine workflow (Figure 5) was set up in a fashion similar to the submitted state. In other words, a Code activity was used in both the StateInitialization and the StateFinalization activities to write a message to the console. In addition, a SetState activity was used in the StateInitialization activity to transition control to the completed state in the state machine workflow.

We are now ready to use the workflow. The code to instantiate the workflow runtime, create an instance of this workflow, and start the workflow from a console application is shown here:

static void Main(string[] args) {
    // Create the workflow runtime. 
    using(WorkflowRuntime workflowRuntime = new WorkflowRuntime()) {
        WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(SimpleStateMachine));
        instance.Start();
        Console.ReadLine();
    }
}

After this code is executed, the following output will appear in the console output window:

The Submitted state has been initialized at 8/6/2007 7:40:36 PM. 
The Submitted state has been finalized at 8/6/2007 7:40:36 PM. 
The Ordered state has been initialized at 8/6/2007 7:40:36 PM. 
The Ordered state has been finalized at 8/6/2007 7:40:36 PM.

Notice that the workflow described in this section implemented three of the needed states (Submitted, Ordered, and Completed) for the Supply Fulfillment workflow I described at the beginning of the article. I will now add the other states (Assigned, Approved, and Rejected) to this workflow to illustrate backtracking as well as states that can transition to more than one possible state. I will also add events.

Communicating with State Machine Workflows

Now that you've seen the basic moving parts of a WF state machine, I'd like to discuss some real-world considerations. State machines in the real world are often long-running, spending most of their lifetime paused within a state, waiting for input that is external to the workflow itself. An executing state can be paused to wait for external information using the EventDriven activity. As I stated previously, the EventDriven activity is one of the few activities that can be dropped into a State activity (the others are StateInitialization and StateFinalization). External information is passed to the EventDriven activity by way of events. Specifically, events are raised by an external data service and are received by the EventDriven activity. The EventDriven activity can be a little tricky to set up, but the following steps should help to get you started.

  1. Determine the events required for data exchange and state transitions.
  2. Define an External Data Exchange interface.
  3. Add EventDriven activities to the state machine workflow.
  4. Reference the external data exchange interface.
  5. Implement the External Data Exchange interface.
  6. Add the External Data Exchange to the workflow runtime.
  7. Use the External Data Exchange to exchange data and transition states.

Figure 7 shows exactly how the pieces mentioned in the previous steps fit together.

Figure 7 Configuring and Utilizing a State Machine Workflow

Figure 7** Configuring and Utilizing a State Machine Workflow **(Click the image for a larger view)

Events Required for Data Exchange and State Transitions

The first step is to determine which events are needed by each state in the state machine workflow. State machine events typically cause the workflow to change states. Therefore, the best way to determine the needed events is to review requirements and look for key words that indicate state transitions. Recall that to build the logical design of Figure 1, we needed to review the requirements to determine the necessary states and state transitions. That information can also be used to determine the events that are needed. Figure 8 expands upon the data in Figure 2, which listed the state transitions for each state within the Supply Fulfillment workflow. Figure 8 shows each state in the workflow, the events that each state will listen for, and the state that will be transitioned to for each event listed. If data is needed by the workflow instance in order to move to a new state, then Workflow Foundation provides capabilities that allow information to be passed as event parameters.

Figure 8 Supply Fulfillment Workflow Events

State Event Name Transition
Submitted OnAssigned Assigned
Assigned OnApproved Approved
Assigned OnRejected Rejected
Approved OnOrdered Ordered
Rejected OnReassigned Assigned
Rejected OnCanceled Completed
Ordered OnOrderReceived Completed

Notice that the Assigned state is listening for two events. The Assigned state will listen for both of these events at the same time. The event received determines the state transition. This is how external entities can determine the path that a state machine workflow will follow. (The Rejected state will also listen for two events.)

Defining an External Data Exchange Interface

The next step is to define these events within a standard .NET interface. Figure 9 shows the interface needed for the Supply Fulfillment workflow and Figure 10 shows the custom EventArgs class that is used to pass data to an instance of a workflow whenever one of these events is raised.

Figure 10 The Custom EventArgs Class

namespace ExternalDataExchange {
    [Serializable] public class SupplyFulfillmentArgs: ExternalDataEventArgs {
        public SupplyFulfillmentArgs(System.Guid InstanceId): base(InstanceId) {}
        private string assignedTo;
        private string orderID;
        public string AssignedTo {
            get {
                return assignedTo;
            }
            set {
                assignedTo = value;
            }
        }
        public string OrderID {
            get {
                return orderID;
            }
            set {
                orderID = value;
            }
        }
    }
}

Figure 9 External Data Service Interface

namespace ExternalDataExchange {
    [ExternalDataExchange()] public interface IEventService {
        event EventHandler < SupplyFulfillmentArgs > Assigned;
        event EventHandler < SupplyFulfillmentArgs > Approved;
        event EventHandler < SupplyFulfillmentArgs > Rejected;
        event EventHandler < SupplyFulfillmentArgs > Reassigned;
        event EventHandler < SupplyFulfillmentArgs > Canceled;
        event EventHandler < SupplyFulfillmentArgs > Ordered;
        event EventHandler < SupplyFulfillmentArgs > OrderReceived;
    }
}

 

There are a few things to note about this interface. The first is the use of the ExternalDataExchange attribute. A class that implements an interface marked with the ExternalDataExchange attribute can be added to the workflow runtime engine just like any other service. (Persistence and tracking are examples of other services that can be added to the workflow runtime.)

Also notice that SupplyFulfillmentArgs, which is used as a parameter in the events declared in the interface, must inherit from ExternalDataEventArgs. Furthermore, ExternalDataEventArgs requires the workflow instance ID to be passed to its constructor so that the event will get raised to the correct workflow instance. Finally, SupplyFulfillmentArgs must be marked as serializable in case the workflow runtime needs to serialize it to persistent storage.

Adding EventDriven Activities

Once the interface for the External Data Exchange is set up, the next step is to add EventDriven activities to the state machine. Similar to the StateInitialization and the StateFinalization activities, the EventDriven activity is a composite activity. Its purpose, therefore, is to contain other activities. Once the EventDriven activity is added to a State activity, we will be able to drill into it in the same way we drilled into the StateInitialization and the StateFinalization activities for the purposes of defining additional logic. Unlike the StateInitialization and the StateFinalization activities, we have to follow a few rules when placing activities inside the EventDriven activity. It turns out that the EventDriven activity inherits from the Sequence activity and adds the restriction that the first child activity must be an activity that implements the public interface IEventActivity. All subsequent activities can be of any type. In the example presented here, the HandleExternalEvent activity is used. This activity is capable of receiving an event that is external to the workflow runtime. Two other activities that also implement the IEventActivity interface are the Delay activity and the WebServiceInput activity.

In the SupplyFulfillment workflow, there are seven logical events that will require an EventDriven activity. Figure 8 contains the information needed for placing the EventDriven activities into the correct state. It is also important to note that logic placed within these activities will ultimately result in the state changes. Figure 11 shows the designer view of the SupplyFulfillment state machine once all the EventDriven activities have been added. Notice that the transition arrows originate from the EventDriven activities and end on the State activities. Figure 12 shows the logic contained within the OnAssigned EventDriven activity.

Figure 11 EventDriven Activities in a State Machine Workflow

Figure 11** EventDriven Activities in a State Machine Workflow **(Click the image for a larger view)

Figure 12 Logic within an EventDriven Activity

Figure 12** Logic within an EventDriven Activity **

Technically, the StateInitialization and the StateFinalization activities which are shown in Figure 11 are no longer needed. They are included here and equipped with console messages for demonstration purposes only.

It is worth mentioning that the visual depiction of the Supply Fulfillment workflow in Figure 11 is just as easy to read as the logical diagram in Figure 1 that was created with Microsoft Visio®. Development artifacts that developers, architects, and end users can easily understand and verify are a core tenet of business process management.

Referencing the External Data Exchange Interface

Once the EventDriven activities are in place and the child activities for each EventDriven activity are set up in a manner similar to Figure 11, the next step is to configure the HandleExternalEvent activities. Remember that when setting up an EventDriven activity, one of the rules of construction is that the first child activity must be an activity like the HandleExternalEvent activity. This can be seen in the logic in Figure 12 where an EventDriven activity named OnAssigned has as its first child activity a HandleExternalEvent activity named handleAssigned, followed by a SetState activity named SetStateAssigned.

The HandleExternalEvent activity is what will cause a StateMachine workflow to block while waiting to handle an event that is raised by an external data exchange service. The specific event that a HandleExternalEvent activity will wait for is specified in the properties dialog (shown in Figure 13). The first property to set is the InterfaceType property, which must be set to an interface that is marked with the ExternalDataExchange Attribute. In the case of the Supply Fullfilment workflow, this interface is the IEventService interface shown in Figure 9. Once the InterfaceType property is set, the EventName property can be specified. This properties dialog will reflect over the interface specified via the InterfaceType property and provide a dropdown box for the EventName property. The dialog shown in Figure 13 is for the Supply Fulfillment workflow's OnAssigned event.

Figure 13 handleAssigned Properties

Figure 13** handleAssigned Properties **(Click the image for a larger view)

Before leaving this dialog, there is one more consideration. Remember that all the events in the ExternalDataExchange.IEventService interface require a SupplyFulfillmentArgs argument. This argument can be used to pass data into an instance of a workflow when an external event is raised and the workflow receives the event. There are two ways to make this data type accessible to other activities within the workflow. One way is to use the "e" property shown in Figure 13. This property allows you to specify a property within the workflow that can be used to hold the argument passed by the event. Once this event parameter is set into a workflow property, it can be utilized by other activities in the workflow. Obviously the property specified will need to be of the same type as the event parameter. In the case of the SupplyFulfillment workflow, this would be the SupplyFulfillmentArgs type. Alternatively, the Invoked property can be used to specify an event handler within the workflow. This event handler will be called after the external event is received and it will be passed a parameter of type ExternalDataEventArgs, which can be cast into the correct type (SupplyFulfillmentArgs).

It is important to note at this point that the HandleExternalEvent activity deals only with the ExternalDataExchange interface and not with an actual implementation of the interface.

Implementing an External Data Exchange Service

In order to successfully implement an external data exchange service, you need to implement the interface referenced by the HandleExternalEvent activities in the workflow. Specifically, the IEventService interface must be implemented. The following code creates the EventService class and implements the interface:

[Serializable] public class EventService: IEventService {
    public event EventHandler < SupplyFulfillmentArgs > Assigned;
    public event EventHandler < SupplyFulfillmentArgs > Approved;
    public event EventHandler < SupplyFulfillmentArgs > Rejected;
    public event EventHandler < SupplyFulfillmentArgs > Reassigned;
    public event EventHandler < SupplyFulfillmentArgs > Canceled;
    public event EventHandler < SupplyFulfillmentArgs > Ordered;
    public event EventHandler < SupplyFulfillmentArgs > OrderReceived;
    ...
}

Since events can only be raised from within the class in which they are defined, we need to add helper functions to facilitate raising events. This class will need one helper function per event. The following code shows the helper function for the Assigned event:

public void RaiseAssignedEvent(System.Guid instanceId, string assignedTo) {
    // Check to see if event is defined 
    if (this.Assigned != null) {
        // Create the EventArgs for this event 
        LocalService.SupplyFulfillmentArgs args = 
          new LocalService.SupplyFulfillmentArgs(instanceId);
        args.AssignedTo = assignedTo;
        // Raise the event 
        this.Assigned(this, args);
    }
}

Note that when the event is raised there are two parameters that are passed to all registered delegates. The first parameter is the sender. The code above passes the current instance of the EventService class, which must be marked as serializable for an instance of it to be passed as the sender. If it is not, then you will receive an error stating that the event could not be delivered. Here is an example of this error message:

Event "Assigned" on interface type "ExternalDataExchange.IEventService" for instance id "0f9e8545-39bf-48b6-b890- c12dec11cb9f" cannot be delivered.

The second parameter is the SupplyFulfillmentArgs parameter. When this class is instantiated, it requires the workflow instance identifier—the Guid that was generated when the workflow was first instantiated. It is this Guid that will be used by the workflow runtime to locate the workflow instance that should receive the event. For this to occur, however, an instance of the EventService class must be added to the workflow runtime as an external service.

Add an External Data Exchange Service to the Workflow Runtime

Since the workflow runtime manages all execution and interaction of instantiated workflows, the class that was just created previously must be added to the workflow runtime as an external data exchange service. The following code shows how to add an instance of the EventService class to the workflow runtime as an ExternalDataExchange service class:

// Create a new instance of the local service and host it in an 
//ExternalDataExchangeService. 
ExternalDataExchangeService externalDataSvc = new ExternalDataExchangeService();
wr.AddService(externalDataSvc);
eventService = new EventService();
externalDataSvc.AddService(eventService);

The workflow runtime is very particular about how this is done. The external data service must first be created and added to the workflow runtime before the EventService class can be added to the ExternalDataExchangeService class. If you try to use the ExternalDataExchangeService object before it is added to the workflow runtime, you will receive an error that states "WorkflowRuntime container does not contain ExternalDataExchangeService."

Once this is complete, events raised from the instance of the EventService class will find their way to the correct instance of a workflow and to the correct HandleExternalEvent activity within the workflow.

Using the External Data Exchange

Everything is now set up and the External Data Exchange can be used to exchange data and transition the states of the workflow. Using the External Data Exchange is as easy as calling into the helper functions that were set up within the EventService class shown earlier. The code snippet below shows the use of the helper function that will change the workflow state from Submitted to Assigned. Notice that the workflow instance ID and an "assignTo" parameter are passed into the function. Both of these parameters will be packaged into an instance of the SupplyFulfillmentArgs class so they can be passed to the workflow via the Assigned event. As I stated, the workflowInstanceID parameter is used by the workflow runtime so it can raise the Assigned event to the correct workflow instance. The assignTo parameter ("Joe") will be delivered directly to the workflow to be used during workflow execution:

eventService.RaiseAssignedEvent(workflowInstanceId, "Joe");

It is important to note that at any given point, a running state machine workflow is only listening for the events within the current State activity. If any other events are raised, the calling code will receive an exception. For example, once the SupplyFullfilment workflow is instantiated, it is in its initial state (the Submitted state), which means the workflow is waiting for the Assigned event to be raised. If any of the other helper functions are called that raise other events (Approved, Rejected, Reassigned, Canceled, Ordered, and OrderReceived), you will receive an error stating that the event could not be delivered. Below is an example of an error message that will occur if the Ordered event is called when the workflow is in any state other than Approved:

Event "Ordered" on interface type "ExternalDataExchange.IEventService" for instance id "59657ed0-1532-42c5-8abe-6523bf121fff" cannot be delivered.

As you can see, this very generic error message occurs whenever anything goes wrong with the delivery of an event and there is no information in this message that helps to determine exactly what went wrong.

Obviously, this raises a few questions as to how to write workflow code that is not fragile. Specifically, how can your code know the current state of a state machine instance so you can programmatically ensure that the correct event is sent to the workflow? It turns out that there are a handful of useful APIs that can be used by the code consuming a state machine workflow.

Useful State Machine APIs

WF comes with a utility class named StateMachineWorkflowInstance specifically designed for managing instances of state machine workflows. Figure 14 provides a brief description of the constructor, properties, and methods of the StateMachineWorkflowInstance class.

Figure 14 Constructor, Properties, and Methods of StateMachineWorkflowInstance

Public Constructor
Name Description
StateMachineWorkflowInstance This is the constructor. It requires two parameters. The first is an instance of the workflow runtime. The second is a Guid representing the state machine instance that will be queried or managed by this class' properties and functions.
Public Properties
Name Description
CurrentState Read only. Returns a StateActivity object for the current state.
CurrentStateName Read only. Returns a string that represents the name of the current state activity.
InstanceID Read only. Returns a Guid that uniquely identifies the state machine workflow within the workflow runtime.
PossibleStateTransitions Read only. Returns a ReadOnlyCollection<string> representing the possible state transitions from the current state.
StateHistory Read only. Returns a ReadOnlyCollection<string> of the state transitions that the current workflow has already made. This property requires a tracking service to be added to the workflow runtime.
StateMachineWorkflow Read only. Returns a StateMachineWorkflowActivity type. This is a copy of the workflow definition and not the live instance tree, so this property should never be used to retrieve runtime values.
States Read only. Returns a ReadOnlyCollection<string> collection of all the states in the definition of the current workflow. Like the StateMachineWorkflow property, this property contains only definitions and not live data.
WorkflowInstance Read only. Returns the current WorkflowInstance type which represents the current instance. This property provides access to the live instance tree.
Public Methods
Name Description
EnqueueItem Posts a message to the queue of a state machine workflow. Usually not needed. The HandleExternalEvent activity described in previous sections enqueues messages using a programming model that is less prone to error.
SetState Provides a transition to a state specified via its only parameter. Can be used to transition to any state in the workflow—even if the state specified is not a transition that would occur under ordinary execution of the workflow.

If you do use this class, however, heed these warnings. First, state machines that are event-driven (like the SupplyFulfillment workflow) are inherently asynchronous. Therefore, if the public properties shown in Figure 14 are queried immediately after raising an event to a state machine instance, they will not accurately reflect the state of the workflow. Event-driven workflows will be idle once they have finished processing the current event and are waiting for the next event. Therefore, the OnWorkflowIdled event of the workflow runtime is a good place to query an instance of a state machine using these properties.

Second, the StateHistory property requires a tracking service to be added to the workflow runtime. If this property is used without a tracking service, you will receive the following error:

'StateHistory' property requires the 'System.Workflow.Runtime.Tracking.SqlTrackingService' service.

Finally, be careful when exposing the functionality of the SetState method to end users. This method can cause a transition to any state in the workflow and is best used by administrators or managers dealing with non-standard scenarios.

Tying Up Loose Ends

While I covered state machine theory, the design-time experience, and the basic activities needed to build a usable state machine, there are still a few important issues to consider. First, if a state machine is to run in a server environment, it must optimize system resources while executing and must be able to survive system restarts. Additionally, it may be desirable to track and save historic data so that business processes can be optimized based on tracking reports. For example, if the Supply Fulfillment workflow in this article were used to run a small business, it would be desirable to see how long it takes for each workflow to move from the Ordered state to the Completed state. If a specific supplier constantly violated service level agreements, the business process of acquiring new supplies could be optimized by simply omitting the offending supplier.

Another topic that deserves further investigation is human workflows. State machines are a good starting point for building human workflows. If a state machine is to be used to manage business processes with human input, care must be taken when building the user interface. The user should have a clear view of the desired path through the state machine and should only be presented with options that are relevant for the current state.

Keith Pijanowski is a Platform Strategy Advisor for the Microsoft Developer and Platform Evangelism team, helping customers apply new ideas and new technologies to business problems. Keith is also a frequent speaker at Microsoft events in the New Jersey and New York area.