Manage application processes with Windows Workflow Foundation

Windows Workflow Foundation is part of the .NET 3.0 Framework. Using it, you can build portions of your application that orchestrate, or manage, the process flow for the use cases your application supports.

This Content Component encountered an error

Windows Workflow Foundation (or WF for short) is a new technology that is part of the Windows Framework (WinFX -- the new managed API for Windows). WF allows you to compose applications out of high-level building blocks representing the activities that the application performs. Using WF, you can build portions of your application that orchestrate, or manage, the process flow for the use cases your application supports. You can use WF...

in the middle-tier business components, or you can use them in the UI tier to control the flow from one view in your UI to another.

This article will give a brief introduction to what Windows Workflow Foundation is and will demonstrate how to use a few of its features through a sample application walkthrough and a more detailed download sample. (Note: The samples in this article were built using the February 2006 Community Technology Preview of WinFX.)

If you have been exposed to BizTalk Server workflows before, you can think of WF (in a somewhat oversimplified way) as an extraction of the orchestration definition and processing capabilities of BizTalk into a separate framework that can be used in any application. If you have not been exposed to BizTalk orchestrations, you should get a good sense of what WF is shortly. Because of the level of abstraction involved, one of the easiest ways to understand what WF is and does for you is to start by considering what workflows are and how you build them today outside the BizTalk arena.

Introduction

Often applications, or portions of them, are composed of individual processes or activities that need to occur at a particular time and in a particular order to satisfy the requirements of the application. These may be sections of logic in your application that are inherently sequential, such as a set of steps that has to happen in a given order, possibly with complex conditional logic on what path is taken when. They may also be state driven, where your application needs to do things at indeterminate points in time that are determined by user or external events. These chunks of functionality are called workflows.

In a traditional .NET application, you would construct these portions of your application in the same way that you do other pieces of logic -- you would write code, mostly by hand. You would declare classes, add methods to those classes, create instances of the classes and call the methods in a particular order to achieve the sequential or state-driven processing that you need. You would have to follow the thread of execution through your code to get it all written correctly to satisfy the requirements.

Often the way you know what to code for this kind of functionality is to start with a diagram -- a flow chart or UML activity diagram. You would identify the high-level oncepts that different portions of the code need to perform. These are called activities. For example, "retrieve customer data," "check account status," "compute discount based on subscription level" and "process trade" may be activities that you identify as part of a flowchart representing a sequential processing workflow in a securities trading application.

So what is a Workflow?

A workflow is a composition of activities that achieves an end goal. Workflows include both human processes, such as reviewing and approving a document, and automated processes, such as retrieving data and executing sets of logic based on what is retrieved. Workflows can be long running processes that take hours, days, weeks or longer to execute, or they can be short automated processes that execute in seconds or less on today's powerful computing machines.

Workflows have a state that is associated with the ongoing process that the workflow is designed to address, and they have transitions from one activity to another within the workflow. Workflows are just a grouping of activities, and, when viewed from a different level of abstraction, can be treated as a single activity themselves that are part of a larger workflow. For example, you could have a workflow that describes the process of creating a new customer account, and that workflow could just be treated as a single activity that is part of a workflow for applying for a new customer account.

Building Workflow Applications

What Windows Workflow Foundation allows you to do is use the flowchart itself to compose the application. You compose the processes in your application out of activities that describes steps in the process. The activities in the flowchart become components themselves that can be satisfied with built-in activities defined in WF, custom activities that you code yourself or third-party activities that you purchase from other WF activity vendors. When you build WF applications in Visual Studio 2005, you get a design surface interaction model for declaratively programming your workflow in a manner similar to designing UI forms (see Figure 1).

Figure 1: Designing a Sequential Workflow in Visual Studio 2005

When you install the WinFX runtime components and SDK, you get a set of class libraries that you can reference from any application to create and run workflows from within that application. The Visual Studio 2005 Extensions for Windows Workflow Foundation installs a set of new project templates and Toolbox items for creating workflow projects. One you have those installed, you can create new workflows and design them by dragging and dropping activities from the Toolbox onto the design surface. You then set properties for the activities in the Properties Window. Finally, you write any imperative code that supports those activities in the code view of the workflow.

Workflow Types

WF supports two top-level models for workflows: sequential processing and state machine. You can create workflows using a variety of project types, including console applications or libraries. For production applications, you will likely design your workflows in class libraries, and then create and run those workflows from other kinds of hosting applications.

Workflow uses a layered architecture within any application into which it is integrated (see Figure 2). The part you will generally focus on is the top layer, the workflow model, where activities are defined, configured, and connected. At runtime, there is an infrastructure that the workflow will run on that includes a runtime layer, which takes care of the execution aspects of the workflow, and a hosting layer, which takes care of interactions between the workflow and the outside world.

Fig.2: Layered Architecture Workflow

Once you have defined a workflow, it can be hosted in any kind of application. It can be run from a Windows Communication Foundation service, Web service, Web page, WinForms application, console application or Windows service, depending on the needs of your application.

To run a workflow, you have to first create an instance of the WF runtime for the host AppDomain. Using that WF runtime instance, you then start instances of the Workflow as needed to complete the processing that the workflow represents. An example flow of initiation for a workflow is shown below:

 

 WorkflowRuntime workflowRuntime = new WorkflowRuntime(); // Add the External Data Exchange Service ExternalDataExchangeService dataService = new ExternalDataExchangeService(); workflowRuntime.AddService(dataService); // Add trade processing data service // to the data exchange service TradeService trades = new TradeService(); dataService.AddService(trades); // Start the runtime workflowRuntime.StartRuntime(); // Create a workflow instance WorkflowInstance workflowInstance = WorkflowHelper.WorkflowRuntime.CreateWorkflow( typeof(ProcessTradeWF)); // Start the instance running workflowInstance.Start();

In the sample above, the ExternalDataExchangeService is a runtime communications service that allows you to pass data back and forth between the host application and the workflow. To use this service, you define specific data service types that set up the communications paths between the workflow and the host to exchange data of a particular kind. In the sample code above, the TradeService allows the host application to pass in the data associated with a securities trade for processing.

Once the workflow is running, its lifetime and processing will be determined by the activities from which it is composed. The workflow runtime will take care of controlling the execution of the activities in the workflow.

Activities: The Building Blocks of a Workflow

Activities are the components that define the behavior and processing that a workflow will perform once it is started. The type of the workflow (sequential or state machine) determines the kinds of activities that are appropriate for that workflow. Activities can be thought of as little blocks of active code that are invoked by the workflow runtime when it is a given activity's turn to run. Every workflow has a start and end point. The start point is where an execution thread enters the workflow when it is started. When the end point it reached, the workflow is done with its processing, and execution will cease for the workflow until another instance is created and run. Each instance gets run on its own thread from the thread pool.

You can see in Figure 1 the set of activities that are available for a sequential workflow. There are activities for control flow, event handling, external communications via Web services and for controlling execution through transactions and exceptions. You can create your own custom activities by deriving from base classes or the built-in activities in the Windows Workflow Foundation libraries.

There is a Code activity type that you can add to any workflow that will invoke a method in the workflow code that you define. Using that activity, you can do whatever you need to do that goes beyond the available activities, such as calling out to another layer (such as the data layer) in your application, or making a remote call. In Figure 1 you can see examples (from top to bottom) of activities for event handling, executing a custom code method, checking a conditional clause, throwing an exception if that condition is met, checking another processing rule, and executing different code blocks depending on the value of that condition.

The transitions between activities are determined by the activity type. Each activity itself has an entry point and a completion point. The runtime will conceptually call into the activity allowing it to start doing whatever it was designed to do; when the activity completes, the runtime will pass execution on to the next activity in the processing chain (as determined by the workflow diagram). This execution processing within an activity is easiest to understand for a Code activity. Since it is just a method in code, that method is called by the runtime when it is that activity's turn to run. When the method returns, the runtime calls the next activity in the flow that follows the Code activity.

However, there are a number of more complex activities whose lifetime is determined by other factors. There is a Delay activity that will use a timer internally to block for a certain amount of time. There is a HandleExternalEvent activity that will block until an event is fired by a call from the host application on a data service (such as the TradeService that was added to the runtime in Figure 3).

Getting Data Into and Out of an Application

Windows Workflow Foundation supports a number of models for getting data into and out of a running workflow. If the calling pattern is such that you just need to pass some data into the workflow when it starts running and need that data accessible to different parts of the workflow instance, you can pass named parameters into the workflow at the point where you start it running as shown below.

 

 public void InitiateOrderWorkflow(string customerId, DateTime orderDate, int productId) { // Create the parameters for the workflow Dictionary
 
   parameters = new Dictionary
  
   (); parameters.Add("CustomerID", customerId); parameters.Add("OrderDate", orderDate); parameters.Add("ProductID", productId); WorkflowInstance workflowInstance = workflowRuntime.CreateWorkflow( typeof(OrderProcessingWorkflow), parameters); workflowInstance.Start(); }

  
 

When you take this approach, the workflow type is expected to have public properties with the names used in the Dictionary of parameters, and those properties will be set on the workflow instance when it starts running using the values placed into the dictionary (see code below). Thus the values of the parameters are accessible throughout the workflow while it is running just by accessing one of its own properties from the designer or from the workflow code.

 

 public sealed partial class OrderProcessingWorkflow: SequentialWorkflowActivity { private string m_CustomerID; private int m_ProductID; private DateTime m_OrderDate; public OrderProcessingWorkflow() { InitializeComponent(); } public string CustomerID { get { return m_CustomerID; } set { m_CustomerID = value; } } public int ProductID { get { return m_ProductID; } set { m_ProductID = value; } } public DateTime OrderDate { get { return m_OrderDate; } set { m_OrderDate = value; } } }

Windows Workflow Foundation also supports an event-driven model where the host application can use the ExternalDataExchangeService to dispatch calls between the host application and the workflow, or vice versa, at the point where an appropriate activity is reached (HandleExternalEvent from host to workflow, and CallExternalMethod for workflow to host). The process for defining and hooking these up is considerably more complicated, but the download code for this article includes a trade processing example (whose workflow is shown in Figure 1) that uses an HandleExternalEvent activity to pass the data into the workflow for demonstration purposes.

The main difference between passing parameters and using an external event has to do with when the data becomes available relative to the running of the workflow instance. To use parameters, the data has to be available when the workflow instance is started, whereas the external event can occur at any point in the future. You can also get parameters back out from the workflow properties by handling the WorkflowCompleted event, as shown later in the sample walkthrough. There is also a CallExternalMethod activity to explicitly pass data back out of a workflow at any point while it is running.

There is also a built-in activity for exposing a Web service method directly from a workflow (WebServiceInput), and that activity will block until the Web service method is called. You can also call out to a Web service from a running workflow with the WebServiceOutput activity.

Finally, you could write a custom activity or just use a Code activity to go make external calls from the Web service, such as to make a data access layer call to retrieve or store some data from the data tier.

Order Processing Workflow Example Walkthrough

One challenge in describing Windows Workflow Foundation at an introductory level in a short article is that WF is well suited for complex, state-driven, process-oriented applications, which are not easy to "quickly" describe. But to give you a quick sense of the process of building a workflow application, I'll step you through a simple example of using a workflow to drive the application processing for a Web service method call into a middle tier application. If you follow along, you will create a Sequential Workflow Library project to contain the workflow definition. Then you will invoke the workflow from an ASP.NET Web service application.

To get started, create a new Project, and select the Workflow category and the Sequential Workflow Library template type (see Figure 3). Name the project OrderProcessingWorkflowLib.

 

Figure 3: Create a Sequential Workflow Library Project

Drag and drop a Code activity from the Toolbox onto the workflow diagram between the top (green) start node and the bottom (red) end node (see Figure 4). Select the activity in the designer, and select the (Name) property in the Properties window and change the name to GetProductData.

 

Figure 4: Adding a Code Activity to the Workflow

Add a typed data set definition to the project for the Products table in Northwind. To do this, right click on the project in Solution Explorer, select Add > New Item, and select DataSet out of the template types. Give it a name of ProductsDataSet. This will drop you into the designer for the data set. Open Server Explorer and add a Data Connection to Northwind if you do not already have one. Then expand the tree and drag and drop the Products table onto the designer surface. You should end up with a data set definition as shown in Figure 5. Save the xsd file that defines the data set and close it.

 

Figure 5: Creating a Products Typed Data Set Definition

Double click on the GetProductData activity in the designer to hook up an ExecuteCode method for the activity and go to the code view.

Add the following using statements to the top of the file:

 

 using System.Data; using OrderProcessingWorkflowLib.ProductsDataSetTableAdapters;
Also the following members to the workflow class: private ProductsDataSet.ProductsRow m_Product; private double m_Discount = 0; private bool m_SuperCust; public bool SuperCustomerStatus { get { return m_SuperCust; } set { m_SuperCust = value; } } private int m_ProductID; private double m_Cost; public int ProductID { get { return m_ProductID; } set { m_ProductID = value; } } public double Cost { get { return m_Cost; } set { m_Cost = value; } }

Then add the following code to the GetProductData_ExecuteCode method:

 

 private void GetProductData_ExecuteCode(object sender, EventArgs e) { ProductsTableAdapter adapter = new ProductsTableAdapter(); // Note - inefficient data access to keep demo simple, should // create custom query method to return single row ProductsDataSet.ProductsDataTable products = adapter.GetData(); DataRow rawRow = products.Select("ProductID = " + m_ProductID.ToString())[0]; m_Product = rawRow as ProductsDataSet.ProductsRow; }

Bring the workflow designer back up by double clicking on the workflow in Solution Explorer. Drag and drop an IfElse activity onto the diagram, just below the GetProductData Code activity (a small green dot will appear on the connectors between activities indicating where valid drop points are for the activity as you drag it out of the Toolbox). This will create an activity that looks like Figure 6. Click on the right branch of the IfElse so that it is selected. Then right click on it and select Delete from the context menu, effectively making it just an If branch.

 


Figure 6: Adding an IfElse Activity

Select the remaining branch inside the IfElse activity and go to the Properties window. Select RuleConditionReference from the Condition property drop down list, then expand the Condition property to see the sub-properties as shown in Figure 7.

 


Figure 7: Setting RuleConditionReference Properties for an IfElseBranch

Click on the ellipses for the ConditionName, and you will be presented with the Select Condition Rule editor (see Figure 8).

 


Figure 8: Condition Rule Editor

Click on New Condition, and enter the following in the editor, then click OK:

 

 this.SuperCustomerStatus = true

Click OK to exit the condition editor and accept that condition as the conditional clause for the IfElseBranch. What this does is to declaratively set the condition under which the activities contained in the IfElseBranch will be executed (specifically that the customer has to have Super status).

Next, drag a Code activity into the IfElseBranch where it says "Drop Activities Here," and rename it through the Properties window to SetDiscount. Also add another Code activity to just after the IfElse activity, and name this one ComputeCost. At this point you should have a diagram that looks like Figure 9. There, error icons include a smart tag drop down that will tell you what semantic rules for the activity have not been set correctly yet.

 


Figure 9: Sequential Workflow

Double click on the SetDiscount Code activity to hook up an execute method for it, then switch back to the designer and do the same for the ComputeCost activity. Add the following code to those methods:

 

 private void SetDiscount_ExecuteCode(object sender, EventArgs e) { m_Discount = .1; } private void ComputeCost_ExecuteCode(object sender, EventArgs e) { m_Cost = (double)m_Product.UnitPrice - (double)m_Product.UnitPrice * m_Discount; }

Right click at the solution level and select Add > New Web Site, then select the ASP.NET Web Service template. Use a File System location, and add a folder name of OrderProcessingService to whatever root folder you are putting your projects into (i.e. c:\temp) as shown in Figure 10.

 


Figure 10: Add New Web Service Web Site to Solution

Add a reference to the Web Service site to the OrderProcessingWorkflowLib project. Replace the code in Service.cs with the following:

 

 using System; using System.Web; using System.Web.Services; using System.Web.Services.Protocols; using System.Workflow.Runtime; using OrderProcessingWorkflowLib; using System.Collections.Generic; using System.Threading; [WebService(Namespace = "http://idesign.net/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class Service : System.Web.Services.WebService { public const string WorkflowRuntimeCacheKey = "WFRocks"; AutoResetEvent m_CompletedEvent = new AutoResetEvent(false); private double m_Cost; private object m_ObjLock = new object(); public double Cost { get { double cost; lock (m_ObjLock) { cost = m_Cost; } return cost; } set { lock (m_ObjLock) { m_Cost = value; } } } [WebMethod] public double ProcessOrder(int productId, bool superCustomer) { // Since we can only have one Workflow Runtime per app domain, // use the HTTPContext cache to store it. WorkflowRuntime workflowRuntime = (WorkflowRuntime)HttpContext.Current.Cache[ WorkflowRuntimeCacheKey]; if (workflowRuntime == null) { // Hasn't been create yet, so do so workflowRuntime = new WorkflowRuntime(); HttpContext.Current.Cache[WorkflowRuntimeCacheKey] = workflowRuntime; workflowRuntime.StartRuntime(); } // Hook up an event handler for when the workflow completes workflowRuntime.WorkflowCompleted += OnWorkflowCompleted; // Create a dictionary of parameters Dictionary
 
   parameters = new Dictionary
  
   (); parameters.Add("ProductID", productId); parameters.Add("SuperCustomerStatus", superCustomer); // Create the workflow instance, passing in the parameters WorkflowInstance workflowInstance = workflowRuntime.CreateWorkflow( typeof(ProcessOrderWorkflow),parameters); // Start it running workflowInstance.Start(); // Block until complete m_CompletedEvent.WaitOne(); // Remove event subscription workflowRuntime.WorkflowCompleted -= OnWorkflowCompleted; return Cost; } void OnWorkflowCompleted(object sender, WorkflowCompletedEventArgs e) { Cost = (double)e.OutputParameters["Cost"]; m_CompletedEvent.Set(); } }

  
 

The workflow runtime can only be created once per app domain. So the ProcessOrder web method tries to retrieve it out of the cache, and it only creates and starts it if it has not been initialized. It then sets up the workflow input parameters as a dictionary of key value pairs, where the keys are strings that correspond to property names on the workflow type. An instance of the workflow is then created, passing in the parameters. The WorkflowCompleted event on the runtime is hooked so that the code can be notified when the workflow is complete -- in this case to retrieve the results of the workflow through the output parameters.

Since the workflow runs on a separate thread from the thread pool, the class includes a locking mechanism with an AutoResetEvent to block the main request processing thread until the results of the workflow processing become available. This is not the most scalable solution, because it means two thread pool threads will be tied up for each request. But the asynchronous processing model of workflows dictates this kind of solution if you need to return results from the workflow as the response to the request that kicks off the workflow. Workflows are usually more long-running processes that will span multiple requests, and you will usually use the data exchange model discussed earlier to get data into and out of the running workflow, in which case this locking approach would not be used.

With that code in place, you should be able to run the Web service in the debugger, invoke the ProcessOrder method, input some values (you will need a valid ProductID from Northwind to avoid an exception), and get a cost value back out.

This sample is a little contrived - again, just to keep it relatively simple to fit in an article of reasonable size.

Summary

Windows Workflow Foundation is a very elegant model for composing complex process-oriented applications out of high-level graphical abstractions of what the process performs. There is a fair amount of complexity to tackle just to put together a simple "Hello World" example, which is not the kind of application that WF was really designed to address. However, WF starts to really shine when you put together a relatively complex process flow with lots of branches, conditions, inputs and outputs at various places, wait states and so on. Or if you are building an application that is driven by a state machine, WF provides a very easy-to-understand abstraction layer on top of the code the makes it all come together.

Once you get over the initial learning curve of how to compose applications out of WF activities and integrate them into your host applications, you should find the resulting applications artifacts easier to understand, maintain and change. You can start at the top level abstraction of an overall workflow and drill down to just the activities that concern you through the graphical flowchart design model.

Workflows can be used for a wide variety of application types. They can implement business layer processing or UI layer controllers. They can run as a service to implement a batch process, or they can be invoked through a Web service. As you get more comfortable with what WF is, you will start to see more and more opportunities to use this new technology to build your application out of a set of nice, abstracted activities in a diagram instead of trying to do surgical hookup of methods and properties at the code level.

Download the code here.

About the author:

Brian Noyes is a Microsoft Regional Director and MVP, and an international speaker, trainer, writer and consultant with IDesign Inc. He speaks at Microsoft TechEd US, Europe and Malaysia, Visual Studio Connections, SDC Netherlands, DevTeach Montreal, VSLive!, DevEssentials and other conferences, and is a top rated speaker on the INETA Speakers Bureau. He has published numerous articles on .NET development for The Server Side .NET, MSDN Magazine, MSDN Online, CoDe Magazine, Visual Studio Magazine, asp.netPRO, .NET Developer's Journal, and other publications. Brian's latest book, Data Binding with Windows Forms 2.0, part of the Addison-Wesley .NET Development Series, hit the shelves in January 2006, and will be followed this summer by Smart Client Deployment with ClickOnce.


This was first published in November 2007

Dig deeper on .NET Workflow

0 comments

Oldest 

Forgot Password?

No problem! Submit your e-mail address below. We'll send you an email containing your password.

Your password has been sent to:

-ADS BY GOOGLE

SearchCloudComputing

SearchSoftwareQuality

SearchSOA

TheServerSide

SearchCloudApplications

Close