WCF Workflow Services Using External Data Exchange

This article shows how to create a WCF service which makes use of a WF workflow library which contains the business logic code. One way to communicate with the workflow from the service operation is by using the external data exchange service.

Introduction

One way to make a WCF service operation and a WF workflow communicate with each other is by adding a HandleExternalEvent or CallExternalMethod activity to a workflow.

 A HandleExternalEvent activity is much like an ordinary event handler. The workflow just waits for a certain event to be triggered by the service. The event arguments are used as parameters passed to the workflow.

The CallExternalMethod activity does the opposite. It can call a method on a local service, which is accessible to the WCF service operation. This activity could be used by the workflow to return values to the service.

To illustrate this, let’s create a very simple client-server application where the server provides a WCF service that accepts a string input and returns the string in reverse, which is actually done by a workflow. Take note that the mentioned activities need not be at the start or at the end of a workflow.

Creating the Workflow Service

Create a new WCF service application project. The following files are automatically generated:  IService1.cs, Service1.svc and Web.config. Rename all references of Service1 to the desired name of the service, which is StringService in this example. The following listing shows the WCF service interface.

[ServiceContract]

public interface IStringService

{

    [OperationContract]

    string ReverseString(string stringToReverse);

}

We need to two things before we can implement this interface: the workflow and a service that facilitates data exchange between the service and workflow.

The Workflow

Add a new workflow project to the solution. You may add either a state-machine or sequential workflow library. A sequential workflow library will suit our needs of our example so add a project of this type. We can name the project StringWorkflows. Let’s rename Workflow1 to ReverseStringWorkflow. The following screenshot shows the workflow.

The activity labeled handleReverseStringStarted is the HandleExternalEvent activity. When this workflow is started, it will still wait for a certain event to be raised by the WCF service. This event is specified as a property of the HandleExternalEvent activity. The string to be reversed is passed from the service to the workflow using this event.

The activity labeled callSetReversedString is the CallExternalMethod activity. This activity calls a method found in the data exchange service. We would return the reversed string using this activity.

The reverseString Code activity represents the business logic code of our example. It is responsible for reversing the string.

The Data Exchange Service

You might have first tried creating a workflow and added HandleExternalEvent and CallExternalMethod activities. However, you will notice that both activities need an interface type marked with an ExternalDataExchangeAttribute. We’ll have to create the interface first. Let’s create another project of type class library called LocalServices where we can add the interface and implementation. Be sure to add references to required assemblies. The interface is shown in the code below.

[ExternalDataExchange]

public interface IReverseStringLocalService

{

    event EventHandler<ReverseStringStartedEventArgs> ReverseStringStarted;

 

    void ReverseString(Guid instanceId, string stringToReverse);

 

    void SetReversedString(string reversedString);

}

     

The ReverseStringStarted event is supplied as one of the required property of the HandleExternalEvent activity as explained earlier. The ReverseStringEventArgs is defined below.

 

[Serializable]

public class ReverseStringStartedEventArgs : ExternalDataEventArgs

{

    public ReverseStringStartedEventArgs(Guid instanceId, string stringToReverse)

        : base(instanceId)

    {

        StringToReverse = stringToReverse;

    }

 

    public string StringToReverse { get; private set; }

}

 

The ReverseStringStartedEventArgs class must inherit from ExternalDataEventArgs class to be used in the HandleExternalEvent.  It must also be marked as Serializable because an instance must be serialized before it is passed to the workflow. I presume that this is done since the workflow can be persisted to a persistence store.

Another requirement is that the ReverseStringStartedEventArgs’ constructor must call the base class’ constructor, which accepts a workflow instance’s id as its parameter. This is used so that only the workflow instance with that id is notified of the event. Note that there could be many workflow instances waiting for the same event.

Now let’s take a look at how the interface is implemented.

 

[Serializable]

public class ReverseStringLocalService : IReverseStringLocalService

{

    public event EventHandler<ReverseStringStartedEventArgs> ReverseStringStarted;

 

    public string ReversedString { get; private set; }

 

    void IReverseStringLocalService.ReverseString(Guid instanceId, string stringToReverse)

    {

        if (ReverseStringStarted != null)

        {

            ReverseStringStarted(this, new ReverseStringStartedEventArgs(instanceId, stringToReverse));

        }

    }

 

    void IReverseStringLocalService.SetReversedString(string reversedString)

    {

        ReversedString = reversedString;

    }

}

 

The ReverseString() method just raises the ReverseStringStarted event and is called by a WCF service operation.

 

On the other hand, the SetReversedString() method is called by a WF workflow to return the reversed string. The property ReversedString is set in this method, which is then accessed by the WCF service operation.

 

It is not required to mark the class as Serializable if you do not intend to pass the instance to the workflow. In this case, I’m passing this as the sender parameter.

 

Setting Activity Properties

 

Now that we have an interface marked with ExternalDataExchangeAttribute, we can now set the required properties of the HandleExternalEvent and CallExternalMethod activities. Let’s start first with the HandleExternalEvent activity.

 

First, add a reference to the LocalServices project to the StringWorkflows project. The following screenshot shows the properties of the HandleExternalEvent activity. The InterfaceType and EventName properties are required.


 

 

Set the InterfaceType to the IReverseStringLocalService like the one shown below. After this, choose the ReverseStringStarted event as the value of the EventName property.

 

 

You will see additional properties when the EventName property is set. These are e and sender. The property e refers to the event arguments, which contains the string to be reversed. The sender is the object which raised the event. Since we only need the string to reverse, bind e to a field or property. This is shown in the following screenshot.

 


 

We have to do the same for the InterfaceType property of the CallExternalMethod activity. Afterwards, set the MethodName property to the SetReversedString() method. Notice that once again, an additional property is added which is called reversedString. Bind this to a field or property.

 

The following code listing shows the workflow’s code-behind. I’ve already added the OnReverseString() method’s implementation.

 

public sealed partial class ReverseStringWorkflow : SequentialWorkflowActivity

{

    public LocalServices.ReverseStringStartedEventArgs reverseStringStartedEventArgs = default(LocalServices.ReverseStringStartedEventArgs);

 

    public String reversedString = default(System.String);

 

    public ReverseStringWorkflow()

    {

        InitializeComponent();

    }

 

    private void OnReverseString(object sender, EventArgs e)

    {

        reversedString = new string(reverseStringStartedEventArgs.StringToReverse.ToCharArray().Reverse().ToArray<Char>());

    }

}

 

Implementing the WCF Service Interface

 

The following code listing shows the implementation of the WCF service interface shown earlier.

 

public class StringService : IStringService

{

    public string ReverseString(string stringToReverse)

    {

        using (WorkflowRuntime runtime = new WorkflowRuntime())

        {

            AutoResetEvent waitEvent = new AutoResetEvent(false);

 

            ExternalDataExchangeService dataExchangeService = new ExternalDataExchangeService();

            runtime.AddService(dataExchangeService);

 

            ReverseStringLocalService localService = new ReverseStringLocalService();

            dataExchangeService.AddService(localService);

 

            runtime.WorkflowCompleted +=

            (

                (object sender, WorkflowCompletedEventArgs e) =>

                {

                    waitEvent.Set();

                }

            );

 

            WorkflowInstance instance = runtime.CreateWorkflow(typeof(ReverseStringWorkflow));

            runtime.StartRuntime();

            instance.Start();

 

            localService.ReverseString(instance.InstanceId, stringToReverse);

 

            waitEvent.WaitOne();

 

            return localService.ReversedString;

        }

    }

}

 

First, we need to create a WorkflowRuntime instance. This manages execution of workflows. In able to use the HandleExternalEvent and CallExternalMethod activities in our workflow, we need to create an instance of an ExternalDataExchangeService class and add it as a service of the workflow runtime.

 

After this, we can add the instance of our ReverseStringLocalService as a service of the ExternalDataExchangeService instance. All that’s left to do is start the workflow runtime, start the workflow instance and call our ReverseString() method.

 

However, since we are triggering an event, the WCF service operation may end before the workflow completes. That’s why we have to use an AutoResetEvent and subscribe to the WorkflowCompleted event of the runtime.

 

The Client

 

I wouldn’t elaborate more on how to use the WCF service in the client application. Just add a service reference and call the ReverseString() method on the service proxy. The following screenshot shows the application UI.

 


 

The Visual Studio 2008 example can be downloaded here.

 

Remarks

 

A problem that I encountered when I used the example I presented is how to throw exceptions from the workflow, since I want to notify the client of any exception that may have occurred. This is because of how exceptions are notified to the WCF service, which is by using an event. It might be possible to re-throw an exception but I ended up using a ReceiveActivity instead because it is much cleaner.

 

By Michael Detras   Popularity  (13454 Views)
Biography - Michael Detras
.NET developer. Interested in WPF, Silverlight, and XNA.
My blog