Cutting Edge

Windows Workflow Foundation

Dino Esposito

Code download available at:CuttingEdge0603.exe(663 KB)

Contents

A Common Business Scenario
Applying Windows Workflow Foundation
The Helpdesk Solution
The Human Factor
The Helpdesk Front End
State Persistence
Resuming the Workflow Instance
Conclusion

In the January 2006 issue, Don Box and Dharma Shukla introduced Windows® Workflow Foundation and discussed the overall architecture of the framework and its constituent components ( WinFX Workflow: Simplify Development With The Declarative Model Of Windows Workflow Foundation). It inspired me to take this topic one step further and discuss how you can use Windows Workflow Foundation to address a common business scenario where automatic processes intersect with human activity. It provides a framework for developing and executing a wide range of applications based on complex processes. Typical examples are document management, business-to-business, and consumer applications. You can use Visual Studio® 2005 to help design the underlying workflow in addition to the top-level applications and assemblies involved.

A Common Business Scenario

Organizations typically have a number of internal processes for tasks such as order processing, purchase requests, travel expenses, and the like. Workflow brings order to these independent processes in a transparent, dynamic, and robust fashion.

Let's consider a typical helpdesk workflow process. It begins when the helpdesk employee gets a customer call and opens a ticket recording the name of the customer, the time of the call, and a brief description of the issue. Once the ticket has been created, the employee forgets about it and waits for another incoming call. At the end of the day, he logs off the computer and goes home. At the same time, in another department, perhaps in another city, a bunch of technicians are alerted to open issues. Each active technician picks up a request and either solves it or escalates it to a second level of help. How would you write code to implement this process?

You might have a Windows Forms application that collects input data about the call and creates a record in a database—the ticket, with time, description, status, and a unique ID. Users of a second Windows Forms application will see a real-time list of pending requests and pick one up. The operator then does whatever is in his power to resolve the issue (call back the customer, retrieve requested information, send e-mail, or perform some remote activity) and indicates whether the problem was solved or will need to be investigated further. This decision can be represented with an imperative action such as clicking a button to update the ticket in the same underlying database. Finally, if there's another category of users to involve, a custom made front end will give them a chance to indicate that the issue has been successfully closed or aborted.

Although this process clearly represents a workflow with some human-driven decision points, it can be easily implemented with traditional sequential code written with classic programming languages and databases.

Applying Windows Workflow Foundation

When you have a workflow-based system composed of activities, like you have with Windows Workflow Foundation, you can implement an application using a powerful mix of imperative code and declarative maps of activities and the declarative rules that bind them. The major benefit is that you can model (even visually) the solution and have Windows Workflow embed a run-time server to interpret your graph and proceed along the links that you defined among building blocks. The more complex the process, the simpler it is to devise and implement a flow for it. The more dynamically changeable the process, the less code you'll need to write and maintain. Let's see how to implement a Windows Workflow Foundation solution for the helpdesk scenario.

The Helpdesk Solution

The helpdesk workflow I've built begins by creating a ticket and then stop while waiting for a response from a connected user or technician. Whether the ticket is closed or escalated, the workflow gets an external event and updates the internal state of the application to track the event. In light of this, the workflow requires an interaction with the outside world. These kinds of asynchronous activities are one of the problems inherent in real-world workflow processes that Windows Workflow Foundation addresses. Because interaction with an entity outside the system is required, the host application and the workflow can define a contract for any data exchange that proves necessary. The IHelpDeskService interface you see here describes the communication interface that is established between the workflow and its host:

[DataExchangeService] public interface IHelpDeskService { event EventHandler<HelpDeskTicketEventArgs> TicketClosed; event EventHandler<HelpDeskTicketEventArgs> TicketEscalated; void CreateTicket( string description, string userRef, string createdBy); void CloseTicket(string ticketID); void EscalateTicket(string ticketID); }

Now let's begin by writing a service class named HelpDeskService, as shown in Figure 1. The HelpDeskService class is added to the workflow runtime and represents the point of contact between the host application and the workflow. The host application invokes the public methods on the class to fire external events to the workflow. Fired events signal domain-specific events that will guide the flow of operations. Methods on the IHelpDeskService interface represent the only pieces of code that a workflow can invoke on external components through the InvokeMethod activity. Factored out in this way, the logic of the HelpDeskService adds a lot of flexibility to the workflow as it basically lets the workflow call back the host for the actual implementation of basic actions. By including a class that implements the IHelpDeskService interface different hosts can create, solve, or escalate tickets using different algorithms and storage media while keeping the workflow logic intact. You can compile the interface and the class in a distinct assembly or just keep these files inside the same assembly where the workflow is defined.

Figure 1 Implementing Workflow with HelpDeskService

using System; using System.Threading; using System.Workflow.ComponentModel; using System.Workflow.Runtime; using System.Workflow.Runtime.Messaging; namespace MySamples { public class HelpDeskService : IHelpDeskService { // Implement events public event EventHandler<HelpDeskTicketEventArgs> TicketClosed; public event EventHandler<HelpDeskTicketEventArgs> TicketEscalated; public void RaiseCloseTicketEvent(Guid instanceId) { // Raise the event to the workflow HelpDeskTicketEventArgs args = new HelpDeskTicketEventArgs(instanceId, ""); if (TicketClosed != null) TicketClosed(this, args); } public void RaiseEscalateTicketEvent(Guid instanceId) { // Raise the event to the workflow HelpDeskTicketEventArgs args = new HelpDeskTicketEventArgs(instanceId, ""); if (TicketEscalated != null) TicketEscalated(this, args); } void IHelpDeskService.CreateTicket( string description, string userRef, string createdBy) { // Fill up a ticket: same ID as the workflow string ticketID = WorkFlowEnvironment.CurrentInstanceId.ToString(); // Create ticket on the DB HelpDeskHelpers.CreateTicket( ticketID, description, userRef, createdBy); } void IHelpDeskService.CloseTicket(string ticketID) { // Update ticket on the DB HelpDeskHelpers.UpdateTicket(ticketID, TicketStatus.Closed); } void IHelpDeskService.EscalateTicket(string ticketID) { // Update ticket on the DB HelpDeskHelpers.UpdateTicket(ticketID, TicketStatus.Escalated); } } }

HelpDeskWorkflow is a sequential workflow hosted by a Windows Forms app. Figure 2 shows its graphical model. It begins with an InvokeMethod activity that causes the app to create the ticket based on information provided by the user. After that, the ticket sits in a database waiting for a technician to pick it up and either solve it or escalate it.

Figure 2 Helpdesk Workflow in Visual Studio 2005

Figure 2** Helpdesk Workflow in Visual Studio 2005 **

After creating the ticket, the workflow waits, potentially for a long time—even hours or days. What happens during this time? Should the workflow instance stay loaded in memory? If the workflow becomes idle, you can unload it from memory—a process also known as passivation. A local service, such as the helpdesk service, remains the main point of contact between the host application and the sleeping workflow.

The Human Factor

When the human factor interacts with the host application and does something to wake up the workflow, the local service posts a request to the runtime to resume the passivated workflow. The Windows Workflow Foundation toolbox contains an activity, called Listen, that just idles the workflow and listens for incoming wake-up calls. The block labeled WaitForSolution in Figure 2 is an instance of the Listen activity.

The Listen activity is basically useless without one or more child branches, each representing a possible event that can be hooked up at this point. In the helpdesk example, the Listen activity contains two branches—ticket closed and ticket escalated. Each branch is an EventDriven activity. Both Listen and EventDriven activities require no special settings and are mere containers of child activities. To catch an external event, the workflow needs an EventSink component.

In Figure 2, the TicketClosed block is an EventSink bound to the TicketClosed event in the local helpdesk service. Figure 3 lists the EventSink properties set here. To begin, you select the interface of the service that exposes the event. Setting the InterfaceType property is a requirement; in addition, you can only select an interface decorated with the [DataExchangeService] attribute, as in Figure 1.

Figure 3 EventSink Properties

Figure 3** EventSink Properties **

Once the data exchange interface is set, the EventName property is prefilled with all events found on the interface. You pick up the event of choice and set its parameters. If you want a further notification about when the event has been processed, you set the Invoked property, too.

The EventSink returns control to the workflow after a period of idleness due to waiting for human intervention. Following the event, you proceed with any further activity that suits the workflow. For example, in the helpdesk application I call another method on the local service to update the status of the ticket in the ticket database.

In a real-world scenario, there's at least one database the workflow needs to interact with, directly or indirectly. In this example, I'm using a SQL Server™ 2000 table, Tickets, that stores a row for each ticket being processed. By design, the ID of the ticket matches the ID of the workflow that is used to manage it.

The Helpdesk Front End

Once compiled, the workflow is nothing more than a reusable assembly and thus can be referenced by any type of .NET-based application. Let's now imagine a front-end application for helpdesk operators. The application is a Windows Forms program that allows users to fill in a form and create a ticket to initiate the workflow, as shown in Figure 4.

Figure 4 HelpDesk Front-End App

Figure 4** HelpDesk Front-End App **

The workflow calls into the CreateTicket method of the local service and has the service add a new record to the tickets database. After the operator has opened a new ticket, he continues answering phone calls and replying to e-mails while the workflow is started and becomes idle waiting for a human intervention. Each ticket is represented with a different instance of the workflow. For simplicity, the ID of the workflow is also the ID of the ticket.

State Persistence

At the end of the day, a workflow is a tree of activities, so how is its lifetime managed? The Windows Workflow Foundation run-time engine manages the execution of any workflow and allows workflows to remain active for a long time and even survive machine reboots. The run-time engine is powered by pluggable services that provide an overall rich execution environment—transactions, persistence, tracking, timer, and threading.

It's unlikely that you want your workflow to stay in the memory of the host process until some intervention causes it to proceed. As mentioned, many real-world human-based workflows may take hours or longer for this to happen. As in the preceding example, the helpdesk operator starts the workflow and loads the workflow class inside the front-end application process. That operator may then shut down his machine and go home. However, the ticket must remain active and available to other operators, even from within another application. For this to happen, the workflow must support serialization to a durable medium.

A workflow may be a long-running operation and it is impractical for that object to remain active in memory for days. First of all, a host process can't generally afford to cache all the objects that accumulate in days of continued activity; second, the host process might shut down or reboot more than once.

The solution is to configure the runtime to unload workflows as they go idle. In this case, the host program will use the following code to initialize the runtime:

WorkflowRuntime wr = new WorkflowRuntime(); wr.StartRuntime();

You can also set a few event handlers on the WorkflowRuntime class to run some code when the workflow is idle, persisted, or unloaded. Configured in this way, the runtime will automatically serialize the workflow to a storage medium as the workflow encounters a Listen activity or enters a wait state. Another alternative is to instruct the host program to programmatically persist the workflow at a well-known point of execution. In this case, you call the EnqueueItemOnIdle method on the WorkflowInstance class. You get an instance of the WorkflowInstance class as the return value of the CreateWorkflow method on the run-time class:

WorkflowInstance inst = theRuntime.CreateWorkflow(type); ... inst.EnqueueItemOnIdle(); inst.Unload();

Just remember though that a call to EnqueueItemOnIdle doesn't necessarily persist the workflow immediately. Persistence is immediate only if the workflow is idle or suspended. Otherwise, the runtime takes note and persists the workflow instance later when the workflow is suspended or idle. Note that EnqueueItemOnIdle is limited to saving the state of the workflow instance and doesn't unload it from memory. To unload a workflow instance, you need to call the Unload method. One of the standard run-time services supported by the workflow runtime is the workflow persistence service. You turn it on through the code in Figure 5.

Figure 5 Workflow Persistence Service

private WorkflowRuntime InitWorkflowRuntime() { // Get a new workflow runtime WorkflowRuntime wr = new WorkflowRuntime(); // Add custom HelpDesk service theHelpDeskService = new HelpDeskService(); wr.AddService(theHelpDeskService); // Add system SQL state service SqlWorkflowPersistenceService stateService = new SqlWorkflowPersistenceService("Data Source=localhost;" + "Initial Catalog=WFState;UID=...;"); wr.AddService(stateService); // Start wr.StartRuntime(); return wr; } private void btnCreate_Click(object sender, EventArgs e) { // Fill the Parameters collection for this instance of the workflow Dictionary<string, object> parameters = new Dictionary<string, object>(); parameters.Add("TicketDescription", txtDesc.Text); parameters.Add("TicketUser", txtUser.Text); parameters.Add("TicketCreatedBy", txtCreatedBy.Text); // Get the type of the workflow Type type = typeof(MySamples.HelpDeskWorkflow); // Start the workflow instance WorkflowInstance inst = theWorkflowRuntime.CreateWorkflow(type, parameters); // Feedback to the user string msg = String.Format("The ticket '{0}' has been successfully " + "created!", inst.InstanceId); MessageBox.Show(msg, "HelpDesk", MessageBoxButtons.OK, MessageBoxIcon.Information); FillGrid(); }

The WorkflowPersistence service adds serialization capabilities to all the instances of workflows that execute inside a given run-time engine. The workflow service core functionality is represented by the WorkflowPersistenceService class. Windows Workflow Foundation provides an implementation of it through the SqlWorkflowPersistenceService. This class saves workflow data to a SQL Server 2000 or SQL Server 2005 database with a well-known schema. There are a couple of scripts and dialog-based utilities that you can use to easily create the target database. You can download everything you might need from Windows Workflow Foundation Web. An instance of the SQLWorkflowPersistenceService must be created and registered with the runtime before the runtime is actually started. The constructor of the persistence service takes the connection string as its sole argument.

As mentioned, run-time services are a pluggable part of the overall Windows Workflow Foundation architecture, so you can create your own services and replace standard services with your own.

Resuming the Workflow Instance

In the scenario being discussed, another operator—the technician—is expected to pick up any open tickets and do whatever he can to solve or escalate them. The technician will use another application or, at least, another instance of the app shown in Figure 4. In both cases, another instance of the workflow runtime must be created with persistence support and the right workflow state must be loaded and resumed. As you can see, this can only happen if the ID of the idled workflow has been saved. In the helpdesk sample app, by design, the ticket ID matches the workflow ID and each ticket corresponds to an instance of the workflow created to handle it.

Figure 6 The Problem Solver

Figure 6** The Problem Solver **

The Problem Solver application (see Figure 6) initializes the workflow runtime using nearly the same code as the HelpDesk front-end application in Figure 4. The only minor difference is that in the Problem Solver application I register handlers for the WorkflowLoaded and WorkflowCompleted events on the runtime:

WorkflowRuntime wr = new WorkflowRuntime(); wr.WorkflowLoaded += OnWorkflowLoaded; wr.WorkflowCompleted += OnWorkflowCompleted;

The operator selects a ticket from the displayed list and works with the user who raised it. When done, she clicks to solve or escalate the ticket, thus terminating the workflow. The event handler should retrieve the saved instance of the workflow based on the ticket ID and wake it up from the idle state. Here's the code you need:

string workflowID = (string)ticketList.SelectedValue; Guid id = new Guid(workflowID); Type type = typeof(MySamples.HelpDeskWorkflow); try { WorkflowInstance inst = theWorkflowRuntime.GetLoadedWorkflow(id); theHelpDeskService.RaiseCloseTicketEvent(inst.InstanceId); } catch { ... }

The core part of the preceding code is in the call to the GetLoadedWorkflow method. GetLoadedWorkflow takes the GUID that represents the workflow instance and retrieves it from the configured durable medium, if it is not currently in memory. In this case, the workflow is loaded into memory and scheduled for execution. This happens even if the workflow instance has been previously aborted. The WorkflowLoaded event fires when the workflow is loaded back into memory.

GetLoadedWorkflow returns an object of type WorkflowInstance that you can use for inspecting the current status of the workflow as well as its activities. At this point, you raise one of the events the workflow was waiting for. The corresponding EventSink activity will trap the event and proceed until the end of the workflow is reached or a subsequent wait point is encountered.

When the workflow completes, it is automatically removed from the Windows Workflow Foundation persistence database. The WorkflowCompleted event fires when the workflow has reached its end. In the helpdesk scenario, the workflow completes after the operator clicks to solve or escalate (see Figure 6).

From a development perspective, it is essential to note two things. First, to raise an event to the workflow, you have to post the request to a pooled thread:

public void RaiseCloseTicketEvent(Guid instanceId) { ThreadPool.QueueUserWorkItem(JustCloseTheTicket, new HelpDeskTicketEventArgs(instanceId, "Jim")); } public void JustCloseTheTicket(object o) { HelpDeskTicketEventArgs args = o as HelpDeskTicketEventArgs; if (TicketClosed != null) TicketClosed(null, args); }

Second, the WorkflowCompleted event may not be raised on the main Windows Forms thread, so you cannot directly update any of the UI controls. (This is due to the fact that Windows Forms controls are not thread-safe.) Updating Windows Forms controls from within a thread different from the one which created them is definitely possible, but requires an indirect call to the method that updates controls:

private delegate void UpdateListDelegate(); private void UpdateList() { this.Invoke(new UpdateListDelegate(FillList)); }

You call UpdateList from within the WorkflowCompleted event handler. The Invoke method on the Form class guarantees that FillList gets called on the right thread so that any control can be safely refreshed.

Conclusion

Windows Workflow Foundation allows you to visually design complex algorithms to solve business problems and model processes. A workflow is a tool used to describe a flow of data and actions. Therefore, any scenario where an IF or a WHILE statement are required can be a workflow. However, nobody would ever use a workflow for just an IF statement; the workflow runtime does have a cost that can be amortized as the complexity of the flow grows beyond a given threshold.

Where's the boundary that delimits the cost effective use of a workflow? The helpdesk scenario, as implemented in this column, is probably too simple for a workflow and could have been implemented in an equivalent way by employing a tickets database—even the same tickets database that the workflow deals with through the helpdesk service.

The real benefit of a workflow-based solution consists of making complex processes simpler to model and implement, and, more importantly, evolve and extend. Windows Workflow Foundation provides a managed execution environment for Windows Workflow programs. It provides durability, robustness, suspension/resumption, and compensation characteristics to the programs. In a sense, activities are analogous to intermediate language (IL) opcodes or program statements, but contain domain-specific wisdom. In short, Windows Workflow Foundation makes your program semantics declarative and explicit, and it lets you model your applications close to the real-world process. It's the right tool for the job. You wouldn't write a front-end visual application using IL, but rather you would use a RAD development tool and a more human-readable language. The Windows Workflow Foundation SDK provides an extensible programming language designed for modeling complex business processes, especially when these processes may evolve over time. In this case, the key benefit is that you add ad hoc activities to address the flow of work and use built-in behavioral activities to control that flow. You focus on tasks, the runtime does the rest. If you want to further explore Windows Workflow Foundation or look for other ideas, visit Windows Workflow Foundation.

Send your questions and comments for Dino to  cutting@microsoft.com.

Dino Esposito is a mentor at Solid Quality Learning 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 with Dino at cutting@microsoft.com or join the blog at weblogs.asp.net/despos.