image

This chapter focuses on theWindows Communication Foundation (WCF) support that is provided by Windows Workflow Foundation (WF). This broad category of WCF support falls under the moniker of workflow services. Included with this support is the ability to declaratively author WCF services using WF as well as to invoke WCF services from within a workflow.

The chapter begins with a brief introduction of workflow services and WCF followed by an in-depth discussion of the WF classes and activities that you will use to declare and invoke WCF services. The centerpiece example of this chapter is an order processing service that simulates an ecommerce transaction. The example begins with a relatively simple request/response service operation. This first basic example is used to demonstrate how to initially test the service, how to publish the service to Internet Information Services (IIS), how to develop a self-hosting application for the service, and how to implement nonworkflow and workflow client applications that invoke the service.

In Chapter 10, you will expand on this basic example by implementing additional workflow services that are consumed by this original workflow.

Introducing Workflow Services

WF provides the ability to author workflows that use WCF to communicate. Using WF, you can author WCF services that are fully implemented as declarative workflows. These services can be hosted by a centralized Microsoft environment such as IIS or WAS, or they can be self-hosted by an application that you develop yourself. Once hosted, these services can be consumed by any WCF client. Depending on the WCF bindings that you use, the services can also be consumed by web service clients. As is true for all WCF services, the WCF client is unaware of the implementation details of the service. In the case of WF workflow services, the client is not necessarily aware that it is communicating with a workflow instance.

In addition to authoring new WCF services, you can also use WF to consume existing WCF or web services. WF provides standard activities that allow you to declaratively send messages to WCF services as well as handle any response that is part of the service contract. The services that you consume need not be implemented using WF—they can also be implemented in traditional procedural languages.

Before diving deeply into the WF support for WCF services, the next section provides a brief overview of WCF. It should provide you with just enough WCF background information to allow you to effectively consume and author services in WF. If you are an experienced WCF developer, you will already be familiar with the concepts that are presented.

images Note The goal of the next section is to provide an overview of how WCF works from the standpoint of traditional non-WF applications. The basic concepts that are discussed also apply to services that are implemented using WF. However, some of the tasks needed to implement a service are not necessary when you are implementing a service with WF.

Understanding WCF

In this section, I provide a brief overview of the key WCF concepts. Understanding just a few WCF concepts will provide the background that you’ll need to implement and use WF workflow services.

images Note WCF is a rich and complex Microsoft foundation that is at least as substantial as WF. In an effort to keep the focus of this book on WF, I’ve kept this overview as concise and high-level as possible. The information that I present here barely scratches the surface of WCF, and entire books have been written on the subject. If you plan on making extensive use of workflow services, you might want to purchase one of the many good WCF books, such as Pro WCF: Practical Microsoft SOA Implementation from Apress, that provide more in-depth coverage.

WCF is the enabling Microsoft technology for building service-oriented applications that was introduced in .NET 3.0. It provides a unified programming model for applications that are providers or consumers of contract-based services. WCF is really a superset of previous communication technologies including web services, Microsoft Message Queuing (MSMQ), and plain old sockets. It doesn’t replace any of these technologies, since they are all still available for use in your applications. But it does replace multiple communication APIs with a single one that can simultaneously target different transport mechanisms.

When using WCF, you implement an application using a single unified model and API. WCF decouples the transport mechanism from the service contract. This allows you to change the transport mechanism without affecting application code.

At its core, WCF is a framework for sending messages between systems. It is not a distributed component-based framework like remoting or Distributed COM (DCOM). When you are working with WCF (including when you are working with workflow services), this is an important distinction to keep in mind. When you use WCF to consume a service, you are exchanging a request message and (if applicable) a response message. The exact type and number of objects that are instantiated by the service provider to handle your request are not under your direct control (and really shouldn’t be your concern). In contrast with this, mechanisms like .NET remoting give you the ability to directly create instances of objects on a remote system. With remoting, you have intimate knowledge of the type of each object that you create as well as complete control over the object lifetime.

Defining Service Contracts

All WCF services define a service contract that identifies one or more operations. In a non-WF service implementation, the service contract usually takes the form of a C# interface that is decorated with the ServiceContract attribute. Each service method defined in the interface is decorated with the OperationContract attribute to identify it as a service operation.

images Note When declaring WCF services using WF, the service contract and operation are inferred from properties of the messaging activities. There is no need to explicitly define the service contract using a C# interface.

Service operations can pass or return simple built-in types (for example, integers or strings) or complex types that you define. You define each complex type as you normally would a C# class. However, the class can be decorated with the DataContract attribute to identify it as part of the data contract for an operation. The data contract simply describes the data that is transmitted between the client and server and serialized by the DataContractSerializer (the default serializer used by WCF).

Data contracts are defined on an opt-in basis. When defining a data contract, you must explicitly select the members that you want to include in the contract. You do this by applying the DataMember attribute to a member of the class. Since you explicitly identify the members that are serialized as part of the data contract, you can choose to include any combination of public and private members.

WCF is all about messages, and it supports several well-established message exchange patterns:

  • Request/response: With this message exchange pattern, a client sends a request message to the service and then expects (and waits for) a response message. This is the most common message exchange pattern since it nicely maps to service operations that accept parameters and return a result.
  • One-way: Operations that use this pattern do not return a response message. They are considered fire-and-forget operations since the client receives no direct feedback concerning the success or failure of the operation.
  • Duplex: With this message exchange pattern, a client initiates contact with a service and provides the service with a direct callback address to the client. The service uses the callback address to send messages (most typically one way) back to the client.

After defining all the necessary contracts, a service class is developed that provides an implementation for all the operations of the service contract. The service itself takes the form of a normal C# class that implements the service interface. Additional attributes such as the ServiceBehavior attribute can be applied to the service class to control aspects of the runtime service behavior.

images Note When you are using WF to implement a service, there is no need for a service class. The implementation of the WCF service is fully provided in a declarative way by a workflow service definition.

Configuring Endpoints and Bindings

A WCF endpoint identifies a location where messages are sent. Each WCF service is exposed as one or more endpoints. For instance, the same service can be simultaneously exposed to web service clients using HTTP and to local clients using TCP. In both cases, the same service implementation is used, but it is exposed as two separate endpoints, each with their own set of capabilities and behaviors.

Each endpoint is composed of an address, a binding, and the contract. The address is a Uniform Resource Identifier (URI) that identifies the location that clients use to invoke operations on the service contract. The address takes different forms depending on the transport being used for the endpoint. For instance, a service that is exposed to web service clients using HTTP might specify the address like this:

http://www.myserverhost.com:8000/MyServices/SampleService1

The same service exposed to TCP clients might use an address such as this:

net.tcp://myserverhost:9876/MyServices/SampleService1

The contract that is associated with the endpoint is the service contract that was discussed in the previous section. It defines all the operations that are available from the service.

The binding describes how the messages are exchanged between systems using this endpoint. It is a named stack containing the transport, encoding, and protocols that control the way WCF processes inbound and outbound messages on an endpoint. At a minimum, the binding identifies the transport protocol (for example, HTTP, TCP, MSMQ) and the encoding (for example, Text, Binary). The encoding determines the form that serialized messages take across the wire.

In addition, a binding can include a number of other capabilities or protocols that are provided with WCF or developed on your own. For example, a binding can also specify the type of security that is used (or not used), security credentials, reliable sessions, transaction support, throttling behavior (to limit the size of messages processed), and so on.

WCF includes a number of system-provided bindings that you can use, configure, or extend (for example, BasicHttpBinding, WSHttpBinding, NetTcpBinding). Each system-provided binding comes with a default set of capabilities and behaviors that are enabled, but the defaults can usually be configured to meet your needs. And you always have the option of building your own stack of capabilities and behaviors to create a custom binding that meets your exact needs.

Hosting and Configuration

WCF services can be self-hosted by your own .NET application, by Internet Information Services (IIS), Windows Process Activation Service (WAS), or Windows Server AppFabric.

To configure and host a service, you need to tie together all the elements that were just reviewed. These elements include the service implementation (the service class), the contract (the interface), an endpoint (address and transport), and a binding (capabilities and behaviors). The configuration of the service can be accomplished using application configuration files (App.config or Web.config) or in code if you are self-hosting.

Clients can consume the service in a couple of ways. One option is to create a set of client proxy classes based on the service metadata. To accomplish this, you must configure the service so that it exposes the service metadata. This metadata provides a complete description of the service contract (along with any data contracts used by the service) in a standard format. This metadata is then consumed by clients (or utilities) to understand how to use the service. In the Microsoft WCF world, the svcutil.exe utility (ServiceModel Metadata Utility) generates the necessary client proxy classes from the exposed metadata.

In addition to using a generated proxy, a client application can also make use of the WCF ChannelFactory class to invoke the service. To do this, the ChannelFactory references the interface that defines the service contract.

Understanding Workflow Services

In this section, I provide the background information that you need to know in order to implement and use workflow services. In Chapter 10, I expand on this basic information to discuss more specialized topics. I begin with an overview of the messaging activities that are included with WF.

Messaging Activities

WF provides a set of standard messaging activities that you use to implement a workflow service or to declaratively consume a WCF service within a workflow. These activities are used to implement a workflow service:

  • Receive
  • SendReply

And these activities are used when you need to consume a WCF service from within a client workflow:

  • Send
  • ReceiveReply

All the standard WCF message exchange patterns (request/response, one-way, duplex) can be implemented using this standard set of messaging activities. Each of these activities is briefly discussed in the sections that follow.

Receive Activity

The Receive activity defines a single service operation that can be consumed by WCF client applications. If a workflow implements multiple service operations, you would declare a separate Receive activity for each operation. The request parameters for each operation are defined using the Receive.Content property.

Here are the most important properties of the Receive activity:

image

If you are modeling a request/response operation, a Receive activity would be paired with a SendReply activity to send the response message. Any business logic that is necessary to implement the service operation would be declared between the Receive and SendReply activities.

If you are modeling a one-way operation, there is no need for additional messaging activities to implement the operation. Once the client application makes the one-way request, there is no expectation of a response.

Valid service contract names include a namespace and a contract name (typically specified as an interface name). For example, http://bukovics.com/IMyService is a fully qualified service contract name. If you don’t specify a namespace, http://tempuri.org is assumed. The ServiceContactName property is defined as an XName, which provides the means to define the namespace and contract name of the service contract.

The CanCreateInstance property is especially significant. If it is your intent to create a new workflow instance each time the operation is invoked, you need to set this property to true. Otherwise, the service operation can be invoked only on an existing workflow instance.

The KnownTypes property allows you to optionally specify additional types that may need to be serialized during the operation. This property is typically used when the content for the operation defines a base type that has multiple derived types. You would specify the derived types using the KnownTypes property. This informs the serializer that these additional types might be passed as part of the content.

The correlation-related properties are used to manually control correlation for the service operation. Please refer to the sections on correlation in Chapter 10 for more information on how to use these properties.

SendReply Activity

The SendReply activity provides the implementation for the second half of a request/response operation. It can never be used by itself. It can be paired only with a Receive activity to send the response for the service operation.

Here are the most important properties of the SendReply activity:

image

The Request property identifies the Receive activity that initiated the operation (the request). This property is set for you when a ReceiveAndSendReply activity template is added to the workflow. The workflow designer and property editor does not provide a way to manually set this property.

ReceiveAndSendReply Activity Template

A request/response operation requires a coordinated pair of activities (Receive, SendReply) to complete the operation. Because this is a common messaging pattern, an activity template is provided that preconfigures this combination of activities.

When you add a ReceiveAndSendReply activity template to a workflow, three activities are actually added. A Sequence activity is added as the container for a Receive and SendReply activity. The SendReply.Request property references the Receive activity to logically link the two activities.

Send Activity

The Send activity is used to declaratively invoke a service from within a workflow or custom activity. The service that you are invoking may have been implemented using WF, but that is not a requirement.

Here are the most important properties of the Send activity:

image

If you are invoking a request/response operation, a Send activity would be paired with a ReceiveReply activity to receive the response from the service. If you are invoking a one-way operation, the Send activity is used by itself without any additional messaging activities.

The three endpoint-related properties provide three different ways to specify the endpoint of the operation that you are invoking. Only one of these properties needs to be specified for any single Send activity.

ReceiveReply Activity

The ReceiveReply activity receives the response from a request/response operation. It cannot be used by itself and must be paired with a Send activity.

Here are the most important properties of the ReceiveReply activity:

image

SendAndReceiveReply Activity Template

The SendAndReceiveReply activity template is used to declare the activities that consume a request/response operation. When you add a SendAndReceiveReply activity template to a workflow, a set of Sequence, Send, and ReceiveReply activities are added. The template sets the ReceiveReply.Request property to logically relate the activity to the Send activity that initiates the request.

Service Contracts and Message Types

To invoke a WCF service operation, a message is sent to an endpoint for the service. If the operation uses the request/response message exchange pattern, another message is returned to the original caller as the response. WCF (and WF workflow services) supports two ways to describe these messages:

  • Data contract. A high-level, opt-in approach to defining the data that is exchanged in a service message.
  • Message contract. A single class that describes the low-level shape of the message header and body.

These two message types have slightly different characteristics, but in general, you can use either type. A data contract uses classes that are decorated with the DataContract attribute. Individual members of the class that you want to include in the message are decorated with the DataMember attribute. One or more of these classes can be included as individual parameters that are passed with the message. The parameters can also be primitive types (such as a string, float, or integer) or a collection of objects or values. A data contract can be likened to a method of a C# class that is passed multiple arguments. Internally, WCF does some wrapping of the individual parameters to create a single message.

In contrast with this, a message contract uses a single argument to represent the entire message. That argument can be a primitive type, or it can be a C# class that defines the individual properties of the message. Since a message contract is limited to a single argument, it avoids the additional layers of wrapping when the message is created. This usually results in a more concise XML representation of the message. A message contract also provides more control over the shape of the resulting message. For example, you can apply attributes to the properties of the message class that determine which properties are sent as message headers (MessageHeader attribute) versus the message body (MessageBodyMember attribute).

When you declare a new service operation, you determine the message type using the Content property of the Receive activity. The Content Definition dialog that you use to set this property presents you with a choice of Message or Parameters. This can be seen in Figure 9-1 shown later in this chapter. If you select the Message option, you are declaring a WCF message contract and are limited to a single argument that completely defines the message. If that single argument is a class, you can optionally decorate it with the MessageContract, MessageHeader, and MessageBodyMember attributes to fine-tune the exact shape of the message. If you select the Parameters content option, you are declaring a WCF data contract that supports single or multiple arguments. If an argument is a class, you can optionally decorate it with the DataContract and DataMember attributes to control which members are included in the message.

If the operation returns a response, the Content property of the SendReply activity determines the message type of the response.

images Warning A class that is decorated with the MessageContract attribute and other attributes related to message contracts cannot be used if you select the Parameters content option. However, the inverse is not true. A class that is decorated with the DataContract and DataMember attributes can be used for either content type (Message or Parameters).

When you are the consumer of a service operation, the Content property of the Send and ReceiveReply activities must match the signature of the operation being invoked. For example, if the service operation you are invoking uses a data contract, then you must do the same when you set the Content property of these activities.

Controlling Serialization of Complex Types

Regardless of the message type, complex types such as a C# class can be included in the message. By default, all public fields and properties of the class are serialized (using the DataContractSerializer) and included in the message.

However, you can determine which members are included and how they are serialized by applying a set of attributes to the class. Applying the DataContract attribute to the class allows you to control aspects of the contract, such as specifying a namespace used to scope the type. When you apply the DataContract attribute to a class, you must also apply the DataMember attribute to each member that you want to include. These attributes change the default behavior that includes all public fields and properties into opt-in serialization instead. Only the members that have a DataMember attribute applied to them are included in the message.

Controlling Message Contracts

If you are declaring a message contract, you can control aspects of the contract by applying the MessageContract attribute to the class. You then apply the MessageBodyMember or MessageHeader attribute to each field or property that you want to include in the message. One advantage to using a message contract is that it allows you to selectively control the signing and encryption of individual members of the message class. This is controlled by additional properties on the attributes.

Inferred Contracts

The contract for a conventional WCF service is declared using a C# interface that has been decorated with ServiceContract and OperationContract attributes. However, there is current no facility provided with WF to import or otherwise reuse existing contracts that are defined in C# interfaces. Perhaps this is something that Microsoft will address with a post-4 release of .NET.  Instead, all contracts for workflow services are inferred based on the properties of the Receive and SendReply activities.

These properties of the Receive activity combine to form the service contract:

  • ServiceContractName. The namespace and name of the contract.
  • OperationName. The name of the operation.
  • Content. The number, type and sequence of parameters that define a message or data contract.

If you are defining a request/response operation, the Content property of the SendReply activity determines the parameters that constitute the response portion of the contract.

Correlation

Correlation is really an overloaded term in WF. There are two ways that correlation is used with workflow services:

  • Associating one messaging activity with another in the same workflow
  • A way to route messages to the correct workflow instance
Associating Activities

In the first case, correlation is used to logically relate one messaging activity with another. For example, if you are using the duplex message exchange pattern, two workflows can independently exchange messages with each other. Each workflow has a Receive activity that is used to receive messages from the other workflow and a Send activity that sends a message to the other workflow. To logically relate the two messages (sent and received), correlation is used within each workflow.

images Note Chapter 10 presents a workflow example that uses the duplex message exchange pattern.

On the other hand, if you are implementing a request/reply message exchange pattern, the activities that declare the service (Receive and SendReply) are already logically related to each other via the Request property of the SendReply. In this case, there is usually no need for additional correlation. However, it may be necessary to implement correlation when a workflow has multiple, parallel messaging activities that are active at the same time.

Identifying Workflow Instances

The second use of correlation is to ensure that messages are routed to the correct workflow instance. For example, you might declare a stateful workflow that implements multiple operations. One operation would be used to create a new workflow instance (a Receive activity with the CanCreateInstance property set to true), while the other operations use the existing instance. Correlation is used to route the operations to the correct workflow instance.

Correlation for the purpose of identifying the correct workflow instance can be accomplished using context-based or content-based correlation. Context-based correlation requires a WCF binding that supports the exchange of context information in the SOAP headers or HTTP cookies. For example, the WSHttpContextBinding, NetTcpContextBinding, or BasicHttpContextBinding can be used for this purpose.

Content-based correlation uses data within the message itself to route the message. For example, each message may contain an account or order number that can be used to logically relate each operation to the others. This identifying value could be used to route each message to the correct workflow instance. Content-based correlation has the advantage that it does not require one of the special bindings that supports context exchange.

images Note In Chapter 10 you will see both correlation types in action. You will declare a workflow that first uses context-based correction and then modify it to use content-based correlation.

Controlling Correlation

Within a workflow, you can correlate messaging activities using one of these mechanisms:

  • Initialize and reference a CorrelationHandle.
  • Place the messaging activities in a CorrelationScope activity.

To use a CorrelationHandle, you first define it as a workflow variable. One messaging activity initializes the handle, while the other messaging activities reference it. The initialization is accomplished using one of the available correlation initializers provided with WF. All of these initializer classes derive from the abstract CorrelationInitializer class. Each initializer serves a slightly different correlation scenario:

  • RequestReplyCorrelationInitializer. Used for correlation in request/response operations.
  • ContextCorrelationInitializer. Used for context-based correlation.
  • QueryCorrelationInitializer. Used for content-based correlation.
  • CallbackCorrelationInitializer. Used with a duplex message exchange pattern to correlate a message with the calling service.

If you simply need to correlate two or more messaging activities with each other, you can choose to use a CorrelationScope activity. This is a container for one or more activities. The correlation is managed for you by placing the messaging activities that you want to correlate in the CorrelationScope. This eliminates the need to manually define and manage a CorrelationHandle.

Declaration and Hosting Options

WF provides two packaging options for workflows that use the WCF messaging activities:

  • As a Xaml workflow
  • As a Xamlx workflow service

A Xaml workflow is the same type of workflow that you have been using throughout this book. You can use messaging activities such Receive and SendReply in the workflow to declare a service operation. An instance of the workflow would typically be created when a client invokes the service operation (assuming that the CanCreateInstance property of the Receive activity is set to true). Xaml workflows can also act as a client and invoke a WCF service using the Send and ReceiveReply activities.

A Xamlx workflow service is designed to provide additional properties that simplify hosting by one of the Microsoft hosting environments (IIS, WAS, Windows Server AppFabric). If you plan to use one of these environments, it is recommended that you declare your workflows as Xamlx workflow services.

The top-level node of a Xamlx file is an instance of a WorkflowService class instead of an Activity. This class provides these properties:

image

In addition to declaring service operations (using Receive and SendReply), a Xamlx workflow service can take on the role of the client and invoke other services using Send and ReceiveReply.

When it comes to hosting your Xamlx workflow services, you have these options available to you:

  • Self-hosted in your application using the WorkflowServiceHost class
  • Hosted in IIS/WAS

images Note Xaml workflows that contain messaging activities can be self-hosted using the WorkflowServiceHost class, or they can be deployed to IIS and hosted with the addition of an .SVC file. However, it is recommended that you use Xamlx workflow services if you are planning on using a Microsoft-provided hosting environment. Doing so simplifies the deployment of your workflow services since the additional SVC file is not required. Hosting of workflow services is discussed and demonstrated later in this chapter.

Controlling Workflow Service Instances

A new workflow service instance is created each time an operation is invoked (assuming that the CanCreateInstance property is set to true). For many operations, the lifetime of the instance is short-lived. The workflow performs the necessary work to complete the operation, returns a response to the caller (if necessary), and is removed from memory.

However, long-running workflow services are also possible. These services may declare multiple operations that are invoked over a span of minutes, hours, or days. While they are waiting for the next service operation to be invoked, these instances are safely persisted in a durable store such as a SQL Server database.

To help with the management of these long-running workflow service instances that are hosted by WorkflowServiceHost, WF provides a standard WCF management endpoint (WorkflowControlEndpoint). This endpoint supports operations that manage existing service instances such as Cancel, Suspend, and Terminate. A client proxy class (WorkflowControlClient) is also provided that simplifies the calls to this control endpoint.

images Note You can’t effectively use the control endpoint unless you are working with long-running workflow instances that have been persisted. For this reason, use of the WorkflowControlEndpoint and the WorkflowControlClient is discussed in Chapter 12, which covers the larger topic of workflow persistence.

Declaring a Workflow Service

In this section, you will declare the first of several workflow services in this chapter. This first workflow forms the foundation for the subsequent examples in this chapter. In the sections that follow this one, you will declare additional service operations that will be invoked by this workflow service.

Tasks for a Request/Response Operation

This first service operation uses the request/response message exchange pattern. A client invokes the operation by sending it a request message and then waits for a response message. In general, you can follow these steps to implement a service operation using this pattern:

  1. Use the WCF Workflow Service Application new project template to create a project that is suitable for workflow services.
  2. Use the WCF Workflow Service add item template to add Xamlx files to the project if needed for additional services.
  3. Set the Name and ConfigurationName properties of the workflow.
  4. Add a ReceiveAndSendReply activity template to the workflow.
  5. At a minimum, set the ServiceContractName, OperationName, and Content properties of the Receive activity. It is permissible to omit setting the Content property if the request does not require any input parameters.
  6. Set the Receive.CanCreateInstance property to true to create a new instance of the workflow each time this operation is invoked.
  7. At a minimum, set the Content property of the SendReply activity. It is permissible to leave this property unset if the response does not return any data.
  8. Add additional activities between the Receive and SendReply to implement the business logic of the service operation.
  9. Modify the Web.config (or App.config if self-hosting) to include the necessary WCF entries for the new service (endpoints, bindings, behaviors, and so on).
  10. Deploy the workflow to a Microsoft hosting environment such as IIS or implement a self-hosting application.

images Note A new WCF Workflow Service includes a ReceiveAndSendReply activity template by default. You can choose to modify the properties of the existing Receive and SendReply activities or delete them and add a new ReceiveAndSendReply activity template to the empty workflow.

Implementing the OrderProcessing Workflow Service

The business scenario for this example is an order processing workflow service. If you think of the typical ecommerce site with a shopping cart, you’ll quickly get the idea. The request that is sent to this order processing service contains some basic customer information (their name, address, email, and so on), the items that they have ordered, and credit card information that is used for payment processing. This main workflow controls the overall process of assigning an order number, authorizing the credit card purchase, instructing the warehouse to ship the items, and determining a shipping date. The response from the workflow includes the order number, a ship date, and a credit card authorization code.

The initial version of this workflow will complete the service operation internally by populating the response with default values. Subsequent versions will enhance this example by invoking other service operations to perform the individual order processing tasks.

You will complete these tasks to implement this example:

  1. Create a new WCF Workflow Service Application project, and add a new WCF Workflow Service to the project.
  2. Implement C# classes that represent the request and response for the service operation.
  3. Add a ReceiveAndSendReply activity template to the workflow service, add the necessary workflow variables, and configure the individual messaging activities.
  4. Add additional activities to populate the response with valid values before it is returned to the caller.
  5. Review the Web.config file, and test the workflow service using the ASP.NET Development Server and WCF Test Client.

Creating the ServiceLibrary Project

To begin this example, create a new project using the WCF Workflow Service Application project template. Name the new project ServiceLibrary. Delete the Service1.xamlx file that is created with the new project since it won’t be used. The Web.config file that is created for the project will be used, so do not delete it.

Add a new WCF Workflow Service to the project, and name it OrderProcessing.xamlx.

images Tip I recommend that you start Visual Studio with elevated administrator privileges (the Run as Administrator option in Vista and Windows 7). Doing so avoids any security issues that you would otherwise encounter opening ports, publishing to IIS, and so on.

Implementing Request and Response Classes

The message content type, which supports only a single argument, will be used for this service operation; therefore, C# classes must be implemented for the request and response messages. Add a new C# class to the ServiceLibrary project, and name it OrderProcessingRequest. Here is the implementation for this class:

using System;
using System.Collections.Generic;

namespace ServiceLibrary
{
    public class OrderProcessingRequest
    {
        public OrderProcessingRequest()
        {
            Items = new List<Item>();
        }

        public String CustomerName { get; set; }
        public String CustomerAddress { get; set; }
        public String CustomerEmail { get; set; }
        public Decimal TotalAmount { get; set; }
        public String CreditCard { get; set; }
        public String CreditCardExpiration { get; set; }
        public List<Item> Items { get; set; }
    }
}

This class defines the properties that are passed to the service operation as a request message. The Item type referenced in the final property defines a single item to be sold along with the quantity to be ordered of that item. Add a new class named Item to the ServiceLibrary project to implement this type:

using System;

namespace ServiceLibrary
{
    public class Item
    {
        public Int32 ItemId { get; set; }
        public Int32 Quantity { get; set; }
    }
}

Finally, add a new class named OrderProcessingResponse to the ServiceLibrary to define the response from the service operation:

using System;

namespace ServiceLibrary
{
    public class OrderProcessingResponse
    {
        public Int32 OrderId { get; set; }
        public DateTime ShipDate { get; set; }
        public String CreditAuthCode { get; set; }
        public Boolean IsSuccessful { get; set; }
    }
}
Controlling the Data or Message Contract

As you can see, these are all ordinary C# classes that don’t require any additional attributes. By default, all the public properties are serialized and become part of the message. Optionally, you could explicitly control the serialization by adding instances of the DataContract and DataMember attributes to the classes. For example, here is the OrderProcessingRequest class after the data contract attributes have been added:

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace ServiceLibrary
{
    [DataContract]
    public class OrderProcessingRequest
    {
        public OrderProcessingRequest()
        {
            Items = new List<Item>();
        }

        [DataMember]
        public String CustomerName { get; set; }
        [DataMember]
        public String CustomerAddress { get; set; }
        [DataMember]
        public String CustomerEmail { get; set; }
        [DataMember]
        public Decimal TotalAmount { get; set; }
        [DataMember]
        public String CreditCard { get; set; }
        [DataMember]
        public String CreditCardExpiration { get; set; }
        [DataMember]
        public List<Item> Items { get; set; }
    }
}

These attributes allow you to explicitly determine which properties are included with the message as well as to override the name or sequence of each property. If you add the DataContract attribute to the class, only those properties that include the DataMember attribute will be included.

Alternatively, since the OrderProcessingRequest class is designed to represent the entire message contract, you can choose to apply the MessageContract, MessageBodyMember, and MessageHeader attributes to the class. Collectively, these attributes provide a fine degree of control over the exact shape of the serialized message. For example, here is the same request class decorated with these message contract attributes:

using System;
using System.Collections.Generic;
using System.ServiceModel;
namespace ServiceLibrary
{
    [MessageContract]
    public class OrderProcessingRequest
    {
        public OrderProcessingRequest()
        {
            Items = new List<Item>();
        }

        [MessageBodyMember]
        public String CustomerName { get; set; }
        [MessageBodyMember]
        public String CustomerAddress { get; set; }
        [MessageBodyMember]
        public String CustomerEmail { get; set; }
        [MessageBodyMember]
        public Decimal TotalAmount { get; set; }
        [MessageHeader]
        public String CreditCard { get; set; }
        [MessageHeader]
        public String CreditCardExpiration { get; set; }
        [MessageBodyMember]
        public List<Item> Items { get; set; }
    }
}

The placement of the MessageHeader or MessageBodyMember attributes determine whether each property is included in the header or body of the message.

images Note The lack of these message contract attributes doesn’t prevent a class from being used as a message contract. A service operation uses a WCF message contract if you select the Message option when you set the Receive.Content property. The single type that you designate for the Message option can be any of the variations that were just reviewed (no attributes, data contract attributes, or message contract attributes). If you use data contract attributes on the class, it simply means that you are refining elements of the data contract serialization for the class, but it is still considered a WCF message contract since that is the option you chose for the Receive.Content property.

Declaring the Service Operation

Now that the request and response classes have been defined, you can turn your attention to the workflow service. Rebuild the solution at this point to ensure that the classes that you have defined build successfully. After opening the OrderProcessing.xamlx workflow in the designer, you can follow the steps outlined next to declare a new ProcessOrder service operation.

images Note The instructions that follow and the test results that are shown assume that the original version of the request and response classes (without any additional attributes) have been used.

The workflow requires two variables, but you won’t be able to add them immediately. The step-by-step instructions will indicate when the variables can be added. Here is a recap of the variables required by the workflow:

image

Please follow these steps:

  1. Select the Sequence activity that was included with the new workflow service, and delete it. This also deletes the two messaging activities that are children of the Sequence activity. I’ve included this step since I find that it’s easier to drag a new activity template to the empty workflow to modify the property values of the one that is added by default.
  2. While the empty workflow is selected, set the Name and ConfigurationName properties to OrderProcessing. This sets the name for the service and also identifies the section within the Web.config (or App.config) file that defines WCF settings for this service.
  3. Add a ReceiveAndSendReply activity template (found in the Messaging category of the Toolbox) to the empty workflow. The template adds a Sequence activity with a Receive and SendReply activity (named SendReplyToReceive) as children.
  4. Add the two workflow variables indicated in the previous table. Both of them are scoped by the Sequence activity. Please note that the Response variable requires a default value to construct a new instance of the response object.
  5. Open the Content Definition dialog for the Receive activity by selecting Content on the activity or by clicking the ellipsis for the Content property in the Properties window. Once the dialog is opened, select the Message option if it is not already selected. Then set the Message data value to Request in order to reference the variable that you defined. When a new message is received for this operation, the value of that message will be used to populate the properties of the Request variable. The Message Type property does not need to be set. The type is inferred from the Message data value. Figure 9-1 shows the Content Definition dialog.

    images Note The Message Type is normally inferred from the Message data value that you specify. However, the Message Type property determines the data type that is published as part of the service description. You might need to explicitly set the Message Type if the data value that you set is a derived type but you want to expose a service description with the base type. If you set it, the Message Type must be the same type as the Message data value or its base class.

    images

    Figure 9-1. Receive activity content definition

  6. Set other properties of the Receive activity. Set the ServiceContractName property to {http://tempuri.org/}IOrderProcessing and the OperationName property to ProcessOrder. Set the CanCreateInstance property to true (checked). Figure 9-2 shows the completed properties for the Receive activity.
    images

    Figure 9-2. Receive properties

  7. Select the SendReplyToReceive activity (a SendReply), and set the Content property in a similar way. Select Message for the content type, and set the Message data to the Response variable. Figure 9-3 shows the Content Definition dialog for this activity.
    images

    Figure 9-3. SendReplyToReceive content definition

images Note The ReceiveAndSendReply activity template also creates a CorrelationHandle variable named __handle1. This handle is initialized with a RequestReplyCorrelationInitializer by the Receive activity, but it is not referenced anywhere in the workflow and is not needed for this example. The activity template includes it for you to use when correlating other messaging activities with this Receive activity. For this example, you can choose to keep this handle or remove it. If you choose to remove it, make sure that you also remove the correlation initializer from the Receive activity (see the CorrelationInitializers property).

Populating the Response

You have now declared the service operation, but the workflow doesn’t contain any real business logic. For this initial version of the workflow, you will add a set of Assign activities that populate the properties of the Response variable with valid values without actually implementing any business logic. In subsequent examples, you will replace these Assign activities with other activities that invoke additional service operations or use a workflow extension.

Add four new Assign activities to the workflow. All of these activities should be placed between the Receive and SendReplyToReceive activities. Set the properties for each of these Assign activities using the values shown here. Use one set of Assign.To and Assign.Value values for each Assign activity:

image

Figure 9-4 shows the completed OrderProcessing workflow service.

images

Figure 9-4. OrderProcessing workflow

Configuring the Service

This first example does not require any changes to the default Web.config file that was created with the ServiceLibrary project. Here are the contents of the default Web.config file with only a few cosmetic formatting changes to fit the format of this book:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4" />
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
</configuration>

Testing the Service

After building the solution, you should be ready to test this new service operation. Please follow these steps to test the service:

  1. Open the project properties for the project, and select the Web tab; make sure the Start Action is set to Current Page. This causes the currently selected page to be opened in the WCF Test Client. Make sure the Use Visual Studio Development Server option is set, and choose the Specific Port option. Set the port number to 8080. Setting a specific port isn’t strictly necessary, but it will ensure that your results match those shown here.
  2. Make sure the OrderProcessing.xamlx file is highlighted in the Solution Explorer, and press the Ctrl-F5 combination to start without debugging.
  3. After a brief wait, the ASP.NET Development Server should start, followed by the WCF Test Client.
  4. If all goes well, the WCF Test Client should be able to retrieve the metadata for the service and allow you to invoke the service operation. Enter test data for the request and then invoke the operation. Figure 9-5 shows the request data that I used along with the results of my test.
images

Figure 9-5. ProcessOrder using WCF Test Client

You may notice that the BasicHttpBinding was used for the service. This is the default binding that is used by services that use the HTTP protocol scheme and that are not explicitly defined in the Web.config file.

images Note Since many of the response properties are populated with random numbers, your results will be similar to the results shown here but will not match them exactly.

Publishing a Workflow Service to IIS

The ASP.NET Development server is fine for the initial development and testing of the workflow service, but it is not suitable as a permanent hosting environment. In this section, you will walk through the steps needed to publish the OrderProcessing workflow service to IIS.

Enhancing the Web.config

When you publish a project to IIS, the Web.config file for the project is deployed along with any required assembly files. Therefore, it is appropriate to make any adjustments to the Web.config file before publishing. To make things slightly more interesting, I’ve made a few slight modifications to the Web.config file for the ServiceLibrary project. Here is the modified file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4" />
  </system.web>
  <system.serviceModel>
    <services>
      <service name="OrderProcessing">
        <endpoint contract="IOrderProcessing" binding="wsHttpBinding"
          bindingConfiguration="OrderProcessingBinding" />
      </service>
    </services>
    <bindings>
      <wsHttpBinding>
        <binding name="OrderProcessingBinding">
          <security mode="Message">
            <message clientCredentialType="Windows"
             algorithmSuite="TripleDesRsa15"
             establishSecurityContext="true"
             negotiateServiceCredential="true" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
</configuration>

Instead of relying on the default WCF settings, the file now explicitly defines the OrderProcessing service. The single endpoint defines the contract (IOrderProcessing), the binding (wsHttpBinding), and a binding configuration (OrderProcessingBinding). The name specified here for the service (OrderProcessing) must match the value that you used when you set the ConfigurationName property of the workflow service.

The binding configuration provides values for a few security-related properties. These binding values are not absolutely necessary for this service. I’ve included them only to demonstrate how binding properties can be set within a configuration file.

images Tip You can hand-code the WCF configuration settings, or you can use the WCF Service Configuration Editor. This tool should be available as one of the standard options in the Visual Studio Tools menu. You can also start it outside of Visual Studio from the command line. The executable name is svcconfigeditor.exe, and it is distributed with the Windows SDK.

Publishing to IIS

Assuming that IIS has been installed and configured on your development machine, you can follow these steps to publish the OrderProcessing service to IIS:

  1. Right-click the ServiceLibrary project in the Solution Explorer, and select the Publish option. Select File System as the Publish Method. Create a new folder named ProWF under the Inetpubwwwroot folder. Select the new ProWF folder as the Target Location. This should publish all the files needed to host the service to the new folder.
  2. Open the IIS management console plug-in. I start this by right-clicking Computer and selecting the Manage option. You can also start the IIS Manager by running the executable InetMgr.exe directly.
  3. Find the ProWF folder under the Default Web Site, right-click it, and select the Convert to Application option. You should also verify that the ASP .NET 4 application pool is selected for this application.

The OrderProcessing service should now be published and ready for use. To test the service under IIS, you can start the WCF Test Client (WcfTestClient.exe) and add the service located at this URL: http://localhost/ProWF/OrderProcessing.xamlx. When you invoke the service, you should see similar results as your previous test using the ASP.NET Development Server.

Implementing a Client Application

In this section, you will implement a Windows console application that invokes the OrderProcessing workflow service. The purpose of this short example is to demonstrate how to invoke a WCF service from a nonworkflow application.

images Note The section following this one demonstrates how to invoke a WCF service from a workflow.

Adding a Service Reference

Create a new project using the Windows Console Application template (not a workflow project). Name the new project ConsoleClient, and add it to the solution that you created for this chapter. The first order of business is to add a service reference to the OrderProcessing workflow service. Adding a service references generates a set of classes that you can use to invoke the service. Please follow these steps to add the service reference:

  1. Right-click the new ConsoleClient project, and select the Add Service Reference option. This opens a dialog that allows you to select, configure, and add a service reference.
  2. Click the Discover button to search for any services in the same solution. You should see the OrderProcessing.xamlx service displayed in the list of available services.
  3. Select the OrderProcessing.xamlx service, and change Namespace to OrderProcessing. Before clicking OK, click the Advanced button. Change the Collection type option from the default of System.Array to System.Collections.Generic.List. This causes the generated client proxy code to use a generic class for any collections instead of a simple array.
  4. After making the changes indicated in the previous steps, click OK to generate the service reference. The ASP.NET Development Server should start to enable the service metadata to be retrieved. Once started, the server should continue running until you stop it, close the solution, or shut down Visual Studio.

Invoking the Service

Once the service reference has been created, the code to actually invoke the service is fairly simple. Here is the code that should be added to the Program.cs file of the ConsoleClient project:

using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Channels;
using ConsoleClient.OrderProcessing;

namespace ConsoleClient
{
    class Program
    {
        static void Main(string[] args)
        {

In this example code, I’ve chosen to execute the workflow service twice: the first time using a set of generated proxy classes and the second time using a generated interface. Both mechanisms are a valid way to access the service.

            CallViaProxy();
            CallViaInterface();

            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }

The service is first invoked using a set of generated proxy classes. This is the simplest way to invoke the service since the proxy classes hide most of the WCF-specific details. To invoke the service, you first create an instance of the request class and populate it with appropriate values. You then create an instance of the client proxy class and invoke the method named for the service operation (ProcessOrder), passing it the request object. The result is a response object containing the output from the service operation.

By default, the proxy class retrieves the WCF endpoint and binding configuration from the App.config file. This configuration file was also added to the project when you added the service reference.

Proper WCF etiquette requires that you call the Close method of the proxy class when you are finished with it. This closes the WCF channel to the server in a graceful and controlled manner.

        static void CallViaProxy()
        {
            OrderProcessingClient client = null;
            try
            {
                Console.WriteLine("About to invoke OrderProcessing service");
                client = new OrderProcessingClient();

                OrderProcessingRequest request = new OrderProcessingRequest();
                request.CreditCard = "4444111111111111";
                request.CreditCardExpiration = "0611";
                request.CustomerName = "Joe Consumer";
                request.CustomerAddress = "100 Main Street";
                request.CustomerEmail = "[email protected]";
                request.TotalAmount = 75.00M;
                request.Items = new List<Item>
                {
                    new Item { ItemId = 1234, Quantity = 1 },
                    new Item { ItemId = 2345, Quantity = 3 },
                };

                OrderProcessingResponse response = client.ProcessOrder(request);

                Console.WriteLine("Response IsSuccessful: {0}",
                    response.IsSuccessful);
                Console.WriteLine("Response OrderId: {0}",
                    response.OrderId);
                Console.WriteLine("Response ShipDate: {0:D}",
                    response.ShipDate);
                Console.WriteLine("Response CreditAuthCode: {0}",
                    response.CreditAuthCode);
            }
            catch (Exception exception)
            {
                Console.WriteLine("Unhandled exception: {0}", exception.Message);
            }
            finally
            {
                client.Close();
            }
        }

The second way to invoke a service operation is to use a generated interface. To use the interface, the CreateChannel method of the ChannelFactory<TChannel> generic class is invoked. The call to this method returns an object that implements the service interface. There are several overloads of the CreateChannel method. The one used here specifies the binding and endpoint address. The ProcessOrder method of the returned object can then be invoked in a similar way as was done in the previous code.

        static void CallViaInterface()
        {
            IOrderProcessing client = null;
            try
            {
                Console.WriteLine("About to invoke OrderProcessing service");

                WSHttpBinding binding = new WSHttpBinding(
                    "WSHttpBinding_IOrderProcessing");
                EndpointAddress epAddr = new EndpointAddress(
                    "http://localhost:8080/OrderProcessing.xamlx");

                client = ChannelFactory<IOrderProcessing>.CreateChannel(
                    binding, epAddr);

                OrderProcessingRequest request = new OrderProcessingRequest();
                request.CreditCard = "4444111111111111";
                request.CreditCardExpiration = "0611";
                request.CustomerName = "Joe Consumer";
                request.CustomerAddress = "100 Main Street";
                request.CustomerEmail = "[email protected]";
                request.TotalAmount = 75.00M;
                request.Items = new List<Item>
                {
                    new Item { ItemId = 1234, Quantity = 1 },
                    new Item { ItemId = 2345, Quantity = 3 },
                };

                ProcessOrderResponse poResponse = client.ProcessOrder(
                    new ProcessOrderRequest(request));

                OrderProcessingResponse response =
                    poResponse.OrderProcessingResponse;

                Console.WriteLine("Response IsSuccessful: {0}",
                    response.IsSuccessful);
                Console.WriteLine("Response OrderId: {0}",
                    response.OrderId);
                Console.WriteLine("Response ShipDate: {0:D}",
                    response.ShipDate);
                Console.WriteLine("Response CreditAuthCode: {0}",
                    response.CreditAuthCode);
            }
            catch (Exception exception)
            {
                Console.WriteLine("Unhandled exception: {0}", exception.Message);
            }
            finally
            {
                ((IChannel)client).Close();
            }
        }
    }
}

Reviewing the Configuration

When you added a service reference to the ConsoleClient project, an App.config file was also added to the project. This file contains the WCF configuration settings that are appropriate for the service that you referenced. You should be able to use the generated settings without any changes. Your App.config file should look similar to this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="WSHttpBinding_IOrderProcessing" closeTimeout="00:01:00"
          openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
          bypassProxyOnLocal="false" transactionFlow="false"
          hostNameComparisonMode="StrongWildcard"
          maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
          messageEncoding="Text"
          textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
          <readerQuotas maxDepth="32" maxStringContentLength="8192"
            maxArrayLength="16384"
            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <reliableSession ordered="true" inactivityTimeout="00:10:00"
            enabled="false" />
          <security mode="Message">
            <transport clientCredentialType="Windows" proxyCredentialType="None"
              realm="" />
            <message clientCredentialType="Windows"
              negotiateServiceCredential="true"
              algorithmSuite="TripleDesRsa15" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:8080/OrderProcessing.xamlx"
        binding="wsHttpBinding"
        bindingConfiguration="WSHttpBinding_IOrderProcessing"
        contract="OrderProcessing.IOrderProcessing"
        name="WSHttpBinding_IOrderProcessing">
      </endpoint>
    </client>
  </system.serviceModel>
</configuration>

images Note This App.config file and the code in Program.cs contain endpoint addresses that reference the OrderProcessing service that is hosted by the ASP.NET Development Server. It is assumed that you are using the recommended port (8080) for the service. If not, you’ll need to adjust the settings shown here to match your development environment.

Testing the Client Application

After building the ConsoleClient application, you should be ready to execute it. Before you do, you’ll need to make sure that the ASP.NET Development Server is running and is hosting the ServiceLibrary project. If it is not already running, you can set the ServiceLibrary project as the startup project and press Ctrl-F5 to start it without debugging. Once it starts, you can set the ConsoleClient project as the startup project and execute it. Optionally, you can change the startup project option to start multiple projects (ServiceLibrary and ConsoleClient).

Here are my results when I execute the ConsoleClient project:


About to invoke OrderProcessing service

Response IsSuccessful: True

Response OrderId: 1320239653

Response ShipDate: Friday, December 25, 2009

Response CreditAuthCode: 61478

About to invoke OrderProcessing service

Response IsSuccessful: True

Response OrderId: 696000473

Response ShipDate: Friday, December 25, 2009

Response CreditAuthCode: 32410

Press any key to exit

Implementing a Workflow Client

The previous client application populated the request class with a fixed set of values and then invoked the service operation entirely in code. In this example, you will develop a client workflow that declaratively invokes the service operation. To make the example slightly more interesting, the workflow will prompt the user (via the console) to enter the values that are needed for the request.

To begin this example, create a new project named WorkflowClient using the Workflow Console Application template, and add it to the existing solution for this chapter. You can delete the Workflow1.xaml file since it won’t be used.

images Tip The workflow for this client application contains a large number of activities, most of which are not directly related to the WCF messaging activities. The nonmessaging activities demonstrate one way to interact with a real human being to obtain input. Because of the size of this workflow, you may want to download the code for this particular example and review it instead of constructing the application from scratch.

Implementing Custom Activities

This example uses a custom activity that provides a way to prompt the user with a message and then wait for input from the console. To implement this activity, add a new Code Activity to the WorkflowClient project, and name it ReadLineWithPrompt. Here is the code for this activity:

using System;
using System.Activities;
using System.Reflection;

namespace WorkflowClient
{
    public class ReadLineWithPrompt<TResult> : AsyncCodeActivity<TResult>
    {

This generic activity defines a single input argument named Prompt that allows you to specify the message that is displayed to the user on the console. The generic type identifies the return type of the activity. In the example workflow that uses this activity, you will use return types of String and Int32.

        public InArgument<string> Prompt { get; set; }

        protected override IAsyncResult BeginExecute(
            AsyncCodeActivityContext context,
            AsyncCallback callback, object state)
        {

After displaying the prompt message, the code to read input from the console (WaitForConsoleInput) is asynchronously executed.

            Console.WriteLine(Prompt.Get(context));
            Func<TResult> getInput = () => { return WaitForConsoleInput(); };
            context.UserState = getInput;
            return getInput.BeginInvoke(callback, state);
        }

        private TResult WaitForConsoleInput()
        {
            TResult value = default(TResult);
            String stringInput = Console.ReadLine();

If the generic type is String, the work of this activity is done and no conversion of the input data is required. If it is some other type, reflection is used to execute the static Parse method. The assumption of this activity is that only types that support the Parse method will be used as the generic type.

            if (typeof(TResult) == typeof(String))
            {
                value = (TResult)(Object)(stringInput);
            }
            else
            {
                MethodInfo parse = typeof(TResult).GetMethod(
                    "Parse", BindingFlags.Static | BindingFlags.Public,
                    null, new Type[] { typeof(String) }, null);
                if (parse != null)
                {
                    try
                    {
                        value = (TResult)parse.Invoke(
                            null, new Object[] { stringInput });
                    }
                    catch
                    {
                        //ignore any parsing errors
                    }
                }
                else
                {
                    throw new InvalidOperationException(
                        "Parse method not supported");
                }
            }

            return value;
        }

        protected override TResult EndExecute(
            AsyncCodeActivityContext context, IAsyncResult ar)
        {
            return ((Func<TResult>)context.UserState).EndInvoke(ar);
        }
    }
}

Before moving on to the next step, you should build the solution to ensure that this custom activity builds correctly. This also adds the activity to the Toolbox to make it available for your use. If you receive an error that the Workflow1 type is missing, you can delete the offending code in the Program.cs file.

Adding a Service Reference

Just as was the case with the previous client application, the WorkflowClient project requires a service reference to the OrderProcessing workflow service. However, adding a service reference to a workflow project generates a custom activity for each service operation instead of the proxy classes that you used in the previous example. These generated activities enable you to easily invoke a WCF service operation declaratively within a workflow.

You can follow the same set of steps to add a service reference that you completed for the previous client application. After the service reference has been added to the project, build the solution to ensure that everything builds correctly. This also adds the generated messaging activity to the Toolbox, making it available for your use.

Implementing the InitiateOrderProcessing Workflow

Add a new workflow to the WorkflowClient project using the Activity add item template. Name the new workflow InitiateOrderProcessing. The goal of this workflow is to invoke the ProcessOrder service operation. It begins by prompting the user to enter property values for the service request. Once the request has been populated with the user’s input, the ProcessOrder operation is invoked. When the operation has completed, the properties of the service response are displayed on the console.

images Tip This workflow references a number of types that were generated by adding the service reference. To make it easier to quickly work with these types, you may want to add the WorkflowClient.OrderProcessing namespace to the Imports list of the workflow. To do this, click the Imports button on the design canvas, and enter the namespace at the top of the list. Adding a namespace to this list is similar in concept to adding a using statement to the top of a C# class. It allows you to access the types defined within the namespace without the need to fully quality them with the namespace.

Here is a recap of the workflow variables that you will add to the workflow. The instructions that follow will tell you when to add the variables.

image

Please follow these steps to declare the workflow:

  1. Add a Flowchart activity to the empty workflow.
  2. Add the Request and Response variables that are scoped by this topmost Flowchart activity.
  3. Add another Flowchart as the child of the topmost Flowchart activity. Change the DisplayName of this new Flowchart to EnterCustInfoFC to more easily distinguish from other Flowchart activities. Drag a connection from the starting point of the Flowchart to the top of the EnterCustInfoFC activity that you just added.
  4. After opening the EnterCustInfoFC activity, add a set of five ReadLineWithPrompt activities. Set the generic type for all of them to String. Connect the activities so that they are all executed in sequence. The purpose of these activities is to prompt the user to enter values for the customer-related properties of the request. Please refer to the following table for a list of the properties that should be set for each of these activities. The completed EnterCustInfoFC activity should look like Figure 9-6.

    image

    images

    Figure 9-6. EnterCustInfoFC flowchart

  5. Return to the topmost Flowchart, and add another Flowchart activity as a child. Change the DisplayName of this new activity to EnterItemsFC. Drag a connection from the bottom of the EnterCustInfoFC to this new activity.
  6. Open the EnterItemsFC activity, and add the Item and Items variables that are scoped by the EnterItemsFC activity.
  7. Add an Assign activity as the first child of the EnterItemsFC activity. Connect the starting point of the flowchart to the top of the Assign activity. Set the Assign.To property to Item and the Assign.Value property to New Item(). This initializes the Item variable and prepares it for the steps that follow this one.
  8. Add a ReadLineWithPrompt activity below the Assign. Set the generic type to Int32. Drag a connection between the bottom of the Assign and the top of this new activity. Set the Prompt property to "Enter an item ID (enter 0 if done entering items)" and the Result property to Item.ItemId.
  9. Add a FlowDecision control below the ReadLineWithPrompt. Add a connection between the two activities. Set the Condition property to Item.ItemId <> 0.
  10. Add a ReadLineWithPrompt activity below and to the left of the FlowDecision. Set the generic type to Int32. Set the Prompt property to "Enter the quantity of the item" and the Result property to Item.Quantity. Drag a connection from the true side of the FlowDecision (the left side) to the top of the ReadLineWithPrompt.
  11. Add an AddToCollection<T> activity below the last ReadLineWithPrompt activity. Set the generic type of the activity to Item. Set the Collection property to Items and the Item property to Item. This adds the newly constructed Item object to the collection.
  12. Drag a connection from the side of the AddToCollection<T> activity to the side of the Assign activity at the top of the flowchart. This causes the flowchart to loop back to the top to allow entry of another item.
  13. Turning your attention to the false side of the FlowDecision, add an Assign activity to the right and below the FlowDecision. Set the Assign.To property to Request.Items and the Assign.Value property to Items. This populates the Items property of the Request variable with the collection of items that has been constructed by this flowchart. Drag a connection from the false side of the FlowDecision to the top of this Assign activity.
  14. Add another Assign activity below the one that you just added. Set the Assign.To property to Request.TotalAmount and the Assign.Value property to Items.Count * 1.99D. Add a connection between the two Assign activities. This completes the EnterItemsFC flowchart activity, which is shown in Figure 9-7.
    images

    Figure 9-7. EnterItemsFC flowchart

  15. Return to the topmost Flowchart activity, and add a WriteLine activity below the EnterItemsFC. Set the Text property to "Invoking ProcessOrder service method". Add a connection from the EnterItemsFC to this activity.
  16. Add an instance of the ProcessOrder custom activity below the WriteLine. This is the activity that was generated for you when you added a service reference to the project. Set the OrderProcessingRequest property to Request and the OrderProcessingResponse property to Response. Add a connection between this activity and the WriteLine that is directly above it.
  17. Add another WriteLine below the ProcessOrder activity. Set the Text property to "Back from ProcessOrder". Connect it to the ProcessOrder activity directly above it.
  18. Add another Flowchart activity below the WriteLine. Change its DisplayName to DisplayResponseFC and connect it to the WriteLine that is directly above it.
  19. Open the DisplayResponseFC activity, and add a set of five WriteLine activities. Connect all of the activities so that they are executed in order. Set the Text property of each WriteLine activity as indicated in the following table. Figure 9-8 shows the completed DisplayResponseFC activity.

image

images

Figure 9-8. DisplayResponseFC flowchart

Figure 9-9 shows the completed InitiateOrderProcessing workflow.

images

Figure 9-9. InitiateOrderProcessing workflow

Hosting the Workflow

The final task for this example is to add the code that executes the InitiateOrderProcessing workflow. Here is the code for the Program.cs file of the WorkflowClient project:

using System;
using System.Activities;
using System.Collections.Generic;
using System.Threading;

namespace WorkflowClient
{
    class Program
    {
        static void Main(string[] args)
        {
            ExecuteClientWorkflow();
            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }

        private static void ExecuteClientWorkflow()
        {
            WorkflowApplication wfApp = new WorkflowApplication(
                new InitiateOrderProcessing());

            AutoResetEvent waitEvent = new AutoResetEvent(false);
            wfApp.Completed = (a) =>
            {
                waitEvent.Set();
            };

            wfApp.OnUnhandledException = (e) =>
            {
                Console.WriteLine("OnUnhandledException: {0}",
                    e.UnhandledException.Message);
                return UnhandledExceptionAction.Cancel;
            };

            wfApp.Run();
            waitEvent.WaitOne(90000);
        }
    }
}

Testing the Client Application

Adding the service reference to the project should have also added an App.config file containing the necessary WCF configuration settings. The contents of this App.config file should look exactly like the previous client application.

To test the application, you need to also start the ServiceLibrary project using the ASP.NET Development Server. The easiest way to do this is to change the startup project option to start multiple projects (ServiceLibrary and WorkflowClient). After pressing Ctrl-F5 (run without debugging), you should be prompted to enter the customer and order information. When you have finished entering one or more items with their quantity, you can just press Enter without entering a value or enter a value of 0. This is the signal to the workflow that entry of the request properties is complete and that it should invoke the ProcessOrder service operation.

Here is a representative example of my results:


Enter the customer name

Bruce Bukovics

Enter the customer email

[email protected]

Enter the customer address

100 Main Street

Enter credit card number

4444111122223333

Enter credit card expiration date (mmyy)

0611

Enter an item ID (enter 0 if done entering items)

200

Enter the quantity of the item

1

Enter an item ID (enter 0 if done entering items)

300

Enter the quantity of the item

2

Enter an item ID (enter 0 if done entering items)

Invoking ProcessOrder service method

Back from ProcessOrder

ProcessOrder response:

IsSuccessful: True

ShipDate: Sunday, December 27, 2009

OrderId: 351417896

CreditAuthCode: 38789

Press any key to exit

Self-hosting the Workflow Service

Up to this point, you have used the ASP.NET Development Server and IIS to host the workflow service. You can also choose to self-host the service in an application that you develop. The advantage to self-hosting is that it is typically easier to deploy your application since you don’t require a dependency on IIS. Self-hosting also presents you with additional opportunities to fine-tune the hosting environment in code.

Understanding the WorkflowServiceHost

The WorkflowServiceHost class that is included with WF provides the ability to self-host a workflow service in your own application. You construct this class by passing an instance of an Activity (a Xaml workflow) or WorkflowService (a Xamlx workflow service) in the constructor. You then provide binding and endpoint information for the service (either in code or using entries in an App.config file). Finally, you call the Open method to enable the service to begin processing messages. During the shutdown processing of your application, you call the Close method to gracefully transition the service host to a state that no longer processes incoming messages.

images Warning Be aware that .NET 4 actually includes two types with the same WorkflowServiceHost name. The one that is capable of hosting .NET 4 workflow services is in namespace System.ServiceModel.Activities. The one that is located in the System.ServiceModel namespace is used for hosting WF 3.x workflows.

The WorkflowServiceHost class derives from the ServiceHostBase class, which in turn, derives from the base CommunicationObject class. Because of this class hierarchy, the WorkflowServiceHost inherits a large number of members that are defined by these base classes. Here are the most important members of the WorkflowServiceHost class:

image

images Note Chapter 11 discusses the use of persistence with workflow services along with other persistence topics.

Tasks for Self-hosting a Service

In general, you can follow these steps to self-host a workflow service:

  1. Create an instance of the Activity or WorkflowService that you want to host.
  2. Use the Activity or WorkflowService instance to construct a WorkflowServiceHost instance.
  3. Add any workflow extensions (if required).
  4. Configure persistence options (if required).
  5. Provide a binding and endpoint for the service (either in code or via App.config entries).
  6. Open the workflow service to enable it to receive requests.
  7. Close the workflow service to gracefully transition the service to a closed state before exiting the application.

Implementing the ServiceHost Application

To see the WorkflowServiceHost in action, you will implement a simple console application that hosts the OrderProcessing workflow service. Create a new project using the Workflow Console Application template. Name the new project ServiceHost, and add it to the existing solution for this chapter. Delete the Workflow1.xaml file since it won’t be used.

By default, the Workflow Console Application project template targets the .NET Framework 4 Client Profile. This profile does not include the server-related types needed to host a workflow service. For this reason, you need to open the project properties and change the Target framework to .NET Framework 4 (the full framework).

You also need to add a project reference to the ServiceLibrary project. You will be loading the workflow service definition directly from the Xamlx file instead of referencing the compiled types in the ServiceLibrary project. However, you do need to reference the ServiceLibrary assembly in order to deserialize the types that are used by the workflow service such as the request and response classes. Adding the project reference causes the ServiceLibrary assembly to be copied into the output folder for the ServiceHost project. This is strictly a runtime dependency—not a design-time dependency.

Here is the complete Program.cs file that contains the code needed to host the service:

using System;
using System.Collections.Generic;
using System.IO;
using System.ServiceModel.Activities;
using System.Xaml;

namespace ServiceHost
{
    class Program
    {
        private static List<WorkflowServiceHost> _hosts =
            new List<WorkflowServiceHost>();

        static void Main(string[] args)
        {
            try
            {

A private CreateServiceHost method is called to create a WorkflowServiceHost instance for the requested workflow service. The code was organized in this way to make it easier to host the additional services that you will develop in Chapter 10. Each service host that has been created is then opened. For this example, that’s a single host. Code in the finally block closes any service hosts that have been opened.

This code assumes that the necessary WCF configuration entries for this service will be provided by an App.config file. Alternatively, you could call the AddServiceEndpoint method to add an endpoint directly in the code.

                CreateServiceHost("OrderProcessing.xamlx");

                foreach (WorkflowServiceHost host in _hosts)
                {
                    host.Open();
                    foreach (var ep in host.Description.Endpoints)
                    {
                        Console.WriteLine("Contract: {0}",
                            ep.Contract.Name);
                        Console.WriteLine("    at {0}",
                            ep.Address);
                    }
                }

                Console.WriteLine("Press any key to stop hosting and exit");
                Console.ReadKey();
            }
            catch (Exception exception)
            {
                Console.WriteLine("Service Exception: {0}", exception.Message);
            }
            finally
            {
                Console.WriteLine("Closing services...");
                foreach (WorkflowServiceHost host in _hosts)
                {
                    host.Close();
                }
                Console.WriteLine("Services closed");
                _hosts.Clear();
            }
        }

The CreateServiceHost method first loads the service definition directly from the named Xamlx file. The root node of a Xamlx file is actually a WorkflowService instead of an Activity. It is for this reason that the code defines the output from the LoadService method as a WorkflowService and uses it to construct the WorkflowServiceHost instance.

The WorkflowService object includes the additional Name and ConfigurationName properties that associate the workflow service with configuration entries in the App.config file. Alternatively, you could reference the WorkflowService.Body property if you only want to use the Activity definition instead of the entire WorkflowService definition. You would do this if you wanted to provide your own Name and ConfigurationName values for the service.

        private static WorkflowServiceHost CreateServiceHost(String xamlxName)
        {
            WorkflowService wfService = LoadService(xamlxName);
            WorkflowServiceHost host = new WorkflowServiceHost(wfService);

            _hosts.Add(host);

            return host;
        }

The private LoadService method deserializes a WorkflowService instance from the named Xamlx file. For this example, the Xamlx file is assumed to be found in a relative path under the ServiceLibrary project. This relative path was used as a convenience since that is the source location of the Xamlx file. This code assumes that these projects are located in the same solution. You will need to adjust this path if the Xamlx files are in a different location. To deserialize the Xamlx file, the static XamlServices.Load method is called.

        private static WorkflowService LoadService(String xamlxName)
        {
            String fullFilePath = Path.Combine(
                @"......ServiceLibrary", xamlxName);
            WorkflowService service =
                XamlServices.Load(fullFilePath) as WorkflowService;
            if (service != null)
            {
                return service;
            }
            else
            {
                throw new NullReferenceException(String.Format(
                    "Unable to load service definition from {0}", fullFilePath));
            }
        }
    }
}

images Tip Please remember that the XamlServices.Load method requires runtime access to the types in the ServiceLibrary assembly. In particular, any types referenced by the Xamlx file such as the request and response classes must be available in order to deserialize the workflow service. If you receive an exception that the Xamlx file cannot be deserialized, it is most likely caused by a missing ServiceLibrary.dll assembly. If you make sure that this assembly is in the same indebug folder as ServiceHost.exe, the deserialization should work correctly.

Configuring the Service Host

Before you can run the ServiceHost project, you need to provide WCF configuration entries in the App.config file. Here is a complete App.config file that provides the necessary settings:

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="OrderProcessing">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:9000/"/>
          </baseAddresses>
        </host>
        <endpoint contract="IOrderProcessing"
          address="http://localhost:9000/OrderProcessing"
          binding="wsHttpBinding"
          bindingConfiguration="OrderProcessingBinding" />
      </service>
    </services>

    <bindings>
      <wsHttpBinding>
        <binding name="OrderProcessingBinding">
          <security mode="Message">
            <message clientCredentialType="Windows"
             algorithmSuite="TripleDesRsa15"
             establishSecurityContext="true"
             negotiateServiceCredential="true" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>

    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceDebug includeExceptionDetailInFaults="True"
           httpHelpPageEnabled="True"/>
          <serviceMetadata httpGetEnabled="True"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

  <startup>
    <supportedRuntime version="v4" sku=".NETFramework,Version=v4"/>
  </startup>
</configuration>

This configuration file defines the OrderProcessing service with an endpoint address of http://localhost:9000/OrderProcessing. The port number of 9000 was chosen somewhat at random because it’s just a nice round number that is easy to remember. The name specified for the service (OrderProcessing) matches the ConfigurationName property that is set within the Xamlx file. A binding configuration is also included which has the similar set of properties that you have used in previous examples.

Testing the Self-hosted Service

After building the solution, you should be ready to test the ServiceHost project. Select this project as the startup project, and press Ctrl-F5 to run the project without debugging. After a short wait, you should see these results:


Contract: IOrderProcessing

    at http://localhost:9000/OrderProcessing

Press any key to stop hosting and exit

The workflow service is now hosted and is awaiting client requests. You can use the WCF Test Client to perform an initial test of the service. You should be able to point to the base service address of http://localhost:9000/ to retrieve the metadata for the service.

images Tip These instructions assume that you have started Visual Studio with elevated administrator privileges. If you run the ServiceHost.exe application outside of Visual Studio, you will also need to run it with these increased privileges. If you don’t, you will receive an error indicating that you do not have the authority to open the designated port. Alternatively, you can use the netsh scripting utility to add the current machine user to the access list for the desired port. Please refer to the "Configuring HTTP and HTTPS" topic in the WCF online documentation for more information.

Using the WorkflowClient Application

At this point you should also change the configuration for the WorkflowClient application to reference this self-hosted service. Previously, the WorkflowClient application referenced the service that was hosted by the ASP.Net Development Server (port 8080). To reference the self-hosted service, change this port number to 9000. The .xamlx extension has also been removed from the endpoint address. Here is an abbreviated version of the App.config file showing the section that requires a change:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>

    <client>
      <endpoint address="http://localhost:9000/OrderProcessing"
        binding="wsHttpBinding"
        bindingConfiguration="WSHttpBinding_IOrderProcessing"
        contract="OrderProcessing.IOrderProcessing"
        name="WSHttpBinding_IOrderProcessing">
      </endpoint>
    </client>
  </system.serviceModel>
</configuration>

After making this configuration change, you should be able to execute the WorkflowClient application and reference the self-hosted service. Make sure that the ServiceHost project is also running. Your results should be consistent with the previous test for this application.

Summary

The focus of this chapter was the WCF and workflow services support provided with WF. The chapter presented a brief introduction of workflow services and WCF followed by an in-depth review of the WF messaging-related activities and classes.

The chapter presented a workflow service that declared a relatively simple request/response operation. This example service was used to demonstrate how to publish a workflow service to IIS as well as how to implement an application that self-hosted the service. Two client applications were developed. One was a nonworkflow application and the other invoked the workflow service declaratively from another workflow.

In the next chapter, you will build upon the example service that you began in this chapter to learn about correlation and other message exchange patterns.

..................Content has been hidden....................

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