Integrating Windows Workflow Foundation and Windows Communication Foundation

 

Jeremy Boyd
Intergen

January 2007

Applies to:
   Windows Workflow Foundation
   Windows Communication Foundation
   Microsoft Visual Studio 2005

Summary: This article provides an overview of how workflows that are built by using Windows Workflow Foundation (WF) can be hosted inside services that have been created by using Windows Communication Foundation (WCF). The article also describes how we can make use of some of the broad capabilities that are provided by WCF to facilitate client-event callbacks by making use of a duplex channel. (15 printed pages)

Contents

Introduction
Expense Reporting Sample
Checklist for Integration
Defining Service Contracts
Hosting the Workflow Runtime
Configuring Deployment
Consuming the Service
Configuring Duplex Channels
Conclusion
For More Information
Acknowledgements

Click here to download "Windows Workflow Foundation Sample Integrating WF and WCF."

Introduction

With the availability of Windows Workflow Foundation (WF), Microsoft is introducing workflow capabilities to the .NET developer platform. These capabilities allow developers to build workflows to meet a wide number of scenarios, from simple sequential workflows to complex state machine-based workflows with sophisticated human interactions.

At the same time, there is a move towards promoting business capabilities to be exposed through encapsulated service endpoints allowing reuse and composition of business functions and processes giving rise to Service-Oriented Architectures. Windows Communication Foundation (WCF) has been made available to help provide developers with capabilities to develop connected systems easily by providing a consistent developer API, a robust hosting runtime, and a flexible configuration-driven solution to aid deployment.

At the end of this document is a list of additional resources that can be used to learn more about both WF and WCF.

Expense Reporting Sample

The code sample for this article is based on the Expense Reporting workflow sample that models a standard business process of submitting and approving an employee's expense claim. The original sample has been updated to demonstrate how you can leverage WCF and the .NET 3.0 Framework to host this scenario more effectively.

When the first Expense Reporting sample was released, it used .NET Remoting to provide communication between the client applications and the host application that contained the workflow-runtime instance.

We have refactored the implementation of Expense Reporting to perform the communication between clients and service by using WCF. The solution has also been logically structured to separate out the various concerns within the solution.

Bb266709.intgrwfwcf01(en-us,MSDN.10).gif

Figure 1. The structure of our refactored solution

It is important to understand how messages are used within the context of the business process, so that you can incorporate these in your design. In the life cycle of the Expense Reporting, there are several points of interaction. Let us review these briefly:

  • There are three parties in the process: a client, a manager, and the Expense Reporting host system.
  • The process starts by a client submitting a new expense claim.
  • We use a policy of rules to determine if the expense claim can be automatically approved.
  • If the expense claim has not been automatically approved, we need a manager to approve the report. The manager would need to either check for a new report to approve or be notified of this.
  • If the manager does not approve within a flexible delay of time, the process will automatically reject the claim.
  • After an expense claim has been reviewed, the client and the manager must be updated as to the outcome.

By using WF, we can model this process by using the standard activities that are provided with the framework. We can use the DelayActivity to manage an event triggering after a period of time has passed, and we can use the Rules engine and PolicyActivity to manage a flexible set of rules being interrogated for an outcome.

Because it is a human-oriented process, we must interact with the end users and raise that interaction back into the workflow. WF provides a comprehensive programming model for allowing communication between a host and a workflow by providing Local Services, the HandleExternalEventActivity, and the CallExternalMethodActivity.

Because this is an important concept in building interactive workflows, let us quickly cover how this has been designed into WF.

For interactions to be modeled in WF, we must design a contract that exposes a number of events and methods. This contract will be understood by both the workflow and host process. The contract/interface that we build must be marked with the [ExternalDataExchange()] attribute that identifies it as being designed for workflow-data exchange. In our sample, we use the IExpenseLocalService interface with our workflow.

We then subscribe a class (known as a Local Service) that implements that interface with the workflow runtime. The workflow activities can register for events or consume methods that are defined on the interface type, and will be wired up to the Local Service that we have registered. This uses a pattern called Inversion of Control that removes the tight coupling between the workflow and the concrete type of the Local Service. In our sample, the ExpenseLocalService class implements our IExpenseLocalService contract.

When the workflow first runs, it can be provided an initial bag of data on which to operate. After the workflow reaches a point at which it needs external interaction, we can raise an event that can be bound to a HandleExternalEventActivity within the workflow. This activity takes an interface type and event as arguments, and the workflow will be awoken when the event is raised allowing execution to continue.

If the workflow must call back to a Local Service, it can do so by using a CallExternalMethodActivity and giving the interface and method name as arguments.

By using these activities, we can perform bi-direction communication within the host process with a running workflow; and, through use of the Inversion of Control pattern within WF, you are shielded from tight coupling between workflows and your local services.

Wider than just the host process, however, we must allow interactions that are driven by other systems or even human beings. We can achieve this level of interaction by distributing our interactive operations through services that can, in turn, be called by other services or user-driven applications. WCF is the framework on which we can build this messaging capability in a flexible manner.

The key benefits in our scenario of integrating with WCF are that:

  • We are able to decouple our service implementation from the messaging plumbing code.
  • There is a lot less code and complexity to connect our systems.
  • We have flexibility for deployment.
  • We are able to make use of direct callbacks from the host to the clients, to provide faster and lower-overhead updates of information.

Checklist for Integration

To complete an integration of WF and WCF, we must expose a service interface that is going to provide a number of interface points to consumers, where they can initiate or interact with a running workflow. The service should be modeled around the points in which the business process interacts with external entities, such as humans involved in the process.

Bb266709.intgrwfwcf02(en-us,MSDN.10).gif

Figure 2. Interaction points in the Expense Reporting scenario

To achieve this, we must:

  • Define service contracts.
  • Implement service operations that will create new (or interact with existing) workflows through events.
  • Host a workflow-runtime instance inside a service host.

In addition to simply hosting our workflow, we can also make use of WCF duplex channels to raise events out of the workflow back to consuming clients. For the Expense Reporting, this is of benefit because the solution relies on the clients polling the service for regular data updates. Instead, they can be notified directly from the service.

For this, we must:

  • Define a callback contract.
  • Use a binding that will support a duplex channel.

Defining Service Contracts

Windows Communication Foundation (WCF) required that a formal contract be declared that abstractly defines the capabilities of a service and the exchange of data. This is defined in code by declaring an interface.

When you are designing a business service, you will typically be using a Request/Response collaboration pattern. When using this pattern, you must cater for three aspects in the contract that is being offered, those being:

  • The operations being published. These are the capabilities that the service publishes to its consumers. These are the methods on the interface.
  • Messages that encapsulate the structured data for each request and response. These are the arguments and return types for each method. In WCF terminology, these are typically message contracts or, in simpler scenarios, will be data contracts.
  • Data definitions of the core business entities that can be exchanged through the service. These will form part of the messages. In WCF terminology, these will be our data contracts.

A service contract is defined by using an attribute-based markup that defines the contracts that expose operations, and then the specific operations that are being published over the wire.

Each service contract is explicitly marked up with the [ServiceContract] attribute. This attribute can be declared with parameters that include:

  • Name. Controls the name of the contract declared in the WSDL <portType> element
  • Namespace. Controls the name of the contract declared in the WSDL <portType> element
  • SessionMode. Specifies if the contract requires a binding that support sessions
  • CallbackContract. Specifies the contract to be used for client callbacks
  • ProtectionLevel. Specifies if the contract requires a binding that supports the ProtectionLevel property, which is used to declare requirements for encryption and digital signatures

Declaring the Operations

The service is then made up of a number of published operations. Operations are explicitly opted-in to the contract by being marked up with the [OperationContract] attribute. Like the ServiceContract, an OperationContract has a number of parameters that control how it can be bound to an endpoint. These include:

  • Action. Controls the name to identify this operation uniquely. When messages are received by an endpoint, the dispatcher uses the control and action to determine the method to call.
  • IsOneWay. Indicates that the operation will take a Request message, but produces no response. This is different from simply returning a void return type that will still generate a Result message.
  • ProtectionLevel. Specifies the requirements for encryption or signing that the operation will require.

Here is an example of what a service contract will look like in code.

[ServiceContract]
public interface IExpenseService
{
        [OperationContract]
        GetExpenseReportsResponse GetExpenseReports();

        [OperationContract]
        GetExpenseReportResponse GetExpenseReport(GetExpenseReportRequest 
getExpenseReportRequest);
}

Declaring the Messages and Data Entities

You will want to model your messages as classes that define the payload or body for each of the messages that you will be sending. This is similar to how messages were modeled by using tools such as WS Contract First (WSCF) when building Web services using ASP.NET.

WCF by default uses a serialization engine called the DataContractSerializer to serialize and deserialize data (that is, to convert it to and from XML). We make use of the DataContractSerializer by adding a reference to the System.Runtime.Serialization namespace, and then mark our class with a [DataContract] attribute and the members to be published with [DataMember].

[DataContract]
    public class GetExpenseReportsResponse
    {
        private List<ExpenseReport> reports;

        [DataMember]
        public List<ExpenseReport> Reports
        {
            get { return reports; }
            set { reports = value; }
        }
    }

The data entities that are used within the messages represent the entities within your business domain. Like the message contracts, we can use the DataContractSerializer and attributing to explicitly opt-in members that are being distributed; or, if we are only going to model data, we can use an approach of public fields and mark the class as serializable.

In the sample, we have used the data-contract approach for marking up messaging. In real-world scenarios, you will often find yourself dealing with more complicated schemas, use of attributes in your schemas, and requirements to use SOAP headers. WCF caters for these edge cases by offering the ability to define a class marked with the [MessageContract] attribute that will describe the entire SOAP envelope, instead of just the body.

For more information on data contracts and message contracts, refer to the respective MSDN Library articles in the For More Information section, at the end of this article.

Hosting the Workflow Runtime

Services normally allow for concurrent behavior in which a new instance of the service type will be created and maintained for the life cycle of a session. To use workflow in this situation, we must create an instance of the workflow runtime and maintain it for the life of the service-host instance, instead of on a per-call basis.

The recommended approach for this is to use an extension class that will be activated upon creation of the service host. This extension will create and maintain a global instance of the workflow runtime, allowing it to be accessed by each of the independent service instances.

To implement an extension on ServiceHost, create a class that implements IExtension<ServiceHostBase>. In the solution, you can find an example of this in the WfWcfExtension class that resides under the WcfExtensions code project.

We are required to implement two methods: Attach, which will be called as the extension is attached to its parent object, and Detach, which will be called as the parent object is being unloaded.

The Attach method, shown here, creates a new instance of WorkflowRuntime and instantiates it with the required services. We will store this in a local private field called workflowRuntime.

void IExtension<ServiceHostBase>.Attach(ServiceHostBase owner)
{
   workflowRuntime = new WorkflowRuntime(workflowServicesConfig);
   ExternalDataExchangeService exSvc = new ExternalDataExchangeService();
   workflowRuntime.AddService(exSvc);
   workflowRuntime.StartRuntime();
}

As you can see, our initialization of the workflow runtime also involves adding service instances into the runtime, prior to starting it. When building your solutions, it is generally recommended to add any services prior to starting the runtime. However, if coupling is a concern, you might find it more sensible to use a late binding approach.

In our sample, as part of the SetUpWorkflowEnvironment method in the ExpenseService class, we add an instance of ExpenseLocalService into the ExternalDataExchangeService after the WorkflowRuntime has already been started.

The Detach method shown here shuts down the runtime by calling StopRuntime.

void IExtension<ServiceHostBase>.Detach(ServiceHostBase owner)
{
   workflowRuntime.StopRuntime();
}

As the WorkflowRuntime is created and initialized as part of the service-host startup, any existing workflows will be able to continue prior to service calls being made. When the service host terminates, the workflow runtime will be cleanly shut down.

Note It is recommended that you use workflow persistence (for example, SqlWorkflowPersistenceService) when hosting workflows and modeling long-running workflows, which is the norm. This will provide you with a mechanism for state persistence to survive any application or process restarts.

Creating Service Operations

To create a class that contains service behavior, you must implement one or more interfaces that define a service contract.

public class ExpenseService :
        IExpenseService,
        IExpenseServiceClient,
        IExpenseServiceManager

To integrate with our workflow, our service methods will not contain business logic, but instead will contain code to control or raise events into a running workflow that will encapsulate the business process.

For operations that deal with workflow, we will be either starting a new workflow or interacting with an already running workflow.

Creating a new workflow instance requires us to use a WorkflowRuntime to instantiate a new instance of the desired workflow type. We already have created one of these in our ServiceHost extension class. To obtain a reference to this instance, we must find our custom extension by using the OperationContext.

WfWcfExtension extension = 
OperationContext.Current.Host.Extensions.Find<WfWcfExtension>();
workflowRuntime = extension.WorkflowRuntime;

OperationContext is a class that provides us with access to the execution context of the service method. As you can see in the previous code, it provides a singleton named Current that gives us the context for the current service method. We call the Host property to fetch an instance back to the ServiceHost in which we are running, and then find our extension based on its type.

After we have a reference to our extension instance, we can return the WorkflowRuntime through our public property and use it to create a new instance of our SequentialWorkflow.

Guid workflowInstanceId = 
submitExpenseReportRequest.Report.ExpenseReportId;

Assembly asm = Assembly.Load("ExpenseWorkflows");
Type workflowType = asm.GetType("ExpenseWorkflows.SequentialWorkflow");

WorkflowInstance workflowInstance =
   workflowRuntime.CreateWorkflow(workflowType, null, workflowInstanceId);
workflowInstance.Start();

expenseLocalService.RaiseExpenseReportSubmittedEvent(
   workflowInstanceId, submitExpenseReportRequest.Report);

In the previous code, we are creating a new workflow instance based on a predefined type. While this could be achieved by direct type instantiation, this shows that we could actually have the flexibility of creating a workflow based on a dynamic rule at runtime, instead of through a strongly typed binding.

The last line signals the start of the workflow by raising an event that is handled by the first HandleExternalEventActivity in the workflow. We raise this through an instance of the ExpenseLocalService class. In the sample, the ExpenseLocalService is used to interact with the workflow by starting new workflows or raising events to existing workflows. We use this class as a mechanism for encapsulating our business process. Internally, we are implementing this by using WF.

Bb266709.intgrwfwcf03(en-us,MSDN.10).gif

Figure 3. Our workflow starts with a HandleExternalEventActivity.

The other type of situation with which we are going to deal is calling back into an existing workflow and raising events. We must raise an event to the workflow engine that will cause our existing workflow to receive the event and continue processing.

An example of where this will occur within the Expense Reporting flow is when Manager Approval is required. The workflow will call the external method to RequestManagerApproval that will raise an alert to the manager that they must approve or reject a new expense report.

The workflow contains a ListenActivity that will block until one of the possible events has occurred. In this case, we receive an event indicating that a manager has reviewed the report, or we time out based on a DelayActivity.

Bb266709.intgrwfwcf04(en-us,MSDN.10).gif

Figure 4. The ManagerApproval custom activity flow

Guid workflowInstanceId = 
submitReviewedExpenseReportRequest.Report.ExpenseReportId;

ExpenseReportReviewedEventArgs e =
   new ExpenseReportReviewedEventArgs(workflowInstanceId, report, review);

if (ExpenseReportReviewed != null)
{
   ExpenseReportReviewed(null, e);
}

When a manager reviews a report by using the ManagerApplication, a service call is made back to the host calling the SubmitReviewedExpenseReport method that raises the ExpenseReportReviewed event.

When raising an event to a HandleExternalEventActivity in workflow, you must know the GUID of the workflow instance with which we are dealing, so that the event can be routed.

Each event is raised with EventArgs, which allow us to pass data back into the workflow through the eventing model. In this case, we are able to pass through details of both the current state of the report and the data that gives us context on the review activity.

In workflow, events are automatically wired up to workflow through the properties on a HandleExternalEventActivity.

Bb266709.intgrwfwcf05(en-us,MSDN.10).gif

Figure 5. We are wiring up the HandleExternalEventActivity to the IExpenseLocalService interface.

You specify the interface type that must be marked with the [ExternalDataExchange] attribute, and then the event on that interface to which HandleExternalEventActivity will subscribe.

The event arguments must be derived from the ExternalDataEventArgs class. At a minimum, this means that each event will contain context such as the InstanceId of the workflow. The workflow runtime then manages routing of the event to the correct workflow instance to continue it. If we are using a persistence service, the runtime will also manage hydration and rehydration of any running state for the workflow over the duration of its execution.

Hosting the Service

To host a WCF service, we must run inside the ServiceHost container.

To review how hosting can be achieved with WCF, let us first understand the alternatives that we have available:

  • For standard Windows processes, an instance of ServiceHost can be manually created and opened.
  • When hosting a Web endpoint (Web service) through Microsoft Internet Information Services (IIS) 6.0, we use a custom HttpHandler provided under the System.ServiceModel namespace.
  • When hosting under IIS 7, we can use the Windows Activation Service (WAS) to host our endpoints.

Normally, you will want to choose to host by using Internet Information Services if you are building Web services. If you are building a single instance endpoint that acts as a daemon, you will normally choose to host by using a Windows Service.

In our example, we are hosting the main service instance inside a Windows Console Application that is similar to how you would host a Windows Service.

To deploy a service, we must create an instance of the ServiceHost class and open its endpoints for each service type that we want to publish. ServiceHost takes a number of arguments as part of its constructor; however, the primary argument is either a Type argument or an instance of a class that implements a ServiceContract.

  • Use a Type when you want to use PerCall or PerSession instancing.
  • Use a single instance when you use Single instancing.

For more details on instancing and concurrency within WCF, see Sessions, Instancing, and Concurrency in the MSDN Library.

After a host has been established, it will parse any available configuration (read more about this in the Configuring Deployment section that follows) and merge this with any explicitly added configuration, to determine the available endpoints and open those endpoints for publishing. As calls are received from clients, the requests are processed on new background worker threads and routed to the appropriate service operations, as directed by the SOAP contract name and action on the message.

using (ServiceHost serviceHost = new ServiceHost(new ExpenseService()))
{
   WfWcfExtension wfWcfExtension =
      new WfWcfExtension("WorkflowRuntimeConfig");
   serviceHost.Extensions.Add(wfWcfExtension);
   serviceHost.Open();

   // block the process at this point, for example Console.ReadLine();

   serviceHost.Close();
}

When configuring a ServiceHost, you must do so prior to opening the endpoints for connections. You can do so, as shown previously, by interacting with the host object prior to calling .Open(). It is recommended that you use a using scope to have the ServiceHost disposed prior to use, and that you explicitly call Close() at the end of that scope to shut down cleanly any active connections and endpoints.

Configuring Deployment

WCF provides a mechanism for separating deployment concerns from implementation by allowing endpoints to be configured through XML configuration. This provides administrators with the ability to modify policy of a service without requiring code to be redeveloped.

Each service is published on one or more endpoints. An endpoint is simply an addressable connection point to which clients can consume the service. In WCF, each endpoint is declared with three attributes that have been popularized as the ABCs of WCF.

These are an Address, a Binding, and a Contract.

Address: The unique addressable location of this endpoint. Typically, this will be a URI that gives you the absolute address where the service is listening for requests—for example: http://myhost/myservice or net.tcp://myhost:400/myservice

Binding: The policy that dictates the protocol for communication between the service and its consumers. Binding specifies aspects such as the type of transport being used, the way messages are being encoded, and the way data is being serialized. WCF comes with a number of "out-of-the-box" bindings that support most common scenarios.

Contract: The operations and data being published as we have defined through an interface in code.

To configure our service, we must declare configuration that declares our service and configure any number of endpoints for the service. Because a service might be implementing any number of contracts, this will also affect the number of endpoints that you will need to publish.

An example configuration is shown here.

<services>
   <service name="ExpenseServices.ExpenseService">
      <endpoint
         address="https://localhost:8081/ExpenseService/Manager"
         binding="wsHttpBinding"
         contract="ExpenseContracts.IExpenseServiceManager" />
<endpoint
         address="https://localhost:8081/ExpenseService/Client"
         binding="wsDualHttpBinding"
         contract="ExpenseContracts.IExpenseServiceClient" />
   </service>
</services>

In this configuration example, we are declaring configuration for the service of type ExpenseServices.ExpenseService. This allows the runtime to find the configuration when we instantiate a new ServiceHost based on this type. For more details on bindings, see WCF Bindings in the MSDN Library.

Consuming the Service

Consuming services by using WCF is done by using the ChannelFactory class. ChannelFactory uses the factory pattern to provide us proxy instances of a service contract that connect to the endpoint specified in configuration. We can configure the factory with runtime information, such as security credentials and certificates for message encryption, or to determine the endpoint information dynamically.

private IExpenseServiceManager CreateChannelExpenseServiceManager()
{
   ChannelFactory<IExpenseServiceManager> factory = new
ChannelFactory<IExpenseServiceManager>("ExpenseServiceManager");
   IExpenseServiceManager proxy = factory.CreateChannel();

   return proxy;
}

As you can see, we initially create an instance of the factory, which uses a generic argument for the service contract to allow us to construct a more precise factory that will return only instances of the desired contract. We also specify an argument that determines the configuration that will be used for the endpoint. In this case, we are using an endpoint configuration named ExpenseServiceManager, which refers to configuration that we have in our application configuration file.

<system.serviceModel>
   <client>
         <endpoint name="ExpenseServiceManager"
            address="https://localhost:8081/ExpenseService/Manager"
            binding="wsHttpBinding"
            contract="ExpenseContracts.IExpenseServiceManager" />
   </client>
</system.serviceModel>

You can see that the endpoint definition exactly matches the definition that is declared in the host's configuration. Generally, the only time that you will have configuration that differs is where either the address differs between client and server because of network configuration or the custom behavior is being implemented.

If you have installed the Windows SDK, you can find a tool (svcutil) that has been provided that automates the creation of a proxy class and endpoint configuration, which you can integrate into your solution. The target service must publish a description of its metadata through WSDL or WS-MetadataExchange to make use of this tool.

Configuring Duplex Channels

Until now, we have made the assumption that our communication flow will be using a request response collaboration pattern, with messages being sent by a consumer and answered by a service. WCF supports a number of alternate message flows, such as one-way (fire and forget) or two-way duplex communication. If we are dealing with a message flow in which either party can initiate a conversation, we will need to use a duplex or two-way channel. Duplex channels can be very effective for more strongly connected systems in which data can be sent from either direction. An example of where this might be useful to you is to provide callbacks from eventing.

Implementing Client Callbacks

Client callbacks are implemented in WCF through a concept called CallbackContracts. For a contract that we publish, we can nominate a second contract to define operations that the clients will be publishing that can be called back to by code running on the service.

To declare a CallbackContract, specify the interface type as part of the service contract that we will be calling back from.

[ServiceContract(CallbackContract = 
typeof(IExpenseServiceClientCallback))]

You are also required to use a binding that will support duplex channels, such as netTcpBinding or wsDualHttpBinding. Duplexing over TCP is achieved through a two-way connection being established and maintained throughout the message exchange. Over HTTP, this is achieved by a callback to a client listener. Because the client might not be aware of its return path or you might want to have this strongly defined through configuration, we can use a custom binding configuration to declare an alternate clientBaseAddress.

<endpoint binding="wsDualHttpBinding" 
bindingConfiguration="AlternativeClientCallback"/>
<bindings>
   <wsDualHttpBinding>
      <binding name="AlternativeClientCallback" 
clientBaseAddress="https://localhost:8082/ExpenseService/ClientCallback"/>
   </wsDualHttpBinding>
</bindings>

Implementing Callback on the Client

Implementing a callback contract is exactly the same as implementing a service contract. We must provide an implementation of the interface that we have defined.

class CallbackHandler : IExpenseServiceClientCallback
{
   public void ExpenseReportReviewed(
ExpenseReportReviewedRequest expenseReportReviewedRequest)
        {
            // We implement client logic to respond to the callback here.
        }
}

To allow the host to have an instance of our CallbackHandler class to call back into, we must set up our client channel in such a way that it knows about the duplex nature of the connection.

Firstly, as we have described previously, we will use a binding that supports duplex channels. Secondly, when we initialize our connection to the service endpoint, we use a subclassed version of ChannelFactory called DuplexChannelFactory that will create for us a duplex connection to the service.

private IExpenseServiceClient CreateChannelExpenseServiceClient()
{
   InstanceContext context = new InstanceContext(new CallbackHandler());

   DuplexChannelFactory<IExpenseServiceClient> factory =
new DuplexChannelFactory<IExpenseServiceClient>(context, 
"ExpenseServiceClient");
   IExpenseServiceClient proxy = factory.CreateChannel();

   return proxy;
}

The key difference when using a DuplexChannelFactory is that we initialize an instance of our CallbackHandler class and pass this to the constructor for the factory to initialize a context to be used for callbacks.

Implementing Callback from the Host

From the host's perspective, we can get a reference to call back to our client through the callback channel that is defined in our IExpenseServiceClient contract.

[ServiceContract(CallbackContract = 
typeof(IExpenseServiceClientCallback))]
public interface IExpenseServiceClient : IExpenseService

The CallbackContract attribute declares the interface that defines the contract for the callbacks made from the host.

To make the callback, we get a reference to the callback contract by calling OperationContext.Current.GetCallbackChannel, as shown here.

IExpenseServiceClientCallback callback =
                   OperationContext.Current.GetCallbackChannel
<IExpenseServiceClientCallback>();
callback.ExpenseReportReviewed(new 
ExpenseReportReviewedRequest(e.Report));

After we have a reference to our callback channel, we can call it normally.

Conclusion

Windows Workflow Foundation provides a general framework for defining workflows, and a robust runtime engine that allows you to host and interact with the running workflows.

Windows Communication Foundation provides a general framework for building connected systems, and gives developers a consistent API and broad capability set to define how communication occurs.

You can use these two frameworks together to provide a flexible and comprehensive application platform for building and deploying distributed business processes within your environment. WF allows you to model and encapsulate your business logic and process, while WCF provides you with messaging infrastructure that gives you the means to distribute your systems.

Here are some guidelines to remember when designing your services:

  • You should cater for long-running workflows through use of a persistence service.
  • A service operation can interact through a running workflow, through raising events. Your workflows should be designed to raise events when it must demand attention, and respond to events when it is being interacted with externally (for example, from an external service or human).
  • Workflows will execute asynchronously to the service call; so, design appropriately when thinking about returning data from the service, and what state that data might be in at the time. If you want to use a synchronous approach, you can use the ManualWorkflowSchedulerService class to allow manual scheduling of workflow execution.

For More Information

  1. Sessions, Instancing, and Concurrency
  2. WCF Bindings
  3. Using Data Contracts
  4. Using Message Contracts
  5. .NET Framework 3.0 Community Site
  6. Windows Workflow Foundation Forums

Acknowledgements

Many thanks to the following contributors and reviewers for their efforts:

  • Christian Weyer, thinktecture
  • Paul Andrew, Microsoft Corporation
  • Khalid Aggag, Microsoft Corporation
  • Patrice Manac'h, Microsoft Corporation

 

About the author

Jeremy Boyd is a senior technical consultant for Intergen, a New Zealand-based solution provider and Microsoft Gold Certified Partner, as well as an MSDN Regional Director in the New Zealand community. Over the past 12 months, Jeremy has actively been working with customers to help implement solutions that are based on WF and WCF, as well as helping other developers learn the benefits of these technologies through his Weblog.