Foundations

Workflow Services

Matt Milner

Code download available at: Foundations2008_Launch.exe(174 KB)

Contents

What's Required for Integration
Modeling Communications with Send and Receive Activities
Hosting Workflows as Services
Hosting Using IIS or Windows Activation Services
Using Context to Manage Long-Running Services

When a new version of Visual Studio® is released, the first thing developers typically want to know is what new features they will find in their favorite programming tool. Although Windows® Workflow Foundation (WF) and Windows Communication Foundation (WCF) were released in November 2006 as part of the Microsoft® .NET Framework version 3.0, it is only with the release of Visual Studio 2008 and the .NET Framework 3.5 that people are becoming aware of the great features these technologies provide.

Unfortunately, with the .NET Framework 3.0, there was not a very good model for combining the declarative programming model of Windows WF with the simplified yet rich communications model exposed by WCF. What's compelling about Windows WF in the .NET Framework 3.5 is that it provides true integration with WCF. This integration means that workflows can now more easily consume and coordinate services and be exposed through service endpoints.

What's Required for Integration

When considering the integration of Windows WF and WCF, there are several key areas of focus. The primary components of any workflow are the activities that allow modeling of application logic. In the .NET Framework 3.5 two new activities, Send and Receive, provide a generic model both for consuming services and for implementing service endpoints within a workflow. I will talk more about how to use these activities shortly.

In addition to being able to model communication, integration requires a mechanism for hosting the runtimes of both WCF and Windows WF. Because each technology has its own runtime, configuration, and extensibility model, we need a way to host both technologies in a single process. The tools provided in the new framework are based on extensibility points in WCF that allow the workflow runtime to be hosted by the WCF runtime.

The final item required for integration is really the crux of the matter: how to marshal WCF messages into workflow instances and map those operations onto activities in the workflow. Again, this feature relies on the WCF extensibility model and is responsible for issues around security, workflow instance management, and marshaling the message bodies as data to activities.

A new assembly, System.WorkflowServices.dll, holds the new classes that provide the integration between Windows WF and WCF. This new assembly is specific for .NET 3.5, while the existing 3.0 assemblies remain unchanged except for bug fixes. By using the existing extensibility models of WCF and Windows WF, it was possible to provide integration without having to modify existing components. I will cover the core classes used when integrating these technologies and explain how they fit together.

In addition to the new assembly, Visual Studio 2008 comes with a few new project and item templates specifically for building workflows to be published as services. In the new project dialog, you will find both the Sequential Workflow Service Library and State Machine Workflow Service Library projects under the WCF category. You will also find new item templates for Windows WF Sequential Service and Windows WF State Machine Service items and their XAML counterparts. Each of these new templates adds the workflow, the interface defining the service contract, and an app.config file that configures the endpoints for the service.

Modeling Communications with Send and Receive Activities

The new receive activity is used to model points in the workflow where your process will receive data as part of a WCF service contract. This means that each receive activity in your workflow will need to be configured with the appropriate information related to the service contract and be tied to an operation on that contract. In order to accomplish this binding between the activity and the operation, the activity has a ServiceOperationInfo property.

The contract can be declared in one of two ways: using WCF service contract attributes or directly within the workflow. The latter is often referred to as "workflow first" design, in contrast to "contract first" design. When defining the contract directly in the workflow, the contract information is embedded within the workflow definition rather than exposed as a .NET interface, as it is when defining a contract with WCF.

When you edit the ServiceOperationInfo property, you are presented with a dialog that allows you to choose the operation, and this is also where you must decide if you are going to use an existing service contract or define a new one. The dialog is shown in Figure 1 and has two buttons in the upper-right corner that allow you to import a service contract or add a new contract. If you choose to import a contract, all operations marked with an OperationContract attribute are imported into the dialog where you can choose which operation your activity implements. In the bottom of the dialog, there are three tabs showing parameters, properties and permissions for the operation. When you import the contract, the parameters and properties are drawn directly from the service contract and may not be changed.

Figure 1 ServiceOperationInfo Configuration Dialog

Figure 1** ServiceOperationInfo Configuration Dialog **(Click the image for a larger view)

During the creation of a service contract, the dialog lets you configure the properties on each operation to properly shape the service contract. You specify the parameters and return types of the operation and determine the message exchange pattern by setting the IsOneWay property. Additionally, you can specify the protection level to be applied to your messages so that they will be signed and/or encrypted.

After the contract is imported or defined and the operation selected for the receive activity, there are still a few more items to configure on the activity for basic receiving. First is the CanCreateInstance property, which tells the runtime whether the workflow can be started with this operation. Each workflow that will be exposed as a service must have at least one receive activity with the CanCreateInstance property set to true; otherwise there's no way to start the workflow through a service invocation.

The receive activity is implemented as a composite activity, meaning it can have child activities. Essentially, the receive activity works like a sequential activity in that it executes all of its child activities in order until completion. Figure 2 shows a receive activity implementing an operation using a child activity. These child activities make up your implementation of the service operation. At the end of the execution, the receive activity will either return the value bound to its return value property, or it will raise the fault bound to its fault property.

Figure 2 Receive Activity Implementing an Operation

Figure 2** Receive Activity Implementing an Operation **

There are two key points to remember about the work you do within the receive activity: you should set the return value or fault property during execution, and any work you do must complete before the service call can return. Setting the output or fault is important, but missing this step presents itself pretty early in the testing cycle. What is sometimes less obvious is the duration of your work, which can impact performance of your service. Because you are in the workflow designer, it can be easy to forget that you are coding a service operation and that a client application is waiting for a response. If your operation is a one-way operation, obviously this is less of a concern and there's also no need to set the output or fault properties, as the client will not receive either.

The send activity consumes services from within a workflow, allowing you to use workflows to coordinate services or to take advantage of logic and data residing in a service. In WCF, clients create channels configured with endpoint information to invoke a service operation. The send activity uses the same ServiceOperationInfo dialog to allow you to import a service contract and choose the operation you want to invoke. The send activity designer then dynamically creates properties for the parameters and return value of the operation you have chosen so that you can bind or set those values in your workflow before calling the service.

To configure the client endpoint, you use two properties: ChannelToken and CustomAddress. The ChannelToken has an EndpointName property that should be configured with the name of a WCF client endpoint found in the configuration file of the process hosting the workflow. This endpoint will contain the address and binding information the runtime needs to create a client channel and invoke the operation at runtime. In addition, the CustomAddress property allows you to change the address, probably the most dynamic part of the endpoint, at runtime or get that value from data that is present in the workflow context.

Other key features of the send activity include events for pre- and post-processing and caching of WCF channels at a scoped level. The events provide hooks for your code to run and either prepare the send activity and its parameters for processing or process the result of the operation invocation before control proceeds to the next activity in the workflow. The caching provides a performance improvement if you are going to make multiple calls to the same service within a workflow. The creation of the client channel and negotiation of security and other protocols can be expensive, and the ability to cache the channel can reduce the time spent on these steps. You configure the scope of the caching by using the ChannelToken property on the send activity and choosing a parent composite activity for the OwnerActivityName property on the token. To reuse this same channel on another send activity, you set the ChannelToken property to the one used in the other activity.

Hosting Workflows as Services

For workflows that receive data through WCF services, the workflow must be hosted just like any other WCF service. The hosting of the service creates the WCF runtime, which is responsible for receiving messages over the network, processing those messages, and then delivering them to a service. If the service is implemented as a workflow, the message payload must be sent to the workflow instance where the receive activity is waiting for data. For more information on how data is communicated to the workflow, see my previous column on workflow communications (msdn.microsoft.com/msdnmag/issues/07/09/Foundations).

In order to host a workflow service you use the new class, WorkflowServiceHost, which derives from the ServiceHostBase class found in System.ServiceModel. Note that the ServiceHostBase class provides the basic logic to host a WCF service and is responsible for building up the WCF runtime including the channels, behaviors, and so forth. When a developer wants to customize the WCF runtime, creating a custom service host is one option for doing so. The hosting model used with workflow services depends heavily on extending the WCF runtime model. For more information on extending WCF, see the Service Station column in the December 2007 issue of MSDN® Magazine (msdn.microsoft.com/msdnmag/issues/07/12/ServiceStation).

The code that follows shows hosting a simple workflow type using the WorkflowServiceHost class in a .NET application:

WorkflowServiceHost host = new WorkflowServiceHost(typeof(Workflows.SimpleWorkflow)); host.Open();

In this example, I have passed the type information for the workflow to the constructor of the WorkflowServiceHost. This is exactly how you construct a ServiceHost for a standard WCF service.

When defining workflows, you can either define the structure in a new class or define your workflow using Extensible Application Markup Language (XAML). The WorkflowServiceHost provides several overloaded constructors (see Figure 3). Not only can you provide the path to your XAML file, but you can also provide a stream. This allows you more flexibility in where you store workflow definitions. In addition, these overloads take paths or streams for the XML files containing the rules for your workflow. In these overloaded constructors you can begin to see the overlap of the two technologies as the CreateWorkflow method of the WorkflowRuntime and the constructor of the ServiceHost class get combined into the WorkflowServiceHost.

Figure 3 WorkflowServiceHost Constructors

public WorkflowServiceHost(Stream workflowDefinition, params Uri[] baseAddress); public WorkflowServiceHost(string workflowDefinitionPath, params Uri[] baseAddress); public WorkflowServiceHost(Type workflowType, params Uri[] baseAddress); public WorkflowServiceHost(Stream workflowDefinition, Stream ruleDefinition, params Uri[] baseAddress); public WorkflowServiceHost(string workflowDefinitionPath, string ruleDefinitionPath, params Uri[] baseAddress); public WorkflowServiceHost(Stream workflowDefinition, Stream ruleDefinition, ITypeProvider typeProvider, params Uri[] baseAddress); public WorkflowServiceHost(string workflowDefinitionPath, string ruleDefinitionPath, ITypeProvider typeProvider, params Uri[] baseAddress);

Once the WorkflowServiceHost has been created, you can configure the WCF runtime just as you would with any other service using either code or configuration. This allows you to add endpoints or behaviors to the runtime or modify the service description. When editing workflow definitions using pure XAML, the name used to identify the service in the configuration file depends on the markup you use. For example, if you use the x:Name attribute, the value becomes the name of your service. However, if you omit the name attribute, then the name of the first activity will be used, which is generally not a very unique name across workflows.

Because the WCF hosting model is used for the service, you access the WorkflowRuntime through the WCF host. New to the .NET Framework 3.5 is the WorkflowRuntimeBehavior that gets added automatically by the WorkflowServiceHost if you do not explicitly add it to the WCF description. Through this behavior you can configure the WorkflowRuntime object in code or in the configuration file. In the configuration file, you define the services you want to add to the runtime using a service behavior configuration that includes this new element. Then you configure the services as you would normally when configuring the Windows WF runtime. Figure 4 shows an example of a configuration file where the runtime is being configured.

Figure 4 Configuring the Workflow Runtime via Behaviors

<behaviors> <serviceBehaviors> <behavior name="wfService"> <serviceMetadata httpGetEnabled ="true"/> <workflowRuntime cachedInstanceExpiration="00:10:00" validateOnCreate="false"> <services> <add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService ..." ConnectionString="server=.;database=wfpersist;integrated security=SSPI" UnloadOnIdle="false"/> </services> </workflowRuntime> </behavior> </serviceBehaviors> </behaviors>

One configuration item not available on the standard workflow configuration section is the cachedInstanceExpiration attribute, which lets you configure how long the runtime keeps a workflow instance in memory after it goes idle. This provides a bit more control than the base functionality of the SQL persistence service workflows as soon as they go idle.

When you want to access the workflow runtime in the host code, you first must access the behavior, and then get the runtime from the properties on the behavior. To access the behavior, you navigate to the service description on the WorkflowServiceHost and then find the WorkflowRuntimeBehavior by type. The behavior has a strongly typed property for accessing the runtime, which you can then interact with to add services, register event handlers, or write any other host-specific, workflow runtime-related code. Figure 5 provides an example of accessing the runtime to add services and register handlers for hosting related events.

Figure 5 Accessing the Workflow Runtime in the Host Code

WorkflowServiceHost host = new WorkflowServiceHost(typeof(Workflows.SimpleWorkflow)); WorkflowRuntimeBehavior wfbehavior = host.Description.Behaviors.Find<WorkflowRuntimeBehavior>(); WorkflowRuntime wfruntime = wfbehavior.WorkflowRuntime; wfruntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs wte) { Console.WriteLine(wte.Exception.Message); }; host.Open();

After you have modified the objects in the WCF and Windows WF runtimes in code, simply call Open on the WorkflowServiceHost, and it builds and configures both runtimes and registers all service endpoints to listen for data. Using WorkflowServiceHost directly is known as self-hosting, in contrast to using a provided host process such as IIS or Windows Process Activation Service (WAS)—each of these is discussed in the next section.

Because the WorkflowRuntime is managed by a WCF behavior, there will be a single WorkflowRuntime for each WorkflowServiceHost and thus for each service in your host. (This is in contrast to the common practice in Windows WF of having a single WorkflowRuntime that hosts many different types of workflows.) You will also have multiple instances of the runtime services you have chosen, and each will be running independently. As an example, if you have three services hosted in the same application, and each is configured to use the SQL persistence service, then you will have three instances of the persistence service running, each one polling the database periodically for expired timers. If you configure these services with the same connection string, they will act as three different host processes so ownership duration and locking may become an issue. Finally, note that this behavior exists even if you configure all services with the same behavior configuration. Because it is only configuration, each WorkflowServiceHost will create a new instance of the behavior, which will in turn create a new WorkflowRuntime.

Hosting Using IIS or Windows Activation Services

Hosting services in IIS or WAS is similar in many ways to previous Web service technologies such as the ASMX Web services in the .NET Framework 2.0, but with more extensibility options. A typical WCF service endpoint hosted in IIS uses a file with the .svc extension to act as the addressable resource that will be invoked by clients. This file simply indicates the service type used to implement the service, and the .svc extension is mapped to the WCF handler so it can properly route messages to that service.

One key extensibility point in this model is that you can take control of the ServiceHost creation process by creating a custom factory. In general, the default IIS hosting works out of the box for a large number of scenarios and creates a ServiceHost instance for the service named in the .svc file. However, by allowing you to customize the service host, you can take advantage of the hosting features in IIS without having to give up control of the host completely.

To host a workflow service, you create the .svc file and specify either the workflow type or the path to a XAML file containing the workflow definition, and you specify a new type, WorkflowServiceHostFactory for the Factory attribute. Here's a basic workflow service defined in an .svc file:

<%@ ServiceHost Language="C#" Service=" SimpleWorkflow.xoml" Factory="System.ServiceModel.Activation.WorkflowServiceHostFactory" %>

Notice that the service is defined as the path to a XAML file. The WorkflowServiceHostFactory is responsible for loading the XAML file and using it, along with a rules file if there is one present, to create a service host for the workflow and return it when the WCF runtime requests it. The rules file must have the same name as the XAML file but with a .rules extension. This provides a very powerful model where you can drop a few text files—the XAML, .rules, and .svc files—into a virtual directory and have only to add configuration for endpoints to the configuration file.

Using Context to Manage Long-Running Services

Using workflows to implement your service becomes especially compelling when the service is long-running and has multiple interactions with a client. For example, suppose you have a service to manage an expense report approval process. This service, implemented as a workflow, could have at least two interaction points, including the initial submission by the employee and the approval or rejection by the manager. In such scenarios, you need a way to manage the workflow over time and to route messages to the correct workflow instance. In other words, you need a way to manage the context of your messaging.

In .NET Framework 3.5, the support for context is manifested in a new binding element called ContextBindingElement. This element allows developers to create bindings that are context-aware and can help manage the routing of messages to the correct workflow instance. The framework also includes three pre-built bindings for the most common scenarios: WSHttpContextBinding, BasicHttpContextBinding, and NetTcpContextBinding. Each of these bindings matches its non-contextual counterpart in features and configuration but adds the context channel into the runtime stack. This context channel manages the unique conversation identifiers for the interactions as they flow through the channel stack.

Figure 6 shows how this context management works with the included context channel. When a client channel is used to interact with a service, the context information is sent back to the client on the first message exchange. This information minimally contains the instance ID of the workflow that was created to handle the request. The next time the client channel is used to call the service, the context information is sent back to the service where it is used to route the message to the already running workflow instance.

Figure 6 Context Management

Figure 6** Context Management **(Click the image for a larger view)

In addition to routing messages to the correct instance, some interactions between services get more complex. For example, a workflow might initiate communication with three different instances of a service using the send activity and then wait to receive responses from all three instances. In this case, the workflow will have three instances of the receive activity waiting for the same message type to arrive. As a result, there needs to be a mechanism for routing the incoming message not only to the correct workflow but also to the correct receive activity within the workflow. The context mechanism supports not only the passing of the instance ID but also includes a conversation identifier for those scenarios where the workflow is waiting for multiple messages on the same operation. Figure 7 provides an example of this type of scenario.

Figure 7 Conversation Management

Figure 7** Conversation Management **(Click the image for a larger view)

The context is managed as a collection of name-values pairs and can be passed via SOAP headers or an HTTP cookie. The context channel handles this for you in most cases at the lower layers, but as a workflow author, you are responsible for managing the context within your workflow. In the previous example where I called out to three instances of a service, each instance of that service would need to have code that set the context before it could send a response to my workflow service. This means that, in some way, the calling workflow must send the context information without using the built-in channel features, and the called service must use that context information when later calling back into the workflow. The send activity conveniently contains a context property that can be set before the service is called, which then causes the activity to set the context on the channel before invoking the service.

If all communication is initiated from the calling workflow, the need to pass the context to the called workflow is irrelevant as the called workflow can simply send responses to the requests it receives. While this implementation mainly targets intranet-style applications and has some limitations, it does provide a framework for extensibility, which I'll cover in a later issue.

Send your questions and comments to mmnet30@microsoft.com.

Matt Milner is an independent software consultant specializing in Microsoft technologies including .NET, Web services, Windows Workflow Foundation, Windows Communication Foundation, and BizTalk Server. At Pluralsight, Matt teaches courses on Workflow, BizTalk Server, and Windows Communication Foundation. Contact Matt via his blog at pluralsight.com/blogs/matt.