Up to this point, the chapter has concentrated on activities but has not discussed workflows. A workflow is simply a list of activities, and indeed a workflow itself is just another type of activity. Using this model simplifies the runtime engine, because the engine just needs to know how to execute one type of object – that being anything derived from the Activity class.
Each workflow instance is uniquely identified by its Instanceld property – this is a Guid that can be assigned by the runtime, or this Guid can be provided to the runtime by your code. A common use of this is to correlate a running workflow instance with some other data maintained outside of the workflow, such as a row in a database. You can access the specific workflow instance by using the GetWorkflow (Guid) method of the WorkflowRuntime class.
Two types of workflows ate available with WF – sequential and state machine.
The root activity in a sequential workflow is the Sequential Workflow Activity. This class is derived from Sequence Activity, which you have already seen, and it defines two events that you can attach handlers to as necessary. These are the Initialized and Completed events.
A sequential workflow starts executing the first child activity within it, and typically continues until all other activities have executed. There are a couple of instances when a workflow will not continue through all activities – one is if an exception is raised while executing the workflow, and the other is if a Terminate Activity exists within the workflow.
A workflow may not be-executing at all times. For example, when a DelayActi vi ty is encountered, the workflow will enter a wait state and can be removed from memory if a workflow persistence service is defined. Persistence of workflows is covered in “The Persistence Service” section later in this chapter.
State Machine Workflows
A state machine workflow is useful when you have a process that may be in one of several states, and transitions from one state to another can be made by passing data into the workflow.
One example is when a workflow is used for access control to a building. In this case, you may model a door class that can be closed or open, and a lock class that can be locked or unlocked. Initially when you boot up the.system (or building!), you start at a known state – for sake of argument, assume that all doors are closed and locked, so the state of a given door is closed locked.
When an employee enters his or her building access code at the front door, an event is sent to the workflow, which includes details such as the code entered and possibly the user ID. You might then need to access a database to retrieve defail such as whether that person is permitted to open the selected door at that time of day, and assuming that access is granted, the workflow would change from its initial state to the closed unlocked state.
From this state, there are two potential outcomes – the employee opens the door (you know this because the door also has an open/closed sensor), or the employee decides not to enter because he has left something in his car, and so after a delay you relock the door. The door could revert to its closed locked state or move to the open unlocked state.
From here, assume that the employee enters the building and then closes the door. Again, you would then like to transition from the open unlockld state to closed unlocked, and again, after a delay, would then transition to the closed locked state. You might also want to raise an alarm if the door was open unlocked for a long period.
Modeling this scenario within Windows Workflow is fairly simple. You need to define the-states that the System can be in, and then define events that can transition the workflow from one state to the next. The following table describes the states of the system and provides details of the transitions that are possible from each’known state and the inputs (either external or internal) that change the states.
One other feature you might want to add to the system is the capability to respond to a fire alarm. When the fire alarm goes off, you would want to unlock all of the doors so that anyone can exit the building, and the fire service can enter the building unimpeded. You might want to model this as the final state of the doors workflow, because from this state the full system would be reset once the fire alarm had been canceled.
The workflow in Figure 43-15 defines this state machine and shows the states that the workflow can be in. The lines denote the state transitions that are possible within the system.
The initial state of the workflow is modeled by the Closed Locked activity This consists of some initialization code (which locks the door) and then an event-based activity that awaits an external event – in this case, the employee entering his building access code. Each of the activities shown within the state shapes consist of sequential workflows, so we have defined a workflow for the initialization of the system (Clilnitialize) and a workflow that responds to the external event raised when the employee enters her PIN (RequestEntry). If you look at the RequestEntry workflow, it is defined as shown in Figure 43-16.
Each state consists of a number of subworkflows, each of which has an event-driven activity at the start and then any number of other activities that form the processing code within the state. In Figure 43-16, there is a HandleExternalEventActi vi ty at the start that awaits the entry of the PIN. This is then checked, and if it is valid, the workflow transitions to the ClosedUnlocked state.
The ClosedUnlocked state consists of two workflows – one that responds to the door open event, which tr~Uions the workflow to the OpenUnlockedstate, and the other, which contains a delay activity that is used to change the state to ClosedLocked. A state-driven activity works in a similar manner to the ListenActivity shown earlier in the chapter – the state consists of a number of event driven workflows, and on arrival of an event, just one of the workflows will execute. ,
To support the workflow, you need to be able to raise events in the system to aHect the state changes .. This is done by using an interface and an implementation of that interface; this pair of objects is termed an external service. The interface used for this state machine is described later in the chapter.
The code for the state machine example is available in the 04 solution. This also includes a user interface in which you can enter a PIN and gain access to the building through one of two doors.
Passing Parameters to a Workflow
A typical workflow requires some data in order to execute. This could be an order ID for an order processing workflow, a customer account ID for a payment-processing workflow, or any other items of
The parameter-passing mechanism for workflows is somewhat different from that of standard .NET classes, in which you typically pass parameters in a method call. For a workflow, you pass parameters by storing those parameters in a dictionary of name-value pairs, and when you construct the workflow, you pass through this dictionary.
When WF schedules the workflow for execution, it uses these name-value pairs to set public properties on the workflow instance. Each parameter name is checked against the public properties of the workflow, and if a match is found, the property setter is called and the value of the parameter is passed to this setter. If you add a name-value pair to the dictionary where the name does not correspond to a property on the workflow, an exception will be thrown when you try to construct that workflow.
As an example, consider the following workflow that defines the OrderID property as an integer:
The following snippet shows how you can pass the order ID parameter into an instance of this workflow:
In the example code, you construct a Dictionary<string, object> that will contain the parameters you wish to pass to the workflow and then use this when the workflow is constructed. The preceding code includes the WorkflowRuntime and Workflowlnstance classes, which haven’t been described yet but are discussed in the “Hosting Workflows” section later in the chapter.
Returning Results from a Workflow
Another common requirement of a workflow is to return output parameters, which might then be used to record data within a database or other persistent storage.
Because a workflow is executed by the workflow runtime, you can’t just call a workflow using a standard method invocation – you need to create a workflow instance, start that instance, and then await the completion of that instance. When a workflow completes, the workflow runtime raises the WorkflowCompleted event. This is passed contextual information about the workflow that has just completed and contains the output data from that workflow.
So, to harvest the output parameters from a workflow, you need to attach an event handler to the WorkflowCompleted event, and the handler can then retrieve the output parameters from the workflow.
The following code shows an example of how this can be done:
You have attached a delegate to the WorkflowCompleted event, and within this you iterate through the Output Parameters collection of the Workflow Completed EventArgs class passed to the delegate and display the output parameters on the console. This collection contains all public properties of the workflow. There is actually no notion of specific output parameters for a workflow.
Binding Parameters to Activities
Now that you know how to pass parameters into a workflow, you also need to know how to link these parameters to activities. This is done via a mechanism called binding. In the DaysOfWeekActivity
defined earlier, there was a Date property that could be hard-coded or bound to another value within . the workflow. A bind able property is displayed in the property grid within Visual Studio, as shown in Figure 43-17. The icon to the right of the property name indicates that this is a bind able property – in the image the Date property is bindable.
Double-clicking the bind icon will display the dialog shown in Figure 43-18.This dialog allows you to select an appropriate property to link to the Date property.
In Figure 43-18,we have selected the OrderDate property of the workflow (which is defined as a regular .NET property, as shown in an earlier code snippet). Any bindable property can be bound to either a property of the workflow that the activity is defined within or a property of any activity that resides in the workflow above the current activity. Note that the data type of the property being bound must match the data type of the property you are binding to – the dialog will not permit you to bind non matching types.
The code for the Date property is repeated here to show how binding works and is explained in the following paragraphs:
When you bind a property in the workflow, an object of type Activity Bind is constructed behind the scenes, and it is this “value” that is stored within the dependency property. So, the property setter will be passed an object of type Activity Bind, and this is stored within the dictionary of properties on this activity. This Activity Bind object consists of data that describes the activity being bound to and the property of that activity that is to be used at runtime.
When reading the value of the property, the GetValue method of the Dependency Object is called, and this method checks the underlying property value to see if it is an Activity Bind object. H so, it then resolves the activity to which this binding is linked and then reads the real property value from that activity. H, however, the bound value is another type, it simply returns that object from the GetValue.