WF Guidance: Workflow Services
Status: Final Draft, .NET 4 Beta 2
Last Modified: 1/11/2010 11:42:42 AM

1  Introduction and scope

This paper presents example-oriented step-by-step instructions for redesigning workflows that implement Windows Communication Foundation (WCF) web services (commonly referred to as workflow services) created in the current implementation of Windows Workflow Foundation (consisting of the types in the System.Workflow.* namespaces; referred to in this document as WF3) to workflows that use the new WF implementation that is being introduced in .NET 4 (the types in the System.Activities.* namespaces; referred in this paper as WF4). This paper covers common scenarios for out-of-box activities.

This document does not cover persistence or durable WCF services. We also cover the context-based message correlation mechanism, but not any of the other mechanisms available in WF4.

The examples and instructions shown in this document use the Visual Studio workflow designer and the C# language.

Please see the “WF Migration Overview” document for an introduction to WF3 to WF4 migration, and links to related WF3 -> WF4 documents. The latest version of these documents can be found at http://go.microsoft.com/fwlink/?LinkID=153313

2  WF3 WorkflowServices scenarios implemented in WF4

2.1  Simple messaging patterns

Our first example focuses on a simple messaging scenario where one workflow is acting strictly as a service and another workflow is acting strictly as a client. We take the SequentialWorkflowService sample from the .Net Framework 3.5 SP1 SDK as a starting point. The sample implements a calculator service, which exposes six operations: PowerOn, PowerOff, Add, Subtract, Multiply, and Divide. Each operation is represented by WF3 Receive activity configured with a request/reply messaging pattern (that means the One Way Operation option in the activity options dialog is left unchecked). This pattern is also known as a two-way pattern where, the service will send a reply for every message sent by the client.

As the workflow executes, the first operations that it listens for is the PowerOn Receive which is marked with CanCreateInstance set to true so it can instantiate the workflow (this is also known as an activating Receive). After PowerOn is received the workflow starts looping and listening for any of the other operations (made possible by a Listen activity with nested EventDrivenActivity instances). Each operation uses a CodeActivity inside the Receive to perform the appropriate mathematical operation and return the result, with the exception of the PowerOff operation, which causes the loop to end and the workflow to terminate.

On the client side we arrange a series of Send activities (again configured for request/reply messaging) that will exercise the operations of our calculator. We need to specify how each Send activity will communicate with the service, so we use the ChannelToken property to point to an endpoint name specified in the application .config file. Defining the endpoint in the .config file is a manual process that requires knowledge of how the service is configured.

Each Send activity uses its AfterResponse and BeforeSend event handlers to reference methods in code-behind, which generate new random numbers to send to the service for the calculation, and output the result of the calculation to the console.

We proceed to migrate this scenario to WF4; the code for the two versions is available in the SequentialWorkflowService project which accompanies this paper.

The first thing to note is that every WF3 Receive activity has been replaced by a pair of WF4 activities: a Receive activity and a SendReply activity. The work done by the Receive activity is no longer a child of the activity, but a separate activity that comes in the sequence between Receive and SendReply.

In addition, the Listen activity with nested EventDriven activities in WF3, which allowed us to listen to multiple events in parallel has now been replaced by a Pick activity. Lastly, in this implementation, we replace the actual calculation work, previously accomplished in code via the CodeActivity, with a WF4 Assign activity which lets us perform the same operations without dropping into code.

When designing the client Workflow we no longer need to manually configure the activities to send messages to the service. Using the Add Service Reference feature we can point to the running workflow service and generate a set of pre-configured custom activities (such as PowerOn, Add, etc) that we can drop inside our workflow, instead of having to manually configure Send activities. Furthermore, we use a CorrelationScope activity to manage the context correlation; the scope is responsible for taking the incoming context token from the first request (PowerOn) and re-attaching it to each subsequent request (Add, Multiply, etc.). We have also created a custom activity called GenerateOutputValue, which generates a new random number to send, which was done in code in WF3 using the BeforeSend event handler. A WriteLine activity is used to display the result of the calculation instead of the AfterResponse event handler.

2.2  Complex messaging patterns

Our first example focused on a simple messaging scenario where one workflow is acting strictly as a service and another workflow is acting strictly as a client. In this second example both “client” and “server” workflows can send messages to each other, which blurs the notion of client and server. This pattern is called duplex messaging. We start with the Conversations sample from the Net Framework 3.5 SP1 SDK. The sample implements a customer-supplier relationship where the customer asks for a quote for a given shipping order and the supplier calls out to three shipping services, collects their quotes for the order, and returns the result to the customer. The duplex aspect of the communication pattern is evident in two places: first, the supplier calls the customer after it takes a potentially arbitrary amount of time to collect all the shipping quotes from the suppliers; second, each shipper calls back the supplier after spending a potentially arbitrary amount of time pricing the order. These arbitrary delays can be thought of as the human role in the workflow; it is possible that a person needs to evaluate the shipping quotes to find the best one or person may need to assess the shipping order to price it correctly.

In the WF3 implementation below we have the workflow implementing the sevice. It starts with an activating request/reply Receive called ReceiveSubmitOrder with an operation name SubmitOrder which is called by the customer and takes two parameters: an object of type PurchaseOrder which contains the details of the customer’s order, and a “context” object of type Dictionary<string, string>. This second “context” object is required to implement the duplex pattern in WF3. In order for the service to be able to call back the client, the client uses an operation parameter in the first Receive to send a context token for the service to attach to the request back to the client. This will be discussed in more detail further on in this paper. A CodeActivity is used to store the PurchaseOrder and context token as workflow variables and print out the PurchaseOrder to the command line.

The next element in the workflow is a Parallel activity which obtains the best quote by communicating with three shippers. We will omit the details of this process and address the reason for that further on in this paper. The last activity in the workflow is a request/reply Send activity called SendOrderDetails with an operation name OrderDetails. This sends the best shipping quote back to the customer. As we mention above, the service needs to send the customer a context token, which is accomplished by setting SendOrderDetails.Context to the context token provided to the SubmitOrder operation.

The customer workflow has a request/reply Send called SendSubmitOrder as its first activity to activate the supplier workflow. The Send uses a code-behind and its BeforeRequest property to instantiate a PurchaseOrder to send to the supplier and also to send the workflow’s context to the supplier so it can call back the customer.

Below we see the same workflow migrated to WF4. The code can be found in the Conversations solution which accompanies this paper.

In this paper, we do not port the full sample. The portion of the sample where a Parallel activity is used to call out and gather quotes from multiple shippers has not been migrated. Instead a hardcoded ShippingQuote is used to simulate the best shipping quote the service has. The reason for this simplification is that implementing the parallel calls requires the use of the conversations messaging patttern, which is supported in WF4 by using more generic content-based correlations (which are out of the scope of this paper). The correlation model in WF4 is considerably more powerful than in WF3, which allows correlations to be created based on any piece of data in a message, not just a particular conversation id. Content-based correlations are out of the scope of this paper. More details can be found here.

The supplier workflow is shown on the left hand side of the diagram below and the customer workflow is shown on the right. Similar to WF3, the supplier starts with an activating Receive which accepts the PurchaseOrder and stores it to a workflow variable. You will notice there is no explicit operation parameter for the customer’s context token. In an improvement over the WF3 model, the context is passed implicity as part of a callback correlation and the developer does not need to pass the token manually a parameter in the first Receive. Implemeting the duplex pattern where both service and client can make calls to each other has been simplified, as we will discuss in detail further on in the paper. After receiving the PurchaseOrder, the service outputs the data to the command line via a WriteLine activity replacing the same functionality which was previously implemented in code-behind. A SendReply activity is used to reply to the customer that the order is received. In the WF3 the supplier then uses a Parallel activity to ask 3 shippers for quotes on the order. In the WF4 version we replaced that logic with a static ShippingQuote which is generated by the two Assign activities in the worfklow. The supplier workflow then uses a Send + ReceiveReply pair to send the quote back to the customer by calling the OrderDetails operation.

In the customer workflow, the first set of activities instantiate a PurchaseOrder object to send to the supplier. Then we use a Send + ReceiveReply pair generated using Add Service Reference to call the SubmitOrder operation on the service. Then we define Receive + SendReply pair to receive the ShippingQuote from the supplier. WriteLine activities are used to print out the details of the quote, instead of the code-behind used in WF3. In this case, a CorrelationScope activity is used to manage the callback correlation implicitly; the only additional piece of data that the customer workflow needs to specify is the clientCallbackAddress, which is added to the context binding of the client endpoint in the app.config file.

3  Migration guidance

3.1  Contract Design

3.1.1  Defining the service contract

WF3 gives you 2 ways to define the service contract to which the WorkflowService will conform. Both approaches can be accessed by using the Choose Operation dialog, which appears when you double-click on a Receive activity.

The first approach is to use the Add Contract button and create a new service contract by adding operations and configuring their parameters. Using this approach the service contract is generated and becomes part of the workflow definition. The shape of each operation (parameters, permissions, messaging pattern) is configured in the Parameters, Properties, and Premissions tabs. The second approach involves importing an existing service contract defined in code by pressing the Import button.

In WF4 the contract will be inferred at runtime based on the Receive and SendReply activities that are present in the workflow, which in turn will control the WSDL projection of the service. The exact shape of each operation is determined by how the appropriate Receive and SendReply activities are configured, as we will see below. The Choose Operation dialog box is no longer used. For more information on contract inference in WF 4, please go here.

In WF3 you could use the Import button in the dialog box in order to import a contract created in code.

Note: WF4 has no mechanism to do this and contracts cannot be reused between workflows. We hope to add support for this in a future release or service pack.

The Protection Level functionality offered on the WF3 Properties tab is available in WF4 via the ProtectionLevel property on the Receive activity. The ability to set operation-level permissions via the Permissions tab in WF3 is not available out of the box in WF4. The user can accomplish service-level authorization by using WCF’s ServiceAuthorizationManager.

3.1.2  Defining parameters and configuring serialization

In WF3 the parameters of each operation are configured using the Parameters tab in the Choose Operation dialog or in code if the contract is being imported from code.

In WF4, the Receive activity can be used to accept either Message content or Parameters content.

Message content typically corresponds to a System.ServiceModel.Channels.Message type, or a type decorated with System.ServiceModel.MessageContractAttribute. Parameters content matches the method-style programming model where each parameter requires a name and type; each of these types must be serializable, either by decorating it with the System.Runtime.Serialization.DataContractAttribute or any of the XmlSerializer attributes in the System.Xml.Serialization.XmlSerializer namespace. Plain types with no serialization attributes will also be serialized using DataContractSerializer based on the visibility of their members.