Está en la página 1de 117

Windows Workflow Tutorial: Introduction to State Machine Workflows

Introduction

Workflows model business processes. When you design a workflow, your first task is to identify the steps that
occur during the business process. That is true whether the business process is the processing of an order, the
calculation of a bonus payment or the processing of a loan application. The business process consists of steps
and your job is to define those. Once you have the steps defined, you can use Windows Workflow Foundation
(WF) to build a workflow that models the business process.
You can build two types of workflows with WF: sequential and state machine workflows. A sequential
workflow provides a structured series of steps in which one activity leads to another, and steps generally occur
immediately one after another. A step might wait for some event (an email to arrive, for example), but
sequential workflows are often used to model processes that operate without human intervention.
A state machine workflow provides a set of states. The workflow begins in an initial state and ends when it
reaches the completed state. Transitions between the states define the behavior. In general, state machine
workflows react to events. The occurrence of an event causes the workflow to transition to another state.
Whether you build a workflow as a sequential or state machine workflow depends on how the business manages
the process. For example, suppose you need to build a workflow that models processing an order. You
determine the following steps are involved:
• The business receives the order, including all necessary information (customer, product, quantity, credit card information, shipping address, etc).

• The business checks if the customer has sufficient credit. If so, the workflow continues. If not, the order is canceled and the workflow ends.

• The business checks if there is sufficient inventory to fulfill the order. If so, the process continues. If not, the order is canceled and the process ends.

• The business asks the credit card company for payment. If the credit card company approves, the process continues. If not, the order is canceled and the process
ends.

• The item ships to the customer. The order is marked as fulfilled and the process ends.

A sequential workflow is likely the appropriate type of workflow here. The workflow starts when the company
receives an order. It then continues through a number of steps until the order is either canceled as fulfilled. The
workflow runs from start to finish with no delays.
As an alternative, suppose you determine the following steps are involved in processing an order:
• The business receives the order, including all necessary information (customer, product, quantity, credit card information, shipping address, etc).

• The business checks if the customer has sufficient credit. If so, the process continues. If not, the order is canceled and the workflow ends.

• The business checks if there is sufficient inventory to fulfill the order. If so, the process continues. If not, the order is canceled and the process ends.

• If the product is in stock, the process waits for shipping to ship the product.

• Just before shipping the product, the business asks the credit card company for payment. If the credit card company approves, the process continues. If not, the
order is canceled and the process ends.

• After the product ships, the process waits for the customer to acknowledge receiving it. If the product arrives, the process ends. If the customer does not receive
the product, the process waits for shipping to resend it. At this point, either the business or the customer can cancel the order, ending the process.

You can successfully implement this business process either as a sequential workflow or as a state machine
workflow. To decide, think about what makes it different from the previous scenario. There are several places in
the business process where the process needs to pause and wait for some other process to begin. The pause may
be short. It may also be long. It could take weeks or months for new inventory to arrive if the product is out of
stock. It hopefully only takes days, not weeks, for shipping to send the product. It will take anywhere from one

1
day to ten days for the product to arrive to the customer. During these periods, there is nothing for the business
process to do except wait.
One of the main benefits of the state machine workflow is the ability to define states and to define how the
workflow moves from state to state. You can define multiple paths through the workflow. If the product is in
stock, the workflow can take the following path:
Waiting For Shipping -> Waiting For Acknowledgement -> Completed
If the product does not arrive, the workflow can take the following path:
Waiting For Shipping -> Waiting For Acknowledgement -> Waiting For Shipping -> Waiting For
Acknowledgement -> Completed
You can easily include looping and re-execution of states in a state machine workflow. It is difficult, and
potentially not possible, to do this in a sequential workflow.
A sequential workflow moves to the next activity when it is finished executing the previous activity. A state
machine workflow typically moves to a different state when an external action occurs. This external action can
be the host application raising an event handled by the workflow. The action can also be the host application
programmatically setting the next state. You can also use the SetState activity in the workflow to move to a new
state.
Create a State Machine Workflow

To get started, in Visual Studio 2008 select File | New | Project to display the New Project dialog box. In the list
of project types, select Workflow, displaying the list of templates shown in Figure 1.

Figure 1. Visual Studio 2008 provides these workflow templates.


For this demonstration, select the State Machine Workflow Console Application template, name your
application StateMachineDemo1, and select an appropriate folder for the project. Click OK to create the project.
At this point, the workflow contains a single activity, named Workflow1InitialState, as shown in Figure 2.

2
Figure 2. A new state machine workflow contains a single activity.
A state machine workflow contains workflow activities, as does a sequential workflow. You will find that you
use workflow activities such as Code, While, IfElse and Sequence in the same manner regardless of the type of
workflow you build. There are four activities unique to state machine workflows. These are the State, SetState,
StateInitialization and StateFinalization activities (see Figure 3).

Figure 3. These activities are specific to state machine workflows.


The State activity is the main activity you will use in a state machine workflow. Each State activity in a
workflow represents a point where the business process waits for something to happen.
WF requires that your state machine workflows have one state marked as the initial state. Although it is not
required, you should also mark one state as the completed state. When the workflow starts, the workflow starts
with the state marked as the initial state. You can visually identify the initial state by a green circle in the

3
activity’s title bar. When the workflow reaches the completed state, the workflow will end. You can visually
identify the completed state by a red circle in the activity’s title bar.
Define the Workflow States

Select the Workflow1InitialState activity and change its name to Started. Notice that the green circle disappears
from the title bar. To mark this activity as the initial state, right click on it and select Set as Initial State. The
green circle reappears. Note that you can also set the InitialStateName property of the workflow to Started.
To add another state to the workflow, drag a State activity from the toolbox onto the design surface. Name the
activity Working.
Now add the final state to the workflow, naming it Finished. To mark it as the completed state, right click on it
and select Set as Completed State. You should now see a red circle in the activity’s title bar. Note that you can
also set the CompletedStateName property of the workflow to Finished.
The workflow designer now looks like Figure 4. (The Started activity has been widened to see all of the text.)

Figure 4. The state machine workflow contains an initial state, a completed state and an additional state.
The next step in building this workflow is to specify, for each state, what state or states the workflow can move
to next and what happens while the workflow is in that state, including what happens to move it to another state.
State Transitions: Moving from State to State

If this were a sequential workflow, the workflow would execute the activity at the top of the workflow (Started)
and then move to the next activity (Working) and then move to the final activity (Finished) and then end.
Sequential workflows execute from top to bottom.
You set Started as the initial state so when this workflow starts, it will start in the Started state. You set Finished
as the completed state so when the workflow gets to the Finished state it will end.

4
How does the workflow move off the Started state? Does it go to Working or Finished next? If it goes to the
Working state, how does it move off Started? When it moves off Working, can it go back to Started or can it
only go to Finished?
To move the workflow to a new state, you use the SetState activity. To specify the next state, you set the
TargetStateName property of the SetState activity to that state.
Define the Activities that Occur in Each State

State machine workflows move from state to state. While they are in each state (except for the completed state),
they can perform actions, wait for an external event to occur and transition to another state.
You can add four activities to a State activity:
• The StateInitialization activity, if it exists, is the first activity the workflow executes when it enters a state. This is a Sequence activity, so you can add to it all of the
activities you want to execute when the workflow enters that state. You might log the date and time. You might query a database. You could add a SetState activity
to a StateInitialization activity if you want the workflow to move to a new state after performing the logging and querying.

• In a state machine workflow, the workflow will enter a state and stay there until something happens. This is the essence of a state machine workflow. That
something could be internal to the workflow or it could be an external event. You handle external events using an EventDriven activity. It is also a Sequence
activity, so you can add to it all of the activities you want to execute when the host raises the event. One of these activities can be a SetState activity.

• The StateFinalization activity, if it exists, is the last activity the workflow executes as it leaves a state. It is also a Sequence activity, so you can add to it all of the
activities you want to execute before the workflow moves to a new state. You might log the date and time, query a database or clean up resources.

• You can add a State activity within a State activity. This creates a hierarchical state machine workflow. You might do this if you had a number of states and each of
them reacted to the same events. Rather than set up the events for each state, you could set up the event for one state and then add the other states to that state.
The child states would inherit the event driven behavior of the parent state. This is beyond the scope of this tutorial.

Complete the Workflow

You will now finish this workflow. You will define what happens in each state and how the workflow moves
from state to state.
From the toolbox, drag a StateInitialization activity into the Started activity. stateInitializationActivity1 is a
Sequence activity. To add activities to it, double click on it. The workflow designer looks like Figure 5.

Figure 5. The StateInitialization activity is a Sequence activity.


Notice the navigation list, or “breadcrumb” at the top left in the designer. This will enable you to return to the
main workflow view when you are finished adding activities to the StateInitialization activity.

5
Drag a Code activity from the toolbox into stateInitializationActivity1. Double-click codeActivity1, creating the
activity’s ExecuteCode event handler. In the codeActivity1_ExecuteCode method, add the following code to
display the order details:
Visual Basic

Console.WriteLine("The workflow has started" & vbCrLf)


C#

Console.WriteLine("The workflow has started\n");


Select View | Designer to return to the workflow designer. Drag a SetState activity from the toolbox into
stateInitializationActivity1 below codeActivity1. Select Working from the drop-down list associated with the
TargetStateName property. The workflow now looks like Figure 6.

Figure 6. The SetState activity transitions the workflow to another state.


Select the link for Started or Workflow1 in the navigation menu. This returns you to the main workflow view
(see Figure 7). Notice that there is now a line connecting Started to Working. This shows you the flow of the
workflow from the initial state to the next state.

6
Figure 7. The line indicates the flow of the workflow from state to state.
Handle External Events

So far, this workflow executes in a relatively sequential fashion. The workflow starts and then, after displaying
a message, moves to the next state. That scenario doesn’t appear very stateful, does it?
As mentioned previously, the typical scenario for a state machine workflow it enters a state and waits for the
host application to raise an event. You handle external events using an EventDriven activity. You will then
decide what actions the workflow takes, including what state it moves to next.
From the toolbox, drag an EventDriven activity into Working. Select eventDrivenActivity1. Hover the mouse
over the middle square on the left or right border of the activity. The cursor should change to a crosshair (see
Figure 8). Hold down the mouse button. Drag the crosshair and drop it on Finished (see Figure 9). There will
then be a line connecting the two states.

Figure 8. Hover the mouse over the activity and wait for the cursor to change to a crosshair.

7
Figure 9. Drag an EventDriven activity from one state to another to define a transition.
Double click eventDrivenActivity1. You can see that it is a container activity and already includes a SetState
activity. When you dragged the crosshair from Working to Finished, Visual Studio added a SetState activity to
eventDrivenActivity1 and set its TargetStateName property to Finished (see Figure 10).

Figure 10. The EventDriven activity contains a SetState activity.


At this point, the EventDriven activity indicates an error. The first activity in an EventDriven activity must
implement the IEventActivity interface. This interface enables activities to subscribe to events. The
HandleExternalEvent and Delay activities implement this interface.
Use the HandleExternalEvent activity when the host application raises an event. To do this, you first need to
create an interface that defines the events the host can raise. You also need to create a class that defines the
information the host will pass to the workflow when it raises events. You then associate the
HandleExternalEvent activity with an event.
This is too advanced for this tutorial, but it will be covered in a later tutorial. For this workflow, you will use a
Delay activity and simply pause the workflow for a few seconds. Although this is not a realistic scenario, it
serves the purpose here.
From the toolbox, drag a Delay activity into eventDrivenActivity1 above setStateActivity2. Set the
TimeoutDuration property of delayActivity1 to five seconds.

8
Drag a Code activity from the toolbox into eventDrivenActivity1 between the two activities currently in there.
Double-click codeActivity2 and add the following code to the activity’s ExecuteCode event handler:
Visual Basic

Console.WriteLine("Moving on now" & vbCrLf)


C#

Console.WriteLine("Moving on now\n");
Select View | Designer to return to the workflow designer. Select the link for Workflow1 or Working in the
upper left of the workflow designer.
From the toolbox, drag a StateFinalization activity into Working below eventDrivenActivity1. To add activities
to stateFinalizationActivity1, double click on it.
Drag a Code activity from the toolbox into stateFinalizationActivity1. Double-click codeActivity3, creating the
activity’s ExecuteCode event handler. In the codeActivity3_ExecuteCode method, add the following code:
Visual Basic

Console.WriteLine("Transitioning to Finished" & vbCrLf)


C#

Console.WriteLine("Transitioning to Finished\n");
Select View | Designer to return to the workflow designer. Select the link for Workflow1 or Working in the
upper left of the workflow designer. The workflow should look like Figure 11.

Figure 11. The workflow with three states and the transitions between them.
Call the Workflow from the Console Application

To review the code that actually does the work starting up your workflow, in the Solution Explorer window,
double-click Module1.vb or Program.cs. There, you’ll find the following code:
Visual Basic

Using workflowRuntime As New WorkflowRuntime()

9
' Code removed here…
Dim workflowInstance As WorkflowInstance
  workflowInstance = _
    workflowRuntime.CreateWorkflow(GetType(Workflow1))
  workflowInstance.Start()
  ' Code removed here…
End Using
C#

using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
{
  // Code removed here…
  WorkflowInstance instance = workflowRuntime.CreateWorkflow(
    typeof(StateMachineDemo1.Workflow1));
  instance.Start();
  // Code removed here…
}
This code, which runs as your application loads, starts by creating a new instance of the WorkflowRuntime
class. This class provides an execution environment for workflows. The WorkflowRuntime instance creates an
instance of the state machine workflow.
Save and press Ctrl + F5 to run your project. If you’ve followed the directions carefully, you should first see the
following output:
Output

The workflow has started
There should then be a five second delay before you see the following output:
Moving on now
Transitioning to Finished
Press any key to continue...
Press any key to exit the application.

Build a More Useful Workflow

This workflow demonstrates the basics of building a state machine workflow, but it doesn’t really do much. For
the remainder of this tutorial, you’ll modify the workflow to model an order processing workflow.
When the business receives an order, the host application will start the workflow, passing to it the order
information. The workflow will check if there is sufficient inventory to process the order. If not, the order is
canceled. If the item is in stock, the workflow will wait for the order to ship. After the order ships, the workflow
ends.
Change the name of the Working state to WaitingForShipping. The workflow enters this state and waits for
something, so why not identify what it is waiting for in the name?
Next, you will add properties to the workflow so it can receive order information. Select View | Code. Outside
the methods, but inside the Workflow1 class, add the following properties. You’ll use these to keep track of
order details, as well as the status of the inventory check:
Visual Basic

10
Private productIDValue As Integer
Public Property ProductID() As Integer
  Get
    Return productIDValue
  End Get
  Set(ByVal value As Integer)
    productIDValue = value
  End Set
End Property
Private quantityValue As Integer
Public Property Quantity() As Integer
  Get
    Return quantityValue
  End Get
  Set(ByVal value As Integer)
    quantityValue = value
  End Set
End Property
Public inStock As Boolean = False
C#

public int ProductID { get; set; }
public int Quantity { get; set; }
public bool inStock = false;
Change the code in the codeActivity1_ExecuteCode method to display the order details and check if the item is
in stock:
Visual Basic

Console.WriteLine("Order received")
Console.WriteLine(" Product ID: {0}", Me.ProductID)
Console.WriteLine(" Quantity: {0}" & vbCrLf, Me.Quantity)
inStock = Me.Quantity <= 10
C#

Console.WriteLine("Order received");
Console.WriteLine(" Product ID: {0}", this.ProductID);
Console.WriteLine(" Quantity: {0}\n", this.Quantity);
inStock = (this.Quantity <= 10);
In a production workflow, you would likely query a database to see if the item is in stock. For this tutorial, a
quantity of greater than ten will exceed the amount of the item in stock.
Select View | Designer to return to the workflow designer. Double click stateInitializationActivity1 to add
additional activities.

11
Notice that setStateActivity1 indicates an error. This is because you changed the name of the state to which it
transitions. To fix this, change the TargetStateName property to WaitingForShipping.
Drag an IfElse activity from the toolbox into the stateInitializationActivity1activity below codeActivity1 but
above setStateActivity1. The workflow should now look like Figure 12.

Figure 12. The stateInitializationActivity1activity should now contain these activities.


The IfElse activity enables branching in a workflow. It contains one or more IfElseBranch activities. At runtime,
the workflow evaluates the Condition property of the first branch. If the condition evaluates to true, the
activities in that branch execute. If the condition evaluates to false, the workflow evaluates the condition of the
next branch, if there is one. If none of the branch conditions evaluates to true, the activities in the final branch
execute (unless that activity has a condition that evaluates to false).
At this point, the first IfElseBranch activity indicates an error. You haven’t told it yet what condition to evaluate.
To do that, select ifElseBranchActivity1. In the Properties window, find the Condition property, select the drop-
down arrow to the right of the property’s value, and select Declarative Rule Condition (see Figure 13). You have
the option of either defining a rule in the Properties window (Declarative Rule Condition) or creating a
condition in code (Code Condition). For this tutorial, you’ll create the condition in the Properties window.

12
Figure 13. Choose the type of condition you want to use.
Click the “+” sign to the left of the Condition property, expanding the property. Set the ConditionName property
to itemIsInStock. (Naming the condition allows you to use the same condition in a different place within your
workflow.) Click the Expression property and then click the ellipsis to the right of that property. Enter the
condition shown in Figure 14. Click OK to close the editor.

Figure 14. Create the IfElseBranch activity’s rule condition.


Drag setStateActivity1 into ifElseBranchActivity1. You have now set up the workflow to transition to the
WaitingForShipping state if the item is in stock. You will next tell the workflow what to do if the item is not in
stock.
Drag a Code activity from the toolbox into the ifElseBranchActivity2 activity. Double-click codeActivity4 and
add the following code to the activity’s ExecuteCode event handler:
Visual Basic

Console.WriteLine("The item is not in stock")


Console.WriteLine("The order is canceled" & vbCrLf)
C#

Console.WriteLine("The item is not in stock");


Console.WriteLine("The order is canceled\n");

13
Select View | Designer to return to the workflow designer. Drag a SetState activity from the toolbox into
ifElseBranchActivity2 below codeActivity4. Set the TargetStateName property of setStateActivity3 to Finished.
The workflow should now look like Figure 15.

Figure 15. The workflow executes these activities when it starts.


To recap, if the item is in stock, the workflow transitions to the WaitingForShipping state. If the item is not in
stock, the workflow transitions to the Finished state and ends.
Select the link for Workflow1 or Started in the upper left of the workflow designer. Notice that there is now a
line from Started to Finished (see Figure 16). The workflow takes this path if the item is not in stock.

14
Figure 16. The lines indicate the updated flow of the workflow from state to state.
Next, change the messages the workflow displays. Select View | Code.
Change the code in the codeActivity2_ExecuteCode method to indicate the workflow is waiting for the item to
ship:
Visual Basic

Console.WriteLine("The item has shipped" & vbCrLf)


C#

Console.WriteLine("The item has shipped\n");


Change the code in the codeActivity3_ExecuteCode method to indicate the item has shipped:
Visual Basic

Console.WriteLine("The order is complete" & vbCrLf)


C#

Console.WriteLine("The order is complete\n");


Your final step will be to modify the console host application to pass order information to the workflow. In the
Solution Explorer, double click Module1.vb or Program.cs. Add the following code to the Main method,
replacing the existing line of code that calls the CreateWorkflow method:
Visual Basic

Dim parameters As New Dictionary(Of String, Object)


parameters.Add("ProductID", 1)
parameters.Add("Quantity", 10)
workflowInstance = workflowRuntime.CreateWorkflow( _
  GetType(Workflow1), parameters)
C#

var parameters = new Dictionary<string, object>();

15
parameters.Add("ProductID", 1);
parameters.Add("Quantity", 10);
WorkflowInstance instance = workflowRuntime.CreateWorkflow(
typeof(StateMachineDemo1.Workflow1), parameters);
Save and press Ctrl + F5 to run your project. You should first see the following output:
Output

Order received
Product ID: 1
Quantity: 10
There should then be a five second delay before you see the following output:
Output

The item has shipped


The order is complete
Press any key to continue...
Press any key to exit the application.
To see what happens if the item is not in stock, make the following modification to the Main method:
Visual Basic

parameters.Add("Quantity", 15)
C#

parameters.Add("Quantity", 15);
Save and press Ctrl + F5 to run your project. You should see the following output:
Output

Order received
Product ID: 1
Quantity: 15
The item is not in stock
The order is canceled
Press any key to continue...
Press any key to exit the application.

Conclusion

In this tutorial, you learned the basic concepts involved in creating and executing a state machine workflow.
You saw that the first step in designing a workflow is to get an accurate view of the business process you are
modeling. You can then decide whether to build a sequential or state machine workflow. Can you identify
discrete states, places where the workflow waits for an action it does not control? If so, you will likely create a
state machine workflow.
The State, SetState, StateInitialization and StateFinalization activites are specific to state machine workflows.
As you saw, you can use those and other activities to build a state machine workflow. Your use of these other
activities will usually be the same regardless of the type of workflow you build.
In this tutorial, you did not see how a host application raises events. That is obviously an important thing to
learn, and you are encouraged to learn about this in a later tutorial in this series.

16
Windows Workflow Tutorial: Introduction to Sequential Workflows
Introduction
Windows Workflow Foundation (WF), originally introduced as part of the .NET Framework 3.0 with extensions
for Visual Studio 2005’s designers, has continued to be enhanced for the .NET Framework 3.5 and Visual
Studio 2008. WF makes it possible, and for many workflow scenarios, even easy to create robust, manageable
workflow-based applications. WF is actually many things: It’s a programming model, and runtime engine, and a
set of tools that help you create workflow-enabled applications hosted by Windows. (For more information on
WF, drop by the portal site).
In this series of tutorials, you’ll work through several different examples, showing off various important
features of WF, and the corresponding tools and techniques using Visual Studio 2008. Because workflows
generally deal with specific business-oriented processes, and these tutorials can’t begin to emulate your specific
processes, you’ll find that the specific examples shown here tend to focus on either scenarios that you can
control in order to demonstrate the specific workflow features (such as working with files in the file system), or
they focus on business processes with “holes” left for your imagination to insert real-world behaviors.
This first tutorial introduces the basics of creating Workflow applications, using a console application as the
workflow’s host. Along the way, you’ll investigate the various workflow-focused templates that Visual Studio
2008 provides, and you’ll learn what happens when you create and run a workflow application. You’ll try out a
few of the many different workflow activities, as well. (These tutorials assume that you have Visual Studio 2008
installed, along with the .NET Framework 3.5. You can choose to work in either Visual Basic or C#--the steps
listed here call out specific differences between the languages, when necessary.
There’s a lot to cover in this first tutorial, so fire up Visual Studio 2008, and get started!

Investigate the Workflow Templates


To get started, in Visual Studio 2008 select File | New | Project to display the New Project dialog box. In the list
of project types, select Workflow, displaying the list of templates shown in Figure 1.

17
Figure 1. Visual Studio 2008 provides these workflow templates.
In general, WF allows you to create two different types of workflows: sequential, and state machine. Sequential
workflows provide a structured series of steps in which one activity leads to another, and steps generally occur
immediately one after another. A step might wait for some event (an email to arrive, for example), but
sequential workflows are often appropriate for tasks that operate without human intervention. State machine
workflows provide a set of states; transitions between the states define the behavior. In general, state machine
workflows react to events which move the workflow’s current activity from one state to another. Each workflow
that you design is simply a class that inherits from one or the other of the
System.Workflow.Activities.SequentialWorkflowActivity or
System.Workflow.Activities.StateMachineWorkflowActivity classes, and most of the Visual Studio 2008
workflow project templates create a class that inherits from one or the other of these base classes for you. (In
addition, each of these templates includes assembly references to the various assemblies required in order to
design and run workflows.) If you want to create a sequential workflow, you can select any of the following
templates:
• Sequential Workflow Console Application, which includes a Console application host application that helps run your workflow.
You’ll investigate this specific template in this tutorial.

• Sequential Workflow Library, which creates a .NET library containing only a sequential workflow class. This project template
does not include a host application, so you will need to supply your own in order to execute a workflow.

• Sharepoint 2007 Sequential Workflow, which creates a sequential workflow suitable for use with SharePoint 2007. (For more
information on SharePoint 2007 and workflows, visit this site.)

If you want to create a state machine workflow (the topic of another tutorial in this series), you can select any of
the following templates, which correspond to the similarly named templates previously listed:
• SharePoint 2007 State Machine Workflow

• State Machine Workflow Console Application

• State Machine Workflow Library

In addition, you can select the Empty Workflow Project template to create an empty project, with just the
necessary references set; or you can select the Workflow Activity Library template to create a library in which
you could create custom workflow activities.

Create a Workflow Project


For this demonstration, select the Sequential Workflow Console Application template, name your application
WorkflowDemo1, and select an appropriate folder for the project. Click OK to create the project. (Note that
every workflow must be hosted by some application—something has to create an instance of the workflow
runtime, which, in turn, creates an instance of the workflow you want to execute. In this example, you’ll create
a Console application that hosts the workflow.)
At this point, the workflow project contains a single workflow class, named Workflow1. This blank template
should be opened for you (if not, double-click the corresponding code file in the Solution Explorer window to
open it), and the workflow designer is ready for you to add your own activities (see Figure 2).

18
Figure 2. The workflow designer is ready for you to add activities.
In the Solution Explorer window, right-click the Workflow1 file, and select View Code from the context menu.
You’ll find code like the following, clearly pointing out that the workflow that you’re designing is really just a
class that inherits from the SequentialWorkflowActivity class:
Visual Basic
Public class Workflow1
    Inherits SequentialWorkflowActivity
End Class
C#
public sealed partial class Workflow1: SequentialWorkflowActivity
{
  public Workflow1()
  {
  InitializeComponent();
  }
}

Just as a Windows Form that you design within Visual Studio becomes an instance of a class that inherits from
the System.Windows.Forms.Form class when you run the application, the workflow that you design within
Visual Studio becomes an instance of a class that inherits from either the SequentialWorkflowActivity or
StateMachineWorkflowActivity class when you run the application. Later, you’ll add code to this partial class in
order to add behavior to your workflow. (Actually, there’s more of a similarity than just that—in Visual Studio,
when you load a file that contains a class that inherits from the Form class, Visual Studio displays the form
using the Windows Forms designer. The same thing happens with a workflow class—when you ask Visual
Studio to load a file that contains a class that inherits from one of the two base activity classes, it displays the
class using the appropriate workflow designer.)
Select View | Designer to return to design view.
Look in the Toolbox window (if it’s not visible, select View | Toolbox to display it). You’ll see two tabs that deal
with Workflow: the tab labeled Windows Workflow v3.0 contains most of the built-in workflow activities you’ll
use, and the tab labeled Windows Workflow v3.5 contains the new workflow activities added in the .NET

19
Framework 3.5 (this tab contains only two activities dealing with WCF and workflow, and you won’t need these
items for these tutorials). For now, select the Windows Workflow 3.0 tab, and expand it (see Figure 3).

Figure 3. The Windows Workflow v3.0 tab in the Toolbox window contains most of the built-in workflow
activities.
Some of the activities you see in the Toolbox window have obvious behavior—the Code activity allows you to
execute code, the IfElse activity allows you to make choices, and the While activity executes an activity while
some condition remains true. Other activities, such as the SynchronizationScope activity, require a bit more
study to determine their purpose. In this tutorial, you’ll work with the simple Code and IfElse activities—later
tutorials will walk you through using some of the more complex activities, such as the Replicator, While,
Listen, and HandleExternalEvent activities.

Add Activities to the Workflow


To get started, from the toolbox, drag a Code activity onto the design surface, so that the designer looks like
Figure 4 just before you drop the activity. Once you drop the activity, the designer looks like Figure 5. (Note
that as you drag activities onto the designer, you see one or more green “dots” indicating where, on the
workflow, you can drop your activity.)
20
Figure 4. Before dropping the activity, you see an icon representing where it will finally appear within the
workflow.

Figure 5. After dropping the activity, it appears within the workflow design.
At this point, the Code activity indicates an error—you haven’t yet told it what to do when the workflow
attempts to execute it! You’ll add the code soon. (Although this tutorial doesn’t walk you through the steps, you
should get in the habit of providing meaningful names for your workflow activities—otherwise, it gets difficult
to manage all the activities in your workflow. Setting the Name property for each activity is a good habit to get
into, but it’s not necessary for this simple workflow.)
At runtime, the sequential workflow starts at the green arrow, and executes activities, one at a time, in turn, until
it reaches the red circle at the conclusion of the workflow. In your sample workflow, you have included only a
single Code activity. Executing the workflow will cause it to run any code that you have placed in the activity’s
ExecuteCode event handler.
To create the code for your activity, double-click CodeActivity1. This creates an associated procedure, ready for
you to edit in the code editor. Modify the procedure, adding the following code:
Visual Basic
Console.WriteLine("Hello, World!")
C#
Console.WriteLine("Hello, World!");

Select View | Designer, and verify that the error indicator disappears, because you have provided code for the
Code activity to run. Select the Code activity, and examine the Properties window (see Figure 6). You’ll see that
by adding the event handler, you’ve set the activity’s ExecuteCode property, so that the activity “knows” what
to do when it becomes the active activity within the workflow.
21
Figure 6. Adding code for the Code activity sets the activity’s ExecuteCode property.
Just to verify that you’ve created a working workflow, save and press Ctrl+F5 to run your project. (If you use
any other means to run the project, the console window will disappear immediately, once the message appears
on the screen.) After a few seconds, the console window displays your message awaits a key press.
So far, you’ve created the world’s simplest working workflow, and it really only proved that workflow works at
all—clearly, you would never use WF for a simple application like this. Next, you’ll investigate how WF loads
and runs your workflow design.

Debugging a Workflow
Although this simple application certainly doesn’t require any debugging capabilities, you’ll often need to step
through your workflows. As you might expect, you can easily place a breakpoint in the ExecuteCode handler
for a Code activity, and step through the code. But what if you want to debug at a higher level, stepping through
activities as they execute? You can, and Visual Studio makes the process feel much like debugging at the code
level.
In the workflow designer, right-click CodeActivity1. From the context menu, select BreakPoint | Insert
Breakpoint. At this point, the designer places a red dot on the activity, indicating that the workflow will drop
into Debug mode when it begins to execute the activity (see Figure 7).

22
Figure 7. Set a breakpoint on an activity.
Press F5 to start running the application. When the workflow reaches codeActivity1, it pauses execution and
highlights the activity in yellow, as shown in Figure 8.

Figure 8. At runtime, Visual Studio stops at the activity’s breakpoint.


Select Debug | Step Over (or the corresponding keystroke), and Visual Studio steps to the next activity,
executing the previous Code activity’s ExecuteCode procedure. Select Debug | Step Into, and Visual Studio
steps into the second Code activity’s ExecuteCode procedure, as you might expect. Visual Studio 2008 makes
debugging a workflow no more difficult than debugging any other type of application. (In order to step through
your workflow, the startup project within Visual Studio must be a workflow project. That is certainly the case in
this simple example, but might not be the case in a “real-world” scenario in which the workflow exists in a
library, and the host project is separate. In that case, you’ll need to configure Visual Studio so that it starts the
host application when you start debugging.)

Investigate the Startup Code


The project template created a simple console application for you, and the project automatically loaded and ran
your workflow. It’s important to understand exactly how the project does its work, because you’ll often want to

23
host workflows from other types of applications (from Windows or Web applications, or perhaps from a
Windows Service)—in those cases, you’ll need to provide your own code to get the workflow running.
To investigate the code that actually does the work starting up your workflow, in the Solution Explorer window,
double-click Module1.vb or Program.cs. There, you’ll find the following code:
Visual Basic
Class Program
Shared WaitHandle As New AutoResetEvent(False)
  Shared Sub Main()
    Using workflowRuntime As New WorkflowRuntime()
      AddHandler workflowRuntime.WorkflowCompleted, _
        AddressOf OnWorkflowCompleted
      AddHandler workflowRuntime.WorkflowTerminated, _
        AddressOf OnWorkflowTerminated
      Dim workflowInstance As WorkflowInstance
      workflowInstance = _
        workflowRuntime.CreateWorkflow(GetType(Workflow1))
      workflowInstance.Start()
      WaitHandle.WaitOne()
    End Using
  End Sub
  Shared Sub OnWorkflowCompleted(ByVal sender As Object, _
    ByVal e As WorkflowCompletedEventArgs)
    WaitHandle.Set()
  End Sub
  Shared Sub OnWorkflowTerminated(ByVal sender As Object, _
    ByVal e As WorkflowTerminatedEventArgs)
    Console.WriteLine(e.Exception.Message)
    WaitHandle.Set()
  End Sub
End Class
C#
class Program
{
  static void Main(string[] args)
  {
    using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())

24
{
AutoResetEvent waitHandle = new AutoResetEvent(false);
      workflowRuntime.WorkflowCompleted +=
        delegate(object sender, WorkflowCompletedEventArgs e)
          { waitHandle.Set(); };
      workflowRuntime.WorkflowTerminated +=
        delegate(object sender, WorkflowTerminatedEventArgs e)
        {
          Console.WriteLine(e.Exception.Message);
          waitHandle.Set();
      };
      WorkflowInstance instance = workflowRuntime.CreateWorkflow(
        typeof(WorkflowDemo1.Workflow1));
      instance.Start();
      waitHandle.WaitOne();
    }
  }
}

This code, which runs as your application loads, starts by creating a new instance of the WorkflowRuntime
class, which provides a execution environment for workflows. Every application you create that hosts one or
more workflows must create and instantiate an instance of this class. The WorkflowRuntime instance later
creates workflow instances:
Visual Basic
Using workflowRuntime As New WorkflowRuntime()
  ' Code removed here…
End Using
C#
using (WorkflowRuntime workflowRuntime =
  new WorkflowRuntime())
{
// Code removed here…
}

On its own, a Console application simply quits when the code in its Main procedure completes. In this case,
however, you must keep the application “alive” as long as the workflow is still running. The project template
includes an AutoResetEvent variable that helps keep that application running until the workflow has completed:
Visual Basic
Shared WaitHandle As New AutoResetEvent(False)

25
C#
AutoResetEvent waitHandle = new AutoResetEvent(false);

The AutoResetEvent class allows threads to communicate with each other by signaling. In this case, the Console
application runs in one thread, and the workflow runs in a separate thread. The main thread—in this case, the
console application—calls the wait handle’s WaitOne method, which blocks the console application’s thread
until the workflow’s thread calls the wait handle’s Set method, which allows the main thread to complete.
How does this all happen? Given the WorkflowRuntime instance, the code adds event handlers for the runtime’s
WorkflowCompleted and WorkflowTerminated events. The WorkflowCompleted event occurs when the
workflow completes normally, and the WorkflowTerminated event occurs if the workflow completes
abnormally (because of an unhandled exception, for example):
Visual Basic
AddHandler workflowRuntime.WorkflowCompleted, _
  AddressOf OnWorkflowCompleted
AddHandler workflowRuntime.WorkflowTerminated, _
  AddressOf OnWorkflowTerminated
' Later in the class:
Shared Sub OnWorkflowCompleted(ByVal sender As Object, _
  ByVal e As WorkflowCompletedEventArgs)
  WaitHandle.Set()
End Sub
Shared Sub OnWorkflowTerminated(ByVal sender As Object, _
  ByVal e As WorkflowTerminatedEventArgs)
  Console.WriteLine(e.Exception.Message)
  WaitHandle.Set()
End Sub
C#
workflowRuntime.WorkflowCompleted +=
  delegate(object sender, WorkflowCompletedEventArgs e) {
    waitHandle.Set(); };
workflowRuntime.WorkflowTerminated +=
  delegate(object sender, WorkflowTerminatedEventArgs e)
  {
    Console.WriteLine(e.Exception.Message);
    waitHandle.Set();
  };

When either event occurs, the code calls the wait handle’s Set method, which allows the console application’s
thread to continue running (in effect, completing the application and allowing the console window to close). If
the workflow terminates abnormally, the WorkflowTerminated event handler writes the exception to the console
window before calling the Set method. (This is, as you can probably surmise, not terribly helpful—because the
26
message appears in the window immediately before the window closes, you’ll never actually see the message if
the workflow throws an unhandled exception!)
You’ve investigated all the code provided in the project template, except the code that actually starts your
workflow running. This code appears between the code that sets up the event handlers, and the code that calls
the wait handle’s WaitOne method:
Visual Basic
Dim workflowInstance As WorkflowInstance
workflowInstance = _
  workflowRuntime.CreateWorkflow(GetType(Workflow1))
workflowInstance.Start()
C#
WorkflowInstance instance = workflowRuntime.CreateWorkflow(
  typeof(WorkflowDemo1.Workflow1));
instance.Start();

This code starts by creating a WorkflowInstance object, assigning to it the return value of calling the
WorkflowRuntime object’s CreateWorkflow method. By passing the type of the workflow to be created as a
parameter to the CreateWorkflow method, the workflow runtime can determine exactly which type of workflow
to create. Finally, the code calls the Start method of the workflow instance, which begins executing the
workflow at its first activity. (Sequential workflows have an initial activity; state machine workflows have an
initial state. It’s a subtly different concept, and you’ll understand it better once you try out a state machine
workflow.)
Although you’ll probably want to spend the most time fixating on the mechanics of the AutoResetEvent object
and the wait handle, please don’t—these features are only necessary for workflows hosted by a Console
application, to keep the application running as long as the workflow hasn’t completed. When you host a
workflow in any other type of application (a Windows Forms application, for example), you needn’t worry
about keeping the application alive.

Build a Slightly More Useful Workflow


Although it’s important to work through the obligatory “Hello, World” example as you’ve done, the workflow
you built really didn’t do much. For the remainder of this tutorial, you’ll build a workflow application with the
following features for backing up files in a folder:
• When the workflow starts up, it creates the required backup folder, if necessary, using a Code activity.

• Using a While activity, it loops through all the files in the “from” folder.

• Within the activity inside the While activity, a Code activity performs the file copy.

• The workflow receives its “from” and “to” folders passed as parameters from the host application.

In setting up this simple workflow, you’ll learn how to use the While activity, and also how to pass parameters
to a workflow from the host.
In Visual Studio 2008, create a new project, again selecting Sequential Workflow Console Application as the
template. Name the project BackupWorkflow.
In the workflow designer, drag a Code activity, then a While activity, and finally, another Code activity into the
designer. When you’re done, the designer should look like Figure 9. Note that each of the activities displays an
27
error condition: the two Code activities require code to execute, and the While activity requires you to supply a
condition so that it can determine when to stop executing.

Figure 9. Create this simple workflow.


The While activity allows you to drop a single activity within it (note the “Drop and Activity” prompt inside the
activity). What if you want to execute multiple activities in a loop? Although you cannot drop multiple activities
inside the While activity, for this very purpose, WF supplies a Sequence activity. The Sequence activity acts as a
container for other activities, and as far as the While activity is concerned, it contains only a single activity once
you place a Sequence activity inside it. Although this workflow only requires a single activity within the While
activity, iif you were to add more than a single activity, you would need the Sequence activity. On the other
hand, adding the Sequence activity adds significant overhead to the workflow’s execution—therefore, only add
a Sequence activity if you need multiple activities within the While activity. As a “best practice”, consider
minimizing the number of activities within the While activity, if at all possible..
Drag a Sequence activity inside the While activity. When you’re done, the workflow should resemble Figure 10.

28
Figure 11. The completed layout should look like this.

Configure Code Activities


Double-click codeActivity1, creating the activity’s ExecuteCode handler. At the top of the code file, add the
following statement:
Visual Basic
Imports System.IO
C#
using System.IO;

Outside the procedure you just created, but inside the Workflow1 class, add the following declarations. You’ll
use these declarations to keep track of the “from” and “to” folders, as well as the current file and total number
of files as you’re copying files:
Visual Basic
Private currentFile As Integer
Private files As FileInfo()
Private _totalFiles As Integer
Public Property TotalFiles() As Integer
  Get
    Return _totalFiles

29
End Get
Set(ByVal value As Integer)
    _totalFiles = value
  End Set
End Property
Private _toFolder As String
Public Property toFolder() As String
  Get
    Return _toFolder
  End Get
  Set(ByVal value As String)
    _toFolder = value
  End Set
End Property
Private _fromFolder As String
Public Property fromFolder() As String
  Get
    Return _fromFolder
  End Get
  Set(ByVal value As String)
    _fromFolder = value
  End Set
End Property
C#
public string toFolder { get; set; }
public string fromFolder { get; set; }
public int totalFiles ( get; set; }
private int currentFile;
private FileInfo[] files;

In the codeActivity1_ExecuteCode procedure, add the following code, which initializes the variables:
Visual Basic
currentFile = 0
files = New DirectoryInfo(fromFolder).GetFiles
totalFiles = files.Count
' Create the backup folder.
Directory.CreateDirectory(toFolder)
30
C#
currentFile = 0;
files = new DirectoryInfo(fromFolder).GetFiles();
totalFiles = new DirectoryInfo(fromFolder).GetFiles().Count();
// Create the backup folder:
Directory.CreateDirectory(toFolder);

Select View | Designer to switch back to the workflow designer. Double-click codeActivity2, and add the
following code to the activity’s ExecuteCode handler. This code retrieves the name of the current file to be
copied, copies it to the “to” folder (overwriting existing files), and increments the current file counter:
Visual Basic
Dim currentFileName As String = _
  Path.GetFileName(files(currentFile).Name)
files(currentFile).CopyTo( _
  Path.Combine(toFolder, currentFileName), True)
currentFile += 1
C#
string currentFileName =
  Path.GetFileName(files[currentFile].Name);
files[currentFile].CopyTo(
  Path.Combine(toFolder, currentFileName), true);
currentFile++;

At this point, you’ve set up the Code activities and their ExecuteCode handlers, but you’re not done: You still
need to configure the While activity, and you need to pass the fromFolder and toFolder values from the host
application. In addition, you need to add code in the host that reports on the results of executing the workflow.

Configure the While Activity


The While activity in this workflow needs to execute as many times as there are files in the “from” folder. The
code includes the currentFile and totalFiles variables—you simply need to expose that information to the While
activity.
Select View | Designer. In the designer, select the While activity. In the Properties window, find the Condition
property, select the drop-down arrow to the right of the property’s value, and select Declarative Rule Condition
(see Figure 11). You have the option of either defining a rule in the Properties window (Declarative Rule
Condition) or creating a condition in code (Code Condition). For this demonstration, you’ll create the condition
in the Properties window.

31
Figure 12. Set up the declarative rule condition.
Click the tiny “+” sign to the left of the Condition property, expanding the property. Set the ConditionName
property to filesLeft. (Naming the condition allows you to use the same condition in a different place within
your workflow.) Select the Expression property, and then select the ellipsis to the right of the property. Enter the
condition shown in Figure 12. As you type, note the IntelliSense support. Clearly, the Rule Condition Editor
window is able to retrieve information about the properties exposed by your workflow as you type. Although
Visual Basic developers can enter the expression using Visual Basic syntax (using the Me keyword instead of
this), the editor converts the syntax to C# syntax before it closes. Click OK to close the editor.

Figure 13. Create the While activity’s rule condition.


The rule condition specifies that your workflow should loop inside the While activity as long as the currentFile
value is less than the total number of files. In effect, you’ve created a simple For loop using the While activity
and a little bit of code. (You might be wondering if there’s some way to create a For Each loop using an activity.
There is, in fact. The Replicator activity can accomplish this same goal, with less code. You can learn more
about that activity in a later tutorial.)

Configuring Input Parameters

32
You still need to be able to specify the fromFolder and toFolder values from the host application. Because you’ll
often need to pass parameters to a workflow, as you start it up, WF provides a standardized mechanism for
passing parameters from the host to a workflow.
As you did earlier, open the Module1.vb or Program.cs file in the code editor. Currently, the Main procedure
creates the workflow instance using the following code:
Visual Basic
workflowInstance =
workflowRuntime.CreateWorkflow(GetType(Workflow1))
C#
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(typeof(BackupWorkflow.Workflow1));

In order to pass parameters to the workflow, you must create a generic dictionary, using a String type as the key,
and an Object type as the value. Add property name and value pairs to the dictionary, and pass it as a second
parameter in the call to the CreateWorkflow method. The workflow runtime engine passes the parameters by
name to the workflow as it creates the instance.
Because the workflow you created exposes public fromFolder and toFolder properties, you can easily pass these
parameters from the host application. To finish your workflow, add the following code to the Main procedure,
replacing the existing line of code that calls the CreateWorkflow method (feel free to alter the specific folders to
match your own situation.):
Visual Basic
Dim parameters As New Dictionary(Of String, Object)
parameters.Add("fromFolder", "C:\test")
parameters.Add("toFolder", "C:\backup")
workflowInstance =
  workflowRuntime.CreateWorkflow(GetType(Workflow1), parameters)
C#
var parameters = new Dictionary<string, object>();
parameters.Add("fromFolder", @"C:\test");
parameters.Add("toFolder", @"C:\backup");
WorkflowInstance instance =
  workflowRuntime.CreateWorkflow(
  typeof(BackupWorkflow.Workflow1), parameters);

This technique has its own set of tricks. First of all, the parameter names must match public properties (not
fields) in the workflow’s class. If you add an invalid property name to the dictionary, you won’t get a compile-
time error—instead, you’ll find out at runtime that your property name is incorrect. In addition, the property
names are case-sensitive, even when you’re writing code in Visual Basic.

Display Workflow Results


As you’ve seen earlier, the workflow runtime’s WorkflowCompleted executes once the workflow completes
successfully. In addition, you’ve seen how to pass parameters to the workflow, using a custom dictionary. You
can also retrieve values back from the workflow, in the WorkflowCompleted event handler: the
33
WorkflowCompletedEventArgs that the .NET Runtime passes to this event handler exposes its
OutputParameters collection. You can use the name of any public property within the workflow as a string index
into this collection to retrieve the value of the property. To verify this behavior, in Module1.vb or Program.cs,
modify the OnWorkflowCompleted event handler so that it includes the following code (in C#, the event
handler appears at the end of a long line of code, which hooks up the WorkflowCompleted event handler using
an anonymous method. You’ll find it easier to add this code if you reformat the existing code so that it looks
similar to the WorkflowTerminated event handler):
Visual Basic
Console.WriteLine("Copied {0} file(s)", _
e.OutputParameters("TotalFiles"))
Console.WriteLine("Press any key to continue...")
Console.ReadKey()
WaitHandle.Set()
C#
Console.WriteLine("Copied {0} file(s)", currentFile);
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
waitHandle.Set();

Save and run your project. If you’ve followed the directions carefully, the workflow should copy all the files
from the “from” folder to the “to” folder, and you should see the results of the workflow in the Console window.

Conclusion
In this tutorial, you’ve learned many of the basic concepts involved in creating and executing workflows, using
the Windows Workflow Foundation. Of course, the workflows you created are contrived, and exist simply to
demonstrate specific features of WF—you’ll need to consider, as you begin thinking about incorporating WF
into your own environment, what kinds of tasks lend themselves to running as workflows. Consider this:
because WF supports features like persistence, which allows the workflow to persist its state to a data store
(SQL Server, by default), WF is best suited for applications in which you have a long-running task that must
survive even if the host machine needs to be rebooted during the task’s execution. For simple applications like
you’ve seen here, WF is truly overkill. For enterprise solutions in which you have tasks that might not complete
for days, or months, WF provides a perfect solution.
Now that you’ve gotten a taste for what you can do using WF, take the time to try out the remaining tutorials in
this series, and then start building your own workflows. You’ll be amazed at the power in this rich framework.

Windows Workflow Tutorial: Using Basic Flow Control (IfElse, While,


Parallel) for Windows Workflow Foundation
Introduction

As you have seen in the previous tutorials in this series, workflows model business processes. As you build a
workflow, you are answering several questions:

• What happens?

34
• How does it happen?

• When does it happen?

Activities and the custom code associated with them are the “what” and “how” of a workflow. To address the
“when” of a workflow, the order the activities appear in a sequential workflow and the transitions between
states in a state machine workflow are one part, and the activities that control the flow of the workflow are the
other. Windows Workflow Foundation (WF) provides a number of flow control activities, and you’ll spend the
next few tutorials in this series exploring how to use them.
In this tutorial, you will investigate the three workflow flow control basic building blocks: the IfElse, While and
Parallel activities. IfElse provides branching. While provides repeated execution. Parallel provides a “round-
robin” execution of multiple child activities.

Introducing the IfElse Activity

The IfElse activity is the most basic of the flow control activities – it enables you to control whether one or
more activities executes. As an example, in an inventory-checking system, you might want one set of activities
to execute if an item is in stock and a different set of activities to execute if the item is not in stock – the IfElse
activity enables you to do that.
The IfElse activity contains one or more IfElseBranch activities. At runtime, the workflow evaluates the
Condition property of the first branch. If the condition evaluates to true, the activities in that branch execute. If
the condition evaluates to false, the workflow skips that branch and evaluates the condition of the next branch,
if there is one.
Each branch except the last branch must have a condition. If the last branch has a condition, the activities in it
will execute if that condition evaluates to true. If it does not have a condition, the activities in it will execute if
none of the other branches has a condition that evaluates to true. In this scenario, the last branch is the Else
branch.
An IfElse activity can have as many branches as you need. You can also nest IfElse activities. Each branch can
contain as many activities as you need. To return a true or false value for the condition property, you can use a
declarative rule condition, which is a simple expression, or a code condition, which is a method that is evaluated
at runtime.

Create a Workflow Project


To demonstrate the first two flow control activities in this tutorial, you will create a sequential workflow project
to model checking the inventory for an item and if necessary restocking the item.
To get started, in Visual Studio 2008 select File | New | Project to display the New Project dialog box. In the list
of project types, select Workflow, displaying the list of templates. For this demonstration, select the Sequential
Workflow Console Application template, name your application IfElseWhileDemo, and select an appropriate
folder for the project. Click OK to create the project.
Next, you will create an XML file containing inventory data for three products; you’ll be using this XML file
instead of hard-coding the values in this sample project. To create the file, select Project | Add New Item, and
select the XML File template. Name the file Inventory.xml and click Add. Add the following XML:
XML
<Products>
<Product>
<ProductID>1</ProductID>

35
<OnHand>100</OnHand>
<Available>50</Available>
</Product>
<Product>
<ProductID>2</ProductID>
<OnHand>10</OnHand>
<Available>250</Available>
</Product>
<Product>
<ProductID>3</ProductID>
<OnHand>25</OnHand>
<Available>50</Available>
</Product>
</Products>

In the Solution Explorer, select Inventory.xml. Set the Copy to Output Directory property to Copy always.
Now you will create the workflow. Return to the workflow designer by selecting the Workflow1 tab. You can
also double click Workflow1 in the Solution Explorer.
Select View | Code. The console application that you will create will pass the ID of the product for which you
want to check inventory. To enable the workflow to accept this value, add the following property to the
workflow:
C#
public int ProductID { get; set; }

Now add the following declarations to store the amount on hand and the amount on order for the specific
product:
C#
public int onHand = 0;

public int available = 0;

Add Activities to the Workflow


Select View | Designer to return to the workflow designer. Drag a Code activity From the Toolbox onto the
workflow. Name this activity LookupProduct. Double-click LookupProduct and add the following code to the
activity’s ExecuteCode event handler:
C#
var xmlFile = System.Xml.Linq.XDocument.Load(

  System.IO.Path.Combine(

  AppDomain.CurrentDomain.BaseDirectory, "Inventory.xml"));

var inventory =

  from product in xmlFile.Descendants("Product")

  where Convert.ToInt32(

36
product.Element("ProductID").Value) == ProductID

select new

OnHand = Convert.ToInt32(product.Element("OnHand").Value),

Available = Convert.ToInt32(

product.Element("Available").Value)

};

onHand = inventory.First().OnHand;

available = inventory.First(). Available;

This code uses a LINQ To XML query to retrieve the OnHand and Available elements for the specified product.
These are stored in the onHand and available fields.
Select View | Designer to return to the workflow designer. You will instruct the workflow to take one action if
there is sufficient quantity of the item in stock and another action if there is not.
Drag an IfElse activity from the Toolbox onto the workflow below LookupProduct. Name this activity
CheckOnHand. Rename ifElseBranchActivity1 to IfSufficientOnHand. Rename ifElseBranchActivity2 to
IfNotSufficientOnHand.

Conditions: Using a Declarative Rule Condition


At this point, the IfSufficientOnHand activity indicates an error. You haven’t told it yet what condition to
evaluate. To do that, select IfSufficientOnHand. In the Properties window, find the Condition property, select
the drop-down arrow to the right of the property’s value, and select Declarative Rule Condition (see Figure 1).
For the Condition property, you have the option of either defining a rule in the Properties window (Declarative
Rule Condition) or creating a condition in code (Code Condition). For this activity, you are selecting the
Declarative Rule condition and defining the condition in the Properties window; you’ll visit the other option in
our next IfElse activity.

37
Figure 1. Choose the type of condition you want to use.
Click the “+” sign to the left of the Condition property, expanding the property. Set the ConditionName property
to SufficientOnHand. The condition will get a default name if you don’t specify one, but naming the condition
makes it easier for you to to identify the condition later on, as well as use it in a different place within your
workflow. Click the Expression property and then click the ellipsis to the right of that property. Enter the
condition shown in Figure 2. Click OK to close the editor.

Figure 2. Create the IfElseBranch activity’s rule condition.

Using the WF Rule Condition Editor


The Rule Condition Editor is essentially a code editor. You can enter expressions into it just as you would if you
were using expressions in your code. You get the benefit of IntelliSense, just as you do in the Visual Studio code
editor (see Figure 3).

Figure 3. Use IntelliSense to help you create expressions.


The Rule Condition Editor supports the following operators:

• Relational: ==, =, !=

• Comparison: <, <=, >, >=

• Arithmetic: +, - , *, /, MOD

• Logical: AND, &&, OR, ||, NOT, !

• Bitwise: &, |

38
Notice there is a mix of Visual Basic and C# syntax in this list. You can enter expressions using either language.
However, be aware that Visual Studio uses the Visual C# compiler to validate expressions. The editor will
convert simple Visual Basic syntax to C# prior to compiling. For example, you could enter me.onHand in the
Rule Condition Editor. When you click OK, the editor with convert that to this.onHand.
If you are a Visual Basic developer, you are used to case insensitivity (if you enter me.onhand in the code editor,
Visual Studio will automatically convert that to Me.onHand). It is important to note that the Rule Condition
Editor is case sensitive and, like the Visual C# code editor, will enforce case sensitivity. This means that if you
enter me.onhand in the Rule Condition Editor and click OK, you will see the error message shown in Figure 4.

Figure 4. The Rule Condition Editor is case sensitive.

Finish the Workflow


From the Toolbox, drag a Code activity into IfSufficientOnHand. Name the activity ReportOnHand. Double-
click ReportOnHand and add the following code to the activity’s ExecuteCode event handler:
C#
Console.WriteLine("There are {0} units of product {1} on hand\n" ,

onHand, ProductID);

Select View | Designer to return to the workflow designer.


From the Toolbox, drag a Code activity into IfNotSufficientOnHand. Name the activity Reorder. Double-click
Reorder and add the following code to the activity’s ExecuteCode event handler:
C#
Console.WriteLine(

"There are only {0} units of product {1} on hand" ,

onHand, ProductID);

Console.WriteLine("It is time to reorder\n");

Select View | Designer to return to the workflow designer. The workflow should look like

39
Figure 5. The workflow should look like this.

Calling the Workflow from the Console Application


Your final step will be to modify the console host application to pass the id of a product to the workflow. In the
Solution Explorer, double click Module1.vb or Program.cs. Add the following code to the Main method,
replacing the existing line of code that calls the CreateWorkflow method:
C#
var parameters = new Dictionary<string, object>();

parameters.Add("ProductID", 1);

WorkflowInstance instance = workflowRuntime.CreateWorkflow(

  typeof(IfElseWhileDemo.Workflow1), parameters);

Save and press Ctrl + F5 to run your project. You should first see the following output:
Output
There are 100 units of product 1 on hand
Press any key to continue . . .
Press any key to exit the application.

To see what happens if there is insufficient stock for a product, make the following modification to the Main
method:
C#
parameters.Add("ProductID", 2);

Save and press Ctrl + F5 to run your project. You should see the following output:
Output

40
There are only 10 units of product 2 on hand
It is time to reorder
Press any key to continue . . .
Press any key to exit the application.

Modify the Workflow to Add a Nested IfElse Activity


Return to the workflow designer in Visual Studio. You will now add additional flow control logic to handle
inventory reordering - instruct the workflow to perform an action if you can successfully reorder the product
and a different action if you cannot.
Drag an IfElse activity from the Toolbox into IfNotSufficientOnHand below Reorder. Name this activity
CheckReorderStatus. Rename ifElseBranchActivity1 to IfReordered. Rename ifElseBranchActivity2 to
IfNotReordered. The workflow should look like Figure 6.

Figure 6. The workflow should look like this.

Conditions: Using a Code Condition


Select IfReordered. In the Properties window, find the Condition property, select the drop-down arrow to the
right of the property’s value, and select Code Condition (see Figure 7).

41
Figure 7. Choose the type of condition you want to use.
Click the “+” sign to the left of the Condition property, expanding the property. Set the Condition property to
PlaceReorder. Press Enter. Visual Studio creates the PlaceReorder method and opens the code editor. The new
method will look like the below:
C#
private void PlaceReorder(object sender, ConditionalEventArgs e)

The PlaceReorder method is actually an event handler. When you specify Code Condition, Visual Studio creates
an instance of the CodeCondition class and adds it to the workflow. The CodeCondition class has an Evaluate
method. The workflow runtime calls this method when it needs to evaluate the condition. This method causes
the Condition event to occur. The PlaceReorder method handles this event.
The second argument in this method is of the type ConditionEventArgs. This class has a Result property, which
is a Boolean value. Setting this property to true or false will cause the condition to evaluate to true or false.
Add the following code to the PlaceReorder method:
C#
e.Result = onHand + available >= 100;

For the purposes of this tutorial, a reorder is successful if there is enough of the product available to have at
least 100 units in stock.

Finishing the IfElse Workflow


Select View | Designer to return to the workflow designer. From the Toolbox, drag a Code activity into
IfReordered. Name the activity ReportReorder. Double-click ReportReorder and add the following code to the
activity’s ExecuteCode event handler:
C#
Console.WriteLine("{0} units of product {1} will be ordered\n" ,

42
100 – onHand, ProductID);

Select View | Designer to return to the workflow designer.


From the Toolbox, drag a Code activity into IfNotReordered. Name the activity ReportFailure. Double-click
ReportFailure and add the following code to the activity’s ExecuteCode event handler:
C#
Console.WriteLine("You need {0} units of product {1} " +

"but only {2} are available", 100 - onHand,

ProductID, available);

Console.WriteLine("The product has not been reordered\n");

Select View | Designer to return to the workflow designer. The workflow should look like Figure 8.

Figure 8. The workflow should look like this.


Save and press Ctrl + F5 to run your project. You should first see the following output:
Output
There are only 10 units of product 2 on hand
It is time to reorder
90 units of product 2 will be ordered
Press any key to continue . . .

43
Press any key to exit the application.

To see what happens if there is insufficient availability for a reorder, make the following modification to the
Main method:
C#
parameters.Add("ProductID", 3);

Save and press Ctrl + F5 to run your project. You should see the following output:
Output
There are only 25 units of product 3 on hand
It is time to reorder
You need 75 units of product 3 but only 50 are available
The product has not been reordered
Press any key to continue . . .
Press any key to exit the application.

Introducing the While Activity

The While activity allows you to control how many times an activity or a sequence of activities executes. The
While activity is a container and can contain a single activity. You might think that severely limits its usefulness
until you understand that the single activity can be a Sequence activity (or other composite activity), which can
contain as many activities as you want.
Similar to a while loop construct in code, as long as the While activity’s condition evaluates to true, the
workflow will execute the activity or activities it contains. The workflow will continue to do this until the
condition evaluates to false.
For your next exercise in this tutorial, you will modify the workflow you created in the previous exercise.
Rather than process one item at a time, you will have the workflow process all items. You will use a While
activity and the condition will indicate whether there are items remaining.

Modify the Workflow


Return to Visual Studio 2008, and open Workflow1 in the workflow designer. In the previous exercise, you used
a Code activity to read an XML file. Because you are now iterating all items in the inventory, you need to
modify the workflow to load in the entire file and iterate through it. To load the file into memory, you will use
the Initialized event of the workflow. This event occurs after the workflow starts but before it executes any
activities. In the Properties window, locate the Initialized property. Type WorkflowInititialized and press Enter;
Visual Studio creates the Initialized event handler and open the code editor.
Remove the code that defines the ProductID property since the workflow no longer works with a single ID.
Then add the following declarations to the workflow class:
C#
private IEnumerable<System.Xml.Linq.XElement> inventory = null;

public int itemCount = 0;

public int nextItemNumber = 0;

private int productID = 0;

The inventory field will store the contents of the inventory list. You declare this as IEnumerable(Of XElement)
so that you can query the Inventory.xml file using LINQ to XML. You will write that code next. The remaining
44
fields in the declaration will store the number of items in the inventory list, the next item number in the list and
the ID of that item.
Replace all instances of Me.ProductID or this.ProductID in your code with productID.
Add the following code to the WorkflowInitialized method:
C#
var xmlFile = System.Xml.Linq.XDocument.Load(

System.IO.Path.Combine(

AppDomain.CurrentDomain.BaseDirectory, "Inventory.xml"));

inventory =

from product in xmlFile.Descendants("Product")

    select product;

itemCount =

  (from product in xmlFile.Descendants("Product")

   select product.Element("ProductID").Value).Count();

In the previous exercise, you used a LINQ To XML query to retrieve inventory information for a single product.
In this example, the query retrieves inventory information for all products.
Select View | Designer to return to the workflow designer. Drag a While activity from the Toolbox onto the
workflow above the LookupProduct activity. Name the While activity ProcessItems.
In the Properties window, find the Condition property, select the drop-down arrow to the right of the property’s
value, and select Declarative Rule Condition.
Click the “+” sign to the left of the Condition property, expanding the property. Set the ConditionName property
to MoreItems. Click the Expression property and then click the ellipsis to the right of that property. Enter
this.nextItemNumber <= this.itemCount - 1 as the condition. Click OK to close the editor.
ProcessItems still indicates an error because you haven’t added an activity to it yet. From the Toolbox, drag a
Sequence activity into ProcessItems. Now select LookupProduct and CheckOnHand and drag them into
sequenceActivity1. The workflow should look like Figure 9.

45
Figure 9. The workflow should look like this.
In the previous exercise, the LookupProduct activity retrieved the XML file and found a single product. You
will now change it to retrieve the next item in the inventory list. Double-click LookupProduct and replace the
code in the activity’s ExecuteCode event handler with the following:
C#
productID = Convert.ToInt32(

inventory.ElementAt(nextItemNumber).Element("ProductID").Value);

onHand = Convert.ToInt32(

inventory.ElementAt(nextItemNumber).Element("OnHand").Value);

available = Convert.ToInt32(

inventory.ElementAt(nextItemNumber).Element("Available").Value);

nextItemNumber += 1;

Calling the Workflow from the Console Application


Your final step will be to modify the console host application so that it does not pass a product id to the
workflow. In the Solution Explorer, double click Module1.vb or Program.cs. Comment out the following code
in the Main method:
46
C#
var parameters = new Dictionary<string, object>();

parameters.Add("ProductID", 3);

Make the following change to the code that starts the workflow:
C#
WorkflowInstance instance = workflowRuntime.CreateWorkflow(

  typeof(IfElseWhileDemo.Workflow1));

Save and press Ctrl + F5 to run your project. You should see the following output:
Output
There are 100 units of product 1 on hand
There are only 10 units of product 2 on hand
It is time to reorder
90 units of product 2 will be ordered
There are only 25 units of product 3 on hand
It is time to reorder
You need 75 units of product 3 but only 50 are available
The product has not been reordered
Press any key to continue . . .
Press any key to exit the application.

Introducing the Parallel Activity

You can use the Parallel activity to execute two or more branches of activities in parallel. They do not execute
literally in parallel. The workflow executes an activity in the first branch and then switches to an activity in the
next branch, and so on. You can have as many branches as you want and each branch can contain as many
activities as you want. In addition, the branches do not need to contain the same activities. Often they will, but it
is not a requirement.
For your final exercise in this tutorial, you will create a new sequential workflow project to model an auction.
This auction will consist of three bidders, each represented by a Parallel activity in the workflow. Each bidder
has a budget. In each round of the auction, each bidder ups the current bid by half of his or her remaining
budget. The auction continues until two of the bidders are out of money. The third bidder wins the auction.

Create a Workflow Project


To get started, in Visual Studio 2008 select File | New | Project to display the New Project dialog box. In the list
of project types, select Workflow, displaying the list of templates. For this demonstration, select the Sequential
Workflow Console Application template, name your application ParallelDemo, and select an appropriate folder
for the project. Click OK to create the project.
Select View | Code. Now add the following declarations to store the price of the item the workflow will auction
and the budgets of the three bidders:
C#
private decimal budget1 = 5000M;

private decimal budget2 = 10000M;

47
private decimal budget3 = 25000M;

private decimal nextBid1 = 500M;

private decimal nextBid2 = 1000M;

private decimal nextBid3 = 2500M;

public bool outOfMoney1 = false;

public bool outOfMoney2 = false;

public bool outOfMoney3 = false;

private decimal currentBid = 100M;

private int lastBidder = 0;

public int biddersOut = 0;

The first three fields set the available budget for the three bidders. The next three fields keep track of the
amount by which each bidder will increase the current bid when it is their turn. The next three fields keep track
of whether each bidder is out of money, meaning they do not have enough to up the current bid. The currentBid
field represents the current bid. The lastBidder field keeps track of which bidder bid last. The biddersOut field
keeps track of how many bidders are finished bidding.

Add Activities to the Workflow


Select View | Designer to return to the workflow designer. Drag a Code activity From the Toolbox onto the
workflow. Name this activity StartBidding. Double-click StartBidding and add the following code to the
activity’s ExecuteCode event handler:
C#
Console.WriteLine("The starting bid is {0:C}\n" , currentBid);

Select View | Designer to return to the workflow designer. From the Toolbox, drag a While activity onto the
workflow below StartBidding. Name the While activity Bidding.
In the Properties window, find the Condition property, select the drop-down arrow to the right of the property’s
value, and select Declarative Rule Condition.
Click the “+” sign to the left of the Condition property, expanding the property. Set the ConditionName property
to BiddersRemaining. Click the Expression property and then click the ellipsis to the right of that property.
Enter this.biddersOut < 2 as the condition. Click OK to close the editor.
From the Toolbox, drag a Parallel activity into StartBidding. From The Parallel activity contains two Sequence
activities by default. To add a third Sequence activity, right click ParallelActivity1 and select Add Branch.
Name the three Sequence activities Bidder1, Bidder2 and Bidder3. The workflow should look like Figure 10.

48
Figure 10. The workflow should look like this.
Each branch in the Parallel activity represents one of the three bidders. Inside each branch you will model the
process of bidding. If the bidder has money left he or she will increase the current bid by a certain amount.
Drag an IfElse activity from the Toolbox into Bidder1. Right click on ifElseBranchActivity2 and select Delete.
Rename ifElseBranchActivity1 to IfStillBidding1.
In the Properties window, find the Condition property, select the drop-down arrow to the right of the property’s
value, and select Declarative Rule Condition.
Click the “+” sign to the left of the Condition property, expanding the property. Set the ConditionName property
to NotOutOfMoney1. Click the Expression property and then click the ellipsis to the right of that property.
Enter !this.outOfMoney1 as the condition. Click OK to close the editor.
Copy ifElseActivity1 and paste it into Bidder2. Rename ifElseBranchActivity1 to IfStillBidding2. Click the “+”
sign to the left of the Condition property, expanding the property. Set the ConditionName property to
NotOutOfMoney2. Click the Expression property and then click the ellipsis to the right of that property. Enter !
this.outOfMoney2 as the condition. Click OK to close the editor.
Copy ifElseActivity1 and paste it into Bidder3. Rename ifElseBranchActivity1 to IfStillBidding3. Click the “+”
sign to the left of the Condition property, expanding the property. Set the ConditionName property to
NotOutOfMoney3. Click the Expression property and then click the ellipsis to the right of that property. Enter !
this.outOfMoney3 as the condition. Click OK to close the editor.
After you’ve accomplished the above, the Parallel activity should look like Figure 11.

49
Figure 11. The Parallel activity should look like this.

Finishing the Parallel Workflow


To complete the workflow, you will now add the custom code activities into each of the branches to execute the
business logic; in this case, you will have the bidders place their bids.
From the Toolbox, drag a Code activity into IfStillBidding1. Name the activity Bid1. Double-click Bid1 and add
the following code to the activity’s ExecuteCode event handler:
C#
if (!(outOfMoney2 && outOfMoney3))

if (currentBid + nextBid1 > budget1)

outOfMoney1 = true;

biddersOut += 1;

Console.WriteLine("Bidder 1 is out\n");

else

currentBid += nextBid1;

lastBidder = 1;

Console.WriteLine("Bidder 1 bids {0:C}\n", currentBid);

Select View | Designer to return to the workflow designer. From the Toolbox, drag a Code activity into
IfStillBidding2. Name the activity Bid2. Double-click Bid2 and add the following code to the activity’s
ExecuteCode event handler:
C#
if (!(outOfMoney1 && outOfMoney3))

50
{

if (currentBid + nextBid2 > budget2)

outOfMoney2 = true;

biddersOut += 1;

Console.WriteLine("Bidder 2 is out\n");

else

currentBid += nextBid2;

lastBidder = 2;

Console.WriteLine("Bidder 2 bids {0:C}\n", currentBid);

Select View | Designer to return to the workflow designer. From the Toolbox, drag a Code activity into
IfStillBidding3. Name the activity Bid3. Double-click Bid3 and add the following code to the activity’s
ExecuteCode event handler:
C#
if (!(outOfMoney1 && outOfMoney2))

if (currentBid + nextBid3 > budget3)

outOfMoney3 = true;

biddersOut += 1;

Console.WriteLine("Bidder 3 is out\n");

else

currentBid += nextBid3;

lastBidder = 3;

Console.WriteLine("Bidder 3 bids {0:C}\n", currentBid);

Select View | Designer to return to the workflow designer. From the Toolbox, drag a Code activity into the
workflow below Bidding. Name the activity DeclareWinner. Double-click DeclareWinner and add the following
code to the activity’s ExecuteCode event handler:
C#
Console.WriteLine("Bidder {0} wins with a bid of {1:C}\n",

51
lastBidder, currentBid);

Save and press Ctrl + F5 to run your project. You should see the following output:
Output
The starting bid is $100.00
Bidder 1 bids $600.00
Bidder 2 bids $1,600.00
Bidder 3 bids $4,100.00
Bidder 1 bids $4,600.00
Bidder 2 bids $5,600.00
Bidder 3 bids $8,100.00
Bidder 1 is out
Bidder 2 bids $9,100.00
Bidder 3 bids $11,600.00
Bidder 2 is out
Bidder 3 wins with a bid of $11,600.00
Press any key to continue . . .
Press any key to exit the application.

Extra Credit: Comparing the Parallel and the Replicator Activities


You have just seen the basics of how the Parallel activity works. The workflow processes each parallel branch
in turn, starting with the first branch and continuing through to the last. In this example, the parallel activity
evaluates a condition and then executes the Code activity if the condition evaluates to true. In this simple
example, each bidder has the same strategy, so you used the same activities in each branch, using basically the
same code for each bidder.
In the next tutorial in this series, you will learn about the Replicator activity, which provides an alternative way
to construct this workflow logic with less duplication of effort. The Replicator activity is the functional
equivalent of a ForEach activity, and provides you with the ability to execute one or more activities for each
member of a collection. If you used the Replicator here, you would have passed the three bidders as a collection
to a Sequence activity containing an IfElse and Code activity within the Replicator. At runtime, the workflow
would execute the activities for each bidder in turn. You are encouraged to work through the Replicator tutorial
and then redo this example.

Extra Credit: How WF Works with Multiple Activities in a Parallel Branch


In this example, each branch of the Parallel activity contained the same activities. You can therefore reliably
expect the workflow to process each branch completely before processing the next branch. What if one branch
had a different set of activities? For example, suppose the second bidder reevaluated his or her strategy each
round prior to bidding. You might then add a Code activity named Reevaluate before the IfElse activity in the
second branch.
At runtime, the workflow would first execute the Bid1 activity. It would then execute the Reevaluate activity. It
would then execute the Bid3 activity, followed by Bid1 and then it would execute the Bid2 activity. The
workflow does not execute all of the activities in each branch before moving on to the next branch. This
behavior is by design, and has to do with the way the workflow runtime schedules activities for execution. This
can be very helpful when coordinating work across a series of asynchronous processes/services where you can
52
initiate the work in your parallel branches, and then gather them back up. But this topic goes beyond the scope
of this tutorial, and the subject of its own article.
If you wanted to have branch 2 execute completely before moving onto branch 3 - how would you construct this
workflow? You could use a SynchronizationScope activity in branch 2 rather than a Sequence activity. The
SynchronizationScope activity executes the activities in it in a sequential fashion. An easier solution would be
to use the Replicator, because it completely executes its contained activities before moving on to the next item
in the collection.

Conclusion

In this tutorial, you learned how to use the IfElse, While and Parallel activities to control the flow of a
workflow. You saw how to use these activities to specify when activities execute and for how long. For the sake
of simplicity, you used custom code activities in command line applications; in your workflows, you will
typically use custom activities in Windows or web applications, or running services. As you use these control
flow activities to drive your WF workflows, one concern that is raised by WF developers is when to use the
activity-based control flow and when to use code-based control flow.
The IfElse activity performs a similar purpose to the If..Else (Visual Basic) and if..else (C#) blocks in code. For
the IfElse activity, the decision of when to use an IfElse activity or perform branching in code tends to depend
on what action you want to take. If the action involves one or more activities, then you will use the IfElse
activity. If you can perform the action in code, then you will typically perform the branching within the code of
your custom activity.
The same applies to the While activity, which performs a similar purpose to several language constructs that
support looping. If you simply want to execute five lines of code repeatedly, you should just use a Code activity
and perform the looping in code. However, if you want to execute multiple activities repeatedly, then you
should use a While activity.

Windows Workflow Tutorial: Using the Replicator for Windows


Workflow Foundation
To provide support for general programming control flow constructs, the Windows Workflow Foundation (WF)
provides activities that you can easily recognize: the IfElse and While activities, for example, clearly allow you
to make choices, and loop within a workflow. What if you need to execute an action for each element of some
set of objects, however? You might look for a ForEach activity, but your search won’t find the activity you’re
seeking. The activity you need is in the toolbox, however: It’s the Replicator activity. This activity, the
functional equivalent of a ForEach activity, allows you to execute an activity for each member of a collection.
Immediately, you start seeing problems: If the Replicator can only execute a single activity for each element of
a collection, how can you execute complex actions for each element? The answer, of course, is that the
Replicator can not only execute a simple activity, it can execute a composite activity (an activity that can
contain other activities), such as a Sequence activity. To execute multiple activities, you can place a Sequence
activity within the Replicator activity, and the workflow runtime executes all the activities within the Sequence
activity for each element in the source collection.
Although you can use the While activity to provide the same behavior as the Replicator activity, the Replication
activity adds some important functionality that you would need to create yourself, if you were using the While
activity. For example:

53
• The Replicator activity automatically “visits” each item in its input collection, creating a separate instance of its
contained activity for each item in the collection. You don’t need to keep track of the number of items in the
collection, as you would if you were using the While activity.

• The Replicator activity allows you to supply code that it calls before, during, and after processing of each item in the
collection.

• The Replicator activity allows you to specify whether you want to handle the instances of its contained activity in
parallel, or serially. If you execute the activity instances in parallel, the Replicator initializes each instance of the child
activity one after the other, then executes each child activity, and finally, completes the execution of each instance of
the child activity. (This behavior is similar to using a Parallel activity, with one branch for each item in the data
collection.) If you execute the activity instances serially, the Replicator activity initializes, executes, and completes
each instance of the child activity before moving on to the next. (This behavior is similar to using a While activity.)
Based on your needs, you’ll need to decide which technique works best for you.

• The Replicator activity provides an Until property, which allows you to supply a condition (either a declarative
condition, or a code condition). This condition allows your application to “short-circuit” the behavior of the activity.
That is, if the Until condition becomes true, the activity stops executing the child activity instances, even if it hasn’t
completed working through the entire input collection.

This tutorial walks you through the basics of working with the Replicator activity, demonstrating how you can
use this activity to execute another activity (or activities) for each member of some collection of values or
objects.

Build the Sample Workflow

In this tutorial, you’ll create a simple workflow that processes all the files in a particular folder. You might, for
example, want to monitor the status of the contents of a particular folder in the file system, and at regular
intervals, sweep through the files and handle them in some particular way. In this sample workflow, you’ll
simply sweep through all the files and report some information about each to the Console window, but you
might want to back up each file, delete each file, or take some other action based on the existence of the file.
NOTE Although Windows Workflow Foundation doesn’t include an activity to monitor the file system for
changes, you can find a sample custom activity on Microsoft’s site that demonstrates this behavior. Browse to
http://msdn.microsoft.com/en-us/library/ms741707.aspx for more information. If you want to send an email in
response to finding a file (or for any other reason), you can make use of the sample email workflow activity.
Browse to http://msdn.microsoft.com/en-us/library/ms742097.aspx to download a sample email activity.
To get started, in Visual Studio 2008 select File | New | Project to display the New Project dialog box. In the list
of project types, select Workflow, displaying the list of workflow templates. Select the Sequential Workflow
Console Application template, name your application ReplicatorDemo, and select an appropriate folder for the
project. Click OK to create the project.
The point of this exercise is to create an instance of an activity for each element within a collection, and execute
each instance in turn. Therefore, from the Toolbox window, drag an instance of the Replicator activity into the
new workflow. Figure 1 shows the workflow, with the activity in place.

54
Figure 1. The Replicator activity indicates that it can contain multiple activities, but it cannot—it can
contain only a single activity.
Although the caption within the Replicator’s designer invites you to drop multiple activities within it, the
invitation is misleading—you can only add a single activity. At this point, as you can see in Figure 1, the
Replicator currently displays an error alert. Click on the red circle to view the error, and you’ll see a message
indicating that the Replicator activity must contain one activity.
To get started, from the Toolbox window, drag a Code activity to the interior of the Replicator activity. (Of
course, at this point, the Code activity displays an error, because you haven’t yet supplied it code it needs.
Disregard this error, for now.) Attempt to drag a second Code activity within the Replicator activity, to verify
that you can only place a single activity within the Replicator. Your attempts to drop a second activity will
simply never display any green dots within the activity—these green dots are your indication where you can
drop an activity.
TIP: In order to place multiple activities within the Replicator activity, use the Sequence activity; you can
then place multiple activities within the Sequence activity.
Once you’re done, the layout should resemble Figure 2.

55
Figure 2. Place a single activity within the Replicator activity.

Add Support Code

In order to get started with the sample workflow, select replicatorActivity1. Because you’ll need to add code for
each of the activity’s available handlers, the simplest way to set up all the procedures is to let the designer
generate them for you. With the Replicator activity selected, you can either click the Generate Handlers item
within the Properties window (see Figure 3), or select Workflow | Generate Handlers from Visual Studio’s
menu. Either way, once you’ve generated the handlers, Visual Studio displays the workflow’s code window,
with newly generated handler stubs for the Initialized, Completed, ChildInitialized, and ChildCompleted events.

Figure 3. Select Generate Handlers in the Properties window.


TIP: If you don’t see the Generate Handlers link within the Properties window, right-click the Properties
window and select the Commands option on the context menu.
At the top of the code file, add the following statement:
Imports System.IO
using System.IO;
Within the Workflow1 class, add the following two properties, which will track the search folder and the backup
properties:
Private searchFolderValue As String
Public Property searchFolder() As String
Get
56
Return searchFolderValue
End Get
Set(ByVal value As String)
searchFolderValue = value
End Set
End Property
Private backupFolderValue As String
Public Property backupFolder() As String
Get
Return backupFolderValue
End Get
Set(ByVal value As String)
backupFolderValue = value
End Set
End Property
public string searchFolder { get; set; }
public string backupFolder { get; set; }
Add the following two private class-level variables, to keep track of the current file, and the array of files in the
search folder:
Private currentFile As FileInfo
Public files As FileInfo()
private FileInfo currentFile = null;
public FileInfo[] files;
In the replicatorActivity1_Initialized event handler, add the following code. This code retrieves the array of
FileInfo objects corresponding to the files in the search folder, and also creates the backup folder if necessary:
If Not String.IsNullOrEmpty(searchFolder) Then
files = New DirectoryInfo(searchFolder).GetFiles("*.*")
End If
' This example assumes that it can create the backup
' folder. If it already exists, this code silently
' does nothing. If the code can't create the folder,
' the workflow will fail. You should add fault handling,
' and terminate the workflow if you can't
' create the folder. See the tutorial on fault handling
57
' for more information on handling exceptions.
If Not String.IsNullOrEmpty(backupFolder) Then
Directory.CreateDirectory(backupFolder)
End If
Console.WriteLine("Replicator activity initialized.")
if (!String.IsNullOrEmpty(searchFolder))
{
files = new DirectoryInfo(searchFolder).GetFiles("*.*");
}
// This example assumes that it can create the backup
// folder. If it already exists, this code silently
// does nothing. If the code can't create the folder,
// the workflow will fail. You should add fault handling,
// and terminate the workflow if you can't
// create the folder. See the tutorial on fault handling
// for more information on handling exceptions.
if (!String.IsNullOrEmpty(backupFolder))
{
Directory.CreateDirectory(backupFolder);
}
Console.WriteLine("Replicator activity initialized.");
To use the Replicator activity, you must also set the InitialChildData property of the activity. This property
indicates to the activity which set of data it should iterate through. You can either set this property in code, or
you can set it in the designer. You can set the InitialChildData property to any collection that implements the
IList interface, and that includes most collections and arrays. In this example, you must set the InitialChildData
property to the array of FileInfo objects that you just created in code.
To set the InitialChildData property, select View | Designer. In the Workflow designer, select the Replicator
activity. In the Properties window, select the InitialChildData property. Click the ellipsis button to the right of
the property value; this action displays a dialog box which allows you to select the value to which you will bind
the property. In the dialog box, select the files property, as shown in Figure 4. Click OK when you’re done.

Figure 4. Bind the InitialChildData property to the public files variable.


TIP: If you want to set the InitialChildData property in the Workflow designer, you must ensure that, at
runtime, the collection has been filled in before the ReplicatorActivity executes. In this case, you have
accomplished this goal by filling in the array during the Initialized event handler for the Replicator activity.
In the replicatorActivity1_Completed event handler, add the following code:

58
Console.WriteLine("Completed all the items in the data source.")
Console.WriteLine("Completed all the items in the data source.");
In the replicatorActivity1_ChildInitialized event handler, add the following code. This code uses the
InstanceData property of the procedure’s ReplicatorChildEventArgs parameter to retrieve the value of the
current FileInfo object, from the source array of files:
currentFile = CType(e.InstanceData, FileInfo)
Console.WriteLine("ChildInitialized event: " & currentFile.Name)
currentFile = (FileInfo)e.InstanceData;
Console.WriteLine("ChildInitialized event: " + currentFile.Name);
In the replicatorActivity1_ChildCompleted event handler, add the following code. This code again uses the
InstanceData property to display information about the currently processing item:
Console.WriteLine("ChildCompleted event: " & _
CType(e.InstanceData, FileInfo).Name)
Console.WriteLine("ChildCompleted event: " +
((FileInfo)e.InstanceData).Name);
Before you can test your workflow, you must fix all its errors. To do this, select View | Designer to return to the
design surface. Double-click codeActivity1. In the codeActivity1_ExecuteCode procedure, add the following
code:
Console.WriteLine("Handling a file: " & currentFile.Name)
Console.WriteLine("Handling a file: " + currentFile.Name);
Note that while executing the activities within the Replicator activity, you don’t have explicit access to the
current data item. This example stores the current item into a variable named currentFile, but be warned: This
technique doesn’t work if you set the Replicator activity’s ExecutionType property to Parallel (this property is
set to Sequence, by default.)
WARNING! Do not store the current data item into a variable within the Replicator activity’s
ChildInitialized event handler, as you’ve done in this example. Although it grants you access to the data item
when you set the Replicator activity’s ExecutionType property to Sequence, it will not work if you set the
property to Parallel, as you’ll see later in this tutorial.
Configure the Host Application
The workflow’s host application must supply the workflow’s search path, and handle halting the application to
wait for user input. In the Solution Explorer window, double-click Module1.vb or Program.cs. Within the class,
locate the Main procedure. Within the Main procedure, replace the line of code that calls the CreateWorkflow
method, inserting the following code block:
Dim parameters = New Dictionary(Of String, Object)
parameters.Add("searchFolder", "C:\test")
parameters.Add("backupFolder", "C:\backup")
workflowInstance = workflowRuntime.CreateWorkflow( _
GetType(Workflow1), parameters)
59
var parameters = new Dictionary<string, object>();
parameters.Add("searchFolder", @"C:\test");
parameters.Add("backupFolder", @"C:\backup");
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(
typeof(Workflow1), parameters);
This code sets up a dictionary containing the two parameters for your workflow, searchFolder and
backupFolder. (Feel free to change the specific folder names if you want to monitor files and back them up to
different locations.) When you pass the dictionary to the workflow in the call to CreateWorkflow, the Workflow
Runtime sets the value of the matching properties within the workflow instance to the values you specify in the
dictionary.
Add the following procedure to the class:
Shared Sub WaitForKeypress()
Console.WriteLine()
Console.WriteLine("Press any key to quit...")
Console.ReadKey()
End Sub
static void WaitForKeypress()
{
Console.WriteLine();
Console.WriteLine("Press any key to quit...");
Console.ReadKey();
}
Modify the Main procedure, adding a call to WaitForKeyPress immediately following the existing call to the
WaitHandler.WaitOne method. Once you’re done, the end of the Main procedure should look like the following:
workflowInstance.Start()
WaitHandle.WaitOne()
WaitForKeypress()
End Using
End Sub
instance.Start();
waitHandle.WaitOne();
WaitForKeypress();
}
}
60
Test the Workflow
In order to test the workflow, ensure that the C:\Test folder (or whichever folder you specified in the
searchFolder property) exists, and that it contains some old files, and at least one file that you created today.
Execute the application, and examine the output. Imagine that the C:\Test folder contains three files, two of
which (Document.tif and list.txt) have not been modified today, and one (Demo.txt) was created today. The
output in the Console window might look like Figure 4.

Figure 5. With three files, one of which is new, the output might look like this.
Given the array of files that the code created by examining the C:\Test folder, the Replicator activity “visited”
each file in turn, executing its ChildInitialized and ChildCompleted event handlers. In between the two, the
Replicator activity executed its child activity, the Code activity, which displays text as it executes.
As you can see in Figure 4, because the Replicator activity’s ExecutionType property has been set to its default
value (Sequence), the replicator handles each instance of its child activity completely before moving on to the
next.
NOTE If the child activity executes more than once, the Replicator activity creates separate instances of the
child activity. This allows the instances to execute independently (and also makes it possible for the workflow
to execute the child activities in parallel—this wouldn’t be possible if the Replicator activity didn’t create a
separate instance of the child activity for each item in the InitialChildData collection). If you want to retrieve a
collection of all the child activities, use the Replicator activity’s DynamicActivities property from within the
child activity’s code.
So far, you’ve seen that the Replicator activity uses its InitialChildData property as a data source, and iterates
through each item in the collection. For each child, the Replicator activity calls ChildInitialized and
ChildCompleted events, passing each an event argument that includes a reference to the specific child. The
Replicator activity’s child activity, however, has no specific information about the current data item, and you’ll
need to take extra steps (as you’ll see later in this tutorial) to use that information from within the child activity.
Compare Parallel and Serial Execution
In the previous exercise, you ran the sample workflow with the Replicator activity’s ExecutionType property set
to Sequence. Using that execution type, the Replicator activity initializes, executes, and completes each instance
of the child activity before it moves on to the next. How does the execution change if you set the property to
Parallel?
It’s simple to demonstrate this behavior. Open Workflow1 in the workflow designer, and select the Replicator
activity. In the Properties window, set the ExecutionType property to Parallel, and then save and execute the
application. This time, you’ll see output similar to Figure 5. Examine the output carefully, and you’ll find major
changes from the previous test:
• The Replicator activity creates an instance of its child activity for each element of its InitialChildData
property’s collection.
• The Replicator activity raises the ChildInitialized event handler for each instance of the child activity,
in turn. Then, it executes the child activity instances, one after the other. Finally, it raises the ChildCompleted
event for each instance of the child activity.
• The local currentFile variable is set during the ChildInitialized event handler. When you execute the
Replicator activity using the Parallel execution mode, the current file name contains the last initialized child
instance, before the Replicator activity starts executing the child activity instances. As you can see in Figure 5,
this means that the currentFile variable contains the same value for each instance, and is therefore meaningless.
As it is, there’s no way for the Code activity within the Replicator activity to retrieve information about the
61
current file. The recommended solution for this problem is to create a custom activity that contains information
about the current item that the Replicator activity is handling, and to use it in place of the “dumb” activity that
you’re currently using within the Replicator. You’ll learn more about this solution later in this tutorial.

Figure 6. When you set the Replicator activity to run its child activities in parallel, the behavior changes
significantly.
Before continuing, make sure you set the ExecutionType property back to Sequence, its default value.
Add an Until Condition
In addition to executing its child activity for each item in the collection supplied in its InitialChildData property,
you can supply an Until condition in the Replicator activity’s UntilCondition property. Just as when you’re
working with an IfElse or While activity, this condition can be either a declarative rule condition, or it can be a
code condition. Even if the Replicator activity hasn’t finished working through the items in the InitialChildData
property, if the Until becomes true, the Replicator activity halts its march through the data items.
WARNING! If you set the UntilCondition property, you must ensure that at some point, it becomes true. If
the Replicator activity completes all its child activity instances and the UntilCondition property returns false,
the Replicator activity simply hangs. Although you can solve this problem by programmatically adding new
items to the data collection to somehow change the UntilCondition property or make it no longer be false, this is
an extreme solution. You must plan for this problem, and ensure that the UntilCondition property is no longer
false by the time all the child activity instances have completed executing.
Imagine that you wanted to limit the number of files that your Replicator activity handled—you might want to,
for example, limit it to only backing up two files (although this restriction seems arbitrary, it simple allows this
tutorial to show off the use of the UntilCondition property). Based on the previous warning, if you set the
UntilCondition so that it returns true only when you have handled a fixed number of files, but never reach that
specified number of files, your workflow will hang. In this example, verify that you have more than two files in
the test folder, and start by adding the following variable to the class-level variables:
Private currentFile As FileInfo
Private files As FileInfo()
Private fileCount As Integer
private FileInfo currentFile = null;
private FileInfo[] files;
private int fileCount;
Within the replicatorActivity1_Initialized event handler, add code that initializes the fileCount variable:
fileCount = 0
fileCount = 0;
Within the replicator1_ChildInitialized event handler, add code that increments the fileCount variable:
fileCount += 1
fileCount++;
Finally, add the UntilCondition property setting. To do this, start by selecting View | Designer. Select the
Replicator activity, and in the Properties window, find the UntilCondition property. Set the property’s value to
Code Condition, and expand the + sign to the left of the property. Set the Condition sub-property to the name of
62
the new procedure, LimitFiles. Press Enter, and Visual Studio creates the procedure stub. (You could also set the
UntilCondition property to Declarative Rule Condition, and specify the rule directly in the designer, in this
case). Note that Visual Studio creates a stub for the procedure that includes a ConditionalEventArgs object as
the second parameter. This object provides a Result property—set it to a Boolean value that, when True, will
cause the Replicator to halt. Modify the procedure, adding code to limit the number of files so that once it has
processed two files, the UntilCondition property returns true:
Private Sub LimitFiles(ByVal sender As System.Object, _
ByVal e As System.Workflow.Activities.ConditionalEventArgs)
e.Result = (fileCount > 1)
End Sub
private void LimitFiles(object sender, ConditionalEventArgs e)
{
e.Result = (fileCount > 1);
}
Make sure the ReplicatorActivity’s ExecutionType property has been set to Sequence, and run the application.
You’ll find that the output shows only two files, instead of the original number. (For fun, try changing the value
in the LimitFiles method so that it executes until you reach a number of files greater than the actual number of
files that you have. You’ll see that the workflow simply halts, waiting for the fileCount variable to reach the
number you specify. Because there aren’t enough files, it never does. You can simply close the Console window
to end the running application, in that case.) Clearly, you need to be careful when using the UntilCondition
property, to ensure that this situation never occurs.
Create a Custom Activity
Although the goal of the workflow was to back up each file to a specific folder, it currently doesn’t include this
behavior. For this simple example, you could do the work in the ChildInitialized event, but what if you needed
to execute workflow activities within the Replicator activity, and you needed to have information about the
specific item from the Replicator’s data source from within those activities? At this point, the single Code
activity within the Replicator receives no information about the current data item, and because the Replicator
activity might be executing its contents in parallel fashion, you can’t simply store the value into a workflow-
level variable.
The standard solution to this problem is to create a custom activity which has, as one of its properties, a public
property that can receive, and work with, the current data item from the parent Replicator activity. For example,
if you were to create a custom activity with an InputFile and BackupFolder property, you could pass
information from the Replicator to the custom activity, and it could perform the work of backing up the
specified file. In other words, to complete this simple workflow, you’re going to create a custom activity that
has information about the file to be backed up, and the location to which to back it up, and you’ll replace the
existing Code activity with this new activity.
To get started, in Visual Studio, select Project | Add Activity. In the Add New Item dialog box, set the name to
be FileBackupActivity, and click Add. Visual Studio creates a designer for the new activity; select View | Code
to view the activity’s code.
By default, the designer creates a new activity that can contain other activities (it inherits from
SequenceActivity). Because you need to create a simple activity, start by changing the base class to Activity:
Public class FileBackupActivity

63
Inherits Activity
End Class
public partial class FileBackupActivity : Activity
{
public FileBackupActivity()
{
InitializeComponent();
}
}
Within the activity’s class, you’ll need to create two public properties. Although you could use standard .NET
properties for both, you should get in the habit of using Workflow’s dependency properties, which supply extra
functionality, when your intention is to bind the properties using the designer.
TIP: Dependency properties are useful when you intend to use the data binding capabilities in the Windows
Workflow Foundation, but they do add extra overhead, when compared to simple .NET properties. As a rule of
thumb, use dependency properties only when you intend to use workflow binding. In this example, the
BackupFolder property needs to be bound to another property, but the InputFile property will not. For this
example, there’s no need to make the InputFile property be a dependency property.
Although a full discussion of dependency properties is beyond the scope of this tutorial, they serve a very
important purpose within workflows: They allow the workflow runtime to provide a centralized repository for
property instances, keeping track of a workflow’s state. When you create a dependency property, the workflow
runtime registers the value of the property, for the particular activity instance, in a hash table within the
workflow runtime. If your workflow never uses the property value, it never needs to be loaded from the hash
table. This behavior allows the workflow to minimize memory usage. In addition, the workflow designer makes
use of dependency properties in its support for property binding, so you’ll find working with the designer easier
if you make use of dependency properties. (For more information on dependency properties, browse to this
page: http://msdn.microsoft.com/en-us/library/ms734499(VS.85).aspx).
To get started, add the the following statement to the top of the code file:
Imports System.IO
using System.IO;
The easiest way to create a dependency property is to the use the code snippet built into Visual Studio 2008. To
do that, within the FileBackupActivity class, create a blank line outside any other procedure, and type wdp
followed by the Tab key (press the Tab key twice in C#). Fill in the various fields so that the property looks like
the following code fragment (in C#, you do not need to supply the class name; in Visual Basic, you must):
Public Shared BackupFolderProperty As DependencyProperty = _
DependencyProperty.Register("BackupFolder", GetType(String), _
GetType(FileBackupActivity))
<Description("BackupFolder")> _
<Category("BackupFolder Category")> _
<Browsable(True)> _

64
<DesignerSerializationVisibility( _
DesignerSerializationVisibility.Visible)> _
Public Property BackupFolder() As String
Get
Return (CType((MyBase.GetValue( _
FileBackupActivity.BackupFolderProperty)), String))
End Get
Set(ByVal Value As String)
MyBase.SetValue( _
FileBackupActivity.BackupFolderProperty, Value)
End Set
End Property
public static DependencyProperty BackupFolderProperty =
DependencyProperty.Register("BackupFolder", typeof(string),
typeof(FileBackupActivity));
[DescriptionAttribute("BackupFolder")]
[CategoryAttribute("BackupFolder Category")]
[BrowsableAttribute(true)]
[DesignerSerializationVisibilityAttribute(
DesignerSerializationVisibility.Visible)]
public string BackupFolder
{
get
{
return ((string)(base.GetValue(
FileBackupActivity.BackupFolderProperty)));
}
set
{
base.SetValue(
FileBackupActivity.BackupFolderProperty, value);
}
}

65
If you examine the code, you’ll see that the snippet creates a public static/shared field named InputFileProperty
(and BackupFolderProperty)—therefore, there’s only one instance of this field in memory, no matter how many
instances of the activity class get created by the workflow. The code also creates a public standard .NET
property named InputFile (or BackupFolder), and this property simply gets and sets the value of the property
from the hash table managed by the base class (DependencyObject).
In addition, create a standard .NET FileInfo property named InputFile:
Private inputFileValue As FileInfo
Public Property InputFile() As FileInfo
Get
Return inputFileValue
End Get
Set(ByVal value As FileInfo)
inputFileValue = value
End Set
End Property
public FileInfo InputFile { get; set; }
From the calling code’s perspective, the activity exposes two public properties: InputFile and BackupFolder. At
this point, you must add code to cause the custom activity to take some action when it executes. Within the
custom activity’s class, add the following code, which overrides the Execute method from the base class:
Protected Overrides Function Execute( _
ByVal executionContext As ActivityExecutionContext) _
As ActivityExecutionStatus
Try
Dim outputFile As String = _
Path.Combine(BackupFolder, InputFile.Name)
File.Copy(InputFile.FullName, outputFile)
Console.WriteLine("Copied {0} to {1}.", _
InputFile, outputFile)
Catch ex As Exception
' For this simple example, don't do anything if
' the file copy fails.
End Try
Return ActivityExecutionStatus.Closed
End Function
protected override ActivityExecutionStatus Execute(

66
ActivityExecutionContext executionContext)
{
try
{
string outputFile =
Path.Combine(BackupFolder, InputFile.Name);
File.Copy(InputFile.FullName, outputFile, true);
Console.WriteLine("Copied {0} to {1}.",
InputFile, outputFile);
}
catch (Exception)
{
// For this simple example, don't do anything if
// the file copy fails.
}
return ActivityExecutionStatus.Closed;
}
The Execute method requires that you return the current state of the activity—in this example, the activity
always returns Closed, indicating that it has finished its processing.
Save and compile the project. This action allows Visual Studio to display your custom activity in the Workflow
designer’s Toolbox window. Open the workflow in the designer, and verify that you see FileBackupActivity in
the Toolbox window.
In the workflow, delete the existing Code activity, and drag an instance of the FileBackupActivity from the
toolbox into the Replicator activity. Now that you’ve placed the activity in the Replicator, you still must set its
InputFile and BackupFolder properties. You can’t set the InputFile property until runtime, but you can set the
BackupFolder property in the designer. To do that, select the activity, and then in the Properties window, locate
the BackupFolder property. Click the ellipsis to the right of the property. In the dialog box, select the
workflow’s backupFolder property, as shown in Figure 6. This allows you to declaratively select the binding for
the activity’s BackupFolder property, setting its value to the backupFolder property of the workflow.

Figure 7. Bind the activity’s property to a property of the workflow.


The ChildInitialized event handler passes your code a ReplicatorChildEventArgs object as its second parameter.
This object includes, in addition to the InstanceData property you already saw, an Activity property. This
property provides a reference to the current child activity, so that you can work with the child activity (in this
case, setting the InputFile property.)
In the workflow’s code, add the following code at the end of the existing replicatorActivity1_ChildInitialized
event handler so that it creates a reference to the current FileBackupActivity’s instance, and sets its InputFile
property:
67
Dim fba As FileBackupActivity = _
CType(e.Activity, FileBackupActivity)
fba.InputFile = currentFile
FileBackupActivity fba = (FileBackupActivity)e.Activity;
fba.InputFile = currentFile;
Because the child activity exposes a public InputFile property, you can set the property value from the
ChildInitialized event handler, and the child activity can use this information as it performs its actions. Without
creating a custom activity, you couldn’t use that information from within the child activity’s actions.
Save and execute the application. This time, you’ll see output as shown in Figure 7, including the output from
the FileBackupActivity’s Execute override.

Figure 8. The custom activity can use the information about the individual data item that it’s processing.
You might also want to verify the behavior of the workflow using parallel execution. Because of the way the
workflow sets up the file counter, the Until condition won’t operate correctly once you set the ExecutionType
property to Parallel. To fix this, back in the Workflow designer, select the Replicator activity. In the Properties
window, right-click the UntilCondition property, and select Reset from the context menu.
Change the Replicator activity’s ExecutionType property to Parallel (rather than Sequence). You will see that all
the files get copied correctly, because the ChildInitialized event handler sets up the properties of each separate
instance of the child FileBackupActivity.

Windows Workflow Tutorial: Introduction to Fault Handling

Introduction

In all applications you write, you need to trap for errors. This includes data entry errors, such as a user entering
no or invalid information in a text box. You also need to trap for exceptions, which are unexpected errors that
cause execution to stop. For example, if you have code to read a file and that file does not exist, the .NET
Runtime will throw an exception.
In code, your primary means of handling exceptions is the try-catch block. Windows Workflow Foundation
provides an additional means of handling exceptions. In this tutorial, you will see how to use the FaultHandler
activity to handle exceptions in workflows.

Review the Sample Workflow

In this tutorial, you will use an existing sequential workflow that models checking the inventory for a list of
items and if necessary restocking items. In the Basic Control Flow in Workflows tutorial in this series, you saw
how to build this workflow. Rather that rebuild it, you will use this workflow as the starting point for this
tutorial.
To get started, in Visual Studio 2008 select File | Open | Project/Solution to display the Open Project dialog box.
Navigate to the folder when you downloaded this tutorial’s sample project. Select FaultHandlingDemo.sln and
click OK to open the project.
In the Solution Explorer, double click Inventory.xml. This file contains inventory data for three products and
contains the following XML.
68
XML
<Products>
<Product>
<ProductID>1</ProductID>
<OnHand>100</OnHand>
<Available>50</Available>
</Product>
<Product>
<ProductID>2</ProductID>
<OnHand>10</OnHand>
<Available>250</Available>
</Product>
<Product>
<ProductID>3</ProductID>
<OnHand>25</OnHand>
<Available>50</Available>
</Product>
</Products>

In the Solution Explorer, double click Workflow1 in the Solution Explorer to open the workflow designer. The
workflow looks like Figure 1.

69
Figure 1. You will use this workflow in this tutorial.
When the workflow starts, it executes the following code in the WorkflowInitialized event handler:
C#
var xmlFile = System.Xml.Linq.XDocument.Load(

System.IO.Path.Combine(

AppDomain.CurrentDomain.BaseDirectory, "Inventory.xml"));

inventory =

from product in xmlFile.Descendants("Product")

    select product;

itemCount =

  (from product in xmlFile.Descendants("Product")

   select product.Element("ProductID").Value).Count();

This code uses a LINQ To Xml query to read the contents of the XML file and determine how many items exist.
The workflow then starts executing the ProcessItems While activity. The activities in ProcessItems will execute
as long as the KeepProcessing rule condition evaluates to true. This condition is the following:
Rule
this.moreToProcess

The LookupProduct activity retrieves the next item in the inventory list. The activity’s ExecuteCode event
handler contains the following code:

70
C#
productID = Convert.ToInt32(

inventory.ElementAt(nextItemNumber).Element("ProductID").Value);

onHand = Convert.ToInt32(

inventory.ElementAt(nextItemNumber).Element("OnHand").Value);

available = Convert.ToInt32(

inventory.ElementAt(nextItemNumber).Element("Available").Value);

nextItemNumber += 1;

if (nextItemNumber >= itemCount)

moreToProcess = false;

The workflow next checks if there is sufficient inventory on hand. The SufficientOnHand code condition checks
if the amount on hand is greater than 100.
If there is sufficient inventory, the ReportOnHand activity displays a message to that effect. If there is not
sufficient inventory, the workflow executes the Reorder activity.
The workflow next checks if it was able to reorder the product. The IfReordered branch of the
CheckReorderStatus IfElse activity uses a code condition to execute the PlaceReorder method. For the purposes
of this tutorial, a reorder is successful if there is enough of the product available to have at least 100 units in
stock.
If the reorder succeeds, the ReportReorder activity displays a message to that effect. If the reorder fails, the
ReportFailure activity displays a message to that effect. The final activity reports that the workflow is finished.
To review the workflow, press Ctrl + F5 to run the project. You should see the following output:
Output
There are 100 units of product 1 on hand.
There are only 10 units of product 2 on hand.
It is time to reorder.
90 units of product 2 will be ordered.
There are only 25 units of product 3 on hand.
It is time to reorder.
You need 75 units of product 3 but only 50 are available.
The product has not been reordered.
The workflow has finished executing.
Press any key to continue . . .

Press any key to exit the application.

Use the WorkflowTerminated Event Handler to Handle Faults

71
This workflow currently works and successfully evaluates each item in the list. You will now see what happens
when faults occur in the workflow and you will see how you can handle faults.
To introduce an error in the workflow, modify the WorkflowInitialized method so that it fails to load the XML
file. The easiest way to do this is to misspell the name of the file.
C#
var xmlFile = System.Xml.Linq.XDocument.Load(

System.IO.Path.Combine(

AppDomain.CurrentDomain.BaseDirectory, "NotFound.xml"));

Save and press Ctrl + F5 to run your project. You should see the following output:
Output
Could not find file 'D:\FaultHandlingDemo\bin\NotFound.xml'.
Press any key to continue . . .

Press any key to exit the application.


To see the code that handles the error, in the Solution Explorer window, double-click Module1.vb or
Program.cs. There, you’ll find the following code:
C#
workflowRuntime.WorkflowTerminated +=

delegate(object sender, WorkflowTerminatedEventArgs e)

Console.WriteLine(e.Exception.Message);

waitHandle.Set();

};

The workflow attempted to open a file that doesn’t exist. This caused an exception, which caused the workflow
to terminate. The WorkflowTerminated event handler takes as an argument an instance of the
WorkflowTerminatedEventArgs class. The Exception property of this class contains the exception and the
Message property of the Exception class contains the actual message, which the code displays.
When you create a workflow project and choose the Sequential Workflow Console Application or State
Machine Workflow Console Application template, Visual Studio creates the code above. So by default, you
have code to handle exceptions. If you choose any of the other templates, you will have to add this code
yourself.

Exception Handling in Workflows

The WorkflowTerminated event occurs if an unhandled exception occurs anywhere in the workflow. You should
use this as a last resort because you have no opportunity to take an action specific to the error and you have no
opportunity to recover from an error because the workflow terminated. A better practice is for you to handle
exceptions as close to the source as possible.
Activities take actions in a workflow and therefore activities throw exceptions. If the activity that threw the
exception doesn’t handle it, the workflow runtime transfers the exception to the parent activity (if there is one).
If that activity doesn’t handle the exception, it gets passed to the next activity in the workflow hierarchy. If no
activities handle the workflow and the workflow itself doesn’t handle it, the workflow terminates.

72
When you ran the sample application, the workflow started and immediately attempted to load the XML file.
The workflow then threw the exception, but did not handle it. So the workflow terminated.

Use the FaultHandler Activity to Handle Exceptions

There are two ways you can handle exceptions in a workflow. The first is to use a try-catch block in your code.
The try-catch block enables you to handle exceptions in the code that caused the exception. You can then write
additional code to take an action. The second way to handle exceptions is to use the FaultHandler activity. This
activity handles a specific fault type and enables you to execute workflow activities in response to an exception.
You can use these activities to perform cleanup and recovery.
You can associate a FaultHandler activity with the workflow itself or with any container activity in the
workflow (with the exception of the TransactionScope and CompensatableTransactionScope activities). . You
will now add a FaultHandler activity to the workflow to handle the file not found exception. There are multiple
ways to do this, including:

• Right-click on the workflow and select View Fault Handlers.

• Select View Fault Handlers from the workflow’s popup menu (see Figure 2).

• Select Workflow | View Fault Handlers.

• Select the View Fault Handlers link in the Properties window.

Figure 2. Use this menu item to add a fault handler to the workflow.
After you select the View Fault Handlers option, the workflow designer changes to show you the
FaultHandlersActivity designer (see Figure 3). The FaultHandlersActivity is a container activity. You can add
one or more FaultHandler activities to it.

73
Figure 3. Use this menu item to add a fault handler to this activity.
From the Toolbox, drag a FaultHandler activity into faultHandlersActivity1. Name this activity
HandleFileNotFound. At this point, HandleFileNotFound indicates an error. You have not specified what fault
type you are handling. To do that, click the FaultType property in the Properties window and then click the
ellipsis to the right of that property. This displays the Browse and Select a .NET Type dialog box. In the Type
list, expand the mscorlib node. Then select System.IO. In the right hand pane, select FileNotFoundException
(see Figure 4) and click OK. Additionally, if you know the fault type, you can type it directly in the FaultType
property’s text box.

Figure 4. This fault handler activity will handle FileNotFound exceptions.


The FaultHandler activity is also a container. You can add to it one or more activities you want to execute when
a FileNotFound exception occurs. From the Toolbox, drag a Code activity into HandleFileNotFound. Name this
activity ReportFileNotFound. The workflow designer should now look like Figure 5.

74
Figure 5. This fault handler activity will handle FileNotFound exceptions.
Double click ReportFileNotFound and add the following code to the activity’s ExecuteCode event handler:
C#
Console.WriteLine(

"The inventory information can not be found.\n" +

"The workflow will stop executing.\n");

Save and press Ctrl + F5 to run your project. You should see the following output:
Output
The inventory information can not be found.
The workflow will stop executing.
Press any key to continue . . .

Press any key to exit the application.


The workflow handles the exception and displays the information you specified. Before you move on, fix the
existing error. Modify the WorkflowInitialized method so that it correctly loads the XML file.
C#
var xmlFile = System.Xml.Linq.XDocument.Load(

System.IO.Path.Combine(

AppDomain.CurrentDomain.BaseDirectory, "Inventory.xml"));

Next, you will introduce and handle an exception later on in the workflow. Modify the
LookupProduct_ExecuteCode method so that it attempts to read an element that does not exist.
C#
productID = Convert.ToInt32(

inventory.ElementAt(nextItemNumber).Element("Product").Value);

Return to the workflow designer. Return to the view of the workflow itself by taking any of the following
actions:

• Right-click on the workflow and select View SequentialWorkflow.

• Select View SequentialWorkflow from the workflow’s popup menu.

75
• Select Workflow | View SequentialWorkflow.

Save and press Ctrl + F5 to run your project. You should see the following output:
Output
Object reference not set to an instance of an object.
Press any key to continue . . .

Press any key to exit the application.


Figure 6 shows the first few activities in the workflow. The exception occurred in the code in the
LookupProduct activity’s Execute event handler. LookupProduct threw the exception, but did not handle it. The
workflow runtime then checked if sequenceActivity1 handled the exception. It did not, so the workflow runtime
checked if ProcessItems handled it. It did not, so the workflow runtime checked if the workflow handled it. The
workflow would handle a FileNotFound exception, but LookupProduct threw a different exception. So the
workflow terminated.

Figure 6. These are the first few activities in the workflow.


As mentioned before, you can associate a FaultHandler activity with any container activity in the workflow
(except the TransactionScope and CompensatableTransactionScope activities). In the workflow, the
LookupProduct activity throws the exception. This is a Code activity, so you cannot associate a FaultHandler
activity with it. LookupProduct’s parent activity is a Sequence activity (sequenceActivity1) and that activity’s
parent activity is a While activity (ProcessItems). You can associate a FaultHandler activity with either
sequenceActivity1 or ProcessItems or both.
Before you add a FaultHandler activity you should know the exception type it will handle. The easiest way to
know is to have Visual Studio tell you. Press F5 to run your project. When the exception occurs, Visual Studio
will display the exception in the Exception Assistant (see Figure 7 for Visual Basic and Figure 8 for C#). Press
Shift + F5 to stop the application.

76
Figure 7. The Exception Assistant displays the fault type.

Figure 8. The Exception Assistant displays the fault type.


Return to the workflow designer. You will now add a FaultHandler activity to sequenceActivity1 to handle the
exception. Select View Fault Handlers from the activity’s popup menu (see Figure 9).

Figure 9. Use this menu item to add a fault handler to this activity.
From the Toolbox, drag a FaultHandler activity into faultHandlersActivity2. Name this activity
HandleReadError. Click the FaultType property in the Properties window and then click the ellipsis to the right
of that property to display the Browse and Select a .NET Type dialog box. In the Type list, select mscorlib. In
the right hand pane, select NullReferenceException and click OK.
77
From the Toolbox, drag a Code activity into HandleFileNotFound. Name this activity ReportReadError. Double
click ReportReadError and add the following code to the activity’s ExecuteCode event handler:
C#
Console.WriteLine(

"The inventory information can not be read.\n" +

"The workflow will stop processing items.\n");

moreToProcess = false;

Setting the moreToProcess field to false ensures the workflow will stop executing the While loop. Save and
press Ctrl + F5 to run your project. You should see the following output:
Output
The inventory information can not be read.
The workflow will stop processing items.
The workflow has finished executing.
Press any key to continue . . .

Press any key to exit the application.


As you have just seen, if you properly handle exceptions that occur in activities, the workflow does not need to
terminate. The HandleReadError FaultHandler activity handled the null reference exception and the workflow
continued. The While activity ended and the Cleanup activity displayed the message that the workflow finished
executing.

Use the Throw Activity to Throw a Fault

A common programming pattern is to catch an exception in a try-catch block, perform some action and then
rethrow the exception using the Throw statement in Visual Basic or the throw keyword in C#. This enables you
to take some action where the exception occurred and then take additional action at a higher level in the
program stack.
You can apply a similar pattern in your workflows. Suppose you want to log errors that occur in the workflow
and further suppose there are five places in the workflow where a particular error can occur. You could handle
this in code and call a logging method from the five locations.
Another option is to use the Throw activity. This activity enables you to throw an exception. More specifically,
it enables you to handle an exception using a FaultHandler activity, perform an action and then rethrow the
exception. This will cause the workflow runtime to pass the exception up the workflow hierarchy. You could
then handle the exception in either a parent activity or at the workflow level. You can also handle the exception
in the application that calls the workflow.
To see how to do this, return to faultHandlersActivity2. From the Toolbox, drag a Throw activity into
faultHandlersActivity2 below ReportReadError. The workflow designer should now look like Figure 10.

78
Figure 10. Add a Throw activity to the FaultHandler activity.
You now need to specify what fault you are throwing. You have three options, including:

• Throw any .NET fault. You could handle a specific fault and then throw a more general fault.

• Throw a custom exception. You could create a custom exception class in code and throw an instance of that class.

• Rethrow the exception handled by the FaultHandler activity.

To rethrow the exception, click the Fault property in the Properties window and then click the ellipsis to the
right of that property. This displays the Bind ‘Fault’ to an activity’s property dialog box. Expand the
ProcessItems node, then expand the sequenceActivity1 node, then expand the faultHandlersActivity2 node, then
expand the HandleReadError node. Finally, select Fault (see Figure 11). Click OK.

Figure 11. Specify the fault you want to rethrow.

79
The Fault property specifies the exception object. You also need to specify the type of exception. To do that,
double click the small icon to the right of the FaultType property name (see Figure 12). This displays the Bind
‘FaultType’ to an activity’s property dialog box. Drill down to HandleReadError as you did before and select
FaultType. Click OK.

Figure 12. Double click this icon to specify the type of exception to throw.
At runtime, when the null reference exception occurs, the workflow will display the message that the inventory
information cannot be read. It will then rethrow the null reference exception.
The final step is to handle the exception the second time it occurs. To do that, return to the workflow level fault
handlers, and either select View Fault Handlers from the workflow’s popup menu (see Figure 2) or right-click
on the workflow and select View Fault Handlers.
From the Toolbox, drag a FaultHandler activity into faultHandlersActivity1. Name this activity
RehandleReadError. Set the Fault Type property to System.NullReferenceException. You can enter this directly
or use the ‘Browse and Select a .NET Type’ dialog box as you did previously. From the Toolbox, drag a Code
activity into RehandleReadError. Name this activity LogReadError. The workflow designer should now look
like Figure 13.

Figure 13. The workflow will handle the null reference exception.
Double click LogReadError and add the following code to the activity’s ExecuteCode event handler:
C#
Console.WriteLine(

"The workflow will log the read error.\n");

Save and press Ctrl + F5 to run your project. You should see the following output:
80
Output
The inventory information cannot be read.
The workflow will stop processing items.
The workflow will log the read error.
Press any key to continue . . .

Press any key to exit the application.


The FaultHandler activity associated with the Sequence activity handles the null reference exception first and
the first message displays. The exception is rethrown and the workflow’s FaultHandler activity handles it. The
second message displays and the workflow terminates. The Cleanup activity does not execute in this case.

Handling Multiple Faults with a FaultHandlers Activity

As you just saw, you can include multiple fault handlers in a single FaultHandlers activity. This is similar to
how you can handle multiple exceptions in a try-catch block. In both scenarios, you need to be mindful of the
exception hierarchy. For example, the FileNotFoundException class in the System.IO namespace inherits from
the IOException class in the same namespace. IOException inherits from the SystemException class in the
System namespace and SystemException inherits from the Exception class.
If you were handling these exceptions in a try-catch block, you would need to place the more specific handlers
above the more general handlers, as shown in the following code:
C#
try

{ // Code that reads a file }

catch (FileNotFoundException fileNotFoundException)

{ // Code to handle this exception }

catch (IOException ioException)

{ // Code to handle this exception }

catch (SystemException systemException)

{ // Code to handle this exception }

catch (Exception exception)

{ // Code to handle this exception }

If you have multiple fault handlers in a FaultHandlers activity, you need to follow the same rule regarding the
exception hierarchy. The workflow evaluates FaultHandler activities from left to right so you must put more
specific fault handles to the left of less specific ones. Suppose you add handlers for both the
FileNotFoundException and IOException exception to a FaultHandlers activity. The FileNotFoundException
fault handler must be to the left of the IOException fault handler.

Windows Workflow Tutorial: Rules-Driven WCF Services


Windows Workflow Foundation (WF) ships with a robust business rule engine that can be incorporated into
workflows to assist in managing business processes. This same business rule processing can be used in various
capacities with Windows Communication Foundation (WCF) to enrich its message processing capabilities. This
hands on article will walk through using the rules engine to provide routing logic for a WCF message router.

Using rules in a web service router


81
When building web services there are times when it is important to be able to write an intermediary service that
acts as a router making decisions about incoming messages and properly forwarding those messages onto an
actual service or services. This type of router provides an excellent use case for applying business rules using
the rules engine in Windows Workflow Foundation. By defining the routing decisions as rules, the criteria and
endpoints can be expressed as a set of rules which can be managed separately from the router logic and
configuration. An example solution ships in the samples that are part of the Windows Software Development
Kit and can be found in the WCF directory (WCF\Extensibility\Rules\WCF_Router\).
In this article we will build the rules used by the router and review the code used to invoke those rules to control
the message flow in the router. This article also comes with a sample code implementation, available at URL;
the article will walk through how and why that sample code project was created.

Understanding the WCF router basics

A router acts as an intermediary service accepting messages from clients. After evaluating the message, the
router acts as a proxy client, forwarding the message to the service where it can be processed. A router can
receive many different messages and make dynamic decisions about the ultimate destination of those messages
based on message content and context. Routers can be used for many different reasons in your solution
including message filtering for security purposes and service versioning. Figure 1 shows the interaction of the
client, services and router in this scenario.

Figure 1: WCF routing example


In order for this scenario to work, when the client sends messages intended for the services, it must send them to
an address that the router is listening on instead of directly to the service. When configuring an endpoint for a
service, most WCF developers are familiar with configuring the address for the endpoint. However, in addition
to an address, a ServiceEndpoint can also have a ListenUri property set. When no value is specified for the
ListenUri the endpoint is initialized with the address and begins listening. When a value is present for the
ListenUri, this becomes the physical address that the endpoint registers and uses to listen for messages arriving.
Regardless of whether the endpoint has a ListenUri, it is the Address property that is used to create the service
description and is made public to the client. In this scenario, the Address property describes an address that the
router is listening on while the ListenUri describes the physical address on which the actual service is listening.
Thus, when a client gets metadata from the service it contains the correct contracts and binding information for
the service but the address of the router. Figure 2 shows how a given service exposes both the ListenURI and
the Address. The Address is the address of an endpoint on the router which the client will use to call the service.
The ListenURI is the address the service is listening on and which the router uses to communicate with the
service to forward requests from the client.

82
Figure 2: Listen URI and address configuration
Figure 3 shows the service configuration for the EchoService including the endpoint settings to handle the
address. Notice that while both the address and listenUri settings use the same server and port, the virtual path is
unique between them.
Configuration
<service name="Microsoft.ServiceModel.Samples.EchoService"
behaviorConfiguration="metadataBehavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8000/echo" />
</baseAddresses>
</host>
<endpoint address="http://localhost:8000/services/soap12/text"
listenUri="service"
contract="Microsoft.ServiceModel.Samples.IEchoService"
binding="wsHttpBinding"
bindingConfiguration="ServiceBinding" />
<!-- Echo service metadata endpoint. -->
</service>

Figure 3: Configuring an endpoint with a ListenUri


In this example the routing decisions will be made by examining the message headers to determine which
service should receive the message. For the calculator service, a custom header is used and is defined in the
endpoint configuration. The client and service each have their endpoint configured with the details about the
header to indicate that the header should be sent with all messages passing through the endpoint. Figure 4 shows
the configuration in the app.config for the client. Notice the address points to the router and the header will help
the router know where to send the message.
Configuration
<endpoint address="net.tcp://localhost:31080/services/soap12/binary"

83
binding="netTcpBinding"
bindingConfiguration="NetTcpBinding_ICalculatorService"
contract="Microsoft.ServiceModel.Samples.ICalculatorService"
name="NetTcpBinding_ICalculatorService">
<headers>
<Calculator xmlns="http://Microsoft.ServiceModel.Samples/Router" />
</headers>
</endpoint>

Figure 4: Configuring a header on an endpoint

Using rules in the router logic

The router is implemented as a WCF service and in the logic for the service, it uses a class named RoutingTable
to make decisions about where to route messages. Open the Router.sln solution in the solution directory found
in the download this article and open the RoutingTable.cs file in the router project. The fields in the
RoutingTable class are shown in Figure 5 and include several properties that are only used by the rules, as well
as a variable to hold a reference to the RuleEngine and the RuleSet.
C#
public class RoutingTable

    {

        Random randomNumberGenerator;

        Message currentMessage;

        IList<EndpointAddress> possibleAddress;

        EndpointAddress selectedAddress;

        XmlNamespaceManager manager;

        RuleSet ruleSet;

        RuleEngine ruleEngine;

      ….

   }

Figure 5: The RoutingTable class definition


When the RoutingTable class is instantiated by an extension to the ServiceHost, it initializes the RuleEngine and
loads the RuleSet from an XML file. The RuleSet name and path to the file are stored as values in the
app.config for the router application and are passed to a helper method, examined next, which loads the XML
and deserializes it into a RuleSet object. In addition, two other helper variables are initialized which will be
used by the rules: a derivative of the XmlNamespaceManager and the Random class. Figure 6 shows the
constructor for the RoutingTable class. Notice that the rule engine is initialized with both a type and a ruleset as
all rulesets are created based on a given type and must be executed against an instance of that type.
C#
public RoutingTable()

            this.randomNumberGenerator = new Random();

84
this.manager = new XPathMessageContext();

            this.ruleSet = GetRuleSetFromFile(

              ConfigurationManager.AppSettings["SelectDestinationRuleSetName"],

              ConfigurationManager.AppSettings["SelectDestinationRulesFile"]);

        this.ruleEngine = new RuleEngine(ruleSet, typeof(RoutingTable));

Figure 6: Initializing the router table


The GetRuleSetFromFile method is responsible for loading the serialized ruleset from the file location
specified, then finding and returning the named RuleSet. The WorkflowMarkupSerializer class can be used to
serialize and deserialize a RuleSet object to XML. Figure 7 shows the code necessary to load the rules from the
file. In this example all exception handling code has been removed for clarity.
C#
private RuleSet GetRuleSetFromFile(string ruleSetName, string ruleSetFileName)

       XmlTextReader routingTableDataFileReader = new XmlTextReader(ruleSetFileName);

       RuleDefinitions ruleDefinitions = null;

       WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();

       ruleDefinitions = serializer.Deserialize(routingTableDataFileReader) as RuleDefinitions;

       RuleSet ruleSet = ruleDefinitions.RuleSets[ruleSetName];

       return ruleSet;

Figure 7: Loading a RuleSet from a file


The final bit of code needed for the address logic is the method the router service can use to resolve the correct
address. In the SelectDestination method of the RouterTable class a Message object is passed and the rules
executed. This method is called by the router each time a message arrives in order to determine the destination
address to use for forwarding. The business rules update the RoutingTable instance and set the selectedAddress
field which can then be returned as the chosen address. The SelectDestination method is shown in Figure 8.
C#
public EndpointAddress SelectDestination(Message message)

       this.currentMessage = message;

       this.ruleEngine.Execute(this);

       return this.selectedAddress;

Figure 8: Executing rules to select the destination

Defining the rules

With the router service in place and the RoutingTable completed, the routing rules need to be written and saved
to a file so they can be consumed by the router. WF allows you to build your own UI for creating and editing

85
rules, and allows for rehosting the rules editor dialog that comes as part of WF 3.0. For this example, rather than
creating our own UI for creating and editing rules, we will use the External RuleSet Toolkit sample. This sample
demonstrates how to create a ruleset outside of Visual Studio and save the resulting XML to a file or a database;
for our purposes, it provides an easy UI that we don’t have to code in this article. You can download the sample
from MSDN using the link. Once you have downloaded the samples, run the installer to expand them and you
will find the External Ruleset Toolkit in the following directory:
\WCF\Extensibility\Rules\ExternalRuleSetToolkit.
To setup the database used by the External RuleSet Toolkit, double-click the setup.cmd command file found
with the sample. Note: The command file assumes that your instance of SQL Server Express is named
.\sqlexpress. If you have named your instance something different or you are using another version of SQL
Server, you will need to modify the file before executing it. In addition, you will need to update the
configuration files in the toolkit projects to point to the correct instance of SQL Server.
Once the command file has completed and the database has been configured, open the
ExternalRuleSetToolkit.sln solution in Visual Studio. Make any necessary changes to the configuration file for
your database to point at the database you just created, and set the start up project by right-clicking on the
ExternalRuleSetTool project and selecting Set as startup project from the context menu. Press F5 to run the
application and you should see a dialog as shown in Figure 9.

Figure 9: The RuleSet editor tool


After starting the ExternalRuleSetTool project the main dialog appears. Click the New button to create a new
RuleSet definition and set the Name field to “SelectDestination”. Next, the editor needs to know which CLR
type to build the rules against, so click the Browse button to bring up the type selector dialog as shown in Figure
10. Click the Browse button in this new dialog and select the WCF_Router.Router.exe application in the
\solution\router\bin directory. Then select the Microsoft.ServiceModel.Samples.RoutingTable type from the
list. Once the type is selected the dialog shows all of the fields, properties and methods that will be available in
the rules.
86
Figure 10: Type dialog browser in the ExternalRuleSetTool
After selecting the type, click OK to return to the main window which shows the type and the RuleSet being
edited as shown in Figure 11.

Figure 11: Main dialog ready for editing


Click the Edit Rules button to open the Rule Set Editor dialog that ships as part of the .NET Framework runtime
components. This editor can be re-hosted in Windows Forms and Windows Presentation Foundation (WPF)
applications to enable the viewing, creation, or editing of rules and will be installed on a user’s machine as long
as they have the runtime components; no SDK or developer tools need to be installed on the user’s computer.
Creating rules involves defining a Condition, Then Actions, and Else Actions. The concept of an If/Then/Else
statement is familiar to all .NET developers and makes creating rules a fairly simple procedure. In the case of
the rules being defined for this scenario, there are three different steps in the rules in order to correctly identify
and choose a destination address: Initialize, Match, and Select. When defining rules that need to execute in a
particular order, each rule can be given a priority which instructs the rule engine to evaluate those rules from the
87
highest priority to the lowest. For more information on priority-based execution of rules, see the WF
documentation on MSDN. The first rule used in this example initializes several variables before the actual rule
processing occurs. Figure 12 shows the definition of the InitializeVariables rule in the RuleSet Editor. Notice
that the priority for this rule has a value of “3”.

Figure 12: IntializeVariables rule in the RuleSet Editor


To create this rule, click the Add Rule button and enter “InitializeVariables” for the Name and “3” for the
Priority. For the Condition, enter “True” which will ensure that the rule always executes. In the Then Actions
add the following code to initialize the fields in the RoutingTable class instance.
Rule
this.possibleAddress = new

System.Collections.Generic.List<System.ServiceModel.EndpointAddress>()

this.selectedAddress = null

this.manager = new System.ServiceModel.Dispatcher.XPathMessageContext()

this.manager.AddNamespace("rt", "http://Microsoft.ServiceModel.Samples/Router")

The second set of rules involves examining the message received and determining if it is a match for a
particular endpoint. This set of rules includes one rule for the calculator service and one for the echo service.
The priority for both rules is “2” which means both will run after the variables have been initialized. The table
below shows the definition for these rules, use the Add Rule button to add each rule and set the correct values
for each field.

Rule:Calcula
torService
Condition: new
System.ServiceModel.Dispatcher.XPathMessageFilter("/s12:Envelope/s12:Header/rt:Calculator",
this.manager).Match(this.currentMessage)

88
Action: this.possibleAddress.Add(new
System.ServiceModel.EndpointAddress("net.tcp://localhost:31000/calculator/service"))

Rule:
EchoService
Condition: new
System.ServiceModel.Dispatcher.XPathMessageFilter("/s12:Envelope/s12:Header/wsa10:Action
/text()='http://Microsoft.ServiceModel.Samples/IEchoService/Echo'",
this.manager).Match(this.currentMessage)
Action: this.possibleAddress.Add(new
System.ServiceModel.EndpointAddress("http://localhost:8000/echo/service"))
Each of these rules adds the endpoint address for the service if a match is found. In the case of the
CalculatorService, the rule looks for the custom header to be present on the message using the
XPathMessageFilter class. For the EchoService rule the XPathMessageFilter looks at the SOAP:Action header
to determine if the message should be routed to the service. Notice that the rules allow for creation of new
object instances using defined constructors, making it possible to initialize fields and properties with new values
for complex types.
Once each of the match rules has executed, the collection of possible addresses has been populated with 0-2
addresses. The next set of rules is responsible for selecting the address to return. These three rules all have a
priority of “1” indicating that they will run last. The priorities guarantee that these rules will run after all rules
with a higher priority, but there is no guarantee about the order in which rules with the same priority will
execute. Use the Add Rule button and the information below to create these three rules.

Rule:OneMatch
Condition: this.possibleAddress.Count == 1
Action: this.selectedAddress = this.possibleAddress[0]

Rule: Multiple
Match
Condition: this.possibleAddress.Count > 1
Action: this.selectedAddress =
this.possibleAddress[this.randomNumberGenerator.Next(this.possibleAddress.Count - 1)]

Rule: No Match
Condition: this.possibleAddress.Count == 0
Action: This.selectedAddress = null
If only a single address match was found, then that single address is used to set the selectedAddress field in the
RoutingTable class. In the RoutingTable class, after the rules have executed, it is the selectedAddress field that
is returned to the caller of the SelectDestination method. In the case where no matches are found, the
selectedAddress is simply set to a null value. The slightly more complex scenario involves handling multiple
matches. In this case the Random class is used to choose between the list of endpoints that were possible
matches.
89
Once all of the rules have been created the ruleset is complete and ready to be saved to a file. Figure 13 shows
the Rule Set Editor dialog when all rules have been created.

Figure 13: All rules in the editor


Once all of the rules have been configured, click the OK button to complete the editing. Next choose the Data |
Export command from the menu and save the file to “SelectDestionation.rules” in the \solution\router\bin
directory. Once saved to the file as configured in the app.config for the router service the rules are available to
be used by the router code.
Return to the Visual Studio instance with the Router.sln solution loaded and run the solution by pressing F5.
The client application will send two messages to the router, the rules will execute for each message and the
messages will get forwarded to the correct service. You should be able to see information in the router console
about each message being processed including requests and replies.

Windows Workflow Tutorial: Calling a Method in a Host from a


Running Workflow

Introduction

Although it may not otherwise be obvious, a running workflow and its host application run in separate threads.
You may have a need, as you build your application, to provide communication between the host application
and the running workflow; although the .NET Framework provides several cross-application communication
mechanisms, none are suited specifically for use with Windows Workflow Foundation (WF). Instead, WF
provides its own mechanism for allowing the host application to call a method in (and pass information to) a
running workflow, and for a running workflow to call a method in the host application.
The bi-directional communication between a workflow and its host is loosely coupled, and the mechanism
makes it easy for a variety of different applications (perhaps a Windows application, a Web application, a
Console application, and a Windows Service) to each host the same workflow. Because the workflow contains

90
no information about the specific implementer of the method it calls in its host, the workflow can simply
execute a method, and know that its host reacts to the method call.
Clearly, the the host and the running workflow need some mediation—some third party needs to control the
communication. The Windows Workflow runtime takes on this task, and as you’ll see in this tutorial, you must
indicate to the WF Runtime exactly where it should look for the class that provides the implementation of the
interface that defines the communication layer between the host and the workflow.
Although setting up the communication between the workflow and the host isn’t difficult, it requires several
steps, and you must follow the instructions carefully. Therefore, the examples you’ll find in this tutorial aren’t
even vaguely useful, but they do indicate the steps you’ll need to follow in order to communicate both from the
host to the workflow, and from the workflow to the host.

Calling a Method in the Host Application from the Running Workflow

Imagine that you’ve created a workflow, and this workflow processes files in a folder within the file system. As
the workflow processes each file, you’d like the host application to display the file’s name, indicating the
current progress of the workflow. Clearly, a Console application must handle this display differently than does a
Windows application, but either way, the running workflow simply needs to execute a method in the host
application which displays the file name using appropriate means.
In order to call a method in the host application, the workflow must include an instance of the
CallExternalMethod activity. This activity requires you to indicate a particular interface that defines the external
procedure, and allows you to bind parameters (and the return value) for the procedure to properties of the
workflow. You interact with the CallExternalMethod activity as you follow the steps in this tutorial.
In order to create a method in the host application that you can call from the workflow, you’ll need to tackle a
series of steps. You must:

• Create an interface that defines the procedure you want to call. You must attach the ExternalDataExchange attribute
to this interface, which marks the interface as a local service interface. In this interface, define any methods that you
want to be able to call from the workflow. (Note that overloading isn’t allowed in this context, even though it is a
valid code construct.)

• Create the workflow, including an instance of the CallExternalMethod activity. Supply the activity with information
about the interface, including the specific method it should call.

• Create the interface implementation, in the host application. Any class can implement the interface, and in this class,
you must provide the actual method(s) that the workflow will call. This implementation of the interface provides the
code that the WF Runtime executes when the workflow invokes the method.

• Hook up the plumbing, in the code that starts up the Workflow Runtime. Here, you must add a new instance of the
ExternalDataExchangeService class as a service for the Workflow Runtime, and you must indicate to the new service
exactly which class instance it should look in for the method that the workflow called.

Create the Console Application

To get started, in Visual Studio 2008, create a new Sequential Workflow Library project named
FindFilesWorkflow. Once you’ve created the new solution, add a new Sequential Workflow Console
Application project to your solution, and name it ConsoleHost. (At this point, your solution contains two
projects, each of which contains an empty workflow designer. You might wonder why you created both a
workflow library project and a sequential workflow Console application—each project template makes it easier
to interact with WF, because each project already includes the necessary assembly references, and the
Sequential Workflow Console Application template includes the necessary Workflow startup code. By creating
91
this type of application, you don’t need to write that startup code yourself.) In order to create the separation
between the host application and the workflow itself, for the purposes of this demonstration, you’ll delete the
workflow designer from the host application, and modify the host’s startup code to refer to the workflow in the
library application, instead. (See Figure 1.)

Figure 1. Your solution should contain two projects.


In the Solution Explorer window, right-click the ConsoleHost project, and select Select as Startup Project from
the context menu. Right-click the project again, and select Add Reference from the context menu. In the Add
Reference dialog box, select the Projects tab, select the FindFilesWorkflow project, and click OK to add the
reference. Now that you’ve added the reference to the workflow library, you can modify the console
application’s start-up code so that it creates an instance of the library’s workflow. To do that, in the Solution
Explorer window, double-click Module1.vb or Program.cs, loading the class into the code editor. In the code,
find the call to the WorkflowRuntime.CreateWorkflow method. Replace the reference to
ConsoleHost.Workflow1 so that the code creates an instance of FindFilesWorkflow.Workflow1:
C#
WorkflowInstance instance =

workflowRuntime.CreateWorkflow(

typeof(FindFilesWorkflow.Workflow1));

In the Solution Explorer window, in the ConsoleHost project, delete the Workflow1.cs or Workflow1.vb project
item.

Create the Interface

The goal of your workflow is to search for files and take some action as it finds each file; you need some way to
report the progress within the host application. Because the workflow has no information about the host
application, it can’t display information itself. Instead, it must call a method, defined in an interface, that the
host application implements. Although the shared interface could exist within any assembly, for the purposes of
this tutorial, you’ll place it within the workflow library.
Note: In a real application, this choice may not be a good one because of versioning issues. When using the
same assembly, the whoe library’s version number will change every time you change a workflow in the library.
You are better off placing the interface into a separate assembly. Once you’ve created the separate assembly,
you will simply add references from both the host and the workflow assembly to the interface assembly. We will
use the same assembly for the simplicity of this demonstration, but keep it in mind as you create your own
applications.

92
In the Solution Explorer window, select the FindFilesWorkflow project. In the menus, select Project | Add New
Item. In the Add New Item dialog box, select Interface. Name the new interface ICommunicate, and click Add
to create the interface. In C#, add the public keyword, so that the interface is publicly available:
C#
namespace FindFilesWorkflow

public interface ICommunicate

Modify the ICommunicate interface, adding the ReportProgress method definition:


C#
namespace FindFilesWorkflow

public interface ICommunicate

void ReportProgress(String fileName);

In order for the workflow to be able to interact with the interface you’ve created, it must include a marker
attribute; that is, an attribute that indicates that the interface can be added as a service to the
ExternalDataExchange service in Windows Workflow. To do this, add the ExternalDataExchange attribute to the
ICommunicate interface, so that the code looks like the following:
C#
[ExternalDataExchange]

public interface ICommunicate

void ReportProgress(String fileName);

In C#, you must also add a using statement to the top of the file (Visual Basic adds a project-wide Imports
statement for the correct namespace). In C#, right-click the new attribute, and select Resolve from the context
menu. In the fly-out menu, select the first option, which adds a using statement for the
System.Workflow.Activities namespace to the file for you. (Take this as a warning: If you don’t add the
ExternalDataExchange attribute to your interface, your workflow will not be able to call the corresponding
method(s) in the host application.)

Create the Workflow

Now that you’ve created the interface, you can set up the workflow so that it calls the method described in the
interface. In the Solution Explorer window, in the FindFilesWorkflow project, double-click the Workflow1.vb or
Workflow.cs item, opening the workflow in the Workflow designer. Add a While activity. Within the While
activity, add a CallExternalMethod activity. When you’re done laying out activities, the workflow should look
like Figure 2.

93
Figure 2. Lay out the sample workflow so that it looks like this.
Select View | Code. Add the following statement at the top of the file:
C#
using System.IO;

In the Workflow1 class, add the following variable declarations:


C#
public string currentFileName;

private string[] files;

private int totalFiles;

private int currentFile;

In the Workflow1 class, add the following procedure:


C#
private void SetFiles(string Path)

  if (!String.IsNullOrEmpty(Path))

  {

    files = Directory.GetFiles(Path);

    totalFiles = files.Length;

    currentFile = 0;

  }

Within the Workflow1 class, add the following property (note that the property setter calls the SetFiles method,
filling in the list of files in the selected path):
C#
private string pathValue;

94
public string Path

  get { return pathValue;}

  set

  {

    pathValue = value;

    SetFiles(value);

  }

Select View | Designer. Select the While activity, and, in the Properties window, set the Condition property to
Declarative Rule Condition. Expand the + to the left of the Condition property, and set the ConditionName
property to MoreFiles. Select the Expression property, click the ellipsis to the right of the property value, and in
the Rule Condition Editor window, add the following expression (see Figure 3), and click OK when you’re
done:
Rule
this.CurrentFile < this.totalFiles

Figure 3. Set the While activity’s condition.


After configuring the While activity, the Properties window should look like Figure 4.

Figure 4. Configure the While activity.


In order to set up the CallExternalMethod activity so that it can call a method outside the workflow, you must
supply the name of the interface that defines the method the workflow will call, along with the name of the
specific method (and how to bind any parameters, and the return value, if any) for the method.
Select Build | Rebuild Solution. Visual Studio will return errors (because you haven’t completed setting up the
required properties of some of the workflow activities). In the Workflow designer, select the
95
CallExternalMethod activity. In the Properties window, select the InterfaceType property. Click the ellipsis to
the right of the property value, displaying the dialog box shown in Figure 5. This dialog box includes a list of all
the available interfaces that have been marked with the ExternalDataExchange attribute. In this case, the list
contains only the ICommunicate interface, so select that interface and click OK.

Figure 5. Select from the list of interfaces marked with an ExternalDataExchange attribute.
In the Properties window, select the MethodName property. Click the drop-down button to the right of the
property value, and select ReportProgress (the name of the method within the interface) from the list. Of course,
the ReportProgress method requires a single parameter (the name of the file that the workflow has found), and
the Workflow designer adds this property to the Properties window once you select the name of the method you
want to call (see Figure 6). Because the parameter name appears within the Properties window, you can bind it
to a property of the workflow, just like any other bindable Workflow property.

96
Figure 6. Once you specify an interface and a method within that interface, the designer offers to allow
you to bind a value to each of the method’s parameters.
In the Properties window, select the fileName property, and click the ellipsis to the right of the property value.
In the dialog box, select the workflow’s currentFileName property, as shown in Figure 7. Click OK to create the
data binding.

Figure 7. Bind the ReportProgress procedure’s fileName parameter to the workflow’s currentFileName
property.
Now that you have bound the ReportProgress procedure’s fileName parameter to the workflow’s
currentFileName property, when the workflow calls the procedure, it will pass the value in the currentFileName
property as the parameter to the ReportProgress procedure. (Note that you haven’t yet implemented the
ICommunicate interface, which means you haven’t yet created the ReportProgress method that the workflow
will call.)
Of course, you must set the private currentFileName property each time the CallExternalMethod activity is
about to call the external method. The activity provides its MethodInvoking property so that you can specify
such a procedure. In the Properties window, select the MethodInvoking property, and supply the name for the
procedure, SetFileName. Press Enter to create the procedure, and modify it so that it sets the current file name
and increments the current file number:
C#
private void SetFileName(object sender, EventArgs e)

  currentFileName = files[currentFile];

  currentFile++;

Select Build | Build Solution, and verify that the entire solution builds correctly.

Create the Interface Implementation

97
You have created the workflow and the interface; now it’s time to create the interface implementation. In the
Solution Explorer, right-click the ConsoleHost project, and select Add | Class from the context menu. In the Add
New Item dialog box, set the name of the new class to UserInterface, and click Add.
At the top of the new class file, add the following statement:
C#
using FileFilesWorkflow;

Modify the new class so that it’s serializable, and so that it implements the ICommunicate interface.
C#
[Serializable()]

class UserInterface: ICommunicate

In Visual Basic, adding the Implements statement also adds the stub for the ReportProgress procedure. In C#,
right-click the name of the interface, and select Implement Interface | Implement Interface from the context
menu—this creates the ReportProgress procedure stub for you.
Modify the new ReportProgress procedure, adding code to display the output in the Console window:
C#
public void ReportProgress(string fileName)

  Console.WriteLine("Processing file: {0}", fileName);

Build the entire solution again, and verify that it builds without errors.

Hook Up the Plumbing

You’ve done most of the work, but the Workflow Runtime still doesn’t know how to find the code that you’ve
just added, so it calls the code when the workflow executes its CallExternalMethod activity. To hook things up,
you must configure the Workflow Runtime, adding an additional service to the runtime, and telling the service
to look in a specific instance of the class you just created when it needs to call the ReportProgress method.
In the Solution Explorer window, in the ConsoleHost project, double-click the Program.cs or Module1.vb item.
In C#, at the top of the file, add the following statement:
C#
using System.Workflow.Activities;

Within the Program class, add the following declaration:


C#
private static UserInterface ui = new UserInterface();

In the Main procedure, immediately above the declaration of the workflowInstance variable, add the following
code which creates and adds an instance of the ExternalDataExchangeService class as a service:
C#
var dataService = new ExternalDataExchangeService();

workflowRuntime.AddService(dataService);

Continue the same block of code, adding the following line. This code adds an instance of the UserInterface
class (the class that implements the ICommunicate interface), as a service for the ExternalDataExchange service
98
—in other words, the code indicates to the ExternalDataExchangeService instance where it can find the code it
needs to call when the workflow instance calls its external method:
C#
dataService.AddService(ui);

You still need to specify a path in which to search for files, as you create the workflow. Continue the same code
block (immediately above the call to the CreateWorkflow method), adding the following code (this example
looks for files in the C:\ folder—use any folder you like, in your code):
C#
var parameters = new Dictionary<String, Object>();

parameters.Add("Path", "C:\\");

Finally, modify the call to the CreateWorkflow method, so that you pass the parameters dictionary to the
workflow runtime as you create the workflow:
C#
WorkflowInstance instance = workflowRuntime.CreateWorkflow(

  typeof(FindFilesWorkflow.Workflow1), parameters);

At this point, you’ve done all the work necessary to try out your workflow. The workflow looks in the path
you’ve specified for files. As it finds each one within its While activity, it calls the external method you created
outside of the workflow. In order to do this, the Workflow Runtime uses the ExternalDataExchange service that
you created. Because you specified to the ExternalDataExchange service instance where it could find the
instance of the class that implements the interface and method that the workflow expects to call, it routes the
method call appropriately.
Press Ctrl+F5 to run the project. You should see output as shown in
Figure 8. Although the output is somewhat underwhelming, it proves a point: You were able to call a method in
the host application from the running workflow, using the ExternalDataExchange service.

Figure 8. The output should look like this.

Conclusion

It seems like a lot of work to call a method in the host application from a workflow, but it’s not difficult as long
as you follow the required steps. You must:

1. Define the interface. Create a public interface that defines the communication between the workflow and the host.
Make sure to add the System.Workflow.Activities.ExternalDataExchange attribute to the interface.
99
2. Add the CallExternalMethod activity to the workflow. Set at least the InterfaceType and MethodName properties. Bind
parameters and the return value, if you like, to properties of the workflow.
3. Implement the method(s) in the host. Create a class that implements the communication interface, adding code for
the implemented methods. Make this class serializable.
4. Create and configure the data service. In the host application, create a new instance of the
ExternalDataExchangeService class, and add it as a service to the Workflow Runtime. Then, create an instance of the
communication class (the class that implements the communication interface) and add it as a service to the new
ExternalDataExchangeService instance.

If you follow these steps carefully, you should be able to call methods defined in the host application from your
running workflows. In a future tutorial, you’ll learn how to communicate from the host application to the
workflow, sending information to the workflow, by raising an event in the host application. The workflow can
use its HandleExternalEvent activity to handle the event, and retrieve the information passed in from the host.
For now, try creating your own workflow that calls a method in the host application, and verify that you can
follow the list of steps presented here.

Windows Workflow Tutorial: Handling an Event from the Host in a


Workflow

Introduction

In an earlier tutorial in this series, you learned how to call a method in the host application from a workflow
(and how to pass information from the workflow to the host, in the parameters to the method). If you haven’t
worked through that tutorial, you should do so now—without that information, this tutorial will not make much
sense.
(This tutorial starts with the finished solution from the earlier tutorial. If you don’t have that solution handy, you
can download it here)
This technique you’ve already learned doesn’t help, however, if you need to send information from the host
application to the running workflow. Maybe you need to indicate to the workflow that a particular condition has
been met, or that the application has gathered some information it needs to send to the workflow.
Imagine that the host application started the workflow, but the workflow progresses to a point at which it needs
input. It must get that input from the host application, so it must wait until the host has gathered the necessary
information. In order to pass the information from the host application to the workflow, the host application can
raise an event, so that the workflow can handle the event using the HandleExternalEvent activity.
In order to pass information from the host to the workflow, you’ll need a class that defines the event arguments.
If you weren’t building an application using Windows Workflow Foundation, you could simply inherit from the
System.EventArgs class, adding the specific information required by your event. In this case, however, the
stakes are a bit higher, and your event argument class must meet specific requirements. Your class must support
these characteristics:
• It must inherit from System.Workflow.Activities.ExternalDataEventArgs.
• It must be serializable.
• It must provide a non-default constructor that accepts a workflow instance ID (a Guid) as its parameter.
Using this information, the event argument object can determine the specific instance of the workflow to which
it was sent.

100
In order to pass information to the workflow, you’ll create a class that meets these criteria. Just as in the
previous example, you’ll rely on the Windows Workflow Foundation’s ExternalDataExchange service. In this
exercise, you’ll modify the workflow you created in the previous exercise, and have the workflow wait for you
to supply a path in which to search, in the host application.
Modify the Workflow
In order to handle the event raised by the host application, the workflow needs to include a
HandleExternalEvent activity, and this activity is useful when placed within a Listen activity. The Listen
activity contains two or more branches, and the first activity in each branch must implement the IEventActivity
interface—that is, the first activity must be configured so that it waits for some event to occur (either an event
raised externally, or a timer event within the workflow, for example). The Windows Workflow Foundation
includes two activities that implement this interface—the Delay activity and the HandleExternalEvent activity.
Each branch, then, waits until its first child activity’s event occurs, and the Listen activity executes the
remainder of the activities within the single branch. All other branches never execute. In other words, the first
branch whose event occurs “wins”. For this simple example, however, you’ll work with the
HandleExternalEvent activity on its own.
If you closed the solution you created in the first part of this tutorial, re-open it in Visual Studio 2008 now. In
the Solution Explorer window, double-click the Workflow1.vb or Workflow1.cs item, loading it into the
Workflow designer.
From the Toolbox, drag a HandleExternalEvent activity immediately above the existing While activity. When
you’re done, the workflow should look like Figure 1.

Figure 1. The completed layout should look like this.


You should imagine that this small workflow is actually part of a larger workflow in which the workflow has
reached a point in its processing at which it requires input from the user. For this simple workflow, you could
simply supply the path as a parameter, as you did in the previous exercise. Use your imagination here.
At this point, the workflow is broken—until you supply information about the event to the HandleExternalEvent
activity, the application can’t run. The next few sections walk you through building all of the necessary
infrastructure so that the workflow can handle the event.
Add the Event Argument Class
In order to pass information from the host to the workflow, you must create a class that meets specific
requirements to act as the event argument. In the Solution Explorer window, right-click the FindFilesWorkflow
project, and select Add | Class from the context menu. Name the new class InfoEventArgs, and click Add.
Add the following statement to the top of the new code file:
Imports System.Workflow.Activities
using System.Workflow.Activities;
Modify the InfoEventArgs class so that it inherits from the ExternalDataEventArgs class, and ensure that the
class is public. Next, add the Serializable attribute to the class:
<Serializable()> _
Public Class InfoEventArgs
Inherits ExternalDataEventArgs
End Class
101
[Serializable()]
ublic class InfoEventArgs: ExternalDataEventArgs
Inside the InfoEventArgs class, add a property to contain the path in which the workflow should search:
Private pathValue As String
Public Property Path() As String
Get
Return pathValue
End Get
Set(ByVal value As String)
pathValue = value
End Set
End Property
public string Path { get; set; }
Inside the class, create a constructor that accepts a Guid value and the path value, and pass the Guid value to the
base class’ constructor:
Public Sub New(ByVal instanceId As Guid, Path As String)
MyBase.New(instanceId)
Me.Path = Path
End Sub
public InfoEventArgs(Guid instanceId, string Path):
base(instanceId)
{
this.Path = Path;
}
Select File | Save All to save the entire solution.
Modify the Interface
In the previous exercise, you created an interface that defines the interaction between the host and the workflow.
In this exercise, you’ll extend that interface, adding information about the event the host application will raise in
order to send information to the workflow.
In the Solution Explorer window, in the FindFilesWorkflow project, double-click the ICommunicate.vb or
ICommunicate.cs item. Add the following declaration to the existing interface:
Event PathReceived As EventHandler(Of InfoEventArgs)
event EventHandler<InfoEventArgs> PathReceived;
Note: In a real application, you are better off placing the shared interface in a separate assembly. As it is, in this
example, if you modify the workflow, you modify the versioning associated with the interface. This can cause
102
trouble with persisted workflows. For this simple demonstration, it’s not worth the overhead of creating a
separate project, but in more complex projects, place the shared interface in a separate assembly and reference it
from both the host and the workflow assemblies.
Finish the Workflow
Now that you have completed the interface and the event arguments, you can finish the workflow by indicating
the specific event that the workflow should wait for. Again open Workflow1 in the Workflow designer, and
select the HandleExternalEvent activity. In the Properties window, select the InterfaceType property, click the
ellipsis to the right of the property value, and select ICommunicate from the list of available interfaces. Select
the EventName property, and from the drop-down list of event names, select PathReceived. (Note that this
action adds e and sender properties to the Properties window, corresponding to the parameters passed into the
event handler. You don’t need to interact with the sender property, but the e property provides information sent
from the host, and you’ll need to gather than information.) Figure 2 shows the current state of the Properties
window.

Figure 2. The properties window, after you have set the InterfaceType and EventName properties.
Because your workflow needs to capture the Path property of the event argument sent by the host to the
workflow, you must modify the workflow so that it includes an InfoEventArgs variable into which to place the
value. Select View | Code to load the workflow’s code, and add the following property to the class (note that this
property sets the value of the workflow’s Path property, in the property setter):
Private argsValue As InfoEventArgs
Public Property args() As InfoEventArgs
Get
Return argsValue
End Get
Set(ByVal value As InfoEventArgs)
argsValue = value
Path = args.Path
End Set
End Property
private InfoEventArgs argsValue;
public InfoEventArgs args
{
get { return argsValue; }
set
{
argsValue = value;
this.Path = value.Path;
}
103
}
Select View | Designer. Select the HandleExternalEvent activity, and in the Properties window, select the e
property. Click the ellipsis to the right of the property, and select the args property from the list of values, as
shown in Figure 3. Click OK when you’re done. You have specified that the HandleExternalEvent activity
should store the value in its e parameter into the workflow’s args property, which, in turn, sets the value of the
workflow’s Path property. (Note that if you need addition processing when the event occurs, you can create a
handler for the activity’s Invoked event. You can access the argument from this event handler, as well, and you
can add any necessary processing in this handler.)

Figure 3. Bind the second event parameter to the args property in the workflow.
Modify the Host Interface Implementation
Because you have modified the communications interface, you must modify the class within the host
application that implements this interface. In the Solution Explorer window, in the ConsoleHost application,
double-click the UserInterface.vb or UserInterface.cs item. In Visual Basic, click on the line of code including
the Implements keyword, press End, and press Enter. This forces the editor to add the event declaration to the
implementation. In C#, right-click the interface name, and select Implement Interface | Implement Interface
from the context menu. This action adds the following statement to the implementation:
Public Event PathReceived(ByVal sender As Object, _
ByVal e As FindFilesWorkflow.InfoEventArgs) _
Implements FindFilesWorkflow.ICommunicate.PathReceived
public event EventHandler<InfoEventArgs> PathReceived;
It’s up to you to add the code that raises this event, and to do that, add the following procedure to the
UserInterface class. This code accepts the workflow’s instance ID (which you don’t yet have a way to capture)
and the path to be searched. It sets up the new InfoEventArgs object, and raises the PathReceived event:
Public Sub RaisePathReceived( _
ByVal instanceId As Guid, _
ByVal Path As String)
Dim args As New InfoEventArgs(instanceId, Path)
RaiseEvent PathReceived(Me, args)
End Sub
public void RaiseNameReceived(
Guid instanceId, string Path)
{
if (PathReceived != null)
{
InfoEventArgs args = new InfoEventArgs(instanceId, Path);
PathReceived(null, args);
}
104
}
Modify the Startup Code
Everything is in place to send information from the host to the workflow, except for the actual code that raises
the event. In this example, you must modify the application’s Main procedure, to prompt the user for a path.
Given the path, the host raises the appropriate event to indicate to the workflow that the user has entered the
information.
(Note that you although you could place this user-interface code in one of the workflow events, such as the
WorkflowIdled event, doing so would cause a problem. These events block the workflow thread and any long-
running work. If you want to gather user input in one of the workflow events, you should place the code in a
separate thread. Investigate the System.Threading.ThreadPool.QueueUserWorkItem method to gather user input
in a separate thread.)
In the Solution Explorer window, double-click the Program.cs or Module1.vb item. Note that the Main
procedure already includes code that adds the ExternalDataExchangeService, and an instance of the
UserInterface class as the communications class—you don’t have to add this code, it already exists:
Dim dataService As New ExternalDataExchangeService
workflowRuntime.AddService(dataService)
dataService.AddService(ui))
var dataService = new ExternalDataExchangeService();
workflowRuntime.AddService(dataService);
dataService.AddService(ui);
The code currently passes a dictionary containing information about the Path property to the workflow. To test
the event mechanism, remove that code. To do that, start by commenting out the following two lines of code:
' Dim parameters As New Dictionary(Of String, Object)
' parameters.Add("Path", "C:\")
// var parameters = new Dictionary<String, Object>();
// parameters.Add("Path", "C:\\");
Modify the call to the CreateWorkflow method, removing the parameters variable as the second parameter.
When you’re done, the call to the CreateWorkflow method should look like this:
workflowInstance = workflowRuntime.CreateWorkflow( _
GetType(FindFilesWorkflow.Workflow1))
WorkflowInstance instance = workflowRuntime.
CreateWorkflow(typeof(FindFilesWorkflow.Workflow1));
In the Main procedure, immediately preceding the call to the waitHandle.WaitOne method, add the following
code:
Console.WriteLine("Enter the search path:")
Dim path As String = Console.ReadLine()
ui.RaisePathReceived(workflowInstance.InstanceId, path)

105
Console.WriteLine("Enter the search path:");
string path = Console.ReadLine();
ui.RaisePathReceived(workflowInstance.InstanceId, path);
.Finally, press Ctrl+F5 to execute the workflow. When the workflow executes the HandleExternalEvent activity,
it idles, waiting for the host to raise the PathReceived event. This code retrieves the path from you, and once
you supply it, the code raises the event to the workflow, allowing it to continue processing, given the
information you supplied.
Conclusion
Raising an event from the host application to the workflow sure seems like a lot of steps! It does require a lot of
steps, and you need to pay attention to a lot of detail. To raise an event from the host to the workflow, you must
at least accomplish these goals:
1. Handle event arguments. You must create a class that contains the event argument information you
want your event to be able to send to the workflow. This class must inherit from
System.Workflow.Activities.ExternalDataEventArgs, and it must be serializable.
2. Define the communications interface. You must create an interface marked with the
System.Workflow.Activities.ExternalDataExchange attribute applied to it. In the interface, add the definition of
any event you want to raise from the host to the workflow.
3. Define the host. Create the host application (a console application, a Windows application, or any
other kind of application), including an implementation of the communications interface. The class that
implements the interface must also be serializable.
4. Handle the event in the workflow. Add a HandleExternalEvent activity, and set at least its
InterfaceType and EventName properties. If you want to receive information from the event, consider binding
the event argument to a corresponding property in the workflow’s class.
5. Create and configure the data service. In the host application, create a new instance of the
ExternalDataExchangeService class, and add it as a service to the Workflow Runtime. Then, create an instance
of the communication class (the class that implements the communication interface) and add it as a service to
the new ExternalDataExchangeService instance.
6. Raise the event. In the host application, at the appropriate time, raise the event. If you’ve followed all
the steps carefully, the workflow receives the event, and handles it.
Although it seems difficult to communicate between the host application and the workflow, it’s really not.
Follow the steps carefully, and you will be able to send information from the host application to the workflow
without any problems.

Windows Workflow Tutorial: Rules-Driven .NET Applications


Windows Workflow Foundation ships with a robust business rule engine that can be incorporated into
workflows to assist in managing business processes. What some developers do not realize is that the rule
engine can be used outside of workflows in any .NET application to provide robust rule processing against any
.NET object. This hands-on article will walk through how developers can take advantage of using the rule
engine in .NET applications through examples using WPF and ASP.NET.

Using rules in ASP.NET applications

In this example rules will be used to control the user interface for a user to ensure that all required information
is selected. The user interface consists of simple wizard using the MultiView control to collect loan application

106
information (the sample is greatly simplified). Certain steps in the wizard are only required for applications in
particular states and should not be shown to other applicants. Rules will be used to skip certain steps as the user
progresses through the interface.
The MultiView control was chosen over the Wizard control because it has better support for removing steps.

The image below show the main wizard steps in order as shown in Visual Studio.

Figure 1: LoanWizard in Visual Studio


The starter user interface is included in the before directory of the downloadable code sample.

Creating the rules


Once the user interface is created with all of the steps defined, the next step is to create the business rules. WF
allows you to build your own UI for creating and editing rules, and allows for rehosting the rules editor dialog
that comes as part of WF 3.0. For this example, rather than creating our own UI for creating and editing rules,
we will use the External RuleSet Toolkit sample (http://msdn.microsoft.com/en-us/library/bb472424.aspx).
This sample demonstrates how to create a ruleset outside of Visual Studio and save the resulting XML to a file
or a database; for our purposes, it provides an easy UI that we don’t have to code in this article. You can
download the sample from MSDN using the link. Once you have downloaded the samples, run the installer to
expand them and you will find the External Ruleset Toolkit in the following
directory: \WCF\Extensibility\Rules\ExternalRuleSetToolkit.
To setup the database used by the External RuleSet Toolkit, double-click the setup.cmd command file found
with the sample. Note: The command file assumes that your instance of SQL Server Express is named
.\sqlexpress. If you have named your instance something different or you are using another version of SQL
Server, you will need to modify the file before executing it. In addition, you will need to update the
configuration files in the toolkit projects to point to the correct instance of SQL Server.
Once the command file has completed and the database has been configured, open the
ExternalRuleSetToolkit.sln solution in Visual Studio. Make any necessary changes to the configuration file for

107
your database to point at the database you just created, and set the start up project by right-clicking on
theExternalRuleSetTool project and selecting Set as startup project from the context menu. Press F5 to run
the application and you should see a dialog as shown in Figure 2.

Figure 2: External RuleSet Tool


Click the “New” button to create a new ruleset with an initial version of 1.0, and change the name of the ruleset
to “LoanWizardRules”. Typically Rules are articulated using the properties of a Fact – For e.g. in the following
rule, Applicant and Loan are Facts and Age and IsApproved are properties of these facts.
If Applicant.Age < 21 Then Loan.IsApproved = false

A Fact can be thought of as an object instantiation of a type. In the above example Applicant is an object of
type LoanApplicant and Loan is an object of type LoanApplication. In Windows Workflow Foundation rather
than authoring rules against many different fact types, a single type called the root type is used. Thus all rulesets
are defined in relation to a single specific .NET type. For this example, the rules are defined against the
System.Web.UI.WebControls.MultiView type. Defining rules against ASP.NET page types can be difficult as
the type at runtime is generated from the ASPX being compiled so a specific control type was chosen for this
sample. To select the type the rules will be authored against, click the “Browse” button. In the resulting dialog,
shown in Figure 3, the specific type must be found. Click the “Browse” button and select
the System.Web.dll assembly in the c:\windows\microsoft.net\framework\v2.0.50727\ directory. Browse
through the list of types and select the System.Web.UI.WebControls.MultiView type as shown in Figure 3.

108
Figure 3: Choosing the target type for the ruleset
Click OK to close the type selection dialog; you are now ready to edit the actual rules in the ruleset can Click
the “Edit Rules” button which will open the Rule Set Editor dialog.
The Rule Set Editor dialog is not part of the External RuleSet Toolkit, it is installed as part of the .NET
Framework 3.0 runtime installation and resides in the System.Workflow.Activities assembly. The toolkit
provides one example of how the dialog can be re-hosted in an application to allow rule display and/or editing.
For this example, the rules should remove the NY and CA legal views if the user did not select one of those
states on the first page. To add the rule for New York, click the Add Rule button, then edit the Name field to set
the value as “NY”. Next, enter the following code for the Condition field; this is the Boolean expression as you
would use in an IF statement in code.
((System.Web.UI.WebControls.DropDownList)this.FindControl("State")).SelectedValue != "NY"

This condition checks that the State dropdown list on the first page of the wizard is not set to “NY”. In
the Then Action enter the following code to remove the page from the wizard that includes NY specific
content.
this.Views.RemoveAt(1)

Now add a new rule for California by clicking the Add Rule button and then entering “CA” for
the Name field. For the Condition, enter a similar test of the State dropdown control, this time making sure the
value is not “CA”.
((System.Web.UI.WebControls.DropDownList)this.FindControl("State")).SelectedValue != "CA"

In the Then Actions, enter the following code to remove the California specific content from the wizard.
this.Views.RemoveAt(2)

Finally change the value in the Priority field to “2”, giving this rule a higher priority than the other.
109
The result of adding these two rules should look similar to the dialog in Figure 4.

Figure 4: Rules for removing wizard steps


Each rule has a priority indicating that the California rule will run first as it has the higher priority. This ensures
that views are removed based on the index from highest to lowest to so the correct views are removed. Click
OK to exit the rule editor dialog and return to the main window of the toolkit application.
Once the rules are defined, they must be saved in the External RuleSet Tool window. Press CTRL + S or choose
the File | Save menu option). The tool will save the data in the SQL Server database created with the setup
command earlier.

Defining a wrapper class


Within an application, the code to execute the rules involves retrieving the ruleset from the database, file or
other location and then executing them using the rule engine. Rather than code all of this into each page or
window of an application, a static class can be used to simplify the code in each page and centralize the code
used to retrieve and execute the rules.
Right-click the RulesInASP website project and choose Add Reference from the context menu. Select
the System.Workflow.Activities andSystem.Workflow.ComponentModel assemblies. These assemblies
contain the rule related classes and the classes used to serialize rulesets. Next, right-click
the App_Code directory in the RulesInASP website and choose Add New Item from the context menu. In the
new item dialog, select the Class template and name the file “RulesMediator.cs”. Change the class definition
to make the class public and static. Also add a dictionary for caching the rules and a static constructor which
initializes the dictionary to an empty collection. The resulting class should look like Figure 5.
C#
public static class RulesMediator
110
{

static Dictionary<string, RuleSet> ruleCache;

    static RulesMediator()

    {

        ruleCache = new Dictionary<string, RuleSet>();

    }

Figure 5: RulesMediator class construction


Next the class needs the ability to get the ruleset from the database which is a combination of getting the XML
representation of the rules from the database and deserializing that into a RuleSet object. Simple ADO.NET
data access can be used to get the XML as a string and the WorkflowMarkupSerializer class is used to
deserialize the XML into a RuleSet. Add a static method to the RulesMediator class called GetRuleSet which
selects the most recent version of a ruleset by name from the database created by the ExternalRuleSetToolkit.
After retrieving the XML for the ruleset, use the WorkflowMarkupSerializer class’ deserialize method to get the
XML converted to a RuleSet object as shown in Figure 6.
C#
private static RuleSet GetRuleSet(string ruleSetName)

using (SqlConnection cnn = new SqlConnection(

ConfigurationManager.ConnectionStrings[

"Rules"].ConnectionString))

using (SqlCommand cmd = new SqlCommand(

"SELECT TOP 1 [RuleSet] FROM RuleSet WHERE

Name=@name ORDER BY MajorVersion DESC , MinorVersion DESC",

cnn))

                     cmd.Parameters.Add("@name",

                           System.Data.SqlDbType.NVarChar, 128);

                     cmd.Parameters["@name"].Value = ruleSetName;

                     cnn.Open();

                     string rules = cmd.ExecuteScalar().ToString();

                     WorkflowMarkupSerializer serializer =

                            new WorkflowMarkupSerializer();

                     RuleSet ruleset =

                           (RuleSet)serializer.Deserialize(

                                  XmlReader.Create(

                                         new StringReader(rules)));

                     return ruleset;

111
}

Figure 6: Retrieving and deserializing a ruleset


Rather than having to retrieve the rules for each execution they can be cached in the local dictionary. This is
accomplished by creating a GetRules method which gets the rules from the cache or loads them from the
database and then puts them in the cache. Add the method, shown in Figure 7, to the RulesMediator class.
Notice that the GetRules method is public, while the previous GetRuleSet method is a private method.
C#
public static RuleSet GetRules(string ruleSetName)

       if (ruleCache.ContainsKey(ruleSetName))

              return ruleCache[ruleSetName];

       else

       {

              RuleSet rules = GetRuleSet(ruleSetName);

              ruleCache[ruleSetName] = rules;

              return rules;

       }

Figure 7: Caching rules in memory for performance


The other method to add to the RulesMediator is the RunRules method which provides the actual execution of a
set of rules on a given instance of an object. Because rules are authored against a particular type, the execution
of those rules must be against an instance of that type. The RunRules method is defined as a generic method
where the type defines the type of the instance. In addition, the name of the RuleSet to run is passed as a
parameter. Create the RunRules method in the RulesMediator class based on the sample in Figure 8.
C#
public static void RunRules<T>(T target, string rulesName)

       RuleSet rules = GetRules(rulesName);

       RuleEngine engine = new RuleEngine(rules, typeof(T));

       engine.Execute(target);

Figure 8: Executing a ruleset


In order to execute rules, the RuleSet must be created or retrieved as shown. Next, an instance of the
RuleEngine must be created passing in the RuleSet and the type of the object on which the rules will execute.
Finally, the execute method of the RuleEngine class is called which runs the rules. After this method has
executed the object passed to the RuleEngine may have been updated by rules and can be inspected for
changes. Essentially, the object passed to the RuleEngine as the input is also the output.

112
Executing the rules
To execute the rules in a given page of the .NET application is quite simple as it involves a single call to the
static RunRules method of the RulesMediator class. Update the WizardNext_Click event handler with the code
in Figure 9. This code will execute when the user clicks the next button and will ensure that the correct panels
in the wizard are shown or hidden accordingly.
C#
protected void WizardNext_Click(object sender, EventArgs e)

       RulesMediator.RunRules<MultiView>(

              LoanWizard, "LoanWizardRules");

Figure 9: Executing rules in the web page


Now the page has very simple code that runs the rules and updates the user interface based on those rules. To
test the application, choose Debug | Start Debugging from the Visual Studio menus, or press F5. On the
default page, select a state and then click the Next button. If you chose NY or CA, you should be presented
with a legal page specific to that state. If not, you should skip directly to the view where you can input loan
amount and term.

What have we done so far


In this first example, the rules regarding data collection in a user interface were extracted from the code and
encapsulated in an externally editable business rule definition. For this simple example it may not seem like an
incredible achievement, but simply consider how much more complex the ASP.NET code would become with a
real loan application and all the associated rules. The code in the web pages is extremely concise and simple,
while the business rules can become much more complex.

Using rules in a WPF application

The same concepts used in the previous example can also be used in other .NET applications. In this example
the Windows Workflow Foundation business rules engine is used in conjunction with the data validation
features found in Windows Presentation Foundation. Instead of writing rules based on a user interface class, the
rules in this application are written against a business object.

The LoanApplication class


The business object used in this example is a simple LoanApplication class that implements the IDataErrorInfo
interface. As of .NET Framework 3.5 WPF data binding supports the IDataErrorInfo interface for data
validation. If a particular property on the class is in error, this interface exposes the error information returned
using the Indexer property. To get started, open the HandsOn.WFRulesInNetApps.sln file from
the before directory if you don’t already have it open from the previous example. Open
the LoanApplication.cs file in the RulesInWPF project. The class provides a simple business object with
properties for a name and loan terms. Add a declaration of a dictionary to hold any errors that are created by
the business rules and two properties defined on the IErrorInfo interface as shown in Figure 10.
C#
private Dictionary<string, string> errors = new

Dictionary<string, string>();

string IDataErrorInfo.Error

113
{

get { return string.Empty; }

string IDataErrorInfo.this[string columnName]

get

RulesMediator.RunRules<LoanApplication>(

                     this, "WPFLoanRules");

if (errors.ContainsKey(columnName))

return errors[columnName];

else

return String.Empty;

Figure 10: Errors dictionary


Make sure you have your class declared to implement the IErrorInfo interface as shown here:
public class LoanApplication : IDataErrorInfo

Notice that as the first step in checking for errors in the indexer, the business rules policy is called by using the
same RulesMediator class created in the ASP.NET example. This “validate” method runs all of the business
rules for the loan application when requested by the user interface. In your own applications you may choose to
put the validation triggers in different places based on your requirements and when it is most appropriate to
validate your business objects.

Writing the rules

Using the External RuleSet Toolkit the rules are built against the LoanApplication type. The first rule checks
the LoanAmount property and if it exceeds 350,000 uses the SetError method to set an error message indicating
this amount is too high. The second rule checks the combination of the LoanAmount and LoanTerm properties
and if the LoanAmount is less than 30,000 and the LoanTerm is more than 5, sets an error on the LoanTerm
property. To begin, open the ExternalRuleSetToolkit.sln solution from the SDK samples directory as you did in
the earlier sample. Click the New button and enter “WPFLoanRules” for theRuleSet Name field.
Click the Browse button on the main dialog, and then again on the Type Selection dialog. Browse to the
RulesInWPF.exe assembly and select the LoanApplication type as shown in Figure 11.

114
Figure 11: Selecting the LoanApplication type
Now that a type has been selected, click the Edit Rules button to invoke the Rule Set Editor. Click the Add
Rule button and name the new rule “LoanAmount”. Set the Condition to the following code to test the
LoanAmount:
this.LoanAmount > 350000

In the Then Actions, use the following code to set the error on the LoanApplication:
this.errors["LoanAmount"] = "Amount must be less than $350,000"
Next, use the following code in the Else Actions pane to remove any existing errors for the LoanAmount
property.
this.errors.Remove("LoanAmount")

There are two interesting things to note about these statements. First, the Else Action is optional and in this case
it makes sense to remove any existing errors for a particular field, but it may not in cases with more rules and
multiple validation requirements for a given field. Also, the errors field is an internal field, but we are able to
manipulate it directly in the rules rather than having to expose a public method allowing for setting error
messages.
Add another rule by clicking the Add Rule button and setting the Name to “InvalidTermAndAmount”. Also set
the Priority of this rule to “2”, ensuring that it runs before the other rule. This is important as the mechanism
for showing errors, discussed below, will only show a single error. For the Condition enter the following code
to check both the LoanAmount and LoanTerm properties.
this.LoanAmount < 30000 && this.LoanTerm > 5

Set the Then Actions to set errors on both the LoanAmount and LoanTerm properties if the conditions are met.
this.errors["LoanAmount"] =
"The loan term is too long for the loan amount"

115
this.errors["LoanTerm"] =
"The loan term is too long for the loan amount"

Set the Else Actions to clear errors on both fields if the conditions are not met.
this.errors.Remove("LoanAmount")
this.errors.Remove("LoanTerm")

Figure 12 shows the completed rules in the editor.

Figure 12: WPFLoanRules defined


Click the OK button to close the editor dialog and then choose File | Save from the window menu. Close the
ExternalRuleSetTool application and return to the Visual Studio solution for this example.

Provide user feedback


WPF supports the IDataErrorInfo interface and will highlight an input field when the data source reports an
error. However, in order to provide the user the error message, the developer needs to apply a bit more code.
One way this can be accomplished is using a style with a trigger. The example style below is included as a
resource in the XAML file for the input window and causes the tooltip for the control to show the actual error
message.
XAML
<Style TargetType="TextBox">

<Style.Triggers>

116
<Trigger Property="Validation.HasError" Value="true">

<Setter Property="ToolTip"

Value="{Binding RelativeSource={RelativeSource Self},

Path=(Validation.Errors)[0].ErrorContent}"/>

</Trigger>

</Style.Triggers>

</Style>

Now when a user enters text in the dialog, the WPF databinding technology kicks in and when the
IDataErrorInfo indexer is called the rules are executed. The images below show the validation in action on a
simple entry form where the TextBox controls are bound to an instance of the LoanApplication class. Run the
RulesInWPF application to test for yourself.

Figure 13: Rules in action

Conclusion

In this example the validation of a business object in a WPF application is managed by an external ruleset. The
rules about what makes the loan and it’s various properties valid are defined and managed in the rules and can
be updated without having to re-deploy the application. In addition, the rules are integrated into the standard
WPF data validation framework to provide immediate feedback to the user.

117

También podría gustarte