Everything in a workflow is an activity, including the workflow itself. The workflow is a specific type of activity that typically allows other activities to be defined within it – this is known as a composite activity, and you see other composite activities later in this chapter. An activity is just a class that ultimately derives from the Activity class.
The Activity class defines a number of overridable methods, and arguably the most important of these is the Execute method shown in the following snippet:
When the runtime schedules an activity for execution, the Execute method is ultimately called, and that is where you have the opportunity to write custom code to provide the behavior of the activity. In the simple example in the previous section, when the workflow runtime calls Execute on the CodeActivity, the implementation of this method on the code activity will execute the method defined in the code-behind class, and that displays the message on the console.
The Execute method is passed a context parameter of type Activity Execution Context. You will see more about this as the chapter progresses. The method has a return value of type ActivityExecutionStatus, and this is used by the runtime to determine whether the activity has completed successfully, is still processing, or is in one of several other potential states that can describe to the workflow runtime what state the activity is in. Returning ActivityExecutionStatus .Closed from this method indicates that the activity has completed its work and can be disposed of.
Numerous standard activities are provided with WF, and the following sections provide examples of some of these together with scenarios in which you might use these activities, The naming convention for activities is to append Activity to the name; so for example, the code activity shown in Figure 43-2 is defined by the CodeActivity class.
All of the standard activities are defined within the System. Workflow. Activities namespace, which in turn forms part of the System. Workflow .Activities.dll assembly. There are two other assemblies that make up WF-these are System. Workflow.ComponentModel. dll and System. workflow .Runtime.dll.
As its name implies, this activity acts like an If-Else statement in C#.
When you drop an If Else Activity onto the design surface, you will see an activity as displayed in Figure 43-3. The If Else Activity is a composite activity in that it constructs two branches (which
themselves are types of activity, in this case If Else Branch Activity). Each branch is also a composite activity that derives from SequenceActivity – this class executes each activity in turn from top to . bottom. The Designer adds the “Drop Activities Here” text to indicate where child activities can be added.
The first branch, as.shown in Figure 43-3, includes a glyph indicating that the condition property needs to be defined. A condition derives from Activity Condition and is used to determine whether that branch should be executed.
When the If Else Activity is executed, it evaluates the condition of the first branch, and if the condition evaluates to true the branch is executed. If the condition evaluates to false the If Else Activity then tries the next branch, and so on until the last branch in the activity. It is worth noting that the If Else Activity can have any number of branches, each with its own condition. The last branch needs no condition because it is in effect the else part of the If Else statement. To add a new branch, you can display the context menu for the activity and select Add Branch from that menu – this is also available from the Workflow menu within Visual Studio. As you add branches, each will have a mandatory condition except for the last one.
Two standard condition types are defined in WF – the Code Condition and the Rule ConditionReference. The CodeCondition class executes a method on your code-behind class, which can return true or false as appropriate. To create a Code Condition, display the property grid for the If Else Activity and set the condition to Code Condition, then type in a name for the code to be executed, as shown in Figure 43-4.
When you have typed the method name into the property grid, the Designer will construct a method on your code-behind class, as shown in the following snippet:
This code sets the Result property of the passed Conditional Event Args to true if the current hour is between 9 AM and 5 PM. Conditions can be defined in code as shown here, but another option is to define a condition based on a rule that is evaluated in a similar manner. The Workflow Designer contains rule editor, which can be used to declare conditions and statements (much like the If – Else statement shown previously). These rules are evaluated at runtime based on the current state of the workflow.
This activity permits you to define a set of activities that execute in parallel- or rather in a pseudo parallel manner. When the workflow runtime schedules an activity, it does so on a single thread. This
thread executes the first activity, then the second, and so on until all activities have completed (or until an activity is waiting on some form of input). When the ParallelActivity executes, it iterates through each branch and schedules execution of each branch in turn. The workflow runtime maintains a queue of scheduled activities for each workflow instance, and typically executes these in a BFO (first in, first out) manner.
Assuming that you have a Parallel Activity, as shown in Figure 43-5, this will schedule execution of sequenceActivity and then sequenceActivity2. The SequenceActivity type works by scheduling execution of its first activity with the runtime, and when this activity completes, it t~en schedules the second activity. This schedule/wait for completion method is used to traverse through all child activities of the sequence, until all child activities have executed, at which time the sequence activity can complete.
Given that the SequenceActivity schedules execution of”one activity at a time, it means that the queue maintained by the WorkflowRuntime is continually updated with schedulable activities. Assuming that we have a parallel activity P1 that contains two sequences, 51 and 52, each with two code activities, C1 and C2, this would produce entries in the scheduler queue, as shown in the following table.
Here, the queue processes the first entry (the parallel activity PI), and this adds the sequence activities 51 and 52 to the workflow queue. As the sequence activity 51 executes, it pushes its first child activity (51.C1) to the end of the queue, and when this activity is scheduled and completes, it then adds the second child activity to the queue.
As can be seen from the preceding example, execution of the ParallelActivity is not truly parallel it effectively interleaves execution between the two sequential branches. From this, you could infer that it’s best that an activity execute in a minimal amount of time because, given that there is only one thread servicing the scheduler queue for each workflow.a long-running activity could hamper the execution of other activities in the queue. That said, often, an activity needs to execute for an arbitrary amount of time, so there must be some way to mark an activity as “long-running” so that other activities get a chance to execute. You can do this by returning ActivityExecutionStatus. Executing from the – Execute method, which lets the runtime know that you will call it back later when the activity has finished. An example of this type of activity is the DelayActivity.
Call External Method Activity
A workflow will typically need to call methods outside of the workflow, and this activity allows you to define an interface and a method to call on that interface. The WorkflowRuntime maintains a list of services (keyed on a System. Type value) that can be accessed using the ActivityExecutionContext parameter passed to the Execute method.
You can define your own services to add to this collection and then access these services from within your own activities. You could, for example, construct a data access layer exposed as a service interface and then provide different implementations of this service for SQLServer and Oracle. Because the activities simply call interface methods, the swap from SQLServer to Oracle would be opaque to the activities.
When you add a Call ExternalMethod Activity to your workflow, you then define the two mandatory properties of InterfaceType and MethodName.The interface type defines which runtime service will be used when the activity executes, and the method name defines which method of that interface will be called.
When this activity executes, it looks up the service with the defined interface by querying the execution context for that service type, and it then calls the appropriate method on that interface. You can also pass parameters to the method from within the workflow – this is discussed later in the section titled “Binding Parameters to Activities.”
Business processes often need to wait for a period of time before completing. Consider a workflow for expense approval. Your workflow might send an email to your immediate manager asking him or her to approve your expense claim.The workflow then enters a waiting state, where it either waits for approval (or, horror of horrors, rejection),but it would also be nice to define a timeout so that if no response is returned within, say, one day, the expense claim is then routed to the next manager up the chain of command.
The DelayActivity can form part of this scenario (the other part is the ListenActivity defined later). Its job is to wait for a predefined time before continuing execution of the workflow. There are two ways to define the duration of the delay – you can either set the TirneoutDuration property of the delay to a string such as “1.00:00:00″(1 day, no hours, minutes, or seconds), or you can provide a method that is called when the activity is executed that sets the duration to a value from code. To do this, you need to define a value for the InitializeTimeoutDuration property of the delay activity. This creates a method in the code behind, as shown in the following snippet:
Here, the DefineTimeout method casts the sender to a DelayActivity and then sets the TimoutDuration property in code to a TimeSpan. Even though the value is hard-coded here, it is more likely that you would construct this from some other data – maybe a parameter passed into the workflow or a value read from the configuration file. Workflow parameters are discussed in the section “Workflows” later in the chapter.
A common programming construct is to wait for one of a set of possible events – one example of this is the wait Any method of the System. Threading. WaitHandle class. The ListenActivity is the way to do this in a workflow, because it can define any number of branches, each with an event-based activity as that branch’s first activity.
An event activity is one that implements the IEventActivity interface defined in the System .Workflow. Acti vi ties namespace. There are currently three such activities defined as standard in WF – DelayActivity, HandleExternalEventActivity, and the WebServicelnputActivity. Figure 43-6 shows a workflow that is waiting for either external input or a delay – this is an example of the
expense approval workflow discussed earlier.
In the example, the CallExternalMethodActivity is used as the first activity in the workflow. This caIls a method defined on a service interface that would prompt the manager for approval or rejection.
Because this is an external service, this prompt could be an email an message, or any other manner of notifying your manager that an expense claim needs·to be processed. The workflow then executes the ListenActivity, which awaits input from this external service (either an approval or a rejection), and also waits on a delay.
When the listen executes, it effectively queues a wait on the first activity in each branch, and when one event is triggered, this cancels all other waiting events and then processes the rest of the branch where the event was raised. So, in the instance where the expense report is approved, the Approved event is raised and the PayMeactivity is then scheduled. If, however, your manager rejects the claim, the Rejected event is raised, and in the example you then Panic.
Last, if neither the Approved nor Rejected event is raised, the DelayActivity ultimately completes after its delay expires, and the expense report could then.be routed to another manager – potentially looking up that person in Active Directory. In the example, a dialog is displayed to the user when the RequestApproyal activity is executed, so if the delay executes, you also need to close the dialog, which is the purpose of the activity named Hide Dialog in Figure 43-6.
The code for this example is available in the 02 Listen directory. Some concepts used in that example have not been covered yet – such as how a workflow instance is identified and how events are raised back into the workflow runtime and ultimately delivered to the right workflow instance. These concepts are covered in the section titled “Work.flows.”
Activity Execution Model
So far, this chapter has discussed the execution of an activity only by the runtime calling the Execute method. However, an activity may go through a number of states while it executes – these are presented in Figure 43-7. .
An activity is first initialized by the WorkflowRuntime when the runtime calls the activity’s Initialize method. This method is passed an IServiceProvider instance, which maps to the services available within the runtime. These services are discussed in the “Workflow Services” section later in the chapter. Most activities do nothing in this method, but the method is there for you to do any setup necessary.
The runtime then calls the Execute method, and the activity can return anyone of the values from the ActivityExecutionStatus enum. Typically, you will return Closed from your Execute method, which indicates that your activity has finished processing; however, if you return one of the other status values, the runtime will use this to determine what state your activity is in. You can return Executing from this method to indicate to the runtime that you have extra work to do – a typical example of this is when you have a composite activity that needs to execute its children. In this case, your activity can schedule each child for execution and then wait for all children to complete before notifying the runtime that your activity has completed.