Get Your Code Flowing Faster with Windows WorkFlow Foundation

ur daily lives revolve around a series of activities, decisions, and rules. For example, upon waking one of the first activities you do is probably to brush your teeth, followed by washing your face, etc. You have to make decisions, such as what to eat for breakfast and which tie to wear to work. And there are always rules to observe, such as a certain time to arrive at the office, etc. All these activities, decisions, and rules are known as workflows.

Workflows exist everywhere, and the programming world is no exception. Take the case of a simple purchase-order application: the user enters the purchase amount for approval and the approval is routed to his immediate superior. If the amount exceeds a certain figure, approval from higher authority needs to be sought. And if the approval party is currently unavailable, the approval process is suspended until the superior returns and grants the approval.

In the traditional programming paradigm (such as using C# and Visual Basic), you would implement the series above imperatively. That is, you supply a series of instructions describing how to perform a task and how to transition from one task to another. However, a better approach is to declaratively describe the series of tasks and implement each task independently of others. And this is the role of the Windows Workflow Foundation (Windows WF). By declaratively describing the series of tasks, you can then easily change the application logic without affecting the individual components that made up a task. For example, you may have a loan approval system that involves several approving parties. Using Windows WF, you can easily change the rules of the approval process without affecting the software components for each individual party.

Windows Workflow Foundation provides the framework for building workflow-enabled applications on Windows. The workflow can then be hosted in environments such as Windows, Windows Presentation Foundation (WPF), and ASP.NET applications.

What You Need
To try out Windows Workflow Foundation, you can download the Microsoft Windows Workflow Foundation Runtime Components Beta 2.2 and Visual Studio 2005 Extensions for Windows Workflow Foundation Beta 2.2 from: http://www.microsoft.com/downloads/details.aspx?familyid=5C080096-F3A0-4CE4-8830-1489D0215877&displaylang=en.

In this article, I will introduce you to the basics of Windows Workflow Foundation and how you can build workflow-enabled applications using Visual Studio 2005. As Windows Workflow Foundation is suited for large-scale projects, the samples described in this article do not necessarily conform to best practices. My primary aim is to get you started in Windows Workflow Foundation.

Understanding How Windows Workflow Foundation Works
The best way to understand how Windows WF works is to create a simple sequential Workflow console application. This application will calculate the factorial of a number entered by the user. You will prompt the user to enter a positive number and will repeat until a positive number is entered. You will then calculate the factorial of the entered number.

Using Visual Studio 2005, choose the Sequential Workflow Console Application template (it will be added to your project menu once you install the Visual Studio 2005 Extensions for Windows Workflow Foundation?I’m using Beta 2.2) and name it C:WorkflowConsoleApplication1 (see Figure 1).


Figure 1. Create a new Sequential Workflow Console Application project.
?
Figure 2. The workflow designer in Visual Studio 2005 is shown.

Once you’ve done that you’ll be automatically moved into the workflow designer (see Figure 2), where you will be able to graphically build your workflow.

For workflow applications, the Toolbox will display a list of workflow activities you can use for your project (see Figure 3).


Figure 3. The list of workflow activities appears in the Toolbox.
?
Figure 4. Insert the While activity into the workflow.

Let’s now drag and drop the While activity onto the workflow designer (see Figure 4).

The While activity works much like the while loop in programming languages such as C, C#, and Visual Basic; it will perform a specific action repeatedly as long as the condition is true.

Now, drag and drop the Code activity into the While activity (see Figure 5). The Code activity executes an associated set of code. This particular Code activity will be used to read the number entered by the user.

At the same time, drag and drop the IfElse activity after the While activity. The IfElse activity is used for decision making. In this case there will be two branches?the left branch will be executed if the number entered is zero, else it will take the right branch.

Lastly, drag and drop two Code activities into the left and right branches of the IfElse activity (see Figure 5).

Figure 5. The partially completed workflow has two Code activities, one for each branch in the decision. This video-only movie will walk you through the creation of the two branches. Click the Play button twice to begin the movie.

The red exclamation icons in Figure 5 show that there are properties that you need to set before you can use the workflow. You can move your mouse over them to see the exact properties to set. You will come to that in a while.

The workflow is represented visually in the Workflow designer. To switch to its code-behind, click the workflow name (Workflow1.vb in this case) in Solution Explorer and click the View Code button (see Figure 6).


Figure 6. View the code behind of a workflow in order to declare the member variables.
?
Figure 7. You can view the error for the while activity to see what conditions are missing.

Declare the following member variables:

Public class Workflow1    Inherits SequentialWorkflowActivity    Private num As Integer    Private firstTime As Boolean = True

The variable num will be used to store the number entered by the user. The firstTime variable is used to allow the While activity to enter the loop for the first time.

Back to the designer, click on the red exclamation icon displayed next to the While activity (see Figure 7). You can see that the Condition property is not set. This is where you set the condition for the looping of the while loop. You can go directly to the properties window by clicking on the “Property ‘Condition’ is not set” label.

In the Properties window, set the Condition property to Code Condition and then expand the property by clicking on the “+” icon (see Figure 8). Set the condition name to WhileLoopCondition. In this case, this condition name refers to the subroutine that will evaluate the condition for the loop. Alternatively, you can also select “Declarative Rule Condition” and then declaratively set the condition.


Figure 8. Set the Condition property of the While activity.
?
Figure 9. Set the Condition property for the IfElse branch as shown.

Code the following for the WhileLoopCondition() subroutine:

    Private Sub WhileLoopCondition( _       ByVal sender As System.Object, _       ByVal e As System.Workflow.Activities.ConditionalEventArgs)        If firstTime Then            e.Result = True            firstTime = False        Else            If num < 0 Then                e.Result = True            Else                e.Result = False            End If        End If    End Sub

The above subroutine will determine if the While activity should continue executing. In this case, it will execute the loop if this is the first time the loop is executed or if the number entered is negative.

For the codeActivity1 code activity (refer to Figure 5), set the Name property to ReadNumber and the ExecuteCode property to readNum. In the implementation for the readNum() subroutine, code the following:

    Private Sub readNum( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs)        Console.WriteLine("Please enter a positive number")        num = CInt(Console.ReadLine())    End Sub

For the ifElseBranchActivity1 activity, set the properties as shown in Figure 9.

Code the Zero() subroutine as follows:

    Private Sub Zero( _       ByVal sender As System.Object, _       ByVal e As System.Workflow.Activities.ConditionalEventArgs)        If (num = 0) Then            e.Result = True        Else            e.Result = False        End If    End Sub

In the codeActivity2 control, set the Name property to ZeroFactorial and the ExecuteCode property to PrintResultForZero. In the implementation for the PrintResultForZero() subroutine, code the following:

    Private Sub PrintResultForZero( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs)        Console.WriteLine("0! is 1")    End Sub

Note that there is no need to set the Condition property of the right IfElse activity branch (see Figure 10). This is because if the left branch evaluates to False, the right branch will be executed automatically (and vice versa).


Figure 10. You only need to set the condition for one of the branches in the IfElse activity.
?
Figure 11. The completed workflow is shown.

In the codeActivity3 control, set the Name property to NonZeroFactorial and the ExecuteCode property to PrintResult. In the implementation for the PrintResult() subroutine, code the following:

    Private Sub PrintResult( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs)        Dim fac As Integer = 1        For i As Integer = 1 To num            fac *= i        Next        Console.WriteLine(num & "! is " & fac)    End Sub

The workflow should now look like Figure 11.

In Solution Explorer, you will also notice the Module1.vb file, which contains the main console application:

Module Module1    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()                Console.ReadLine()            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 ClassEnd Module
Figure 12. The workflow application at work: Negative numbers result in a new prompt. Entering a positive number provides the factorial.

Tip: Insert the Console.Readline() code (shown in bold above) so that the application will not immediately close after it is completed.

This console application basically creates an instance of the workflow using the WorkflowRuntime class and then starts the workflow. Two events are monitored here: WorkflowCompleted and WorkflowTerminated. By default, workflows are started asynchronously by the Windows Workflow runtime engine; thus, to ensure that the console application does not close before the workflow has finished executing, you can utilize synchronizing threading objects such as the AutoResetEvent.

Press F5 to debug the application. Figure 12 shows the flow of the application. You will repeatedly be prompted to enter a number until a positive number is entered. If you enter a 0, the result will be a 1.

Using a Windows Application as a Workflow Host
In the previous section you saw how to create a sequential workflow console application. Using the sample application, you saw how the workflow interacts directly with the user through the console. In that sample, the console application is the workflow host as it essentially hosts the workflow and executes it.

A much more realistic approach would be to use a Windows application (or ASP.NET Web application) as a workflow host, with the workflow working in the background. In this section, you will learn how to use a Windows application as a workflow host and how it interacts with the workflow.

The application you will build in this section is a simple interest rate comparator. Suppose you have some money to deposit in the bank. Some banks offer different interest rates for different currencies, and hence you want to maximize your returns by investing your money in the currency that offers the highest returns. Here are the assumptions:

  • The principle is in Singapore Dollars (SGD).
  • The interest rate for US Dollars (USD) is 5% p.a. for amounts less than or equal to US$10,000, 7% for amounts greater than US$10,000.
  • The interest rate for Chinese Yuan (CNY) is 4% p.a. for amounts less than or equal to ?10,000, 8% for amounts greater than ?10,000.
  • US$1 = SGD$1.59 and ?1 = SGD$0.20.

The above interest rates and exchange rates are hypothetical and are purely used for illustration purposes only.

Creating the Workflow Library
First, create a new Sequential Workflow Library application using Visual Studio 2005 (see Figure 13).


Figure 13. Create a new Sequential Workflow Library application.
?
Figure 14. The completed workflow is shown.

In the Workflow Designer, drag and drop the following activities:

  • Parallel
  • Code

The Parallel activity allows you to perform two different activities in parallel.

Set the Name property of the first Code control to USDInterests and set its ExecuteCode property to CalculateUSDInterests.

Set the Name property of the second Code control to CNYInterests and set its ExecuteCode property to CalculateCNYInterests.

The workflow should now look like Figure 14.

In the code-view of Workflow1.vb, enter the code that follows in boldface type:

Public class Workflow1    Inherits SequentialWorkflowActivity    Private _amount As Single    Private _USDInterestsAmount As Single    Private _CNYInterestsAmount As Single    Const SGDTOUSD As Single = 1.59    Const SGDTOCNY As Single = 0.2    Public ReadOnly Property USDInterestsAmount() As Single        Get            Return _USDInterestsAmount * SGDTOUSD        End Get    End Property    Public ReadOnly Property CNYInterestsAmount() As Single        Get            Return _CNYInterestsAmount * SGDTOCNY        End Get    End Property    Public WriteOnly Property Amount() As Single        Set(ByVal value As Single)            Me._amount = value        End Set    End Property    Public Sub New()        MyBase.New()        InitializeComponent()    End Sub    Private Sub CalculateUSDInterests( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs)        Dim USDEqv As Single = _amount / SGDTOUSD        If USDEqv <= 10000 Then            _USDInterestsAmount = CSng(USDEqv * 0.05)        ElseIf USDEqv > 10000 Then            _USDInterestsAmount = CSng(USDEqv * 0.07)        End If    End Sub    Private Sub CalculateCNYInterests( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs)        Dim CNYEqv As Single = _amount / SGDTOCNY        If CNYEqv <= 10000 Then            _CNYInterestsAmount = CSng(CNYEqv * 0.04)        ElseIf CNYEqv > 10000 Then            _CNYInterestsAmount = CSng(CNYEqv * 0.08)        End If    End SubEnd Class

Essentially, you are doing the following:

  • Declaring three private member variables to store the principle amount (_amount), the interest amount for USD (_USDInterestsAmount), and the interest amount for CNY (_CNYInterestsAmount).
  • Defining two constants (SGDTOUSD and SGDTOCNY) containing the exchange rate of USD and CNY. For simplicity I am hard-coding the exchange here. In real-life, you can get the rate from a Web service.
  • Defining two read-only properties?USDInterestsAmount and CNYInterestsAmount?to allow the workflow host to access the calculated interest rates.
  • Defining a write-only property (Amount) to let the workflow host set the principle amount.
  • Defining the two subroutines (CalculateUSDInterests() and CalculateCNYInterests()) for the ExecuteCode property of the two Code activites. Here, you will calculate the interests for USD and CNY deposits.

As you can see, the workflow library interacts with host primarily through properties exposed by the workflow library.

Creating the Workflow Host
With the workflow library created, let's create a Windows application to act as the host for the workflow. In Solution Explorer, right-click on the solution name and add a new Windows Application project. Name the project C:WorkFlowWindowsApplication.

Add the references to the following DLLs (see also Figure 15):

  • System.Workflow.Activities
  • System.Workflow.ComponentModel
  • System.Workflow.Runtime

  • Figure 15. Add the references to the three WF DLLs.
    ?
    Figure 16. Populate the Form1 with Label, TextBox, and Button controls as shown.

    In the default Form1, populate it with the following controls (see also Figure 16):

    • Label controls
    • TextBox controls
    • Button control

    In the code-behind of Form1, import the following namespaces:

    Imports System.Workflow.RuntimeImports System.Threading

    Declare the following member variable:

    Public Class Form1    Private WaitHandle As AutoResetEvent

    In the event handler for the Calculate button, you will create an instance of the WorkflowRuntime class. The WorkflowRuntime class exposes functionality required by a host application as well as services to configure and control the workflow runtime engine. It also receives notification of changes to both the workflow runtime engine and any of its workflow instances. Here, you will add two event handlers to monitor the completion and termination events of the workflow: WorkflowCompleted and WorkflowTerminated.

    The principle amount used for calculating interest amounts is used to create an instance of the WorkflowInstance object, together with the workflow library (WorkflowLibrary1.Workflow1) you created earlier. You need to pass in the Amount property through the use of the Dictionary object:

        Private Sub btnCalculate_Click( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs) Handles btnCalculate.Click        WaitHandle = New AutoResetEvent(False)        '---Create the WorkflowRuntime---        Using workflowRuntime As New WorkflowRuntime()            '---monitor workflow events---            AddHandler workflowRuntime.WorkflowCompleted, _                     AddressOf OnWorkflowCompleted            AddHandler workflowRuntime.WorkflowTerminated, _               AddressOf OnWorkflowTerminated           '---prepare the parameters to be passed into the workflow---            Dim parameters As Dictionary(Of String, Object) = _               New Dictionary(Of String, Object)()            parameters.Add("Amount", CSng(txtAmount.Text))            '---Create and start an instance of the workflow---            Dim workflowInstance As WorkflowInstance            workflowInstance = _               workflowRuntime.CreateWorkflow( _                  GetType(WorkflowLibrary1.Workflow1), parameters)            workflowInstance.Start()            '---wait for the event to be signaled---            WaitHandle.WaitOne()        End Using    End Sub

    When the workflow has completed execution, the OnWorkflowCompleted() event will be fired. Here, you will obtain the calculated interest rates exposed by the workflow (as read-only properties) through the OutputParameters collection in the WorkflowCompletedEventArgs object. As this event runs in a separate thread, you need to take special care when updating the TextBox controls by using delegates.

        '---fired when the workflow instance is completed---    Private Sub OnWorkflowCompleted( _       ByVal sender As Object, _       ByVal e As WorkflowCompletedEventArgs)        WaitHandle.Set()        '---update the txtCNYInterests TextBox control---        If Me.txtCNYInterests.InvokeRequired Then            Me.txtCNYInterests.Invoke( _               New EventHandler(Of WorkflowCompletedEventArgs) _               (AddressOf Me.OnWorkflowCompleted), _               New Object() {sender, e})        Else            Me.txtCNYInterests.Text = _               e.OutputParameters("CNYInterestsAmount").ToString()        End If        '---update the txtUSDInterests TextBox control---        If Me.txtUSDInterests.InvokeRequired Then            Me.txtUSDInterests.Invoke( _               New EventHandler(Of WorkflowCompletedEventArgs) _               (AddressOf Me.OnWorkflowCompleted), _               New Object() {sender, e})        Else            Me.txtUSDInterests.Text = _               e.OutputParameters("USDInterestsAmount").ToString()        End If    End Sub
    Figure 17. When you test the application you should be able to see the interest rate for each currency.

    Finally, service the OnWorkflowTerminated event, which will be fired when the workflow is terminated.

        Private Sub OnWorkflowTerminated( _       ByVal sender As Object, _       ByVal e As WorkflowTerminatedEventArgs)        Console.WriteLine(e.Exception.Message)        WaitHandle.Set()    End Sub

    That's it! Right click on WorkflowWindowsApplication in Solution Explorer and select Set as Startup Project. Then press F5 to debug the application. Enter an amount to calculate the interest for each currency and click the Calculate button. You will see the interest rate for each currency (see Figure 17).

    You should also observe that you can change the logic of the application by modifying the code in the workflow. For large-scale systems, this is a great benefit as you can now modify the workflow of the system without worrying about the front end.

    In this article, you have seen the basics of Windows Workflow Foundation and how you can create workflow-enabled applications using Visual Studio 2005. In subsequent articles, I will dive into other detailed features of Windows WF.

    Share the Post:
    Share on facebook
    Share on twitter
    Share on linkedin

    Overview

    Recent Articles: