The base activity library in Windows Workflow contains general-purpose activities for constructing workflows. There are activities for control flow, transaction management, local communication, web services, and more. These activities appear on the toolbox window of the workflow designer. Some of these activities, like the CodeActivity, are simple. The job of the CodeActivity is to execute a block of code. Other activities, like the PolicyActivity, are more complex. The PolicyActivity can evaluate prioritized rules with forward chaining. We can build powerful workflows using just the activities inside the base activity library.
We are about to embark on a tour of base activity library. Many of the activities here deserve a more thorough coverage, but our goal for this tour is to understand the basic capabilities of each activity type, and come away with an idea of when we can use each one. We will start he tour with the most basic activities.
It might seem useful to execute arbitrary code in a workflow, but in reality the Code activity should appear relatively infrequently and as a special case. Instead of using Code activities, we should look to package code into custom activities (a topic for the future). Custom activities can expose properties and allow us to turn arbitrary code into a reusable activity.
Also, many of the activities we will see later will raise events before, and sometimes after, anything interesting happens. We can use these event handlers to write small pieces of customized code for pre and post processing, instead of dropping a new Code activity into the workflow. We will see an example of these event handlers soon.
The IfElse activity will execute the first branch whose condition property evaluates to true. If no conditions evaluate to true, then none of the branches will execute. The branches are evaluated from left to right. If the last branch has no condition, it will execute automatically if no other branches have run. We can add additional branches by right-clicking the IfElseActivity and selecting "Add Branch". We can remove branches by right-clicking a branch and selecting "Delete". Figure 2 shows an IfElse activity in action.
The Condition property of a branch can be either a declarative rule (which the designer persists to an external .rules file in XML format), or a code condition (an event handler). If we set the condition equal to Declarative Rule Condition, then we can launch the rule condition editor from the properties window and enter an expression. For instance, if the workflow has an integer property named Sales, we could enter an expression like the following.
Unlike the IfElseBranchActivity, the WhileActivity can hold only a single child activity inside. This restriction doesn't prevent us from using multiple activities inside of a loop, as we will see in the next section.
A host can subscribe to the workflow runtime's WorkflowSuspended event and retrieve the error message using the Error property of the WorkflowSuspendedEventArgs parameter. Another property of this parameter is the WorkflowInstance property. A host can recommence execution using the Resume method of the WorkflowInstance class, or bring about a sad, but early ending with the Terminate method.
The Terminate activity has an Error property of type string. A host can subscribe to the runtime's WorkflowTerminated event and examine the error. The event handler will receive a parameter of type WorkflowTerminatedEventArgs, and the runtime will wrap the error message into a WorkflowTerminatedException, which is available through the event argument's Exception property.
If we wanted a specific exception to arrive in the WorkflowTerminated event handler, we should use a Throw activity instead of a Terminate activity. However, there is a chance that the workflow can catch a thrown exception and continue, while the Terminate activity will always bring execution to a crashing halt.
If the exception goes unhandled and propagates out of the workflow, the WF runtime will catch the exception, terminate the workflow, and raise the WorkflowTerminated event. The runtime will also pass the thrown exception in the event arguments. The Fault property of the activity will reference the exception to throw. We can data-bind the Fault property to a field in our workflow, or to the property of another activity.
We can use the FaultType property to describe and restrict the exception types the activity will throw. If the FaultType is not set, the activity can throw any type of exception (as long as the type is System.Exception, or derived there from).
In the designer, we set the TargetWorkflow property to reference the workflow type we wish to execute. We can choose from workflow types defined in the same project, or in a referenced assembly. Once we've set the target, the designer will allow us to setup parameter bindings by looking at the public properties of the target type. We can bind fields and properties of our workflow as parameters for the target workflow. Before starting the second workflow, this activity will fire an Invoking event. We can use code inside the Invoking event handler to tweak and initialize the parameters.
As an example, let's pretend we are writing a workflow that requires a yes or no vote from three members of our company: the chief executive officer (CEO), the chief technology officer (CTO), and the chief financial officer (CFO). The host will deliver the votes to the workflow as events.
We could write the workflow so that it would wait for the votes to arrive sequentially - first the CEO, then the CTO, then the CFO. This means the CTO couldn't vote until the CEO cast a vote, and the CFO couldn't vote until the CTO cast a vote. If the CTO is away for a few days and can't vote, the CFO will have to wait. A word of advice from the author - making a CFO unhappy does not increase your chances of career advancement at the company.
If the order of the votes is not important, it would make more sense to let the software collect the votes as they arrive - in any order. The parallel activity in figure 4 4 will listen for three events simultaneously. Whichever officer votes first, the workflow will process the event and then wait for the other two events to arrive. The parallel activity will not finish until all three events have arrived.
Let's go back to our previous workflow example with the CEO, the CTO, and the CFO. Previously we needed a vote from all three officers before the workflow could continue. If we only needed a vote from one of the three officers, the Listen activity would be a better fit. When one of the events arrives, the activity will execute the branch associated with the event and cancel the execution of the other branches.
As alluded to earlier, we can use a Delay activity inside of a Listen activity and simulate a timeout. This arrangement is shown in figure 5. If the delay timer expires before any of the other events arrive, we can take an alternative action, perhaps by emailing a reminders to vote, or moving ahead with a default choice. Silence is consent!
Imagine we are setting up a workflow that will count employee votes over a period of 30 minutes. We could set the main child activity of the Event Handling Scope activity as a Delay activity, with a 30-minute timeout. We can then place event handling activities in the event branches that listen for Yes and No votes. This activity will continues to listen for the Yes and No events until the Delay activity completes in 30 minutes.
The SynchrnoizationHandles property contains the handles that the workflow will acquire before it executes, and release upon completion. A synchronization handle is a simple string object, and the property maintains a collection of strings. Internally, the WF runtime will use each string as the key to a Dictionary of locks. If the activity cannot acquire all the locks specified by the handles, it will wait until it can acquire the locks.
Think back to the example we talked about for the Parallel activity. We needed a vote from exactly three officers of the company before the workflow could proceed. The Replicator is a better fit when we don't know how many events we need to process until runtime. Perhaps a user is checking off the required voters from a company wide directory. A Replicator can create the required number of event listeners we need from the list of voters.
The InitialChildData property will hold the list of data objects for the Replicator to process. The Replicator will create a clone of its child activity to process each item. The Replicator will not finish execution until all the children have finished, however, there is an UntilCondition property that the Replicator will evaluate before starting, and after completion of each child. If the UntilCondition returns true, the Replicator will finish execution, even if it leaves children unprocessed. Like other conditions in WF, the UntilCondition can be a rule condition or a code condition.
The Replicator fires a number of useful events, including Initialized, Completed, ChildInitialized, and ChildCompleted. The ChildInitialized event is a good time to populate the cloned child activity with the data it needs to execute.
For local communication to work, we need to define a contract in the form of a .NET interface. The interface will define the methods that a workflow can invoke on a local service, and the events that a local service can raise to a workflow.
Let's say we are working on a workflow for a bug tracking system. At some point, a bug might need detailed information, like a screen shot, uploaded to the application. If the workflow needs this additional documentation, the workflow can ask the host to upload the document. The host might upload the documents itself, but more than likely it will notify a user that the bug requires more information. In either case, the workflow will have to wait (perhaps a few seconds, perhaps a few days or longer), for the uploaded document to arrive. The host can let the workflow know when the upload is complete via an event. The following interface defines the communication contract we need to enable this scenario:
The InterfaceType property should be set first, as this will allow the designer to discover the available methods on the service. Once we set InterfaceType to the interface we defined, we can select the method to call in the MethodName property. The designer will then populate the Parameters area of the property window. We can bind all the input parameters, and the method return value, to fields and properties in our workflow. The uploadRequested, id, and userName fields are all member variables in the code-behind class of our workflow.
For the Call External Method activity to work, we will need to add the ExternalDataExchangeService to the workflow runtime, and add a service that implements our interface to the data exchange service, as shown in the code below. The BugFlowService class implements the IBugService interface.
Handle External Event is a blocking activity, meaning the workflow is not going to proceed until the event arrives from a local service. If there is a chance the event will never arrive, or if the event needs to arrive within a span of time, then it's best to use this activity inside of a ListenActivity. As we described earlier, the Listen activity has multiple branches, and we can place a DelayActivity in one of the branches to simulate a timeout.
We can pass wca.exe the path to a .NET assembly (.dll) and the tool will look through the assembly for interfaces decorated with the ExternalDataExchange attribute. When the tool finds such an interface it will generate dedicated activities for calling the methods and handling the events of the interface.
For our IBugService interface, the tool will generate a RequestUploadActivity and an UploadCompletedActivity. The tool generates the activities as source code files that we can include in our project. The activities will have their InterfaceType and EventName or MethodName properties pre-populated, and include properties for all parameters in the communications.
Run wca.exe with no parameters to see a list of options.
It is always a bad idea to blindly handle faults if we don't have a recovery plan. This is akin to swallowing exceptions in C# or Visual Basic. If the workflow throws an exception that we don't know how to handle, it is best to let the exception run its course and have the runtime terminate the workflow.
We can view the Fault Handlers activity by right clicking an activity and selecting "View Faults". There is also a shortcut available to view fault handlers at the workflow level. The third tab from the left in the bottom of the workflow designer will take us to the fault handlers for the workflow (see figure 8). Inside of this view we can use Fault Handler activities, discussed next.
The FaultHandlerActivity has a FaultType property. This property represents the type of exception we want to catch. If we set the FaultType property to the System.Exception type, we will handle all CLS compliant exceptions. The handler will catch all exceptions of type FaultType, or any exception deriving from FaultType. The Fault property of this activity will let us bind the caught exception to a field or property.
The runtime will evaluate Fault Handlers in a left-to-right order. If the first Fault Handler has a FaultType of System.Exception, it will catch any exception and the runtime won't need to evaluate the other fault handlers. This is similar to how the multiple catch statements work in C# or Visual Basic - the catch blocks are evaluated in the order they appear until a match is found, and then no other catch statements are evaluated.
If the Transaction Scope activity finishes with no errors it will automatically commit the transaction. If an exception occurs inside the scope and is not caught by a fault handler inside the scope, the activity will abort the transaction and rollback any work.
Before we can make effective use of a Compensate activity, we need to look at the Compensation Handler activity for a transaction. A Compensation Handler activity contains the activities the runtime will execute to reverse a transaction. We can add the compensating activities by right-clicking a Transaction Scope activity and selecting "View Compensation" from the context menu. Figure 10 shows an empty Compensation Handler activity for a Transaction Scope activity.
Any activity implementing the ICompensatableActivity can have an associated Compensation Handler activity. In the WF Base Activity Library, only the Transaction Scope activity implements this interface, but we can write custom activities with the interface.
Once we have activities inside of the Compensation Handler activity, we can trigger compensation with the Compensate activity. The Compensate activity's TargetActivityName property will direct the workflow to the ICompensatableActivity that needs reversed. The runtime will then execute the activities inside the Compensation Handler activity.
We can only place Compensate activities inside of Compensation Handlers and Fault Handlers. Inside the Compensation Handler of a Transaction Scope activity, the Compensate activity can direct the reversal of other nested transactions. Inside a Fault Handler, the Compensate activity can direct the reversal of any in-scope transaction.
The WhenCondition and UntilCondition properties can use either declarative rules or code conditions. If we do not specify a WhenCondition for an activity, the activity will execute only once. If we do not specify an UntilCondition for the CAG, the CAG will continue to execute until all its activity's WhenCondition conditions return false.
We can click on each activity in the CAG's storyboard to set its WhenCondition, and also to preview or edit the activity in the bottom half of the CAG display. The small button in the middle of the CAG will toggle between preview and edit modes. In edit mode we can click on the activity to set properties, or in the case of a composite activity like the Sequence activity, we can drag additional children inside.
The CAG will revaluate its UntilCondition each time a child activity completes. As soon as the condition returns true the CAG will cancel any currently executing activities and close.
Notice each rule has a priority assigned. Rules with a higher priority will execute before rules with a lower priority. Each rule has an If-Then-Else format, and we can assign actions to both the Then and Else results. A rule's actions can modify the fields and properties of a workflow, or the fields and properties of an object inside the workflow. Actions can also invoke methods.
By default, the Policy activity will execute the rule set using full forward chaining. If a rule changes the value of a field that some previous rule depended upon, the Policy activity will re-evaluate the previous rule. The Policy activity supports different forward chaining strategies, and each rule has a re-evaluation setting to control the number of times it can be re-evaluated.
The Web Service Activity includes Invoking and Invoked event handlers that fire before and after the web service call, respectively. We can use these events to pre and post process the parameters of the web service.
Visual Studio 2005 allows us to right-click a workflow project and select "Publish As Web Service". This command creates an ASP.NET project, complete with .asmx and web.config files, that will host our workflow as a web service.
A state machine consists of a set of states. For instance, a state machine to model the workflow of a software bug might include the states open, assigned, closed, and deferred. The workflow must always be in one of these four states. State machines are completely event driven. Only when the workflow receives an event can the current state transition to a new state. A state machine must have an initial state, and optionally an ending state. When the state machine transitions to the ending state, the workflow is complete.
State machine workflows are a good fit for modeling a process where decisions come from outside the workflow. When we make a decision, like closing a bug, we have a local communication service raise an event to the workflow. The workflow keeps track of which state it is in, and which states it can transition into from the current state. For instance, we might say that an open bug has to be assigned before it can be closed, but it can move from the open state directly to the deferred state. The first step in setting up a state machine is defining the states.
Every state machine workflow needs an initial state. We can set the initial state using the InitialStateName property of the workflow itself (see figure 14). We can optionally set the CompletedStateName to a state that represents completion of the workflow. The state machine in figure 14 has the four State activities for bug tracking: OpenState, AssignedState, DeferredState, and ClosedState.
Inside of each state, we can place the activities described below. Notice we can include a State activity inside of a State activity. The recursive composition of states allows contained states to inherit the events and behavior of their containing state.
A State activity can contain more then one Event Driven activity inside. For example, our OpenState state will have two Event Driven activities inside - one to handle a BugAssigned event, and one to handle a BugDeferred event. We do not allow the OpenState to handle a BugClosed event, because we don't want to transition from open to closed without going through the assigned state.
In figure 16, we've double-clicked on an Event Driven activity in OpenState to configure an event handler for the BugAssigned event. The event is part of a communication interface we've built with the ExternalDataExchange attribute, just as we did earlier in the section covering the Handle External Event activity. Notice the last activity inside the sequence is a SetState activity, which we cover next.
The workflow view of a state machine will use examine the possible state transitions and draw lines to represent state transitions. In figure 17, we can see that state machine will only transition to the ClosedState activity from the AssignedState activity. We also see lines representing all the other state transitions.
We are about to embark on a tour of base activity library. Many of the activities here deserve a more thorough coverage, but our goal for this tour is to understand the basic capabilities of each activity type, and come away with an idea of when we can use each one. We will start he tour with the most basic activities.
The Basics
These activities model primitive operations that exist in almost every programming environment, like conditional branching, looping, and grouping of sub-activities. We will start with an activity that appears many times in these samples, the CodeActivity.The Code Activity
The Code activity's only interesting feature is its ExecuteCode event. We will need to pair the event with an event handler before the activity will pass validation. In the workflow designer, we can double-click on a Code activity, and Visual Studio will create and assign the event handler for us - all we need to do is write the code. The following code is an event handler for ExecuteCode that displays a on the screen.private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{ Console.WriteLine("Hello, World");
}
Figure 1 shows the Code activity as it appears in the designer. A red exclamation point hovers above the top right of the activity because we have not assigned an ExecuteCode event handler, and the activity is failing validation. Anytime an exclamation point appears, we can click with the mouse to find the reason for the validation error. { Console.WriteLine("Hello, World");
}
It might seem useful to execute arbitrary code in a workflow, but in reality the Code activity should appear relatively infrequently and as a special case. Instead of using Code activities, we should look to package code into custom activities (a topic for the future). Custom activities can expose properties and allow us to turn arbitrary code into a reusable activity.
Also, many of the activities we will see later will raise events before, and sometimes after, anything interesting happens. We can use these event handlers to write small pieces of customized code for pre and post processing, instead of dropping a new Code activity into the workflow. We will see an example of these event handlers soon.
The IfElseActivity
The IfElse activity is similar to the If...Then...Else statement in Visual Basic, and the if-else in C#. Inside of an IfElse activity are one or more IfElseBranch activities. Each branch activity has a Condition property. We are required to set the condition property on all branches, except for the last branch.The IfElse activity will execute the first branch whose condition property evaluates to true. If no conditions evaluate to true, then none of the branches will execute. The branches are evaluated from left to right. If the last branch has no condition, it will execute automatically if no other branches have run. We can add additional branches by right-clicking the IfElseActivity and selecting "Add Branch". We can remove branches by right-clicking a branch and selecting "Delete". Figure 2 shows an IfElse activity in action.
The Condition property of a branch can be either a declarative rule (which the designer persists to an external .rules file in XML format), or a code condition (an event handler). If we set the condition equal to Declarative Rule Condition, then we can launch the rule condition editor from the properties window and enter an expression. For instance, if the workflow has an integer property named Sales, we could enter an expression like the following.
this.Sales > 5000
The same condition written as a Code Condition would look like the following code. private void checkSalesAmount(object sender, ConditionalEventArgs e)
{
e.Result = Sales > 5000;
}
The activity raises an event to evaluate a Code Condition. We can return the outcome of the condition as a true or false value in the event argument's Result property. {
e.Result = Sales > 5000;
}
WhileActivity
Like the IfElse activity, the While activity has a Condition property which can be either a declarative rule or a code condition. The WhileActivity will continue to run as long as the condition returns true. This activity will evaluate the condition before each iteration (see Figure 3).Unlike the IfElseBranchActivity, the WhileActivity can hold only a single child activity inside. This restriction doesn't prevent us from using multiple activities inside of a loop, as we will see in the next section.
SequenceActivity
A Sequence activity is a composite activity, and can run one or more contained activities. The activities inside a sequence activity will execute one at a time, until the last activity completes. The WhileActivity only permits a single child activity, but if we make the single activity a SequenceActivity, we can drop additional activities inside the sequence activity. All of the children will then run each time the WhileActivity completes an iteration.SuspendActivity
The Suspend activity will halt the execution of a workflow. A Suspend activity might be useful when a workflow encounters an error that requires manual intervention. The activity has an Error property, which is a string.A host can subscribe to the workflow runtime's WorkflowSuspended event and retrieve the error message using the Error property of the WorkflowSuspendedEventArgs parameter. Another property of this parameter is the WorkflowInstance property. A host can recommence execution using the Resume method of the WorkflowInstance class, or bring about a sad, but early ending with the Terminate method.
TerminateActivity
Like the Suspend activity, the Terminate activity will halt the execution of a workflow. Unlike a suspended workflow, a host cannot resume a terminated workflow. We can use this activity if a workflow reaches a point where it cannot continue and has no hope of recovery.The Terminate activity has an Error property of type string. A host can subscribe to the runtime's WorkflowTerminated event and examine the error. The event handler will receive a parameter of type WorkflowTerminatedEventArgs, and the runtime will wrap the error message into a WorkflowTerminatedException, which is available through the event argument's Exception property.
If we wanted a specific exception to arrive in the WorkflowTerminated event handler, we should use a Throw activity instead of a Terminate activity. However, there is a chance that the workflow can catch a thrown exception and continue, while the Terminate activity will always bring execution to a crashing halt.
ThrowActivity
The Throw activity is similar to the throw statements in C# and Visual Basic - the activity raises an exception. Why should we use a Throw activity when we could throw from the ExecuteCode event of a Code activity? Because using a Throw activity makes the exception an explicit piece of the workflow model.If the exception goes unhandled and propagates out of the workflow, the WF runtime will catch the exception, terminate the workflow, and raise the WorkflowTerminated event. The runtime will also pass the thrown exception in the event arguments. The Fault property of the activity will reference the exception to throw. We can data-bind the Fault property to a field in our workflow, or to the property of another activity.
We can use the FaultType property to describe and restrict the exception types the activity will throw. If the FaultType is not set, the activity can throw any type of exception (as long as the type is System.Exception, or derived there from).
InvokeWorkflowActivity
The Invoke Workflow activity will asynchronously execute another workflow. Since the execution is asynchronous, we cannot retrieve output parameters from the other workflow, although we could setup additional communication mechanisms with the host to make the output available.In the designer, we set the TargetWorkflow property to reference the workflow type we wish to execute. We can choose from workflow types defined in the same project, or in a referenced assembly. Once we've set the target, the designer will allow us to setup parameter bindings by looking at the public properties of the target type. We can bind fields and properties of our workflow as parameters for the target workflow. Before starting the second workflow, this activity will fire an Invoking event. We can use code inside the Invoking event handler to tweak and initialize the parameters.
ParallelActivity
The Parallel activity allows multiple activities to execute at the same time. This does not mean the Parallel activity permits parallel processing across multiple threads - only a single thread will execute inside of a workflow. Instead, the Parallel activity allows separate branches inside the activity to execute independently.As an example, let's pretend we are writing a workflow that requires a yes or no vote from three members of our company: the chief executive officer (CEO), the chief technology officer (CTO), and the chief financial officer (CFO). The host will deliver the votes to the workflow as events.
We could write the workflow so that it would wait for the votes to arrive sequentially - first the CEO, then the CTO, then the CFO. This means the CTO couldn't vote until the CEO cast a vote, and the CFO couldn't vote until the CTO cast a vote. If the CTO is away for a few days and can't vote, the CFO will have to wait. A word of advice from the author - making a CFO unhappy does not increase your chances of career advancement at the company.
If the order of the votes is not important, it would make more sense to let the software collect the votes as they arrive - in any order. The parallel activity in figure 4 4 will listen for three events simultaneously. Whichever officer votes first, the workflow will process the event and then wait for the other two events to arrive. The parallel activity will not finish until all three events have arrived.
DelayActivity
The Delay activity will initialize a timer and wait asynchronously for the timer to expire. The TimeoutDuration property contains the TimeSpan that represents the amount of time to wait. We can initialize the property in the designer, or programmatically by assigning an event handler for the InitializeTimeoutDuration, shown below.private void delayActivity1_InitializeTimeoutDuration(object sender, EventArgs e)
{ // a 5 second timeout this.delayActivity1.TimeoutDuration = newTimeSpan(0,0,5);
}
We often find a Delay activity inside of a Listen activity. { // a 5 second timeout this.delayActivity1.TimeoutDuration = newTimeSpan(0,0,5);
}
ListenActivity
Like the Parallel activity, the Listen activity can contain multiple branches. Unlike the Parallel activity, the goal of a Listen activity is to finish just one branch. The branches of a Listen activity are Event Driven activities, and we must start the branches by waiting for an event (the first child must implement the IEventActivity interface). We'll see the Event driven activity in more detail when we cover state machine workflows.Let's go back to our previous workflow example with the CEO, the CTO, and the CFO. Previously we needed a vote from all three officers before the workflow could continue. If we only needed a vote from one of the three officers, the Listen activity would be a better fit. When one of the events arrives, the activity will execute the branch associated with the event and cancel the execution of the other branches.
As alluded to earlier, we can use a Delay activity inside of a Listen activity and simulate a timeout. This arrangement is shown in figure 5. If the delay timer expires before any of the other events arrive, we can take an alternative action, perhaps by emailing a reminders to vote, or moving ahead with a default choice. Silence is consent!
EventHandlingScopeActivity
The Event Handling Scope activity is similar to a Listen activity in that it can have multiple branches waiting for events in parallel. We can view these branches by right-clicking the activity and selecting "View Events". The primary difference between this activity and a Listen activity is that this event continues to listen for all events until its main child activity (the default view) finishes execution.Imagine we are setting up a workflow that will count employee votes over a period of 30 minutes. We could set the main child activity of the Event Handling Scope activity as a Delay activity, with a 30-minute timeout. We can then place event handling activities in the event branches that listen for Yes and No votes. This activity will continues to listen for the Yes and No events until the Delay activity completes in 30 minutes.
SynchronizationScopeActivity
Like the threading synchronization primitives in the .NET libraries, the Synchronization Scope activity can serialize access to shared resources, even across workflow instances. If we have a static (C#) or shared (Visual Basic) field in our workflow definition, the Synchronization Scope can ensure only a single instance will have read and write access to the field.The SynchrnoizationHandles property contains the handles that the workflow will acquire before it executes, and release upon completion. A synchronization handle is a simple string object, and the property maintains a collection of strings. Internally, the WF runtime will use each string as the key to a Dictionary of locks. If the activity cannot acquire all the locks specified by the handles, it will wait until it can acquire the locks.
ReplicatorActivity
The Replicator activity is similar to the While activity, but more sophisticated. The Replicator can process a collection of data either sequentially or in parallel, depending on the setting of the ExecutionType property.Think back to the example we talked about for the Parallel activity. We needed a vote from exactly three officers of the company before the workflow could proceed. The Replicator is a better fit when we don't know how many events we need to process until runtime. Perhaps a user is checking off the required voters from a company wide directory. A Replicator can create the required number of event listeners we need from the list of voters.
The InitialChildData property will hold the list of data objects for the Replicator to process. The Replicator will create a clone of its child activity to process each item. The Replicator will not finish execution until all the children have finished, however, there is an UntilCondition property that the Replicator will evaluate before starting, and after completion of each child. If the UntilCondition returns true, the Replicator will finish execution, even if it leaves children unprocessed. Like other conditions in WF, the UntilCondition can be a rule condition or a code condition.
The Replicator fires a number of useful events, including Initialized, Completed, ChildInitialized, and ChildCompleted. The ChildInitialized event is a good time to populate the cloned child activity with the data it needs to execute.
Local Communication Events
When it comes time for a workflow to communicate with the outside world, there are a handful of built-in activities to do the job. The activities we discuss in this section will communicate with local services provided by the hosting process.For local communication to work, we need to define a contract in the form of a .NET interface. The interface will define the methods that a workflow can invoke on a local service, and the events that a local service can raise to a workflow.
Let's say we are working on a workflow for a bug tracking system. At some point, a bug might need detailed information, like a screen shot, uploaded to the application. If the workflow needs this additional documentation, the workflow can ask the host to upload the document. The host might upload the documents itself, but more than likely it will notify a user that the bug requires more information. In either case, the workflow will have to wait (perhaps a few seconds, perhaps a few days or longer), for the uploaded document to arrive. The host can let the workflow know when the upload is complete via an event. The following interface defines the communication contract we need to enable this scenario:
[ExternalDataExchange]interfaceIBugService{ bool RequestUpload(Guid id, string userName); event EventHandler<UploadCompletedEventArgs> UploadCompleted;
}
The two activities to interact with the interface are the CallExternalMethodActivity and HandleExternalEventActivity.}
CallExternalMethodActivity
The Call External Method activity invokes a method on a local service. All we need to do is setup the properties of the activity, as shown in figure 6.The InterfaceType property should be set first, as this will allow the designer to discover the available methods on the service. Once we set InterfaceType to the interface we defined, we can select the method to call in the MethodName property. The designer will then populate the Parameters area of the property window. We can bind all the input parameters, and the method return value, to fields and properties in our workflow. The uploadRequested, id, and userName fields are all member variables in the code-behind class of our workflow.
For the Call External Method activity to work, we will need to add the ExternalDataExchangeService to the workflow runtime, and add a service that implements our interface to the data exchange service, as shown in the code below. The BugFlowService class implements the IBugService interface.
ExternalDataExchangeService dataService = newExternalDataExchangeService();
workflowRuntime.AddService(dataService);
BugFlowService bugService = new BugFlowService();
dataService.AddService(bugService);
The Call External Method activity includes a MethodInvoking event. The event will fire just before the activity calls the external method, and gives us an opportunity to setup the parameters. We might add code like the following to the event. workflowRuntime.AddService(dataService);
BugFlowService bugService = new BugFlowService();
dataService.AddService(bugService);
private void callExternalMethodActivity1_MethodInvoking( object sender, EventArgs e)
{
id = this.WorkflowInstanceId;
userName = "Scott";
}
{
id = this.WorkflowInstanceId;
userName = "Scott";
}
HandleExternalEventActivity
The Handle External Event activity, like the Call External Method activity, has an InterfaceType property we must set. Once we have set this property we can set the EventName property (see figure 7).Handle External Event is a blocking activity, meaning the workflow is not going to proceed until the event arrives from a local service. If there is a chance the event will never arrive, or if the event needs to arrive within a span of time, then it's best to use this activity inside of a ListenActivity. As we described earlier, the Listen activity has multiple branches, and we can place a DelayActivity in one of the branches to simulate a timeout.
The Activity Generator
The Windows Workflow SDK includes a command line tool called the Windows Workflow Communications activity generator. This tool runs from the command line, and you can find it in the SDK directory with a name of wca.exe.We can pass wca.exe the path to a .NET assembly (.dll) and the tool will look through the assembly for interfaces decorated with the ExternalDataExchange attribute. When the tool finds such an interface it will generate dedicated activities for calling the methods and handling the events of the interface.
For our IBugService interface, the tool will generate a RequestUploadActivity and an UploadCompletedActivity. The tool generates the activities as source code files that we can include in our project. The activities will have their InterfaceType and EventName or MethodName properties pre-populated, and include properties for all parameters in the communications.
Run wca.exe with no parameters to see a list of options.
Roles
The Roles property of this activity can bind to a WorkflowRoleCollection object and allow the runtime to perform role-based authorization checks. The runtime compares the role memberships of the incoming identity against the allowable roles defined in the collection. The collection holds objects derived from the abstract WorkflowRole class. WF provides two concrete implementations of WorkflowRole with the ActiveDirectoryRole and WebWorkflowRole classes. These classes work with Active Directory and ASP.NET 2.0 Role Providers, respectively. If authorization fails, the runtime will throw a WorkflowAuthorizationException exception.Fault Handling
Although fault handling is arguably a type of control flow, this section is dedicated to these activities so we can dive in with more detail. Fault handling in Windows Workflow handles exceptions that occur during execution. We can catch exceptions with fault handlers, and perhaps try to recover from the error. We might try to compensate for a committed transaction, or send an alert to an administrative email address and wait for a missing file to reappear.It is always a bad idea to blindly handle faults if we don't have a recovery plan. This is akin to swallowing exceptions in C# or Visual Basic. If the workflow throws an exception that we don't know how to handle, it is best to let the exception run its course and have the runtime terminate the workflow.
FaultHandlersActivity
The Fault Handlers activity isn't an activity we can drag from the Toolbox into the workflow designer. Instead, the workflow designer will provide the activity for us when the condition is right. Many composite activities (like the WhileActivity, ListenActivity, SequenceActivity, TransactionScopeActivity, and others) can handle faults from their child activities using a fault handlers viewWe can view the Fault Handlers activity by right clicking an activity and selecting "View Faults". There is also a shortcut available to view fault handlers at the workflow level. The third tab from the left in the bottom of the workflow designer will take us to the fault handlers for the workflow (see figure 8). Inside of this view we can use Fault Handler activities, discussed next.
FaultHandlerActivity
A Fault Handler activity is analogous to a catch statement in C# or Visual Basic. The activity can trap an exception and perform some processing. When we are in the Fault Handlers view, we can drag a Fault Handler from the toolbox into the area saying "Drop FaultHandlerActivity Here". This area is the Fault Handlers storyboard. We can drop more than one Fault Handler into the storyboard, and click on the different handlers inside to select the handler we want to edit. Each handler has its own set of child activities that appear below the storyboard. We can drop activities in this area below the storyboard to perform different types of work for each fault handler. This is akin to the code inside of a catch block.The FaultHandlerActivity has a FaultType property. This property represents the type of exception we want to catch. If we set the FaultType property to the System.Exception type, we will handle all CLS compliant exceptions. The handler will catch all exceptions of type FaultType, or any exception deriving from FaultType. The Fault property of this activity will let us bind the caught exception to a field or property.
The runtime will evaluate Fault Handlers in a left-to-right order. If the first Fault Handler has a FaultType of System.Exception, it will catch any exception and the runtime won't need to evaluate the other fault handlers. This is similar to how the multiple catch statements work in C# or Visual Basic - the catch blocks are evaluated in the order they appear until a match is found, and then no other catch statements are evaluated.
Transactions and Compensation
Traditional ACID transactions (atomic, consistent, isolated, and durable) are available in Windows Workflow. Under the covers, the runtime makes use of the Transaction class in the System.Transactions namespace. The Transaction class can manage transactions across different types of durable stores, including Microsoft SQL Server and other relational databases, and products like Microsoft Message Queuing. The Transaction class can make use of the Microsoft Distributed Transaction Coordinator (MSDTC) for heavy-weight two phase commit transactions, when needed.TransactionScopeActivity
Like the TransactionScope class of System.Transactions, the Transaction Scope activity will begin a new transaction and implicitly enlist the activities it contains into the transaction. The TransactionOptions property controls the timeout and the isolation level of the transaction (see figure 9).If the Transaction Scope activity finishes with no errors it will automatically commit the transaction. If an exception occurs inside the scope and is not caught by a fault handler inside the scope, the activity will abort the transaction and rollback any work.
CompensateActivity
In a long running workflow, we can't leave a transaction open for hours, or days, or weeks at a time. What we will typically do is commit the transaction as soon as possible and move on with execution. At some later point, however, if an error might occurs, we might need to reverse the work we committed inside the transaction. The Compensate activity is designed to take care of reversing transactions.Before we can make effective use of a Compensate activity, we need to look at the Compensation Handler activity for a transaction. A Compensation Handler activity contains the activities the runtime will execute to reverse a transaction. We can add the compensating activities by right-clicking a Transaction Scope activity and selecting "View Compensation" from the context menu. Figure 10 shows an empty Compensation Handler activity for a Transaction Scope activity.
Any activity implementing the ICompensatableActivity can have an associated Compensation Handler activity. In the WF Base Activity Library, only the Transaction Scope activity implements this interface, but we can write custom activities with the interface.
Once we have activities inside of the Compensation Handler activity, we can trigger compensation with the Compensate activity. The Compensate activity's TargetActivityName property will direct the workflow to the ICompensatableActivity that needs reversed. The runtime will then execute the activities inside the Compensation Handler activity.
We can only place Compensate activities inside of Compensation Handlers and Fault Handlers. Inside the Compensation Handler of a Transaction Scope activity, the Compensate activity can direct the reversal of other nested transactions. Inside a Fault Handler, the Compensate activity can direct the reversal of any in-scope transaction.
Conditions and Rules
Two activities in Windows Workflow thrive on conditions and rules. These activities are the Policy Activity and the Conditioned Activity Group (CAG). Although we could have listed the CAG as a control flow element, the CAG doesn't control the flow of execution as much as it is controlled by conditions and rules.ConditionedActivityGroup
The CAG is a powerful activity that can use a combination of rules and code to reach a goal. The CAG conditionally executes activities until a condition evaluates to true. Inside of the CAG is a storyboard where we can drop activities for execution (see figure 11). The CAG associates a WhenCondition with each activity, and the CAG will only execute an activity if the activity's WhenCondition evaluates to true. The CAG continues to re-evaluate the WhenCondition and re-execute the storyboard activities until its own UntilCondition evaluates to true.The WhenCondition and UntilCondition properties can use either declarative rules or code conditions. If we do not specify a WhenCondition for an activity, the activity will execute only once. If we do not specify an UntilCondition for the CAG, the CAG will continue to execute until all its activity's WhenCondition conditions return false.
We can click on each activity in the CAG's storyboard to set its WhenCondition, and also to preview or edit the activity in the bottom half of the CAG display. The small button in the middle of the CAG will toggle between preview and edit modes. In edit mode we can click on the activity to set properties, or in the case of a composite activity like the Sequence activity, we can drag additional children inside.
The CAG will revaluate its UntilCondition each time a child activity completes. As soon as the condition returns true the CAG will cancel any currently executing activities and close.
PolicyActivity
The Policy activity is a rules engine that allows us to separate business logic from the workflow and declaratively define a business policy. Rules are everywhere in the business world - from calculating loan approvals to admitting patients into a hospital. A Rule Set is a collection of rules for the Policy activity to execute, and each rule has conditions and actions. We can edit the rules using the WF Rule Set editor, shown in figure 12.Notice each rule has a priority assigned. Rules with a higher priority will execute before rules with a lower priority. Each rule has an If-Then-Else format, and we can assign actions to both the Then and Else results. A rule's actions can modify the fields and properties of a workflow, or the fields and properties of an object inside the workflow. Actions can also invoke methods.
By default, the Policy activity will execute the rule set using full forward chaining. If a rule changes the value of a field that some previous rule depended upon, the Policy activity will re-evaluate the previous rule. The Policy activity supports different forward chaining strategies, and each rule has a re-evaluation setting to control the number of times it can be re-evaluated.
Web Services
No product would be complete today if it did not send or receive SOAP envelopes over HTTP. WF includes a number of activities that revolve around web services, both as a consumer and a producer.InvokeWebServiceActivity
The Invoke Web Service activity can call an external web service. When we drop the activity into the workflow designer, the familiar Visual Studio Add Web Reference dialog box will appear. This same dialog appears when we add a web reference to any type of .NET project in Visual Studio. We merely need to browse to the Web Service Definition Language (WSDL) description of the web service. Visual Studio will retrieve the WSDL and generate a proxy class for the service. We then configure the activity with the method name to invoke, and bind parameters to field or properties in our workflow (see figure 13).The Web Service Activity includes Invoking and Invoked event handlers that fire before and after the web service call, respectively. We can use these events to pre and post process the parameters of the web service.
WebServiceInputActivity
The Web Service Input activity enables a workflow to receive a web service request. Just like the local communication activities we described earlier, this activity will first require us to define a contract (an interface). The activity will implement the interface. Once we've set the InterfaceType property, we can pick a method from the interface for the MethodName property, and then bind the incoming parameters to fields or properties.Visual Studio 2005 allows us to right-click a workflow project and select "Publish As Web Service". This command creates an ASP.NET project, complete with .asmx and web.config files, that will host our workflow as a web service.
WebServiceOutputActivity
A Web Service Output activity pairs with a Web Service Input activity to respond to a service request. We cannot use this activity without first configuring a Web Service Input activity in our workflow. This activity has an InputActivityName property that will pair the activity with it's input. The designer will then know the interface and method name we are implementing, and allow us to bind the ReturnValue property. The ReturnValue property is the web service response.WebServiceFaultActivity
The Web Service Fault activity allows us to raise an exception that the runtime will package into a SOAP exception. Like the output activity we just described, the Web Service Fault activity will pair with an input activity via the InputActivityName property. The Fault property will reference the exception we want to raise.State Activities
All of the workflows we've examined so far have been sequential workflows. Windows Workflow also supports state machine workflows, which is where the activities in this section come into play.A state machine consists of a set of states. For instance, a state machine to model the workflow of a software bug might include the states open, assigned, closed, and deferred. The workflow must always be in one of these four states. State machines are completely event driven. Only when the workflow receives an event can the current state transition to a new state. A state machine must have an initial state, and optionally an ending state. When the state machine transitions to the ending state, the workflow is complete.
State machine workflows are a good fit for modeling a process where decisions come from outside the workflow. When we make a decision, like closing a bug, we have a local communication service raise an event to the workflow. The workflow keeps track of which state it is in, and which states it can transition into from the current state. For instance, we might say that an open bug has to be assigned before it can be closed, but it can move from the open state directly to the deferred state. The first step in setting up a state machine is defining the states.
StateActivity
The State activity represents one of the states in a state machine. For our bug tracking workflow, we would have four State activities to drop into the designer - one for open, closed, deferred, and assigned. Unlike a sequential workflow, we can place these activities anywhere on the design surface, because the state machine doesn't move from one activity to the next in any specific order. We will need to define the legal state transitions.Every state machine workflow needs an initial state. We can set the initial state using the InitialStateName property of the workflow itself (see figure 14). We can optionally set the CompletedStateName to a state that represents completion of the workflow. The state machine in figure 14 has the four State activities for bug tracking: OpenState, AssignedState, DeferredState, and ClosedState.
Inside of each state, we can place the activities described below. Notice we can include a State activity inside of a State activity. The recursive composition of states allows contained states to inherit the events and behavior of their containing state.
StateInitializationActivity
The State Initialization activity is a sequence activity that contains child activities. When the state machine transitions to a state, the children inside the initialization activity will execute. We can only have one initialization activity per state. Once we've dropped this activity inside of a state, we can double click to edit the children. In figure 15, we've added an initialize activity to OpenState, and can now add child activities of any type as children.StateFinalizationActivity
Like the initialize activity, the State Finalization activity is a sequence activity with child activities inside. When the state machine transitions out of a state, the state's finalization activities will execute. There can be only one finalization activity inside of each state.EventDrivenActivity
The Event Driven activity is also a sequence activity with children inside. The Event Driven activity, however, only executes when an event arrives. The first child activity must implement the IEventActivity interface, like the Handle External Event activity. You might remember we described this activity briefly when we discussed the ListenActivity.A State activity can contain more then one Event Driven activity inside. For example, our OpenState state will have two Event Driven activities inside - one to handle a BugAssigned event, and one to handle a BugDeferred event. We do not allow the OpenState to handle a BugClosed event, because we don't want to transition from open to closed without going through the assigned state.
In figure 16, we've double-clicked on an Event Driven activity in OpenState to configure an event handler for the BugAssigned event. The event is part of a communication interface we've built with the ExternalDataExchange attribute, just as we did earlier in the section covering the Handle External Event activity. Notice the last activity inside the sequence is a SetState activity, which we cover next.
SetStateActivity
The SetState activity transitions a state machine to a new state. In figure 16, we are handling the BugAssigned event. When a bug is assigned, we want to transition to the AssignedState, so we set the TargetStateName property to AssignedState. The AssignedState activity will then have it's own set of Event Driven activities, each with SetState activities to transition to other states (and hopefully one day reach the ClosedState).The workflow view of a state machine will use examine the possible state transitions and draw lines to represent state transitions. In figure 17, we can see that state machine will only transition to the ClosedState activity from the AssignedState activity. We also see lines representing all the other state transitions.
No comments:
Post a Comment