Wrox Home  
Search
Professional Windows Workflow Foundation
by Todd Kitta
March 2007, Paperback


Excerpt from Professional Windows Workflow Foundation

Workflow Communication in Windows Workflow Foundation

by Todd Kitta

Workflow-to-host communication and host-to-workflow communication are vital components of Windows Workflow Foundation. Without the necessary hooks to send data back and forth, workflows would not be nearly as useful. Host applications are the most common locations where workflows receive information from the outside world.

For example, in a scenario where a user interacts with a Windows Forms application that is hosting a helpdesk ticket workflow, the Windows application needs to inform the workflow when the user starts a new ticket or updates an existing one. In addition, the workflow might need to tell the host application when an action of interest occurs, such as a request for further information from the user.

There are two main methods of workflow communication. The first, and the simpler of the two, uses parameters to pass data to a workflow when it is created. The second and richer form of communication is called local communication services. This technique uses method and events to facilitate communication. Both of these methods are covered in the following sections.

You might be thinking, "What about web services?" Although web services are becoming more vital for distributed application communication, they are outside the scope of this type of communication. That does not mean that web services and other distributed communication technologies are not important to Windows Workflow Foundation. Chapter 14 covers Windows Workflow Foundation as it relates to web services, and Chapter 15 "Window Workflow Foundation and the Microsoft Office System" of the book, Professional Windows Workflow Foundation (Wrox, 2007, ISBN: 978-0-470-05386-7) covers how Windows Communication Foundation relates to the workflow platform.

Parameters

Parameters provide a simple way to pass data to a workflow instance during its creation. A Dictionary<string, object> generics collection is used to pass parameters to the CreateWorkflow method of the WorkflowRuntime class. Because the collection is passed before the workflow is started, it is helpful only for initialization purposes and cannot be used to communicate with a workflow that is already running.

Each parameter key added to the dictionary collection must correspond to a public property in the workflow definition class that has a set accessor. These properties are used to hold the values added to the collection in the host.

Conversely, parameters can be passed from a workflow out to its host upon completion. Event handlers for the WorkflowCompleted event are passed an instance of the WorkflowCompletedEventArgs class. This class holds a property called OutputParameters, which is of type Dictionary<string, object>. Just as with the input parameters, the workflow class must expose its output parameters as public properties, but this time with the get accessor.

Using this method of communication is quite simple, as displayed in the following code. The input parameters are prepared in the host application and passed to the CreateWorkflow method. The runtime_WorkflowCompleted event handler method uses the OutputParameters collection to access the output parameters.

public static void Main(string[] args)
{
    WorkflowRuntime runtime = new WorkflowRuntime();
    runtime.WorkflowCompleted +=
        new EventHandler<WorkflowCompletedEventArgs>
            (runtime_WorkflowCompleted);
    runtime.StartRuntime();
    Dictionary<string, object> parameters = new Dictionary<string, object>();
    parameters.Add("SomeMessage",
        "This is a message which goes in to the workflow instance...");
    WorkflowInstance wi =
        runtime.CreateWorkflow(typeof(ParametersWorkflow), parameters);
    wi.Start();
}
private static void runtime_WorkflowCompleted(object sender,
    WorkflowCompletedEventArgs e)
{
    Console.WriteLine("The workflow instance with the ID '" +
        e.WorkflowInstance.InstanceId.ToString() + "' has completed.");
    Console.WriteLine("It told us: " +
        e.OutputParameters["SomeOtherMessage"]);
}

The following code shows the workflow definition class. As you can see, there is a property for SomeMessage, which is the input parameter, and SomeOtherMessage acts as the output parameter. These properties have a set and get accessor, respectively.

public sealed partial class ParametersWorkflow : SequentialWorkflowActivity
{
    private string someMessage;
    private string someOtherMessage;
    public string SomeMessage
    {
        set { someMessage = value; }
    }
    public string SomeOtherMessage
    {
        get { return someOtherMessage; }
    }
    public ParametersWorkflow()
    {
         InitializeComponent();
    }
    private void caEchoInputMessage_ExecuteCode(object sender, EventArgs e)
    {
        Console.WriteLine("The host told me: " + this.someMessage);
    }
    private void caSetOutputMessage_ExecuteCode(object sender, EventArgs e)
    {
        this.someOtherMessage = "This message will be accessed by the host...";
    }
}

Local Communication Services

You can use Windows Workflow Foundation to communicate back and forth between a host and an executing workflow instance. Essentially, you use standard .NET interfaces and classes to facilitate workflow communication through method calls and events.

When a workflow wants to tell something to the host, it calls a method predefined in a .NET interface and subsequently implemented in a concrete class. When the host is ready to notify the workflow of some event or data, it raises an event that is then handled by the workflow.

Relevant Classes

The following sections review several classes that enable local communication services.

Custom Communication Service Interfaces and Classes

To allow communications to occur between a workflow host and workflow instances, you must define communication contracts that dictate which messages can be sent back and forth. These contracts are implemented through .NET interfaces, and they can contain any public methods that can be called from the workflow. The interface methods represent concrete methods that will exist on the workflow host. You can pass any type of data as parameters to these methods for communication purposes, and you can specify return values to set variables in the workflow instance. However, any type passed to a workflow and its host must be decorated with the Serializable attribute (more on this requirement later in this chapter).

After you define the communication interfaces, you must create concrete classes to implement the behavior specified in the interfaces. The following sections cover classes and entities important to local communication services. An example is then shown and discussed to further explain these concepts.

ExternalDataExchangeService

This class is a runtime service that manages all the communication service classes. To use local communication services, you must add an instance of this class to the workflow runtime (as you do with any other runtime service that uses the AddService method of WorkflowRuntime). Then you can add communication service classes to the ExternalDataExchangeService instance using its own AddService method, as follows:

WorkflowRuntime workflowRuntime = new WorkflowRuntime();
// create an instance of the data exchange service
ExternalDataExchangeService dataService = new ExternalDataExchangeService();
// add the external data exchange service to the runtime
workflowRuntime.AddService(dataService);
 
// create an instance of my custom communication service
MyCommunicationService commService = new MyCommunicationService();
// add the communication service to the data exchange runtime service
dataService.AddService(commService);

The ExternalDataExchange service class exposes the following public methods for managing local communication services:

  • AddService -- Adds communication service instances to the data exchange service.
  • GetService -- Takes a Type reference as its sole parameter and returns any communication services of that type. Because GetService returns an object, you must first cast it to the appropriate type.
  • RemoveService -- Takes a communication service instance as a parameter and removes it from the data exchange service. RemoveService throws an InvalidOperationException if the class reference passed is not already registered with the data exchange service.

The data exchange service is also responsible for managing the communications between the host and workflow instances. When the communication is handled through interfaces as described previously, the runtime uses .NET reflection to make method calls and raise events.

ExternalDataExchangeAttribute

This attribute is used to decorate custom communication-service interfaces. It acts as a marker so that the Windows Workflow Foundation infrastructure knows which interfaces are to be treated as a communication contract.

The following is a simple example of this attribute on a communication interface:

[ExternalDataExchangeAttribute]
public interface ICommService
{
   void CallTheHost();
   event EventHandler<ExternalDataEventArgs> NotifyTheWorkflow;
}

When you decorate an interface with this attribute, Visual Studio perceives that interface as a communication contract. This is important when you want to designate an interface as the contract for communicating with the host in a workflow.

ExternalDataEventArgs

This class is passed to event handlers of local communication services and represents the context of the event. Like any other event in the .NET Framework, this class inherits from System.EventArgs. Any event that participates in the communication process between workflows and hosts must use this class or an inherited class to represent the event.

Just because you have to use this class for workflow communication events does not mean you are limited in what you information you can pass to the workflow. To create an event-arguments class that passes data specific to your problem domain, you can simply inherit from ExternalDataEventArgs. The following is an example of an inherited class that passes a person's first and last name:

[Serializable]
public class NewPersonEventArgs : ExternalDataEventArgs
{
    private string firstName;
    private string lastName;
    public string FirstName
    {
        get { return this.firstName; }
    }
    public string LastName
    {
        get { return this.lastName; }
    }
    public NewPersonEventArgs(Guid instanceId, string firstName, string lastName)
        : base(instanceId)
    {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

There are a couple of important things to notice in this example. First, the class is marked with the Serializable attribute. This is required because the EventArgs class is actually serialized when it is passed from the workflow's host to the workflow instance. If you do not decorate your custom class with this attribute, an EventDeliveryFailedException is thrown when an event passing your custom class is raised.

Also notice that the constructor receives not only the firstName and lastName variables, but also an instanceId that is subsequently passed to the base class constructor. This is also a requirement because the runtime must know the workflow instance on which to raise an event.

Table 5-4 lists the properties in the ExternalDataEventArgs class.

Table 5-4: ExternalDataEventArgs Properties
Property Description
Identity This property is the identity of the entity that is raising the event. This value is used for security purposes to ensure that the calling entity has access to pass data to the workflow.
InstanceId This property is a Guid that maps to an existing workflow instance that is to handle the event.
WaitForIdle This Boolean property indicates whether the event about to be raised should be raised immediately or when the workflow instance becomes idle.
WorkflowType This property is a Type instance representing the type of the workflow instance.
WorkHandler This property is of type IPendingWork and allows the workflow host to interact with the transactional work batch.
WorkItem This property is a reference to the object that caused the current event to be raised.

This article is excerpted from Chapter 5, "Workflow Hosting, Execution, and Communication," of Professional Windows Workflow Foundation (Wrox, 2007, ISBN: 978-0-470-05386-7) by Todd Kitta. Todd is a Senior Consultant at Covenant Technology Partners, a Microsoft partner specializing in Portals, Business Intelligence, CRM, and .NET. He currently holds his MCSD certification and is an active developer of Microsoft solutions including development on the .NET Framework, Business Intelligence, and of course Windows Workflow Foundation. Todd has also written an article related to Windows Workflow Foundation and ASP.NET for DevX.