Workflows


Up to this point in the chapter, I have concentrated on activities but 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, as 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 InstanceId 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.

There are two types of workflow available with WF - sequential and state machine.

Sequential Workflows

The root activity in a sequential workflow is the SequentialWorkflowActivity. This class is derived from SequenceActivity, 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 where 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 TerminateActivity exists within the workflow.

A workflow may not be executing at all times - for example, when a DelayActivity 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 the chapter.

State Machine Workflows

A state machine workflow is useful in the case where 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 where 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, this sends an event 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 details 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 as they have left something in their car, and so after a delay you relock the door. So, 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 unlocked 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 table below 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.

Open table as spreadsheet

State

Transitions

Closed Locked

This is the initial state of the system.

In response to the user swiping their card (and a successful access check), the state changes to closed unlocked and the door lock is electronically opened.

Closed Unlocked

One of two events can occur when the door is in this state:

  1. The user opens the door - you transition to the open unlocked state.

  2. A timer expires, and the door reverts to the closed locked state.

Open Unlocked

From this state, the workflow can only transition to closed unlocked.

Fire Alarm

This state is the final state for the workflow and can be transitioned to from any of the other 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, as from this state the full system would be reset once the fire alarm had been canceled.

The workflow in Figure 41-15 defines this state machine and shows the states that the workflow can be in, and the lines denote the state transitions that are possible within the system.

image from book
Figure 41-15

The initial state of the workflow is modeled by the ClosedLocked 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 their building access code. Each of the activities shown within the state shapes consist of sequential workflows - so I have defined a workflow for the initialization of the system (CLInitialize) and a workflow that responds to the external event raised when the employee enters their PIN (RequestEntry). If you look at the RequestEntry workflow it is defined, as shown in Figure 41-16.

image from book
Figure 41-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 41-16, there is a HandleExternalEventActivity 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 transitions the workflow to the OpenUnlocked state, 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 workflow will execute.

To support the workflow, you need to be able to raise events in the system to effect the state changes. This is done by using an interface and an implementation of that interface, and this pair of objects is termed an external service. I will describe the interface used for this state machine later in the chapter.

The code for the state machine example presented above is available in the 04 StateMachine solution. This also includes a user interface where 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 data necessary.

The parameter-passing mechanism for workflows is somewhat different from that of standard .NET classes, where 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 then 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, then 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:

  public class OrderProcessingWorkflow: SequentialWorkflowActivity {     public int OrderID     {         get { return _orderID; }         set { _orderID = value; }     }     private int _orderID; } 

The following snippet shows how you can pass the order ID parameter into an instance of this workflow:

  WorkflowRuntime runtime = new WorkflowRuntime (); Dictionary<string,object> parms = new Dictionary<string,object>(); parms.Add("OrderID", 12345) ; WorkflowInstance instance = runtime.CreateWorkflow(typeof(OrderProcessingWorkflow), parms); instance.Start(); ... Other code 

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 code above includes the WorkflowRuntime and WorkflowInstance classes, which I haven’t described as yet - these 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 - maybe these are used to then record data within a database or other persistent storage.

As 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 code below shows an example of how this can be done:

  using(WorkflowRuntime workflowRuntime = new WorkflowRuntime()) {     AutoResetEvent waitHandle = new AutoResetEvent(false);     workflowRuntime.WorkflowCompleted +=         delegate(object sender, WorkflowCompletedEventArgs e)         {             waitHandle.Set();             foreach (KeyValuePair<string, object> parm in e.OutputParameters)             {                 Console.WriteLine("{0} = {1}", parm.Key, parm.Value);             }         };     WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(Workflow1));     instance.Start();     waitHandle.WaitOne(); } 

You have attached a delegate to the WorkflowCompleted event, and within this you iterate through the OutputParameters collection of the WorkflowCompletedEventArgs 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 I mentioned could be hard-coded or bound to another value within the workflow. A bindable property is displayed in the property grid within Visual Studio, as shown in Figure 41-17. The blue icon next to the property name indicates that this is a bindable property.

image from book
Figure 41-17

Double-clicking on the bind icon will display the dialog shown in Figure 41-18. This dialog allows you to select an appropriate property to link to the Date property.

image from book
Figure 41-18

In Figure 41-18 I 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 below to show how binding works and is explained in the following paragraphs.

 public DateTime Date {     get { return (DateTime)base.GetValue(DaysOfWeekActivity.DateProperty); }     set { base.SetValue(DaysOfWeekActivity.DateProperty, value); } }

When you bind a property in the workflow, an object of type ActivityBind 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 ActivityBind, and this is stored within the dictionary of properties on this activity. This ActivityBind 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 DependencyObject is called, and this method checks the underlying property value to see if it is an ActivityBind object. If so, it then resolves the activity to which this binding is linked and then reads the real property value from that activity. If however the bound value is another type, it simply returns that object from the GetValue method.




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net