Getting Started with Microsoft Windows Workflow Foundation: A Developer Walkthrough

 

Dino Esposito
Solid Quality Learning

Updated February 2006

Applies to:
   Microsoft Windows Workflow Foundation
   Microsoft Windows Vista

Summary: Introduces the technologies and features of Microsoft Windows Workflow Foundation that will be of interest to developers in need of creating workflow-driven applications for the Microsoft .NET platform. (39 printed pages)

Note This article is written for beta 2 of Windows Workflow Foundation. Be aware that there are likely to be changes before the technology's final release.

Contents

A Headstart on Adding Workflow Support to the Windows Platform
Creating a First Workflow
Receiving and Consuming Data
The Workflow Runtime
Workflows and Activities
Developing a Custom Activity
Planning a More Realistic Workflow
Conclusion

A Headstart on Adding Workflow Support to the Windows Platform

Microsoft Windows Workflow Foundation (WF) is an extensible framework for developing workflow solutions on the Windows platform. As part of Microsoft .NET Framework 3.0, Windows Workflow Foundation provides both an API and tools for the development and execution of workflow-based applications. Windows Workflow Foundation provides a single, unified model to create end-to-end solutions that span categories of applications, including human workflow and system workflow.

Windows Workflow Foundation is a broad and general-purpose workflow framework, designed for extensibility from the ground up and at every level. Windows Workflow Foundation -based solutions are made of interconnected components backed up by Microsoft .NET code and running in a host application. Just as you create your Web pages visually in a tailor-made environment, you compose the steps of your specific workflow in a visual designer, and add code behind workflow components to implement rules and define the business process.

Windows Workflow Foundation provides a workflow engine, a .NET-managed API, runtime services, and a visual designer and debugger integrated with Microsoft Visual Studio 2005. You can use Windows Workflow Foundation to build and execute workflows that span both the client and server, and that can be executed within all types of .NET applications.

This article provides a smooth introduction to Windows Workflow Foundation and shows how it works, through a few incremental examples.

A workflow is a model of a human or system process that is defined as a map of activities. An activity is a step in a workflow, and is the unit of execution, re-use, and composition for a workflow. The map of activities expresses rules, actions, states, and their relations. Designed by laying out activities, a Windows Workflow Foundation workflow is then compiled to a .NET assembly, and is executed on the workflow runtime and the Common Language Runtime (CLR).

Creating a First Workflow

Windows Workflow Foundation mainly consists of a .NET-powered runtime environment that processes special objects designed and implemented within a Visual Studio designer. The Microsoft .NET Framework 2.0 is required in order to support Windows Workflow Foundation. A separate installer package adds Windows Workflow Foundation designer and project template support for Visual Studio 2005. Once installed, a brand new node is added to the standard list of projects in Visual Studio 2005, as shown in Figure 1.

Aa480214.wwfgetstart01(en-us,MSDN.10).gif

Figure 1. Workflow project templates in Visual Studio 2005

You can choose among various options, each identifying a particular type of workflow application. Table 1 presents a list of workflow project templates.

Table 1. Workflow Project Types in Visual Studio 2005

Type Description
Sequential Workflow Console Application Creates a project for building workflows that contains a default sequential workflow and a console test host application.
Sequential Workflow Library Creates a project for building a sequential workflow as a library.
Workflow Activity Library Creates a project for creating a library of activities that can later be re-used as building blocks in workflow applications.
State Machine Console Application Creates a project for building a state machine workflow and a console host application.
State Machine Workflow Library Creates a project for building a state machine workflow as a library.
Empty Workflow Creates an empty project that can include workflows and activities.

Windows Workflow Foundation supports two fundamental workflow styles out-of-the-box: sequential workflow and state machine workflow.

A sequential workflow is ideal for operations expressed by a pipeline of steps that execute one after the next until the last activity completes. Sequential workflows, however, are not purely sequential in their execution. They can still receive external events or start parallel tasks, in which case the exact sequence of execution can vary somewhat.

A state machine workflow is made up of a set of states, transitions, and actions. One state is denoted as a start state, and then, based on an event, a transition can be made to another state. The state machine workflow can have a final state that determines the end of the workflow.

Let's assume you select and create a new Sequential Workflow Console Application project. The Visual Studio 2005 Solution Explorer will contain two files—workflow1.cs and, initially hidden from view, workflow1.designer.cs. These two files represent the workflow being created. A Windows Workflow Foundation workflow consists of the workflow model file and a code file class. The workflow1.cs class is the code file class where you can write your own workflow business logic. The workflow1.designer.cs class represents the description of the activities map. This file is managed automatically by Visual Studio 2005 in much the same way it happens with forms in a Microsoft Windows Forms project. As you add activities to the workflow, Visual Studio 2005 updates the designer class with Microsoft C# code that programmatically builds the map of activities. To continue with the Windows Forms analogy, a workflow is like a form, whereas activities are like controls.

You can choose another form of persistence for the activity layout—the XML workflow markup format. To try this approach, you delete the workflow1.cs file from the project and add a new workflow item as shown in Figure 2.

Aa480214.wwfgetstart02(en-us,MSDN.10).gif

Figure 2. Adding a sequential workflow item with code separation

Now your project contains two files—workflow1.xoml and workflow1.xoml.cs. The former contains the XML workflow markup that represents the workflow model; the latter is a code file class, and contains source code and event handlers for the workflow. If you double-click the .xoml file, you see the visual workflow designer in action (see Figure 3).

There is no runtime implication of choosing markup or code for the serialization of the workflow model—they are equivalent once the workflow is compiled into an assembly.

Workflow applications are a mixture of activities that do work (for example, send or receive data), and composite activities, such as IfElse and While, that manage the execution of a set of child activities. A workflow can implement sophisticated end-to-end scenarios such as document review, PO approval, IT user management, exchange of information between partners, any sort of wizard, or line-of-business applications.

Figure 3 shows a sample, and extremely simple, workflow that contains just one activity—the codeActivity1 block.

Aa480214.wwfgetstart03(en-us,MSDN.10).gif

Figure 3. The Visual Studio 2005 workflow designer

The Code block corresponds to an instance of the CodeActivity class, and represents an activity within the workflow whose behavior is expressed with user-defined code. The back-end code is entered through Visual Studio 2005 by just double-clicking the selected element in the designer—the familiar programming style of ASP.NET applications and other Visual Studio 2005 projects.

When you double-click the activity, the code file opens up, offering a stub for the code handler.

private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
    // Some code here
}

Any statements you type in the code handler will execute as the workflow runtime gets to process the specified activity block on its way through the workflow. Let's just output a welcome message.

private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
    CodeActivity c = (CodeActivity)sender;
    Console.WriteLine("Hello, from '{0}'.\nI'm an instance of the {1} class.",
       c.Name, c.ToString());
}

Aside from the visual layout, the workflow consists of the following code saved in the workflow1.xoml.cs file.

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;

namespace HelloWorldWorkflow
{
    public partial class Workflow1 : SequentialWorkflowActivity
    {
        private void codeActivity1_ExecuteCode(object sender, EventArgs e)
        {
            CodeActivity c = (CodeActivity)sender;
            Console.WriteLine("Hello, from '{0}'.\nI'm an instance of the {1} class.",
               c.Name, c.ToString());
        }
    }
}

The partial attribute refers to partial classes, a new concept in the .NET Framework 2.0. A partial class is a class whose definition may spread over distinct source files. Each source file appears to contain an ordinary class definition from beginning to end, except that it is partial and doesn't exhaust the logic required by the class. The compiler will merge partial class definitions into a complete definition of the class that can be compiled. Partial classes have nothing to do with object-orientation; they are a source-level and assembly-limited way to extend the behavior of a class within a project. In the .NET Framework 2.0, partial classes are the means used to prevent Visual Studio 2005 from injecting auto-generated code inside code files. Any binding code that is missing in the original class will be added by the runtime, through the addition of a partial class.

A workflow can only be executed by the Windows Workflow Foundation workflow runtime, and the workflow runtime requires an external application to host it, according to a few rules. For testing purposes, Visual Studio 2005 also adds a program.cs file to the project. The file is a simple console application, as follows.

    class Program
    {
        static void Main(string[] args)
        {
            WorkflowRuntime workflowRuntime = new WorkflowRuntime();
            
            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(HelloWorldWorkflow.Workflow1));
            instance.Start();

            waitHandle.WaitOne();

            // A bit of feedback to the user    
            Console.WriteLine("");
            Console.WriteLine("");
            Console.WriteLine("==========================");
            Console.WriteLine("Press any key to exit.");
            Console.WriteLine("==========================");
            Console.ReadLine();
        }
    }

For simplicity, Visual Studio 2005 hard-codes the name of the workflow class in the console application, as you can see in the bold lines in the preceding code. To prevent the console application from quitting immediately after completion, you might want to add a Console.ReadLine call at the end of the Main method. At this point, you're all set to build and test the workflow: press F5 and go. If all goes through fine, you should see the output shown in Figure 4.

Aa480214.wwfgetstart04(en-us,MSDN.10).gif

Figure 4. The sample workflow executed by a console host application

Debugging the workflow application is easy too. All that you have to do, in fact, is place a breakpoint. You can place a breakpoint anywhere in the workflow's code file class (as you usually do with C# code) or (—and this is really interesting)—directly in the designer's view. You select the activity where you want the debugger to kick in, and press F9 to set the breakpoint, as shown in Figure 5.

Aa480214.wwfgetstart05(en-us,MSDN.10).gif

Figure 5. A breakpoint placed in the designer's view of the workflow

As soon as the code flow reaches the activity with the breakpoint set, Visual Studio 2005 yields to the workflow debugger (see Figure 6). From there onward, you can step into the code and through activities in the visual designer by pressing F11, as expected.

Aa480214.wwfgetstart06(en-us,MSDN.10).gif

Figure 6. The workflow application under a debugging session

Receiving and Consuming Data

Let's proceed and modify the workflow to make it receive and consume data when it is instantiated. There are two general approaches for receiving data into a workflow when it is instantiated;—parameters and events. Parameters are represented by properties that you create in code and can be used by activities and the workflow host application. With events, you need to create and add a custom activity that acts as an external source that kicks in at some point in the workflow model and passes in some data. We'll show the event-based approach later in the article; for now let's focus on parameters.

Adding two parameters to a workflow is as simple as creating two corresponding properties in the code behind file for the workflow as shown below:

public partial class Workflow1 : SequentialWorkflowActivity
{
    private string firstName;

    public string FirstName
    {
        get { return firstName; }
        set { firstName = value; }
    }

    private string lastName;

    public string LastName
    {
        get { return lastName; }
        set { lastName = value; }
    }
}

Using public properties is simply a good programming practice that keeps your code neater and cleaner. In no way is it a requirement for consuming parameter data. If you wrap parameters with public properties, feel free to choose the names of properties as you like. Bear in mind, though, that parameter names are case-sensitive in C#.

Who actually provides input data through those parameters? In charge of this task is the host application. It is important to note, though, that the host sets any parameters at initialization time, when the workflow is loaded for execution into a runtime container. To expand a bit on this point, let's write a sample host application based on Windows Forms. The sample application will provide a couple of text boxes for users to enter first and last names (see Figure 7), and pass them on to the workflow code handlers. To consume the parameters, let's rewrite the code handler as follows.

private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
    MessageBox.Show("Welcome, " + FirstName + " " + LastName);
}

The key thing going on in the sample Windows Forms host application is the click handler attached to the Start Workflow button.

Aa480214.wwfgetstart07(en-us,MSDN.10).gif

Figure 7. A workflow host Windows Forms application

The full source code of the form's code-behind class is as follows.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Workflow.ComponentModel;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;

namespace WinFormTestHost
{
    public partial class Form1 : Form
    {
        private WorkflowRuntime wr;

        public Form1()
        {
            InitializeComponent();
        }

        private void btnStartWorkflow_Click(object sender, EventArgs e)
        {
            if (wr == null)
            {
                wr = new WorkflowRuntime();
                wr.StartRuntime();
            }

            Dictionary<string, object> parameters = 
                new Dictionary<string, object>();
            parameters.Add("FirstName", txtFirstName.Text);
            parameters.Add("LastName", txtLastName.Text);

            WorkflowInstance instance = wr.CreateWorkflow(typeof(HelloWorldWorkflow.Workflow1), parameters);
            instance.Start();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (wr != null)
            {
                if (wr.IsStarted)
                {
                    wr.StopRuntime();
                }
            }
        }
    }
}

To populate the parameters collection, you need to use a generics-based dictionary made of strings and objects. The name of the item is a string, whereas the contained value is configured as an object. You add as many items to the dictionary as there are static parameters in the workflow model; in this case, FirstName and LastName. The two parameters take the contents typed into the UI text boxes.

Finally, the workflow executes when you create an instance of the specified model. The StartWorkflow method on the runtime object has several overloads. The version used in the code accepts the workflow type, the collection of input parameters, and a system-generated globally unique identifier (GUID).

You only need one instance of the workflow runtime for each process, and you are not allowed to have more than one instance for each AppDomain. The best thing you can do here is to create the required instance directly in the form's constructor. The same runtime object can take care of a variety of workflow instances. The runtime distinguishes instances based on their GUID, and receives private data for each specific instance.

Aa480214.wwfgetstart08(en-us,MSDN.10).gif

Figure 8. The parameterized workflow in action, hosted by a Windows Forms application

For purely educational purposes, let's take a quick look at both designer's and workflow markup code at this stage of development. The following is the workflow1.designer.cs source file.

public sealed partial class Workflow1
{
   #region Designer generated code
   
   /// <summary> 
   /// Required method for Designer support - do not modify 
   /// the contents of this method with the code editor.
   /// </summary>
   private void InitializeComponent()
   {
        this.CanModifyActivities = true;
        this.codeActivity1 = new System.Workflow.Activities.CodeActivity();
        // 
        // codeActivity1
        // 
        this.codeActivity1.Name = "codeActivity1";
        this.codeActivity1.ExecuteCode += new System.EventHandler(this.codeActivity1_ExecuteCode);
        // 
        // Workflow1
        // 
        this.Activities.Add(this.codeActivity1);
        this.Name = "Workflow1";
        this.CanModifyActivities = false;

   }

   #endregion

    private CodeActivity codeActivity1;
}

The following is the corresponding workflow markup content.

<SequentialWorkflowActivity x:Class="HelloWorldWorkflow.Workflow1" x:Name="Workflow1" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/workflow">
    <CodeActivity x:Name="codeActivity1" ExecuteCode="codeActivity1_ExecuteCode" />
</SequentialWorkflowActivity>

As well as using properties as parameters, we can also use dependency properties. We will have a look at these when we come to creating custom activities.

The Workflow Runtime

The host interacts with Windows Workflow Foundation through the WorkflowRuntime class. Don't let the apparent simplicity of the sample host shown above fool you about such a key point. A host can be made responsible for a number of additional and critical aspects, such as the creation of one or more processes, and one or more AppDomains; marshaling of calls between AppDomains as needed; and setup of isolation mechanisms. A host might need to create multiple processes to take advantage of multiple CPUs in a machine for scalability reasons, or to run a large number of workflow instances on a farm of machines.

There are other things a host might do. For example, it might control the policies to apply when a workflow is subject to a long wait, listen for specific events and communicate them to a user or administrator, set timeouts and retries for each workflow, expose performance counters, and write log information for debugging and diagnostic purposes.

Hosts accomplish most of the additional tasks through predefined and custom services registered with the container at startup. The sample host simply does none of this, and is limited to starting workflow instances. This is acceptable in many common situations.

Workflows and Activities

Let's step back and examine the Visual Studio 2005 toolbox when a workflow project is active. The toolbox, shown in Figure 9, lists the activities you can use to design the sequence of steps, and their interrelation, to form the workflow model.

Aa480214.wwfgetstart09(en-us,MSDN.10).gif

Figure 9. Building blocks of a Windows Workflow Foundation sequential workflow

Table 2 provides a brief description of most of the basic activities, as well some scenarios where their use would be helpful.

Table 2. Windows Workflow Foundation Building Blocks

Activity Description
Code Enables you to add Microsoft Visual Basic .NET or C# code to the workflow to perform custom actions. The code should not, however, block the workflow with a dependency on an external resource such as a Web service.
Compensate Enables you to call code to reverse, or compensate for, operations already performed by the workflow when an error occurs. Typically, you might want to send an e-mail message to a user who had previously been notified of the success of an operation that has now been canceled.
ConditionedActivityGroup (CAG) Enables your workflow to execute a set of child activities conditionally, based on criteria specific to each activity, until a completion condition is met for the CAG as a whole. Child activities are independent of each other and may be executed in parallel.
Delay Enables you to control the timing of the workflow, and build delays into it. You can provide a timeout on the Delay activity so that your workflow pauses before resuming execution.
EventDriven Represents a sequence of activities whose execution is triggered by an event. The first child activity must be able to wait for external events. Feasible first child activities are EventSink and Delay. Delay in this case is used as a timeout.
HandleExternalEvent Enables your workflow to receive data from a data exchange service registered with the WorkflowRuntime when the service raises the specified event.
FaultHandler Enables you to handle an exception of a type that you specify. The FaultHandler activity is a wrapper for other activities that will actually perform any needed work when the specified exception occurs. You can optionally specify a local variable to store the exception and make it available in the code-behind.
IfElse Enables your workflow to conditionally execute one of several alternative branches. You place a condition on each branch, and the first branch for which the condition is true is executed. You don't need to place a condition on the last branch, because it is treated as the "else" branch.
CallExternalMethod Enables your workflow to invoke a method on an interface to send messages from the workflow to a data exchange service registered with the WorkflowRuntime.
InvokeWebService Enables your workflow to invoke a Web service method. You specify the proxy class to use (using WSDL) and the name of the method you want to invoke. Both synchronous and asynchronous calls are supported.
InvokeWorkflow Enables your workflow to call or start another workflow, up to arbitrary depths. For example, a called workflow can call a third workflow, which can call a fourth, and so on. Recursive calls are not supported. The supported call model is fire-and-forget.
Listen Enables your workflow to wait for one of (potentially) several events, or to stop waiting after a specified timeout interval, and branch based on the results. You can add one or more event-driven activities to each branch. Only the first branch for which a condition is met is followed; none of the other branches run.
Parallel Enables your workflow to perform two or more operations independently of each other. The activity waits for both operations to terminate before continuing.
Policy Enables you to represent and evaluate a collection of rules. Each rule may have actions executed if it is evaluated as true.
Replicator Enables your workflow to create an arbitrary number of instances of a given activity, and execute them either sequentially or concurrently.
Sequence Enables you to coordinate the serial execution of a set of child activities. The sequence completes when the final child activity completes.
SetState Enables your state machine workflow to specify a transition to a new state.
State Represents a state in a state machine workflow.
StateInitialization Used within a State activity as a container for child activities that are executed when the state is transitioned to.
StateFinalization Used within a State activity as a container for child activities that are executed when leaving the state.
Suspend Suspends the operation of your workflow, to enable intervention in the event of some error condition. When a workflow instance is suspended, an error is logged. You can specify a message string to help the administrator diagnose what happened. All of the state information associated with the current instance is saved, and it is recovered if and when the administrator resumes execution.
Terminate Enables you to immediately end the operation of your workflow in the event of any abnormal situation. If called inside a Parallel activity, all branches are abruptly terminated, regardless of their current state. When a workflow is terminated, an error is logged, and a message helps the administrator figure out what happened.
Throw Enables you to throw an exception of the specified type. Using this activity is equivalent to a code handler throwing the exception in user code. The activity is a declarative way of throwing a .NET exception.
TransactionalScope A transactional scope is a block used to group activities. This activity is mainly used for transactional execution, compensation, and exception handling.
SynchronizationScope You can use this activity to ensure that any access to shared data within the activity will be properly serialized.
WebServiceInput Enables a workflow that is exposed as a Web service itself to receive a Web service request.
WebServiceOutput Enables a workflow that is exposed as a Web service itself to respond to a Web service request.
WebServiceFault This provides a model for the occurrence of a Web service fault, which is equivalent to throwing an exception in an ASMX Web service framework method.
While Enables your workflow to execute one or more activities while a condition is met. Prior to each iteration, the condition is evaluated. If true, all of the child activities execute; otherwise, the activity completes. You can specify either a declarative condition or a code condition.

Activities represent the declarative approach to workflow programming with Windows Workflow Foundation. Using activities, you compose your workflow model at design time and assign values to each activity's properties. The final result is saved as XML markup to a workflow markup file that has a .xoml extension if you opted for a workflow item with code separation. Otherwise, the model you compose is persisted in a designer-generated C# or Visual Basic .NET class file, as a sequence of calls to the workflow object model. The former approach is akin to ASP.NET pages, whereas the latter resembles what happens with Windows Forms applications.

Visual Studio 2005 hides most of the differences between the two approaches. You always design the workflow visually, and Visual Studio 2005 transparently persists your work to either of two distinct formats. If you opt for a code-only solution (no XOML and code separation), you have the possibility of tweaking the designer code to make it a bit more flexible. For example, you can make it read default values for parameters from a configuration file or a database. If you opt for workflow markup and code separation, you come up with a neat separation between the workflow's code and its model.

Can you modify the workflow model programmatically? At design time, you can do everything programmatically to a workflow that you can do in Visual Studio. At run time, changes to the collection of activities are also possible, and give you the ability to make changes to a running instance of a workflow. Workflow changes are motivated by business changes that were not known at design time, or by the need for business logic that modifies and then completes the business process. In any case, it should involve limited changes;—perfecting rather than redesigning.

Workflow changes apply to a single instance of the workflow in the context of an application. Future instances of the same workflow type won't be affected by changes. Changes to a workflow instance can be made from within that workflow instance itself, and also externally from your application code.

The Windows Workflow Foundation framework supports Web service interoperability, which includes the ability to expose a workflow as Web service to ASP.NET clients and to other workflows. Windows Workflow Foundation supports publishing a workflow as an ASP.NET Web service on a Web server or server farm running ASP.NET on Microsoft IIS 6.0.

The Windows Workflow Foundation framework activity set contains the WebServiceReceive and WebServiceResponse activities, which enable a workflow to be used as Web service endpoints.

To be exposed as a Web service, a workflow must include a WebServiceReceive activity to get incoming calls from clients. A shortcut menu command publishes the workflow as a Web service, as shown in Figure 10.

Aa480214.wwfgetstart10(en-us,MSDN.10).gif

Figure 10. Publishing a workflow as a Web service

Developing a Custom Activity

The key point of extensibility in Windows Workflow Foundation is the authoring of custom activities, since this lets you expand the set of building blocks that you can use to build workflow models.

Let's explore the internal architecture of an activity by developing a custom one to send e-mail messages. Windows Workflow Foundation provides a ready-made Visual Studio 2005 template for custom activities. It is named Workflow Activity Library. The template creates a C# file that you can rename at your leisure;—for example, SendMailActivity. An activity is a plain class that inherits from a parent class. You can derive your activity from any existing activity, be it one of the built-in activities, or one that you created yourself or bought from a third-party vendor. Obviously, the parent class adds a predefined behavior to the new component. To build an activity entirely from scratch, make it derive from Activity. The following code sample shows the skeleton of the new class.

public partial class SendMailActivity : System.Workflow.ComponentModel.Activity
{
    public SendMailActivity()
    {
        InitializeComponent();
    }

    protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
    {
        return base.Execute(executionContext);
    }
}

As you can guess, the Execute method is the heart of the component;—namely, the location where the core tasks of the component are accomplished.

Once developed, an activity is placed on the toolbox, ready for drag-and-drop operations onto new workflow applications. Although a list of properties is not a requirement, an activity without properties is hardly helpful. It is often useful to use dependency properties for custom activities rather than standard properties. Dependency properties allow us to bind their value to relevant data including other properties on other activities within workflows that use the custom activity. Let’s define our first dependency property for the SendMailActivity. You can use the snippet provided with Windows Workflow Foundation to easily insert dependency properties as shown in figure 11.

Aa480214.wwfgetstart11(en-us,MSDN.10).gif

Figure 11. Inserting a snippet

This will create a static instance of DependencyProperty and a wrapping member property that uses the DependencyProperty as a key for retrieving the runtime value of the property. It is like each instance of an activity has a hash table of property values that can be accessed using static DependencyProperty objects as keys. You can tab through the snippet and customize the new property to represent the sender of an email as shown in figure 12 below.

Aa480214.wwfgetstart12(en-us,MSDN.10).gif

Figure 12. Customizing the "From" parameter

Because the runtime value of the property is controlled by the base class of the custom activity, it can be declaratively bound to other properties after the custom activity has been designed and built. For now, create dependency properties for To, Subject, Body and Host, so that users can completely configure the e-mail messages being sent.

The final step is fleshing out the Execute method a bit, to instruct it to send an e-mail message when the activity is executed.

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
    MailAddress toAddress = new MailAddress(To);
    MailAddress fromAddress = new MailAddress(From);

    MailAddressCollection addresses = new MailAddressCollection();
    addresses.Add(toAddress);

    MailMessage msg = new MailMessage(fromAddress, toAddress);
    msg.Subject = Subject;
    msg.Body = Body;

    SmtpClient mail = new SmtpClient(Host);
    mail.Send(msg);
    return ActivityExecutionStatus.Closed;
}

If you develop the activity project inside a workflow solution, the workflow document will automatically find the new activity listed in the toolbox, as shown in Figure 13. Otherwise, you have to add it by right-clicking the toolbox.

Aa480214.wwfgetstart13(en-us,MSDN.10).gif

Figure 13. The SendMail activity shows off the toolbox

Now that the SendMail activity is available to our workflows, you can try out some of the abilities of dependency properties. For example, if you add a SendMail activity to a workflow and select <Promote…> for its Body property, you will be able to pass the body of the email to the workflow’s new parameter just as you did with FirstName and LastName earlier. This is but one example of how dependency properties can be used to centralize workflow behavior.

Figure 14 demonstrates that the SendMail activity actually works.

Aa480214.wwfgetstart14(en-us,MSDN.10).gif

Figure 14. The SendMail activity in action

Planning a More Realistic Workflow

Let's see how to combine some of the activities listed in Table 2 to solve a more realistic task. Imagine a business application where an order might go through several states before it is completed. In a typical scenario, there are rules to indicate what events can occur on an order, depending on the current state. For example, an order that is open can be processed or updated, but neither canceled nor shipped.

As an event occurs, a state machine workflow transitions the state for an order. For example, when an order is open and the BeingProcessed event occurs, the state machine workflow will transition the order to the proper state. Figure 15 shows the diagram for a sample order state machine workflow.

Aa480214.wwfgetstart15(en-us,MSDN.10).gif

Figure 15. A sample schema for a state machine managing orders

Let's start by creating a state machine workflow. You'll use the State activity to model possible states for an order. Then, you'll specify the events that can occur from each state by using EventDriven activities. External events caught through a custom service will transition the state of the order. To perform the transition, you'll use the SetState activity. After crafting the workflow, you'll put it through its paces using a Windows Forms host application.

A workflow communicates with the outside world through a service established specifically for that purpose. The service raises events that event-driven activities inside the workflow will hook up. Likewise, the service exposes public methods for the workflow to call and send data to the host. Methods and events are defined in an interface. This interface is also known as the data exchange service. You need this service whenever your workflow interacts with external components, both in input and output.

A data exchange service is a regular .NET class library that, at the very minimum, includes an interface definition and a class that implements that interface. The interface is tailor-made for the tasks you want to represent. In this case, a state machine representing the lifecycle of an order, the interface consists of five events.

[ExternalDataExchange]
public interface IOrderService
{
    event EventHandler<OrderEventArgs> OrderCreated;
    event EventHandler<OrderEventArgs> OrderShipped;
    event EventHandler<OrderEventArgs> OrderUpdated;
    event EventHandler<OrderEventArgs> OrderProcessed;
    event EventHandler<OrderEventArgs> OrderCanceled;
}

The [ExternalDataExchange] attribute marks IOrderService as a data exchange service interface so that the workflow runtime knows it will be used to exchange data with the workflow instance. In this case, the host will be sending data to the workflow instance by raising events to a bunch of EventDriven activities. Should it be required, methods in the IOrderService interface can be called from within the workflow instance through the CallExternalMethod activity.

The event declaration in the interface uses generics, which are a hot new feature in the .NET Framework 2.0. The EventHandler class is a delegate that represents the prototype of the function used to handle the event. In the .NET Framework 1.x, EventHandler was defined as follows.

void EventHandler(object sender, EventArgs e)

To make the event pass a custom data structure such as OrderEventArgs, you have to create a new delegate and use it in place of EventHandler. An example follows.

delegate void OrderEventHandler(object sender, OrderEventArgs e)

This pattern still works in .NET Framework 2.0. The advent of generics in the .NET Framework 2.0, though, makes it possible to obtain the same result without explicitly defining (and instantiating) a new delegate class. You use the generic version of the EventHandler<T> delegate, where the type of the event data is a parameter.

Events pass clients data of type OrderEventArgs, a custom class that derives from the Windows Workflow Foundation ExternalDataEventArgs class, defined in the same assembly as follows.

[Serializable]
public class OrderEventArgs : ExternalDataEventArgs
{
    private string _orderId;

    public OrderEventArgs(Guid instanceId, string orderId) : base(instanceId)
    {
        _orderId = orderId;
    }

    public string OrderId
    {
       get { return _orderId; }
       set { _orderId = value; }
    }
}

As the next step, you define a class that implements the interface. The class will have as many public methods as there are events in the interface to raise.

public class OrderService : IOrderService
{
    public OrderService()
    {
    }

    public void RaiseOrderCreatedEvent(string orderId, Guid instanceId)
    {
        if (OrderCreated != null)
            OrderCreated(null, new OrderEventArgs(instanceId, orderId));
    }

    public void RaiseOrderShippedEvent(string orderId, Guid instanceId)
    {
        if (OrderShipped != null)
            OrderShipped(null, new OrderEventArgs(instanceId, orderId));
    }

    public void RaiseOrderUpdatedEvent(string orderId, Guid instanceId)
    {
        if (OrderUpdated != null)
            OrderUpdated(null, new OrderEventArgs(instanceId, orderId));
    }

    public void RaiseOrderProcessedEvent(string orderId, Guid instanceId)
    {
        if (OrderProcessed != null)
            OrderProcessed(null, new OrderEventArgs(instanceId, orderId));
    }
        
    public void RaiseOrderCanceledEvent(string orderId, Guid instanceId)
    {
        if (OrderCanceled != null)
            OrderCanceled(null, new OrderEventArgs(instanceId, orderId));
    }

    public event EventHandler<OrderEventArgs> OrderCreated;
    public event EventHandler<OrderEventArgs> OrderShipped;
    public event EventHandler<OrderEventArgs> OrderUpdated;
    public event EventHandler<OrderEventArgs> OrderProcessed;
    public event EventHandler<OrderEventArgs> OrderCanceled;
}

You now compile the assembly with the order service and switch back to the state machine workflow project. In the workflow project, as a first thing, you add a reference to the newly created assembly. Next, you add four State activities, and name them as follows: WaitingForOrderState, OrderOpenState, OrderProcessedState, OrderCompletedState.

Table 3 represents the state diagram for the workflow. Each state has a few events capable of causing a transition to another state.

Table 3. A Sample State Machine for Orders

State Supported events Transition to
WaitingForOrderState OrderCreated OrderOpenState
OrderOpenState OrderUpdated OrderOpenState
  OrderProcessed OrderProcessedState
OrderProcessedState OrderUpdated OrderOpenState
  OrderCanceled Terminate activity
  OrderShipped OrderCompletedState
OrderCompletedState    

To implement the diagram, you add to each State activity as many EventDriven blocks as there are supported events in the table. For example, the State activity named WaitingForOrderState will contain a single EventDriven activity named, for example, OrderCreatedEvent (the name is arbitrary). As shown in Figure 16, the EventDriven activity embeds an HandleExternalEvent activity and a SetState activity to capture the external event and transition to a new state.

Aa480214.wwfgetstart16(en-us,MSDN.10).gif

Figure 16. An interior view of the OrderCreatedEvent EventDriven activity

On the Properties pane of the HandleExternalEvent activity, you select the data exchange service of choice;—the IOrderService interface, in this case;—and the event name to subscribe to. If you click the InterfaceType entry on the HandleExternalEvent activity's Properties pane, Visual Studio 2005 provides the list of data exchange services available to the project. Once the service is selected, the EventName property reflects the list of events exposed by the service. You select the event of interest and go. For the OrderCreatedEvent activity, you select the OrderCreated event.

The SetState activity transitions the machine to the new state indicated by its TargetStateName property. The SetState activity in Figure 16 is set to OrderOpenState.

You repeat the above operation for all the states and event sinks in Table 3, and at the end, your workflow should look like Figure 17.

Aa480214.wwfgetstart17(en-us,MSDN.10).gif

Figure 17. The finalized order state machine

The final step involves building a Windows Forms application to test the workflow. The user interface includes a list view to track all the pending orders, and a text box and button to create new orders. Other buttons will be used to update, process, and terminate the order.

The state machine workflow is initialized in the Form_Load event. The initialization of a state machine workflow is a little more complex than a sequential workflow, especially if you want to be able to track state changes. The following code sample shows how to initialize the workflow runtime.

private void StartWorkflowRuntime()
{
   // Create a new Workflow Runtime for this application
   _runtime = new WorkflowRuntime();

   // Register event handlers for the WorkflowRuntime object
   _runtime.WorkflowTerminated += new 
          EventHandler<WorkflowTerminatedEventArgs>(WorkflowRuntime_WorkflowTerminated);
   _runtime.WorkflowCompleted += new 
          EventHandler<WorkflowCompletedEventArgs>(WorkflowRuntime_WorkflowCompleted);

    // Create a new instance of the StateMachineTrackingService class  
    _stateMachineTrackingService = new StateMachineTrackingService(_runtime);

    // Create a host service for the OrderService and add it to the runtime
    ExternalDataExchangeService dataService = new ExternalDataExchangeService();
    _runtime.AddService(dataService);

    // Add a new instance of the OrderService to the host service
    _orderService = new OrderService();
    _runtime.AddService(_orderService);
}

The StateMachineTrackingService works on top of the runtime and extends it with the ability to track state changes in the workflow. An instance of the data exchange service is also added to the runtime.

When users click to create a new order, the following code executes.

private Guid StartOrderWorkflow(string orderID)
{
   // Create a new GUID for the WorkflowInstanceId
   Guid instanceID = Guid.NewGuid();

   // Load the OrderWorkflows assembly
   Assembly asm = Assembly.Load("OrderWorkflows");

   // Get a type reference to the OrderWorkflows.Workflow1 class
   Type workflowType = asm.GetType("OrderWorkflows.Workflow1");

   // Start a new instance of the state machine with state tracking support
   StateMachineInstance stateMachine = 
          _stateMachineTrackingService.RegisterInstance(workflowType, instanceID);
   stateMachine.StateChanged += new 
          EventHandler<ActivityEventArgs>(StateMachine_StateChanged);
   stateMachine.StartWorkflow();
   _stateMachineInstances.Add(instanceID.ToString(), stateMachine);

   // Return the workflow GUID 
   return instanceID;
}

In first place, the code instantiates the workflow instance and registers an event handler for state changes. Note that the use of .NET Reflection for obtaining type information is not strictly necessary, but it adds a lot of flexibility. The plain old typeof operator would work as well to communicate the type of the workflow instance to the workflow runtime.

Figure 18 shows the sample application in action. The buttons are enabled based on the state of the selected workflow instance.

Aa480214.wwfgetstart18(en-us,MSDN.10).gif

Figure 18. The state machine workflow hosted in a Windows Forms application

When the user clicks a given button, the corresponding event on the communication interface is raised and caught by the workflow's event sink. For example, a click on the Order Processed button for a workflow instance in the open state is handled as follows.

private void btnOrderEvent_Click(object sender, EventArgs e)
{
   // Get the name of the clicked button 
   string buttonName = ((Button)sender).Name;

   // Get the GUID of the selected order
   Guid instanceID = GetSelectedWorkflowInstanceID();

   // Get the ID of the selected order
   string orderID = GetSelectedOrderID();

   // Disable buttons before proceeding
   DisableButtons();

   // Determines what to do based on the name of the clicked button
   switch(buttonName)
   {
      // Raise an OrderShipped event using the Order Local Service
      case "btnOrderShipped":
         _orderService.RaiseOrderShippedEvent(orderID, instanceID);
     break;

      // Raise an OrderUpdated event using the Order Local Service
      case "btnOrderUpdated":
         _orderService.RaiseOrderUpdatedEvent(orderID, instanceID);
         break;

      // Raise an OrderCanceled event using the Order Local Service
      case "btnOrderCanceled":
         _orderService.RaiseOrderCanceledEvent(orderID, instanceID);
         break;

      // Raise an OrderProcessed event using the Order Local Service
      case "btnOrderProcessed":
         _orderService.RaiseOrderProcessedEvent(orderID, instanceID);
         break;
     }
}

The event raised in the workflow is caught by the EventDriven activity in Figure 19.

Aa480214.wwfgetstart19(en-us,MSDN.10).gif

Figure 19. The EventDriven block of the state machine to handle the Order Processed event

The HandleExternalEvent activity captures the event and processes it by transitioning in the state set by the SetState activity. The state change in the workflow is detected by the additional state tracking service, and reported to the host through the StateChanged event, as shown in the aforementioned listings.

You'll find the full source code of all the examples discussed in this paper, and much more workflow content, at https://msdn.microsoft.com/workflow.

Conclusion

Designed to become the workflow framework for new and existing Microsoft products, Windows Workflow Foundation delivers the power of Microsoft .NET Framework 3.0 and ease-of-use of Visual Studio 2005 to any developers in need of creating workflow-driven applications for the .NET platform.

The main benefit that Windows Workflow Foundation brings to the table is a unified workflow model and a set of tools replacing many proprietary libraries. In this regard, Windows Workflow Foundation is also of significant importance to vendors of today's workflow products, because adopting Windows Workflow Foundation means they don't have to maintain their low-level code any more, and can focus on higher-level tasks.

Windows Workflow Foundation is a workflow technology designed to address more than a particular breed of applications and needs. Windows Workflow Foundation is instead a broad framework, architected for extensibility at every level. The best examples of this form of extensibility are custom activities and pluggable runtime services. Custom activities let you extend the set of building blocks that you can use to author workflows. Runtime services such as persistence and tracking can be changed to fit the environment of the application, and to make it persist to Microsoft SQL Server or databases from other vendors.

The Visual Studio 2005 extensions for Windows Workflow Foundation will allow for visual modeling of workflows as well as direct code access.

The visual designer can also be hosted in other design environments, allowing designer providers to embed the visual modeling capability in their own environments and provide a user experience familiar to the users of the application.

This article has barely scratched the surface of all Windows Workflow Foundation technologies and features, presenting an overview of its working, internals, and some representative sample code.

 

About the author

Dino Esposito is a Solid Quality Learning mentor and the author of "Programming Microsoft ASP.NET 2.0" (Microsoft Press, 2005). Based in Italy, Dino is a frequent speaker at industry events worldwide. Get in touch at cutting@microsoft.com or join the blog at https://weblogs.asp.net/despos.