Chapter 6. Workflow Services

WHAT'S IN THIS CHAPTER?

  • Getting started with workflow services

  • Implementing message correlation

  • Configuring workflow services

  • Hosting workflow services

Workflows are presented in software development as an effective tool for solving problems or accomplishing business goals. By splitting them into smaller and more manageable pieces, they can be coordinated into a single process.

Workflows also bring reusability and maintainability — two core quality principles in software engineering. They are promoted by workflows because they have smaller pieces that can be reused across several processes and are easier to maintain. These pieces or reusable assets receive the name of activities, and usually describe the work performed by the people or software involved in the process.

When designing workflows within an enterprise application using a bottom-up approach, they are first designed as a set of primitive activities to control the execution flow, communicate with the outside world, or execute actions to change the workflow's internal state. These workflows can later be converted to activities and used as a compositional unit for creating other workflows that coordinate work at a higher level. In this way, the design process can be applied recursively to a number of abstraction levels that you might find necessary.

The same design paradigm can be applied in the service-oriented world. A service can be implemented through a combination of primitive activities in a workflow, or a service can be the result of a workflow that orchestrates other services to accomplish a business goal.

For instance, a service for submitting a new order in a supply management application might require the coordination of other services to verify or update the stock level of the ordered product. All the work needed to coordinate these service calls can be modeled through a simple workflow.

However, implementing a service as a workflow is not as simple as it sounds. There are challenges that need to be addressed, and without the help of the right technology, they would be almost impossible to achieve:

  • A long-running process that interacts with people might take hours or days to complete. For instance, a service that implements a document approval workflow. For this kind of service, a mechanism is needed to save the workflow state across different service calls. You can't keep the state in memory forever, as it is volatile storage and a performance bottleneck when the number of running workflows gets high.

  • Multiple messages that represent business events might come in or go out from a given service instance. A mechanism to correlate all these messages into the same service instance is required to resume the workflow execution in the correct step and with the correct state.

Workflow Foundation (WF) is the technology that helps you in this area. Fortunately with the introduction of new activities to build workflow-enabled or workflow services, it has been closely integrated with WCF in the .NET Framework 3.5. WCF in that sense provides the necessary infrastructure to communicate the workflows with the external world through incoming and outgoing messages.

Throughout this chapter, you will see this relationship has been improved in version 4.0 with the introduction of new features such as content correlation, declarative services, and the new runtime model and activity library.

ANATOMY OF A WORKFLOW SERVICE

A workflow service created with WF maintains state, gets input messages from and sends output messages to the outside world, and executes code that performs the service's work. This is the same as an ordinary stateful service, which preserves a shared state between different operations during its lifetime.

Figure 6-1 shows the anatomy of a simple workflow service. Every workflow service has an outermost activity, the workflow itself, which contains all other activities. This outer activity can take different shapes, and basically determines the way the execution flow goes through different inner activities. WF 4.0 ships with two built-in activities for modeling workflows: a sequence activity that flows from one activity to the next until the last activity is executed, and a flowchart activity that resembles the concepts that many analysts and application designers go through when creating solutions or designing business processes.

Anatomy of a workflow service

Figure 6.1. Anatomy of a workflow service

In this example, the workflow execution begins with a Receive Activity that receives an input message from the external world using a WCF endpoint. This is followed by a branch activity that evaluates a condition before executing one of the activities (named A1 and A2 in Figure 6-1) defined on each branch. The branch activity is followed by the execution of another custom activity (A3), and a message is finally sent to the world beyond this service by the Send Message Activity (which might represent a call to another service). Another Receive Message Activity appears which gets more input, and is followed by a final Send Message Activity that sends out the final service result.

As you can see, every activity in this example represents a piece of a typical service, with the main difference being that many of the built-in language elements found in a traditional program have been replaced by activity classes provided by the WF activity library.

You should also understand that the WF runtime does not know anything about the internal implementation of the activities it is executing, or the way it behaves.

It can only run one activity after another, until the last activity is reached. It can also persist the workflow state at certain points.

Now, you might wonder why you should go with a workflow service when the same service can be implemented manually with code? The answer is, to address some of the benefits that WF can give in this area:

  • The automatic support for long-running workflows that the WF infrastructure provides is the most evident benefit. The execution of a long-running workflow can be suspended or resumed on multiple points, so the internal state of the execution and activities must be preserved. WF automatically takes cares of saving the workflow state through the use of configurable providers. Therefore, you do not need to worry about these details.

  • The implementation of a complex service that orchestrates several other services with a certain degree of parallelism gets simplified with the use of a workflow. All the aspects related to message correlation are automatically handled by the WF infrastructure. The same implementation made purely on code would require a lot of plumbing code and some knowledge about concurrent programming. This is necessary to be efficient when coordinating all the service calls and correlating all the responses in the same way WF would do it.

  • A service might need to execute some kind of compensation logic or be suspended when an unexpected situation or exception occurs during its execution. For instance, a service that depends on other services to perform an action might not be able to complete its execution when any of these are not available, which might lead to some data inconsistencies. If you use a workflow to implement this service, the workflow itself can be suspended and restarted later when the dependent services are available.

  • The declarative model and visual designer that WF provides becomes handy at the moment of building simple services without worrying about implementation details of the WCF ABC (Address, Binding, and Contract). You can focus on a declarative model that simply creates variables that are handled in the workflow, and activities that initialize these variables and change their values, sending them to the outside world.

DECLARATIVE SERVICES

One of the main problems of implementing workflow services in the .NET Framework 3.5 is that the integration between these technologies leaves something to be desired. You basically have to define all the WCF artifacts using the WCF programming and configuration model, and the workflow using a different model, resulting in a set of multiple artifacts that need to be deployed and managed separately.

However, with the .NET Framework 4.0, you can define all the WCF artifacts, such as contracts and operations, using XAML (eXtensible Application Markup Language) together with the workflow definition in the same language, resulting in a single artifact — a XAML-based service.

In other words, you essentially create a model of the service defining what the service should do, rather than writing code to specify how to do it — the traditional way to do things in .NET.

Listing 6-1 shows the XAML representation of a workflow service with a simple operation, GetData, that receives an input argument in the variable "data," and returns a string (data.ToString()). As you can see, all the activities have been represented as objects with properties using the XAML notation.

Example 6.1. XAML Representation

<WorkflowService ConfigurationName="Service1" Name="Service1" ....>

  <p:Sequence DisplayName="Sequential Service">
    <p:Sequence.Variables>
      <p:Variable x:TypeArguments="CorrelationHandle" Name="handle" />
      <p:Variable x:TypeArguments="x:Int32" Name="data" />
    </p:Sequence.Variables>

    <Receive x:Name="__ReferenceID0" DisplayName="ReceiveRequest"
      OperationName="GetData" ServiceContractName="contract:IService" >
      <Receive.CorrelationInitializers>
        <RequestReplyCorrelationInitializer CorrelationHandle="[handle]" />
      </Receive.CorrelationInitializers>
      <ReceiveMessageContent>
        <p:OutArgument x:TypeArguments="x:Int32">[data]</p:OutArgument>
      </ReceiveMessageContent>
    </Receive>

    <SendReply Request="{x:Reference Name=__ReferenceID0}"
      DisplayName="SendResponse" >
      <SendMessageContent>
        <p:InArgument x:TypeArguments="x:String">[data.ToString()]</p:InArgument>
      </SendMessageContent>
    </SendReply>

  </p:Sequence>

</WorkflowService>

In addition, the contract and operations for this service are automatically inferred from the Receive Activity definition.

A great advantage of having a fully declarative service is that you can store the complete definition in a service repository and take full control of the workflow's instances from a hosting environment such as Windows AppFabric.

Note

You can still develop a complete workflow service in WF 4.0 using an imperative .NET programming language such as C# or VB.NET.

WF 4.0 provides a new way to create a complete workflow definition using code without using a designer and the .NET component model. Because this kind of model requires that you define every activity and its properties using code, it might cause maintainability issues when the number of activities increases or the workflow implementation gets complex — you lose the visual definition that you have in the designer.

If you go with this approach, you need to create activities that represent the workflow model, such as Sequence or FlowChart; create messaging activities to interact with the outside workflow, such as Receive or Send Activities; and create other activities to implement the service itself. The contract and operations from the service are still derived from the Receive Activities — there isn't a formal contract definition as in WCF.

private static WorkflowServiceImplementation CreateWorkflow()
{
    var result = new Sequence();
    var receivedInput = new Variable<string>();
    result.Variables.Add(receivedInput);

    var handle = new Variable<CorrelationHandle>();
    result.Variables.Add(handle);

    var receive = new Receive()
    {
      OperationName = "HelloWorld",
      ServiceContractName = "HelloWorldService",
      Content =
        ReceiveContent.Create(new OutArgument<string>
        (receivedInput)),
      CorrelatesWith = new InArgument<CorrelationHandle>(handle),
      CanCreateInstance = true
    };

    result.Activities.Add(receive);

    var write = new WriteLine()
    {
      Text = new InArgument<string>(env =>
        string.Format("Hello World!!! '{0}'.",
receivedInput.Get(env)))
    };
    result.Activities.Add(write);

    var reply = new SendReply()
    {
      Request = receive,
      Content = SendContent.Create(new InArgument<string>(env =>
        string.Format("Hello World!!! '{0}'.",
        receivedInput.Get(env))))
    };
    result.Activities.Add(reply);
    var service = new WorkflowServiceImplementation
    {
      Name = "HelloWorldService",
      Body = result
    };
  return service;
}

In this example, we receive a message as defined by the Receive Activity Content property. It is a string assigned to the variable receivedInput. The content of the receivedInput variable is printed and returned through the Content property of the SendReply Activity.

SEND AND RECEIVE ACTIVITIES

The .NET Framework 3.5 introduced two new activities, Send and Receive, to simplify the integration between WCF and WF. Using these activities, you can essentially enable WCF endpoints on your WF workflows to make it accessible from the external world. On the other hand, you can employ WF as the implementation technology for your WCF services. A workflow that uses these activities is usually known as a workflow service and that terminology is used throughout this chapter.

WF in the .NET Framework 4.0 still ships with these activities for sending and receiving one-way messages through WCF, but it also includes two new activities, SendAndReceiveReply and ReceiveAndSendReply, that represent a higher-level abstraction for request/response operations.

Next, we discuss these four activities in detail and how they can be used.

Receive Activity

The Receive Activity defines an entry point in a workflow that starts receiving messages from the outside world. This entry point takes the form of a WCF endpoint associated to a specific contract and operation. This activity has been improved in the .NET Framework 4.0 with the introduction of significant changes to simplify the service contract and operation definition, as well as other related aspects, such as message correlation.

Some of the properties that you will find in this activity are discussed in Table 6-1.

Table 6.1. Receive Activity Properties

PROPERTY

DESCRIPTION

DisplayName

The friendly name that identifies the activity within the workflow. This name is used to reference the activity from other activities, or relate this activity to a SendActivity, which sends the result of the operation execution to the outside world.

ServiceContractName

The name of the WCF contract that is associated with the activity. The name chosen for this contract matters because it represents the service contract that the clients will use to consume the service.

OperationName

The name of the WCF operation that is associated with the activity. This operation is added to the contract specified in the ServiceContractName property. The name of the operation is as important as the name of the contract for the same reasons previously discussed — it is one of the visible parts of the service.

Content

The input parameters or arguments that the operation expects to receive. This content is usually mapped to a workflow variable, so it can be used by other activities.

Action

The SOAP Action that will be used to route the messages to the WCF operation. This value is usually sent by the client in the To address header.

CanCreateInstance

This property specifies whether the activity can create a new instance of the workflow when a new message is received through the associated WCF endpoint. Every workflow service definition must have one Receive Activity that can create instances (CanCreateInstance = true), and that activity must be the first activity in the workflow.

KnownTypes

A collection of .NET types representing existing data contracts or primitive types that needs to be associated to the operation definition as known types. A known type is a type that should be included for consideration during the serialization/deserialization process of the send/received messages.

ProtectionLevel

The protection level requirement applied to the operation definition. As discussed in the security chapter, the only possible values for this property are None, Sign, and SignAndEncrypt.

SerializerOption

The message serializer associated to the operation. The possible values for this property are DataContractSerializer and XmlSerializer. These serialization engines were discussed earlier in this chapter.

CorrelatesOn/CorrelatesWith and CorrelationInitializers

These are properties for resolving and associating a correlation handler for the operation. Message correlation is discussed later in this chapter.

A single Receive Activity in a workflow service represents a one-way operation in the service contract unless you define a new SendReply Activity. It is tied to the Receive Activity to send a reply message with the results of the operation execution.

A SendReply Activity is not an activity that you can simply drag and drop from the toolbox to the workflow instance in the designer. It can only be created from an existing Receive Activity by selecting the option Create SendReply in the Receive Activity's context menu.

Figure 6-2 shows a SendReply Activity in action. It has been associated with a Receive Activity ApplyForJobReceive that represents an operation ApplyForJob and assigns the request message payload to the JobApp input variable. As you can see in the figure, the SendReplyToReceive Activity has been tied to the Receive Activity through the Request property, and the response message payload is a simple string value. In a real service implementation, you are also free to associate a variable to the Content property with a value computed during the workflow execution.

The equivalent WCF contract for the Receive Activity in Figure 6-2 should be the following:

SendReply Activity in action

Figure 6.2. SendReply Activity in action

[ServiceContract]
public interface IHRService
{
  [OperationContract]
  string ApplyForJob(JobApp application);
}

The content property also supports multiple parameters in case you do not want to assign the received message into a single variable. This is something that can be selected in the Content property window when using the workflow designer. Figure 6-3 illustrates a Receive operation with the input mapped to multiple arguments.

Receive operation with multiple arguments

Figure 6.3. Receive operation with multiple arguments

Note

In case you are not familiar with the workflow model in WF 4.0, a variable represents a new way to pass values around in the workflow instance. As with any programming language, a variable is also associated to a scope, which basically determines where the variable can be read from and/or written to during its lifetime, or how long it stays in memory. Therefore, the sum of all variables in scope in a specific point of time determines the whole workflow state at that moment. This is a significant improvement in the way the workflows are persisted and loaded into memory from the persistence provider. The WF runtime engine can now perform some optimizations and only the variables in scope need to be considered for that purpose. In the past, the only way to share state between activities within a workflow was to define global variables with dependency properties in the workflow itself.

Send Activity

The Send Activity represents the flip side of the Receive Activity. The Receive Activity defines a WCF entry point for receiving messages, and the Send Activity allows workflows to send messages to the outside world to execute external WCF services, or send response messages with results of an operation execution to a service consumer when it is tied to a ReceiveReply Activity.

In the same way a single Receive Activity represents a one-way operation in the service contract by default, the Send Activity does not expect a response message unless it is associated with a ReceiveReply Activity.

The ReceiveReply Activity is another activity that cannot be directly referenced in an activity toolbox and must be created from an existing Send Activity. You do so by selecting the option Create ReceiveReply in the Send Activity's context menu.

Figure 6-4 shows a ReceiveReply Activity in action. It has been associated with the Send Activity Send that represents an operation SubmitOrder and that gets the request message payload from the Order variable. As you can see in the figure, the ReceiveReplyForSend Activity has been tied to the Send Activity through the Request property, and the response message payload is returned in the Result variable.

ReceiveReply Activity in action

Figure 6.4. ReceiveReply Activity in action

Some of the properties that you find in the Send Activity are discussed in Table 6-2.

Table 6.2. Send Activity Properties

PROPERTY

DESCRIPTION

Endpoint

The definition of the endpoint that will be used to consume the service. This property is made of two parts, the endpoint address and the WCF binding. The endpoint address is a URI representing the address where the service is listening. The WCF binding is one of the built-in bindings used to set up the channel communication.

EndpointConfigurationName

The friendly name of an endpoint in the WCF configuration section. This property provides the flexibility of specifying all the endpoint settings such as address, binding, and behaviors in the file configuration section. You can either set the Endpoint or EndpointConfigurationName properties but not both.

EndpointAddress

A service endpoint address that overrides the address already defined in the Endpoint property or the WCF configuration section — if the EndpointConfigurationName property is used.

DisplayName

The friendly name that identifies the activity within the workflow. This name is also used to reference the activity from other activities, or relate this activity to a ReceiveReply to receive a service response.

ServiceContractName

The name of the WCF contract that is associated with the activity. The name chosen for this contract matters because it represents the service contract that the WF will use to consume the service. The contract name is made up of two parts, a namespace and the contract name, per se (e.g., http://wcfbook/}myServiceContract).

OperationName

The name of the WCF operation that is associated with the activity. The name of the operation is as important as the name of the contract for the same reasons discussed earlier — it is used to consume the service.

Content

A workflow variable that is mapped to the request message for executing the service's operation.

Action

The SOAP Action that is included in the To address header of the request message for executing the service's operation.

KnownTypes

A collection of .NET types representing existing data contracts or primitive types that needs to be associated with the operation definition as known types. A known type is a type that should be included for consideration during the serialization/deserialization process of the send/received messages.

ProtectionLevel

The protection level requirement that is applied to the operation definition. As was discussed in the security chapter, the only possible values for this property are None, Sign, and SignAndEncrypt.

SerializerOption

The message serializer associated with the operation. The possible values for this property are DataContractSerializer and XmlSerializer. Serialization engines were discussed earlier in this chapter.

CorrelatesWith and CorrelationInitializers

Properties for resolving and associating a correlation handler for the operation. Message correlation is discussed in detail later in this chapter.

The Assign Activity is useful for initializing a variable associated to the Content property of the Send Activity.

The Assign Activity is a new activity introduced in WF 4.0 to assign or initialize the value of a workflow variable. It is made up of two parts — a variable definition, which is assigned to the To property, and an expression to initialize that variable in the Value property.

The expressions for initializing the variable can be simple, such as a scalar value, or something more complex that involves functions or other variables already defined in the workflow. The complex expressions must be defined using VB, so you need to be familiar with that language to create them correctly.

For the example shown in Figure 6-4, the Send Activity uses an Order variable to execute the SubmitOrder operation. As that variable needs to be initialized before executing the operation, the Assign Activity can be used.

The following expression can be assigned to the Order variable:

Order  = New Order()
With {.ProductName = "foo", .Quantity = 10}

As you can see, the VB object initializer syntax has been used to create a new instance of the Order class, which represents a data contract in this example.

Note

Is C# supported for creating expressions? The answer is no, and the main reason is timing. The VB team provides an in-memory parser for creating expressions within the WF 4.0 RTM timeframe.

One of the limitations that you will find in the current version is a lack of support for specifying security client credentials through the Send Activity when the target service needs to authenticate the calling application/user. For most scenarios, you can do this through the WCF configuration section using bindings and behaviors. Windows, Certificates, and Issued token credentials can be configured that way, but unfortunately, username token credentials cannot. If that is the case, you will not able to consume the external service unless you create a custom extension or behavior to specify those credentials at runtime.

The Easy Way to Execute External Services

Another interesting feature in WF 4.0 that simplifies the development of workflow services significantly is the automatic generation of typed activities for consuming existing service references in the project. Whenever you add a new service reference in the workflow project, a set of new activities for consuming the service (one per operation) are automatically added to the Visual Studio toolbox. You can simply drag and drop one of those activities within a workflow and fill out all the required properties, such as input/output arguments, to execute service without worrying about the other details already discussed in the Send Activity. The benefits that you get for free by using this generated activity are evident:

  • Typed properties for the input, and output parameters that the service operation associated to the activity is expecting and returning.

  • All the details about the contracts exposed by the service are automatically inferred and included in the generated activities.

As recommended, you should always consider this alternative the easiest way to consume external services unless you need more granular control over the details of the WCF client contract or the endpoint settings.

The WCF contract and the XAML representation of activity generated for consuming the Notify operation in the contract are shown next:

[ServiceContract]
public interface IProductNotification
{
[OperationContract]
  bool Notify(string product, int quantity);
}

<psa:Notify NotifyResult="[NotifyResult]" product="[PurchaseOrder.Product]"
quantity="[PurchaseOrder.Quantity]" />

SendAndReceiveReply and ReceiveAndSendReply Activities

SendAndReceiveReply and ReceiveAndSendReply were introduced to simplify the definition of request/response operations.

These are equivalent to using a combination of Send/ReceiveReply and Receive/SendReply Activities respectively, with a slight difference, as they are wrapped with a Sequence Activity. That sequence activity, in addition to being an organizational unit, also defines a variable scope that you can optionally use for new variable definitions.

IMPLEMENTING YOUR FIRST WORKFLOW SERVICE

You have seen some introductory theories behind workflow services and the messaging activities that can be used in this kind of service to receive messages or invoke external services. It is now time to jump into a more concrete example to see a workflow service in action.

This example represents the implementation of a service for entering purchase orders into a system. For practical purposes, this service will also invoke an external service for every received order.

Let's start creating a C# class library for including the definition of the contract and implementation of the external service invoked within the workflow service. The implementation is simple enough: it receives the product that was ordered, the quantity associated to the order, and returns a Boolean value specifying the success of the operation. See Listing 6-2.

Example 6.2. Contract and Implementation of the Notification Service

[ServiceContract]
public interface IProductNotification
{
  [OperationContract]
  bool Notify(string product, int quantity);
}

public class ProductNotification : IProductNotification
{
  public bool Notify(string product, int quantity)
  {
    return true;
  }
}

Now that you have the external service implementation, you can focus on the implementation of the first workflow service.

Visual Studio 2010 provides a template WCF Workflow Service Application under the Workflow category, which you can use to create workflow services from scratch. When that template is selected and executed, a new web project is generated with a sample workflow service definition that includes a pair of Receive/Send Activities. Start the implementation of your workflow service by removing those activities.

This service will initially receive a purchase order as it is defined in the PurchaseOrder data contract (see Listing 6-3) through a new Receive Activity that you need to create in the workflow. This activity basically represents the initial entry point for the service.

Example 6.3. Purchase Order Data Contract

[DataContract]
public class PurchaseOrder
{
  [DataMember]
  public string Customer { get; set; }

  [DataMember]
  public string Product { get; set; }

  [DataMember]
  public int Quantity { get; set; }

  [DataMember]
  public decimal UnitPrice { get; set; }
}

You should set the Receive Activity properties with the values specified in Table 6-3.

Table 6.3. SubmitPOReceive Activity Properties

PROPERTY

VALUE

DisplayName

SubmitPOReceive

OperationName

SubmitPO

ServiceContractName

{http://wcfbook/}IPurchaseOrder

CanCreateInstance

True

Content (Message Data)

PurchaseOrder

Leave the rest of the properties with the default values. The designer will complain that the PurchaseOrder variable is not defined, and that is exactly what you do next.

The variables can be defined for a workflow instance in the Variables tab, which usually appears at the bottom margin of the designer. A variable definition is made of three parts: a name, a type, and a scope. The variables, as in any language, are referenced by name, and the type is any valid CLR type shipped as part of the framework or a type created for you. In this case, you define a variable called PurchaseOrder whose type is PurchaseOrder (a data contract defined in another project) and is scoped to the current workflow service (Sequential workflow service). As you can see, you have a single data contract parameter to receive all the information, so Message Data is just fine for that purpose. In case you want to support multiple arguments in the operation signature, you need to use parameters and assign them to the corresponding variables or use VB expressions to do more complex assignments.

The workflow is now ready to start receiving messages containing the purchase order information through the operation SubmitPO. As you haven't defined a SendReply Activity, this operation momentarily behaves as one-way.

The next step is to invoke the ProductNotification service for every receive order. The easy way to include that service call in the existing workflow is to use the automatic generated activity. The only thing to do is add a service reference for ProductNotification as illustrated in Figure 6-5.

Service Reference added

Figure 6.5. Service Reference added

When the reference is added to the project, you should be able to see a new activity called Notify with a public interface that resembles the Notify operation in the IProductNotify contract. It has two public properties for setting the product and quantity respectively, and a third property for getting access to the operation return value.

You can drag and drop that activity after the Receive Activity and fill out the properties with the values in Table 6-4.

Table 6.4. Notify Properties

PROPERTY

VALUE

NotifyResult

NotifyResult

Product

PurchaseOrder.Product

Quantity

PurchaseOrder.Quantity

NotifyResult is a new Boolean variable that needs to be defined to receive the result of the operation. The Product and Quantity input arguments are initialized from the existing variable PurchaseOrder by using expressions.

Last but not least, you need to create a new SendReply Activity from the SubmitPOReceive Receive Activity to send the results back to the client. The only thing needed is to set up this activity in the Content property to use Message data and True as value, so it will basically return a value indicating the operation was executed successfully.

At this point, the workflow service is ready to use, and any client can access the service WSDL by browsing the POService.xamlx file directly in the corresponding web host (IIS virtual directory or Visual Studio web server). This means that the service behaves as any other WCF service from the point of view of a client application, and a tool such as svcutil can be used to generate the proxy classes to consume it. (See Listing 6-4.)

Example 6.4. Code Required for Consuming the Workflow Service

PurchaseOrderClient client = new PurchaseOrderClient();
bool? result = client.SubmitPO(new ServiceReference.PurchaseOrder
{
  Customer = "John BarFoo",
  Product = "myproduct",
  Quantity = 10,
  UnitPrice = 1
});

Console.WriteLine(result.GetValueOrDefault());

CONFIGURING A WORKFLOW SERVICE

As with any traditional WCF service, you can separate the configuration of a workflow service from the service implementation by defining it either in an app.config for self-hosted services or a web.config for IIS-hosted services. The connection between the workflow service implementation and the corresponding service configuration in the configuration file is specified in the ConfigurationName attribute — which maps to the config's service element's name attribute. With this link in place, a workflow service is configured in exactly the same fashion as a traditional WCF service using the service model configuration section. The following text shows how the ConfigurationName "MyService" is mapped to the corresponding service configuration section:

<WorkflowService mc:Ignorable="sap" ConfigurationName="MyService"
Name="MyService"..>
</WorkflowService>

<system.serviceModel>
    <services>
      <service name="MyService" behaviorConfiguration="myService" >
        <endpoint binding="basicHttpBinding"
                  bindingConfiguration="myService"
                  contract="IService"></endpoint>
      </service>
    </services>
    <bindings>
<basicHttpBinding>
        <binding name="myService">
          <security mode="None"></security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="myService">
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

The contract name that you need to configure for the service endpoint must match one of the contract names specified in the workflow Receive Activities. For this example, one of the Receive Activities in the workflow has the value {[namespace]}IService for the ServiceContractName property.

You can still use the new configuration improvements in a workflow service. In fact, the Visual Studio template for workflow services automatically includes a configuration file that only overrides a few default WCF configuration settings to enable the service metadata endpoint and disable the includeExceptionDetailsInFault setting:

<system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information,
set the value below to false and remove the metadata endpoint
above before deployment -->
          <serviceMetadata httpGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes,
set the value below to true.  Set to false before deployment to avoid
disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
</system.serviceModel>

WF Runtime Settings

In addition to the bindings and service settings that you need to configure to expose the service, WF 4.0 provides a set of built-in behaviors to initialize or override some of the WF runtime settings. The most important details about each one of these behaviors is addressed in this section.

WorkflowIdle Service Behavior

This behavior, as its name suggests, gives you control over when idle workflow instances are persisted and unloaded from memory. It contains two properties, TimeToPersist and TimeToUnload, that accept a period of time representing the timeout for performing the operations with the same name, persist, and unload the instances. The following example shows how to configure the runtime to persist the idle workflow instances every 10 seconds, and unload them from memory after one minute:

<workflowIdle timeToPersist="00:00:10" timeToUnload="00:01:00"/>

SqlWorkflowInstanceStore Service Behavior

WF 4.0 comes with a persistence provider out of the box called SqlWorkflowInstanceStore, which derives from the abstract class System.Runtime.Persistence.InstanceStore, and implements all the abstract methods to save the workflow data in either SQL Server 2005 or 2008. By using this behavior, you can change some of the default settings for this provider like the connection string or whether the instance should be removed from the database after its completion, to name others.

The following example illustrates how the connection string can be configured for this persistence provider.

<behaviors>
  <serviceBehaviors>
    <behavior name="ServiceBehavior">
      <sqlWorkflowInstanceStore
        connectionString="Data Source=.SQLExpress;Initial
        Catalog=WorkflowInstanceStore;Integrated Security=True">
        </sqlWorkflowInstanceStore>
      </behavior>
   </serviceBehaviors>
</behaviors>

The SQL scripts for creating the database schema and some store procedures required by this instance provider are available in the .NET framework installation folder (e.g., C:WindowsMicrosoft.NETFrameworkv4.0.21006SQLen). The two scripts you need are SqlWorkflowInstanceStoreSchema.sql and SqlWorkflowInstanceStoreLogic.sql.

WorkflowUnhandledException Service Behavior

This behavior enables you to change the action taken by the runtime when an unhandled exception occurs within a running instance. By default, the runtime will leave the instance in the Abandon state. Some other possible values for this action are Cancel, Terminate or AbandonAndSuspend.

While the service host will abort the workflow service instance in memory in all cases, only an instance with state Abandon or AbandonAndSuspended can be resumed later. In addition, when a workflow is cancelled, all the cancellation handlers associated to the instance are invoked so it can be terminated in a graceful manner.

WorkflowRuntime Service Behavior

The WorkflowRuntime service behavior allows changing some specific settings in workflow runtime that is used for hosting the workflow services. This behavior also represents a way to additional services into the runtime that the workflow instances can use. Some of the attributes you can find in this behavior are summarized in Table 6-5.

Table 6.5. WorkflowRuntime Settings

SETTING

DESCRIPTION

CachedInstanceExpiration

An optional setting that specifies the maximum period of time a workflow instance can stay in memory in idle state before it is forcefully unloaded or aborted.

EnablePerformanceCounters

Another optional setting that specifies whether performance counters are enabled. As happen with performance counters, they provide useful information on various workflow-related statistics, but they cause a performance penalty when the workflow runtime engine starts, and when workflow instances are running.

ValidateOnCreate

A Boolean setting that specifies whether validation of workflow definition will occur when the WorkflowServiceHost is opened.

It is enabled by default, so the workflow validation is executed every time WorkflowServiceHost.Open is called. In case a validation is found, a WorkflowValidationFailedException error is thrown.

The following configuration fragment illustrates how some of the settings can be changed for this behavior.

<behaviors>
  <serviceBehaviors>
     <behavior name="ServiceBehavior">
        <workflowRuntime name="WorkflowServiceHostRuntime"
                       validateOnCreate="true"
                       enablePerformanceCounters="true">
           <services>
           </services>
        </workflowRuntime>
     </behavior>
  </serviceBehaviors>
</behaviors>

IMPLEMENTING MESSAGE CORRELATION

As discussed earlier in this chapter, long-running processes represent a common scenario for workflow services. In this kind of process, an execution is usually made up of multiple execution sequences which may last many days or even weeks. They might involve clients that connect to the service, perform work, transition the workflow to a new state, and then disconnect for an indeterminate amount of time before connecting again for workflow execution. This kind of execution generates questions that need to be addressed when a message leaves a workflow instance, and then another message returns later in the same flow. How does that message know which returning workflow instance to come back to? What if there are thousands of these workflow instances running at a single point in time? For this, WF has correlation, which means that you can specify a set of attributes for a given message that the runtime will use to route the message to the appropriate running workflow instance.

A correlation attribute in this context might be as simple as a unique identifier. It might be more complex, such as a composite key made up of business concepts — a customer identifier or a purchase order number.

The correlation story in WF 3.5 was solved by involving the client applications. Basically, when the first operation that created your workflow instance succeeded, a header or a cookie was attached to the outgoing message containing a workflow instance identifier (consider this the workflow instance primary key). It was the client's responsibility to keep this identifier around and attach it to the messages for executing successive operations in the same workflow instance. As you can see, this got a bit tricky, as the client needed to know some of the implementation details of the services. If the client did not know that an identifier must be obtained from the reply message and used again for the next call, nothing worked.

This is not a big deal if you were developing both the client and the service, but if the client was developed by a third party, it certainly becomes more complex.

Ideally you want the message in the second service operation to have a key value with which you can get the workflow instance back (a correlation attribute). Fortunately, the WF team has included a way to route messages to a workflow instance in a feature called content-based correlation.

In this section, the correlation techniques are addressed — the old technique based on the use of a unique identifier or context-based correlation, and the new one based on the use of keys in the message content itself.

Correlation in Action

The correlation mechanisms are based on the use of a few new properties, CorrelatesOn, CorrelatesWith, and CorrelationInitializers, which were previously discussed in the Receive and Send Activities.

The correlation mechanisms work by defining one or more correlation handlers, represented in the workflow as simple variables of the type CorrelationHandler — which can be associated to a messaging activity and are initialized according to the strategy specified in the CorrelationInitializer function. The WF runtime uses these correlation handlers to determine to which workflow instance the incoming messages should be routed.

The correlation initializer function determines the protocol that the runtime uses for initializing the correlation handlers. Some possible values for this function are Context correlation initializer, for context-based routing; Request-Reply correlation initializer, for correlating a pair of request and response messages within the same channel; or Query correlation initializer, for content-based routing. For example, Query correlation initializer also requires an expression that when applied to messages can determine the initial value for the correlation handler.

The other two properties, CorrelatesOn and CorrelatesWith, are only useful for content-based routing and are discussed in detail in the next sections.

Context-Based Correlation

This context correlation mechanism uses a message protocol for passing the routing information between the client and the workflow service. This information minimally contains a unique identifier also known as instance ID, which is initially generated when a workflow instance is created and sent back to the client on the first message exchange. It is the responsibility of the client application to include this information in all subsequent requests to the same workflow service instance. There are some complex scenarios where a workflow might expect the same message in multiple Receive Activities simultaneously. For those scenarios, the instance identifier is simply not enough, as something else is needed to route the messages to the correct Receive Activity within the workflow — a conversation identifier. Both the instanceID and the conversationID identifiers can be included as part of the context information.

The implementation of this context exchange protocol in WCF can use either HttpCookie or SOAPHeader to propagate the context between the service and the client. This protocol is internally implemented through a custom channel that communicates the context to and from the application layer using a ContextMessageProperty property.

The value of this property is either serialized as a SOAP header or as a cookie in the Http header before the messages are transmitted over the wire. WCF also ships with a specific binding element ContextBindingElement where the purpose is to inject the channel into the WCF channel layer. This binding element exposes a public property ContextExchangeMechanism that only accepts two values — HttpCookie or SOAPHeader — for choosing the serialization mechanism.

An application doesn't usually use binding elements directly, as they are often wrapped in high-level bindings. This is also the case for this content binding element, as WCF provides a set of specific bindings known as context bindings, which hide the underlying details about the context exchange mechanism from the applications.

Table 6-6 shows a list of available context bindings.

Table 6.6. Context Bindings

BINDING

DESCRIPTION

BasicHttpContextBinding

A variation of BasicHttpBinding which supports the context exchange protocol using cookies over http.

WsHttpContextBinding

A variation of the WsHttpContextBinding that supports the context exchange protocol using either SOAP headers or cookies over http.

NetTcpContextBinding

A variation of the NetTcpContextBinding that supports the context exchange protocol using SOAP headers over Tcp.

In Table 6-6, you might wonder why NetMsmqBinding is missing. NetMsmqBinding provides one-way reliable/durable messaging capabilities to a workflow service. However, ContextBindingElement requires a channel with an IReplyChannel interface and NetMsmqBinding actually implements an IInputChannel or an IOutputChannel. Which one actually depends on whether you are the client or the service. If you think about the way the context exchange mechanism works in workflow services, this restriction makes sense. The ReceiveActivity that creates the workflow instance is called without a context, and the context identifier that represents the workflow instance is returned as part of the response messages. Therefore, this protocol leaves out all the one-way messaging implementations, and thereby leaves out NetMmsqBinding.

Let's examine how a workflow service with context-based correlation works in action with a concrete example.

Step 1: Implementing the Workflow Service

The workflow service used in this example is made of two Receive Activities — the first activity receives a purchase order, and the second one confirms that order.

The first Receive Activity, which represents the entry point in the service, initially receives a purchase order as it is defined in the data contract Purchase Order (see Listing 6-5).

Example 6.5. Purchase Order Data Contract

[DataContract]
public class PurchaseOrder
{
  [DataMember]
  public string Customer { get; set; }

  [DataMember]
  public string Product { get; set; }

  [DataMember]
  public int Quantity { get; set; }

  [DataMember]
  public decimal UnitPrice { get; set; }
}

Set the Receive Activity properties with the values specified in Table 6-7.

Table 6.7. SubmitPOReceive Activity Properties

PROPERTY

VALUE

DisplayName

SubmitPOReceive

OperationName

SubmitPO

ServiceContractName

{http://wcfbook/}IService

CanCreateInstance

True

Content (Message Data)

PurchaseOrder

Leave the rest of the properties with the default values, and define a new variable PurchaseOrder to receive the request message payload.

Your workflow is now ready to start receiving messages containing the purchase order information through the operation SubmitPO. Because you haven't yet defined a SendReply Activity, this operation momentarily behaves as a one-way.

The next thing you can do is use an Assign Activity to automatically create a new purchase order identifier (something that you keep in the workflow state as a variable). So, you can define a new variable PONumber of type String, and use the following expression in the Assign Activity to initialize PONumber = 1. (You can later change this routine to dynamically assign new numbers.)

After you have the purchase order number, you can create a SendReply Activity from the SubmitPOReceive Receive Activity to send the content of the PONumber variable back to the client. To accomplish that, you need to set up the Content property to use Message data and PONumber as value, so it will return the generated purchase order number.

As you use context-based routing, the context initializer for the Receive Activity should be set to Context correlation initializer. You can associate this function to the correlation handler __handle1, which is created by default with the Visual Studio template.

You have created a service with a single operation for generating purchase orders in the system. You can now move forward with the next operation for confirming the purchase order. A new pair of Receive and SendReply Activities is required to implement this operation.

You should initialize this new Receive Activity with the values discussed in Table 6-8.

Table 6.8. ConfirmPOReceive Activity Properties

PROPERTY

VALUE

DisplayName

ConfirmPOReceive

OperationName

ConfirmPO

ServiceContractName

{http://wcfbook/}IService

CanCreateInstance

False

This activity does not receive any payload, as you do not need anything extra to confirm the purchase order. You already have the information as part of the workflow state. You also might notice that the CanCreateInstance property is set to false, to reuse an existing workflow instance.

The SendReply Activity for this operation sends a response back to the client providing information about the result of the purchase order confirmation. The following expression assigned to the Content property is enough for that purpose: "The purchase order" + PONumber.ToString() + "was confirmed."

Step 2: Configuring the Workflow Service

The next step is to configure the service workflow you created to use one of the available context bindings. For the sake of simplicity, use a BasicHttpContextBinding example. As we discussed in the "Configuring a Workflow Service" section, the name of the workflow service is what matters for referencing it in the configuration.

Assuming that you named Service to your workflow service, code Listing 6-6 shows the resulting configuration with the context binding.

Example 6.6. Service Configuration with Context Binding

<services>
  <service name="Service">
    <endpoint binding="basicHttpContextBinding"
       bindingConfiguration="myService"
       contract="IService"></endpoint>
  </service>
</services>
<bindings>
  <basicHttpContextBinding>
    <binding name="myService" contextManagementEnabled="true"></binding>
  </basicHttpContextBinding>
</bindings>

The contextManagementEnabled setting specifies that your workflow will use the context exchange protocol to receive the instance and conversation identifiers for correlating messages.

Step 3: Implementing the Client Application

One of the major drawbacks of using the context exchange protocol is that the client needs to be aware of internal details about the service implementation. WCF provides a specific interface named System.ServiceModel.Channels.IContextManager with the corresponding concrete implementation System.ServiceModel.Channels.ContextManager, which automatically handles most of the details for you at channel level. This class can only be used when one of the context bindings has been configured, and it represents the main entry point for setting the context information — a scenario with a simple string representing the workflow instance identifier.

Code Listing 6-7 shows the general pattern a client application can use to get the initial instance identifier, and passes that identifier in subsequent calls to the service.

Example 6.7. Purchase Order Data Contract

static void Main(string[] args)
{
  string instanceId;

  string poNumber = SubmitPO(out instanceId);

  Console.WriteLine("The PO Number is {0}", poNumber);
string message = ConfirmPO(instanceId);

  Console.WriteLine(message);
}

private static string SubmitPO(out string instanceId)
{
  PurchaseOrder po = new PurchaseOrder
  {
    Customer = "Foo Bar",
    Product = "Product",
    Quantity = 10,
    UnitPrice = 5
  };

  ServiceClient client = new ServiceClient();

  string poNumber = client.SubmitPO(po);

  IContextManager contextManager =
    client.InnerChannel.GetProperty<IContextManager>();
  IDictionary<string, string> context = contextManager.GetContext();

  instanceId = context["instanceId"];

  return poNumber;
}

private static string ConfirmPO(string instanceId)
{
  ServiceClient client = new ServiceClient();

  Dictionary<string, string> context = new Dictionary<string, string>();

  context.Add("instanceId", instanceId);

  IContextManager contextManager =
    client.InnerChannel.GetProperty<IContextManager>();

  contextManager.SetContext(context);

  return client.ConfirmPO();
}

In the first method SubmitPO, a new purchase order is submitted to the service using an operation with the same name, and the instance identifier is retrieved from the channel thanks to the help of the context manager. The second method ConfirmPO receives the instance identifier as input argument, and invokes the service operation for confirming the order by passing that identifier through the context manager. Note that the purchase order identifier was not passed at all to the service because the order itself is stored as part of the workflow state. The service does not need to perform additional lookups in the ConfirmPO operation to retrieve the purchase order from storage — such as you would with a traditional service.

Step 4: Configuring the Client Application

The client needs to be configured with the same binding settings that were set on the service side.

For this example, your binding configuration is shown in code Listing 6-8.

Example 6.8. Client Configuration with Context Binding

<client>
  <endpoint address="http://localhost:1219/Service.xamlx"
    binding="basicHttpContextBinding"
    bindingConfiguration="myService" contract="ServiceReference.IService"
    name="BasicHttpContextBinding_IService" />
 </client>
 <bindings>
   <basicHttpContextBinding>
     <binding name="myService" contextManagementEnabled="true" />
   </basicHttpContextBinding>
 </bindings>

Content-Based Correlation

Content-based correlation is a new mechanism introduced in WF 4.0 for routing messages to an existing workflow instance based on evidence presented in the messages itself — such as an order number or customer identifier, rather than contextual information as the one used by the context bindings. As you can imagine, the advantages of using this new routing technique are quite evident and simplify the workflow development and configuration:

  • The internal details of the service implementation are not revealed to the external consumer or client applications because the dependency with the context exchange protocol is totally removed.

  • As the context bindings are not required and the routing information travels with the message itself, this technique is suitable for more scenarios than traditional context-based routing. For instance, scenarios that require the use of reliable/durable messaging capabilities can be used with this routing technique.

This correlation mechanism requires the use of a Query correlation initializer function, which is associated with any available messaging activities. It is used for initializing a correlation handler with an XPath expression that is applied over the incoming or outgoing messages.

When a correlation handler is initialized, you can associate it to another Receive Activity thanks to the use of the CorrelatesWith and CorrelatesOn properties. The CorrelatesWith property accepts the correlation handler variable, and the CorrelatesOn property allows the specification of a new XPath expression to get one or more values from the incoming message associated with the activity.

The WF runtime knows how to route the messages to the proper instance by matching the value from the XPath expression set in the CorrelatesOn property with the value already associated with the correlation handler (which was previously initialized in another Receive Activity with the CorrelationInitializers property).

The same example seen previously is discussed with context correlation, but is now using content-based correlation.

Step 1: Implementing the Workflow Service

As the previous example discussed, you use the same Receive Activities — the first activity for receiving a purchase order, and the second one for confirming it.

The first Receive Activity, which represents the entry point in the service, initially receives a purchase order as it is defined in the data contract Purchase Order (see Listing 6-9).

Example 6.9. Purchase Order Data Contract

[DataContract]
public class PurchaseOrder
{
  [DataMember]
  public string Customer { get; set; }

  [DataMember]
  public string Product { get; set; }

  [DataMember]
  public int Quantity { get; set; }

  [DataMember]
  public decimal UnitPrice { get; set; }
}

The first Receive Activity must be initialized with the properties specified in Table 6-9.

Table 6.9. SubmitPOReceive Activity Properties

PROPERTY

VALUE

DisplayName

SubmitPOReceive

OperationName

SubmitPO

ServiceContractName

{http://wcfbook/}IService

CanCreateInstance

True

Content (Message Data)

PurchaseOrder

Leave the rest of the properties with the default values, and define a new variable PurchaseOrder to receive the request message payload.

The next thing you can do is use an Assign Activity to automatically create a new purchase order identifier (something that is in the workflow state as a variable). So, you can define a new variable PurchaseOrderId of type String, and use the following expression in the Assign Activity to initialize it: PurchaseOrderId = System.Guid.NewGuid().ToString(). That expression initializes the PurchaseOrderId with an autogenerated Guid identifier to make sure it is unique.

Your workflow is now ready to receive messages containing the purchase order information through the operation SubmitPO. As you haven't yet defined a SendReply Activity, this operation momentarily behaves as a one-way.

After you have the purchase order number, you can create a SendReply Activity from the SubmitPOReceive Receive Activity to send the content of the PurchaseOrderId variable back to the client. To accomplish this, you need to set up the Content property to use Message data and PurchaseOrderId as value, so it will return the generated purchase order number.

At this point, you also need to define the CorrelationInitializers property for this SendReply Activity to use an existing correlation handle variable (you can use __handle1, which is a variable created out of the box by the Visual Studio template), a Query correlation initializer function and the PurchaseOrderId variable that you assigned to the activity content. (The designer will automatically set the XPath expression for you, which looks like this: sm:body()/xgSc:SubmitPOResponse/xgSc:PONumber.) The purchase order identifier is used to correlate subsequent messages that the client sends to the workflow service.

So far you have created a service with a single operation for generating purchase orders in the system. You can now move forward with the next operation to confirm the purchase order. A new pair of Receive and SendReply Activities is required to implement this operation.

Initialize the new Receive Activity with the values discussed in Table 6-10.

Table 6.10. ConfirmPOReceive Activity Properties

PROPERTY

VALUE

DisplayName

ConfirmPOReceive

OperationName

ConfirmPO

ServiceContractName

{http://wcfbook/}IService

CanCreateInstance

False

Content(Parameters)

A new parameter PurchaseOrderId of type String. Map this parameter to the existing variable with the same name.

As this activity cannot create instances, you need to define the CorrelatesWith and CorrelatesOn properties so the WF runtime can use them to route the messages to the correct workflow instance.

CorrelatesWith should be initialized with the correlation handler variable that was already specified in the SubmitPO Receive Activity (for example, __handle1). The other property, CorrelatesOn, can be initialized with the parameter PurchaseOrderId (again, the designer will infer the XPath expression automatically).

The SendReply Activity for this operation sends a response back to the client providing information about the result of the purchase order confirmation. The following expression assigned to the Content property is enough for that purpose: "The purchase order " + PurchaseOrderId.ToString() + " was confirmed."

Step 2: Configuring the Workflow Service

The main difference with the context-based routing example is that it does not require any specific binding configuration for this one, so you can use the default WCF configuration settings that the Visual Studio template automatically generates for you.

Step 3: Implementing the Client Application

The advantage of using content-based routing is that all the service implementation details are totally hidden from the client application — as should happen with any regular service according to the four service tenets of service orientation. Therefore, you can use the traditional WCF proxy classes for consuming the service, and no extra code or configuration is required for specifying the routing information.

Code Listing 6-10 shows a basic implementation of a client application that consumes your workflow service.

Example 6.10. Purchase Order Data Contract

static void Main(string[] args)
{
  string purchaseOrderId = SubmitPO();

  Console.WriteLine("The PO Number {0} was submitted", purchaseOrderId);

  string message = ConfirmPO(purchaseOrderId);

  Console.WriteLine(message);

}

private static string SubmitPO()
{
  PurchaseOrder po = new PurchaseOrder
  {
    Customer = "Foo Bar",
    Product = "Product",
    Quantity = 10,
    UnitPrice = 5
  };

  ServiceClient client = new ServiceClient();

  SubmitPOResponse response = client.SubmitPO(po);

  return response.PurchaseOrderId;
}
private static string ConfirmPO(string purchaseOrderId)
{
  ServiceClient client = new ServiceClient();

  return client.ConfirmPO(new ConfirmPO { PurchaseOrderId = purchaseOrderId });
}

In the first method SubmitPO, a new purchase order is submitted to the service using an operation with the same name, and the purchase order identifier is obtained from the response. The second method ConfirmPO receives the purchase order identifier as an input argument, and invokes the service operation for confirming the order by passing that identifier into the request message. As you can see, all the complexities associated with the context management protocol have been completely removed from the client application, which is a great thing.

Note

Message correlation is not the only kind of correlation you will find in WF. As you might guess, WF also needs to support correlation for sharing a WCF channel between Send and Receive activities. And although this type of correlation is explicitly handled by the designer, you must be aware of its existence to use in code workflows. When you create a SendReply activity from an existing Receive activity, or do the same with the ReceiveReply and Send activities, the designer automatically sets a link between the activities through a Request-Reply Correlation Initializer function.

HOSTING THE WORKFLOW SERVICES

The hosting experience for workflow services has also been improved in WF 4.0 with the addition of a native ability to host, support, and maintain .xamlx files (the extension used by workflow services, which is equivalent to .svc for regular services) in IIS and the application server extensions known as Windows AppFabric.

A .xamlx file basically contains all the workflow implementation and configuration in terms of XAML. The experience of hosting a .xamlx is nearly identical to hosting a .svc, as the virtual directory only needs to contain this file, the service configuration in the web.config file, and a bin directory with all the assemblies that the service depends on.

As happens with any application that uses the ServiceHost class for hosting regular WCFs services in a standalone process outside of IIS, there is an equivalent class for doing the same with workflow services — the WorkflowServiceHost class. This class derives from the System.ServiceModel.ServiceHostBase and is responsible for extending the WCF runtime model with all the extensions that the workflow services depend on.

The WorkflowServiceHost provides three overloaded constructors for providing the definition of the workflow service that is hosted:

public WorkflowServiceHost(WorkflowService serviceDefinition,
params Uri[] baseAddresses);

public WorkflowServiceHost(Activity activity, params Uri[] baseAddresses);

public WorkflowServiceHost(object serviceObject, params Uri[] baseAddresses);

The first constructor receives an instance of a System.ServiceModel.Activities.WorkflowService class, which represents the workflow definition itself. Using this new class, you can create, configure, and gain access to all the properties of a workflow service. The other two constructors create and initialize a new WorkflowService instance using either a System.Activities.Activity (which represents the root activity in the service), or an object (which can be either a WorkflowService or Activity instance).

After you have the workflow definition, 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. For example, for adding new service endpoints, you can use either the WorkflowService.Endpoints collection as it is shown in the following code or the AddServiceEndpoint method directly on the WorkflowServiceHost instance — the traditional way to do it with regular WCF services:

var service = new WorkflowService();
service.Name = "HelloWorldService";
service.Body = CreateWorkflow();

var endpoint = new Endpoint
{
  AddressUri = new Uri("HelloWorld", UriKind.Relative),
  Binding = new BasicHttpBinding(),
  Name = "HelloWorldService",
  ServiceContractName = "HelloWorldService",
};

service.Endpoints.Add(endpoint);

The WorkflowServiceHost class also provides a property DurableInstancingOptions for configuring the persistence provider that is used by the workflow runtime. Using this property you can change the default InstanceStore by the one that persists the workflow instances in a SQL server database (SqlWorkflowInstanceStore). This can be accomplished with a few lines of code as is shown here:

var host = new WorkflowServiceHost(workflow, baseAddress);
 var connStr =
@"Data Source=.;Initial Catalog=WorkflowInstanceStore;Integrated Security=True";
var instanceStore = new SqlWorkflowInstanceStore(connStr);
host.DurableInstancingOptions.InstanceStore = instanceStore;
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.182.66