Cutting Edge

Windows Workflow Foundation, Part 2

Dino Esposito

Code download available at:CuttingEdge0604.exe(153 KB)

Contents

ASP.NET and Workflows
Consuming a Workflow in ASP.NET
Managing the Execution of the Workflow
Configuring ASP.NET Apps for Workflow
Calling Workflow Components from ASP.NET
Exposing a Workflow as a Web Service
Conclusion

With the RTM release of Windows Workflow Foundation (WF), some of the information described in this article is no longer accurate. For updated information, please see the following resources.
Coming improvements to ASP.NET hosting of Windows Workflow Foundation after WF Beta 2.2
Hosting Quickstart Sample for Windows Workflow Foundation
Windows Workflow Foundation Web Workflow Approvals Starter Kit

In last month's column, I presented a helpdesk workflow sample that focused on Windows® Forms client applications. This month I'll discuss ASP.NET workflow applications and the ability to expose a workflow as a Web service and invoke a Web service from a workflow.

ASP.NET and Workflows

A workflow built on Windows Workflow Foundation is a component that requires an ad hoc runtime environment to function. The workflow runtime environment is represented by the WorkflowRuntime class. To host a workflow library, you create and configure an instance of the WorkflowRuntime class to operate on a particular workflow type. For performance reasons, you normally create the runtime environment only once in the application lifetime and make it serve all incoming requests. In a Windows Forms application, you initialize the workflow runtime in the form's constructor and destroy it with the form when the application shuts down. So how does this work if you're using ASP.NET?

Once you have a workflow component up and running, calling it from within a Web app or Windows Forms shouldn't be an issue. As far as Windows Workflow Foundation-based workflows are concerned, ASP.NET developers have only a few small issues to face that are mostly related to the nature of Web applications.

Just like in Windows Forms, with ASP.NET you need to have just one instance of the workflow runtime created when the application starts. Unlike Windows Forms applications, though, a Web application works by accepting and processing requests. Requests are treated individually and don't know anything about each other. That leads to issues related to workflow persistence and threading.

Here's the persistence problem: the instance of the workflow type you're working with must be persisted across successive requests. You process a request, create an instance of the workflow, and get some results to generate the response for the user. When a successive request arrives, you probably need to resume the workflow from its old state and continue. In Windows, two consecutive user actions occur in the same operational context; in ASP.NET, two user actions take two distinct requests with no state being automatically maintained. In order for a workflow to work properly in the context of ASP.NET applications, you need to be able to persist its state across requests.

Here's the threading problem: by default, the workflow runtime functions asynchronously. When the runtime receives the order to spawn an instance of a given workflow type, the Start method allocates a new thread on which to initiate the workflow. The WorkflowCompleted event gives the caller a chance to detect when the workflow completes so it can refresh the user interface. As you can see, this model raises a structural problem in ASP.NET. Imagine a Web page that places a call to Start in a postback event. The page proceeds as soon as the Start method on the WorkflowRuntime class returns. Since it is asynchronous, the Start method starts a new thread to process the workflow, and then returns immediately to ASP.NET. The page will likely reach its completion before even the world's simplest workflow has completed. As a result, the results of the workflow process can't be incorporated into the page that is sent to the final user. To render the page only after the workflow interaction has completed, you need to synchronize the page rendering with the workflow execution.

Both the persistence and the threading synchronization issues are solved through workflow services. When hosting a workflow in an ASP.NET application, you must employ a richer runtime environment that supports both a synchronous dispatch model for requests and automatic data serialization when the workflow is idle. I'll tackle that requirement here.

Consuming a Workflow in ASP.NET

Let's review all the steps required to set up an ASP.NET page that consumes a workflow. I'll begin by building a simple workflow that includes only a Code activity, a workflow item that lets you add .NET-based code into the workflow. The example will be a workflow that implements an authentication service and validates credentials.

While in most cases using a workflow for such a task is overkill, I have worked on a couple of projects where the authentication process was quite complex, justifying the use of a workflow. Unfortunately there was no Windows Workflow Foundation around at the time. Be that as it may, what matters is that you end up with a workflow like the one in Figure 1. The workflow works by receiving a couple of parameters from its host—typically a user name and password—and returns a Boolean value that indicates whether the user has been authenticated. Figure 2 shows the list of parameters defined on the workflow.

Figure 2 Workflow Input and Output Parameters

Parameter Description
UserName Input parameter indicating the name of the user
Password Input parameter indicating the password for the user
IsAuthenticated Output parameter indicating whether the user has been authenticated

Figure 1 Sample Authentication Workflow

Figure 1** Sample Authentication Workflow **

You add parameters to a workflow by exposing public properties from the workflow class, typically backed by local members. In Figure 2, I have defined two input parameters to bring in the user name and password to authenticate. I have also defined a Boolean output parameter to return the result of the authentication process. In code, you pass input parameters to a workflow through a name/value dictionary where each entry matches the name of a public property on the workflow class. You pass the dictionary to the CreateWorkflow method that creates and runs an instance of the workflow.

Dictionary<string, object> parameters = new Dictionary<string, object>(); parameters.Add("UserName", UserName.Text); parameters.Add("Password", Password.Text);

You receive the output of a workflow through the OutputParameters collection exposed by the WorkflowCompletedEventArgs instance provided to handlers for the WorkflowCompleted event. More precisely, the OutputParameters collection contains values from all public properties on the workflow class and doesn't distinguish between read/write and read-only properties. Figure 3 shows the codebehind file of the LoginWorkflow. The event handler associated with the CheckCredentials activity calls an internal function that performs the task of authenticating the provided credentials. The result is stored in the isAuthenticated private member variable. As you can see in Figure 3, this value acts as the data repository for the IsAuthenticated output parameter.

Figure 3 LoginWorkflow Class Codebehind

namespace Samples { public sealed partial class LoginWorkflow : SequentialWorkflowActivity { private bool isAuthenticated; private string userName; private string passWord; public string UserName { get { return userName; } set { userName = value; } } public string Password { get { return passWord; } set { passWord = value; } } public bool IsAuthenticated { get { return isAuthenticated; } } public LoginWorkflow() { InitializeComponent(); } private void CheckCredentials_ExecuteCode( object sender, EventArgs e) { isAuthenticated = Authenticate(userName, passWord); } private bool Authenticate(string user, string password) { return true; // Do something more serious here :-) } } }

So much for the login workflow. Let's take a look at the ASP.NET application, its pages, and configuration file.The key points to address while building an ASP.NET page that uses workflows can be seen with a relatively simple page that contains user name and password boxes, a Login button, and a message welcoming the user. When the user clicks on the Login button, you need to retrieve or create the workflow runtime and start a new instance of the workflow type you're using.

Managing the Execution of the Workflow

Last month, I created a workflow runtime object using the new operator and the WorkflowRuntime class constructor, like so:

WorkflowRuntime wr = new WorkflowRuntime();

Using this code in ASP.NET is fine as long as you place it in Application_Start and then find a way to make the object that is created reachable from within your page code. Instantiating the workflow runtime for each request results in an exception because the workflow runtime can be loaded more than once in the same AppDomain. Two ASP.NET requests have distinct state and context, but run in the same AppDomain. As a result, the first request works while the second throws an exception.

Windows Workflow Foundation provides a built-in infrastructure to guarantee that there's exactly one workflow runtime object per AppDomain and that you don't have to worry about its creation and instantiation. In ASP.NET, in fact, you can use the WorkflowWebRequestContext class to access the current and correct instance of the workflow runtime. This code shows how to get a reference to the workflow runtime:

WorkflowRuntime wr = WorkflowWebRequestContext.Current .WorkflowRuntime;

Internally, the get accessor of the Current property calls into a public member of a static class that caches the workflow runtime instance, creating a new instance if one cannot be found. Once you've got a reference to the runtime object, you can register the handler for the WorkflowCompleted event and start the actual workflow. This code shows how:

wr.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(LoginCompleted); Type t = typeof(Samples.LoginWorkflow); WorkflowInstance instance = wr.CreateWorkflow(t, parameters); instance.Start();

When the workflow returns, the code of LoginCompleted runs (see Figure 4) and checks the IsAuthenticated entry in the OutputParameters collection. OutputParameters is one of the event data arguments you get through the WorkflowCompletedEventArgs class. The contents of OutputParameters is determined by the runtime when the workflow completes.

Figure 4 ASP.NET Code Calling into a Workflow

using System; using System.Web; using System.Collections.Generic; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Workflow.Runtime; using System.Workflow.Runtime.Hosting.Web; using System.Workflow.Runtime.Hosting; public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { SetFocus("UserName"); } protected void LoginButton_Click(object sender, EventArgs e) { // Define the parametes for the workflow Dictionary<string, object> parameters = new Dictionary<string, object>(); parameters.Add("UserName", UserName.Text); parameters.Add("Password", Password.Text); // Get a reference to the Workflow runtime WorkflowRuntime wr = WorkflowWebRequestContext.Current.WorkflowRuntime; // Attach to the WorkflowCompleted event wr.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(LoginCompleted); // Start the Workflow1 WorkflowInstance workflowInstance = wr.CreateWorkflow(typeof(Samples.LoginWorkflow), parameters); workflowInstance.Start(); } void LoginCompleted(object sender, WorkflowCompletedEventArgs e) { bool isAuth = (bool)e.OutputParameters["IsAuthenticated"]; CurrentUser.Text = isAuth ? String.Format("Welcome, {0}", UserName.Text) : String.Format("Sorry {0}. You can't be authenticated." + " Try again.", UserName.Text); } }

The code in Figure 4 is not enough for the workflow to work correctly and for the ASP.NET page to serve users pages that incorporate workflow results. As I mentioned, you need to add a couple of services to the runtime—one for state persistence and one for synchronous request dispatching. The first idea that springs to mind is to bind services once you've got the workflow runtime instance. Here's some sample code (note that wr denotes the workflow runtime instance):

String connString = "..."; SqlWorkflowPersistenceService servState = new SqlWorkflowPersistenceService(connString); wr.AddService(servState);

When you run this code, you get an exception stating that you can't further configure the runtime once it has started. But who really started the runtime anyway, since there is no code in the page that would?

The culprit is the WorkflowWebRequestContext class I mentioned earlier. This class creates an instance of the workflow runtime from within an ASP.NET application. Then it attempts to read a section in the Web.config file named WorkflowRuntime, which is supposed to list all the services you want to bind to the runtime. Can you guess what happens? The reference to the workflow runtime you get through WorkflowWebRequestContext.Current corresponds to an instance that has already been configured with services and started. To control the services that are bound to the runtime in an ASP.NET application you need to tweak the Web.config file, as shown in Figure 5.

Figure 5 Web.config for Consuming Workflow Components

<?xml version="1.0"?> <configuration xmlns="https://schemas.microsoft.com/.NetConfiguration/v2.0"> <configSections> <section name="WorkflowRuntime" type="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection, System.Workflow.Runtime, ..."/> </configSections> <WorkflowRuntime Name="WorkflowServiceContainer"> <Services> <add type= "System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService, System.Workflow.Runtime, ..." /> <add type= "System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService, System.Workflow.Runtime, ..." /> <add type= "System.Workflow.Runtime.Hosting.DefaultWorkflowTransactionService, System.Workflow.Runtime, ..." /> </Services> </WorkflowRuntime> <system.web> <httpModules> <add name="WorkflowHost" type="System.Workflow.Runtime.Hosting.WorkflowWebHostingModule, System.Workflow.Runtime, ..." /> </httpModules> </system.web> </configuration>

Configuring ASP.NET Apps for Workflow

Windows Workflow Foundation uses the <workflowRuntime> section in the configuration file to store information for internal use. This section is not a default part of the configuration schema; for this reason, you have to register it explicitly.

<configuration> <configSections> <section name="WorkflowRuntime" type="WorkflowRuntimeSection, System.Workflow.Runtime" /> </configSections> ... </configuration>

In ASP.NET 2.0, when you register a new configuration section, you specify the name and assembly of a class that inherits ConfigurationSection. For the <WorkflowRuntime> section, the class is WorkflowRuntimeSection. The class is defined in the System.Workflow.Runtime assembly as part of the System.Workflow.Configuration namespace. Note that in ASP.NET 2.0, custom configuration sections are created in a different way from ASP.NET 1.x, where you had to create a class and implement the IConfigurationSectionHandler interface. The interface methods required that you parsed the XML markup below the node. In ASP.NET 2.0, the base class contains a default parser and, at least in most cases, requires that you define the expected public interface of the section through properties.

The <WorkflowRuntime> section contains a nested section named <Services> where you list all services that you want to be bound to the runtime. Windows Workflow Foundation comes with a number of predefined services including SqlWorkflowPersistenceService and ManualWorkflowSchedulerService. The former service automatically persists the workflow instance to the specified SQL Server™ database as soon as the workflow becomes idle (while waiting for human intervention, for instance). Even more relevant for ASP.NET applications is the scheduler service. You need the service to ensure that the ASP.NET pooled thread in charge of executing the current request waits until the workflow is completed. More precisely, the service guarantees that the execution of the workflow is synchronous and that the Start method returns only when the workflow has ended or is idle.

Calling Workflow Components from ASP.NET

Calling workflow components from within ASP.NET is definitely possible once you resolve two issues: persistence and dispatching. It is interesting to note that, in general, you can create an instance of the workflow runtime from a configuration section—and not just a section in the ASP.NET Web.config file. You do this by using another overload of WorkflowRuntime constructor:

WorkflowRuntime wr = new WorkflowRuntime(sectionName);

The section is assumed to have the structure of the <WorkflowRuntime> section discussed earlier, but can have any name. The WorkflowWebRequestContext takes advantage of this feature and imposes the name <WorkflowRuntime> on the section of the Web.config file. But the underlying mechanism is quite general.

Figure 6 Web Front-End for the HelpDesk Workflow

Figure 6** Web Front-End for the HelpDesk Workflow  **

You can also use the <WorkflowRuntime> section to register custom services. As Figure 6 shows, the HelpDesk workflow that I created in last month's column works through an ASP.NET client. The HelpDesk workflow is based on a custom service providing external events to the workflow. In a Windows Forms client, you would use the following code:

theHelpDeskService = new HelpDeskService(); wr.AddService(theHelpDeskService);

In an ASP.NET client, you resort to an extra node under the <Services> section of the <WorkflowRuntime> element:

<Services> <add type="Samples.HelpDeskService, HelpDeskWorkflow" /> </Services>

There are two main types of workflow: sequential and state machine. Sequential workflows represent a sequence of activities and terminate only when all activities have been completed. State machine workflows are an event-driven, asynchronous graph of activities in which each activity represents a state and can transition to a well-known subset of states. Both types of workflow can be consumed from within ASP.NET applications. Interestingly, you could even craft a user interface, wrap it up in a Web Part, and host the workflow in a SharePoint® (Service Pack 2) application or in a portal-like ASP.NET 2.0 application.

Sequential workflows, though, might be dangerous for ASP.NET applications because they express potentially lengthy operations. A sequential workflow that doesn't stop waiting for external events might be engaged in long-running transactions and represents a serious threat to the scalability of the overall application.

Asynchronous pages are the perfect countermeasure for pages that need to perform long tasks, as Jeff Prosise explained in his October 2005 Wicked Code column. Asynchronous ASP.NET pages are based on a pair of Begin/End methods. As of Beta 2 of Windows Workflow Foundation, there's no such pair of built-in methods to start a workflow. However, a Windows Workflow Foundation workflow can be published as a Web service and, just like a Web service, can be invoked asynchronously through the ad hoc methods of the Web service proxy class.

Exposing a Workflow as a Web Service

Figure 7 Workflow

Figure 7** Workflow **

The Windows Workflow Foundation framework makes it possible for you to expose a workflow as a Web service to .NET-based clients as well as to other workflows. To expose a workflow as a Web service, you have to write it in a special manner. Only a workflow that uses the WebServiceReceive activity can be published as a Web service. The idea is that you create a workflow project and, at least in the simplest case, add a single pair of WebServiceReceive and WebServiceResponse activities to it. In more complex cases you might want to use a Listen activity to receive input about which activity, or set of activities, should run. In the simplest common case the workflow is activated by calling a method on the Web service, proceeds to the end, and returns output values.

The interface exposed through the Web service is defined inside the workflow class. After creating, say, a new sequential workflow, you add an interface definition to the codebehind class:

public interface ILoginWorkflow { bool Authenticate(string user, string pswd); }

Only the methods listed in the interface will be callable from external clients through the Web service. The structure of the workflow may be as simple as a pair of WebServiceReceive and WebServiceResponse activities (see Figure 7).

The WebServiceReceive activity is bound to a particular method of the Web service interface and receives data when the method is called. For example, you bind the WebServiceReceive activity to the Authenticate method of the ILoginWorkflow interface. The input arguments of the method must be bound to public members of the workflow class:

public string receivedUser = default(System.String); public string receivedPswd = default(System.String);

The WebServiceResponse activity responds to a request made through a Web service interface. Note that you can only use a WebServiceResponse activity in a workflow that also has a WebServiceReceive activity. An essential property of the activity is ReceiveActivityId, which indicates the ID of the related WebServiceReceive activity. You can only use WebServiceReceive activities located in the same workflow. The return value of the method must also be bound to a public member in the workflow class:

public bool returnedAuthenticate = default(System.Boolean);

Basically, the interaction between the client the Web service, and the workflow can summarized as follows. The client references the Web service and places a call to one of its methods. The Web service starts the workflow and invokes the WebServiceReceive activity, passing input parameters. The receive activity caches the input data. Possible intermediate activities (Code, Parallel, If, and so on) run as usual. At the end, the WebServiceResponse activity is invoked to generate the return value. The WebServiceResponse activity is expected to cache the return value into the bound public member, the Boolean returnedAuthenticate member in the previous line of code.

A workflow exposed through a Web service can be thought of as a regular workflow that is wrapped by a pair of WebServiceReceive and WebServiceResponse activities.

To generate handlers that manage input and output data, you right-click on the receive/response activities and click Generate Handlers. At the minimum, do that for WebServiceResponse so that some return value can be sent to the caller. When you're done with the code, you right-click on the project and select Publish as Web service (see Figure 8). The workflow designer creates a small Web service project with a Web.config file, an .asmx endpoint, and the assembly that contains the workflow. Then you should deploy these files on IIS 6.0 and call the workflow.

Figure 8 Publishing a Workflow

Figure 8** Publishing a Workflow **

It is interesting to take a look at the Web.config file that is generated for you. In addition to the <WorkflowRuntime> section that I discussed earlier in the column, the configuration requires an HTTP module:

<httpModules> <add type="System.Workflow.Runtime.Hosting.WorkflowWebHostingModule, System.Workflow.Runtime" name="WorkflowHost"/> </httpModules>

The HTTP module is responsible for instantiating and managing the workflow runtime and routing Web service requests to the appropriate workflow instance. The HTTP module maps incoming requests to the right workflow instance using the ASP.NET session state information.

In production code, you should significantly increase the timeout of your session state (the default is 20 minutes) and opt for database storage of session state to ensure that workflow instance IDs are not lost in the event of AppDomain shutdown and process recycling. Note that the HTTP module is designed to work well in Web farm scenarios.

The HTTP module leverages the ASP.NET session state locking mechanism to ensure that requests directed at the same workflow instance are serialized. The ASP.NET session state locking mechanism guarantees that requests in the same session are queued. Due to this locking mechanism, you cannot have interleaved receive/response pairs in a workflow. Most of the time, to expose a workflow through a Web service, you simply take an existing workflow and wrap it with a pair of receive/response activities. The input for the workflow is gathered by the WebServiceReceive activity and the WebServiceResponse component collects the return values.

Conclusion

ASP.NET applications are yet another possible host for Windows Workflow Foundation workflows. Due to the special nature of ASP.NET applications, hosting a workflow requires a bit of attention. In particular, you need to enrich the run-time environment with a couple of run-time services that come out of the box—namely the threading and SQL Server persistence services.

The threading service, in particular, ensures that the page is rendered only when the workflow completes or becomes idle. What if a workflow takes too long to complete? In this case, you can consider exposing the workflow as a Web service and binding it to an ASP.NET asynchronous page. Having workflow exposed as Web services also increases the reach of the workflow and allows you to call it from other platforms.

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 by e-mailing him at cutting@microsoft.com or join the blog at weblogs.asp.net/despos.