Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Fire WorkflowEvents from WCF

4.87/5 (21 votes)
14 May 200715 min read 1   642  
This article describes a loosely coupled connectivity to fire the Workflow Event Activity using a WCF paradigm.

Contents

Features

  • Microsoft Windows Communication Foundation (WCF) and Windows Workflow Foundation (WF) Technologies
  • No custom activity to receive an event message
  • Based on mapping the interface contracts between the WCF and Workflow technologies
  • Transaction support
  • Declaratively programming
  • No requirement to build a Local Service
  • Null Workflow Local Service for correlated events
  • Null WCF Service
  • Capability of pre/post processing in the operation invoker
  • Working side by side with a Workflow Local Service (ExternalDataExchangeService)

Introduction

The Microsoft WF Technology makes it possible to receive external (host) events via a tightly coupled Local Service layer. This layer simplifies the connectivity between the workflow and the host application based on the delegate/event pattern. All this magic plumbing is hidden by ExternalDataExchangeService class that intercepts a Local Service and generates an event message to the specific Workflow queue.

In order to receive an event message by event driven Activity, the ExternalDataExchange interface contract must be declared with an application specific event handler.

The event delegate has a standard .NET signature shown in the following line:

C#
public void EventHandler(object sender, ExternalDataEventArgs eventArgs);

where the eventArgs represents a Workflow event interest such as InstanceId, Identity, application specific data, etc.

The interface contract metadata is used to create a unique workflow queue based on the interface type, event name and collection of the properties for correlation pattern as an additional option. Note that the workflow queue messaging is the only way in which the host application can talk to the workflow.

From the connectivity point of the view, the Workflow represents an event sink and the WCF client's event source (subscriber interest). The following picture shows this logical pattern:

Image 1

On the event source side, we can create many kinds of patterns (such as Remoting, WSE, WCF, etc.) to generate an event message and send it to the local or remote workflow queue.

This article describes how this connectivity can be achieved using the WCF paradigm based on the interface contracts defined differently at each end side. You will see how event source can be logically (declaratively) connected with the event sink metadata.

The following picture shows a representation of the loosely coupled connectivity between the WCF event source and WF event sink:

Image 2

Note, the Workflow side doesn't need to have the Local Service layer in order to generate an event message. For this WCF/WF plumbing we need only metadata of the interface contracts and of course a generic decorator of the operation in order to make all magic work.

The service operation contract can be mapped to the specific WF interface contract explicitly using the WorkflowFireEventAttribute. Behind this attribute is a plumbing code to generate an event message and that is sent to the workflow queue.

Using the WCF connectivity to deliver an event message to the Workflow event sink (such as HandleExternalEventActivity) enables building an application model with a capability of the WCF paradigm based on the Address, Binding and Contract. For example: the netMsmgBinding binding for delivery event message in the async and disconnected manner or the webHttpBinding binding to enable a connectivity for delivery an event message from the browser driven by AJAX/Json (Orcas CTP version).

Let's look at in detail, how the WCF/WF plumbing is done for dispatching an event message to the workflow queue. I will assume that you are familiar with these technologies. In addition, I mentioned earlier, the Workflow side doesn't use any custom activity for this plumbing, so it is a standard workflow definition (sequential or state machine) using a HandleExternalEventActivity. Therefore I will focus on the plumbing concept and implementation.

Concept and Design Implementation

The concept of the loosely coupled WCF/WF connectivity for sending a workflow event message is based on the interceptor (WorkflowFireEventAttribute) on the service operation contract, which will map it to the specific ExternalDataExchange interface contract (see the above picture).

The WCF service is hosted in the same appDomain with a WorkflowRuntime. Basically we need to create a Null Service derived from our WCF Interface contract. The following is an example of the simple Null service and Interface contracts:

C#
// WCF
[ServiceContract]
public interface IFireEventTest
{
    [OperationContract(IsOneWay = true)]
    void Approve(object sender, ExternalDataEventArgs e);
}

public class ServiceTest1 : IFireEventTest
{
    [WorkflowFireEvent(EventType = typeof(IWorkflowSimpleEvents))]
    public void Approve(object sender, ExternalDataEventArgs e) { }
}

// Workflow EventType
[ExternalDataExchange]
public interface IWorkflowSimpleEvents
{
    event EventHandler<ExternalDataEventArgs> Approve;
}

During the start up appDomain, the WorkflowFireEventAttribute will insert a WorkflowFireEventOperationInvoker to the OperationInvoker to intercept a call before invoking a service method. The following code snippet shows a method, where it is going to dispatch a workflow event message:

C#
object IOperationInvoker.Invoke(object instance, object[] inputs, 
                            out object[] outputs)
{
  object retVal = null;
  outputs = new object[0];

  // validation for mandatory inputs
  if (inputs.Length < 2 || 
    !typeof(ExternalDataEventArgs).IsInstanceOfType(inputs[1]))
      throw new InvalidOperationException(string.Format(" ... ");

  // mandatory inputs
  object sender = inputs[0];
  ExternalDataEventArgs eventArgs = inputs[1] as ExternalDataEventArgs;

  if (Transaction.Current != null &&
    Transaction.Current.TransactionInformation.Status == 
                        TransactionStatus.Active)
  {
   WorkflowInstance workflowInstance = null;
   eventArgs.WaitForIdle = false;

   #region action - transactional delivery to the workflow queue
   using(TransactionScope ts = 
        new TransactionScope(TransactionScopeOption.RequiresNew))
   {
     // pre-processing
     if (this.Option == WorkflowFireEventOption.After)
     {
         retVal = this._innerOperationInvoker.Invoke(instance, inputs, 
                                out outputs);
     }

     // fire event
     workflowInstance =
      WorkflowHelper.DispatchEvent(this.EventType,this.EventName,
                        sender,eventArgs,true);

     // storing a workflow into the database
     workflowInstance.Unload();

     // post-process
     if (this.Option == WorkflowFireEventOption.Before)
     {
         retVal = this._innerOperationInvoker.Invoke(instance, inputs, 
                                out outputs);
     }

     // done
     ts.Complete();
   }

   try
   {
     // workaround to detect WorkflowStatus
     workflowInstance.Start();
   }
   catch (Exception ex)
   {
     // WorkflowStatus != Created (therefore we can go ahead and use it)
     Trace.WriteLine(ex.Message);
   }
   #endregion
  }
  else
  {
   #region action - non-transactional delivery to the workflow queue
   // pre-processing
   if (this.Option == WorkflowFireEventOption.After)
   {
     retVal = this._innerOperationInvoker.Invoke(instance, inputs, out outputs);
   }

   // dispatching event
   WorkflowHelper.DispatchEvent(this.EventType, this.EventName, 
                            sender, eventArgs);

   // post-processing
   if (this.Option == WorkflowFireEventOption.Before)
   {
     retVal = this._innerOperationInvoker.Invoke(instance, inputs, out outputs);
   }
   #endregion
 }
 return retVal;
}

Basically, the method is divided into 3 parts such as pre-processing, dispatching and post-processing calls based on the option and transaction context. The option can be useful for application specific logic, for example; creating a workflow, populating eventArgs properties, etc. The default option is None. Other extensions in the call use additional method parameters behind the mandatory parameters such as inputs[0] and inputs[1] or outputs. These extensions are for application specific features.

Firing Event in the Transaction Context

Delivering an event message to the workflow instance in the transactional manner is little bit different from other resources driven by transactional context. Presently, we don't have a transactional memory resource for workflow instance, therefore the ACID transaction has to be done indirectly via the SqlWorkflowPersistenceService. The concept is based on sending the event message to the workflow instance in the synchronously manner (WaitForIdle = false) and dehydrate the workflow within the transaction scope and then after the transaction has been committed, the workflow is returned back to the memory (rehydrating). For this reason, we have to create a new transaction context to avoid a deadlock. Note, the pre/post processing run under the new transaction context and their logic can participate in the ACID transaction.

Let's continue with a DispatchEvent call. This method is responsible for creating and starting a workflow instance based on the eventArgs.InstanceId value, and calling the helper SendEventMessage method to generate a workflow event message and sending it to the instance queue. The following is the code snippet for DispatchEvent method:

C#
public static WorkflowInstance DispatchEvent(Type interfaceType, 
    string eventName, object sender, ExternalDataEventArgs eventArgs)
{
  // input arguments validation
  if (interfaceType == null)
    throw new ArgumentNullException("interfaceType");
  if (string.IsNullOrEmpty(eventName))
    throw new ArgumentException("eventName");
  if (eventArgs == null)
    throw new ArgumentNullException("eventArgs");

  // Find the custom WCF Extension on the Service Host by it's type
  WFServiceHostExtension extension =
    OperationContext.Current.Host.Extensions.Find<WFServiceHostExtension>();

  // Get a reference to the WorkflowRuntime
  WorkflowRuntime workflowRuntime = extension.WorkflowRuntime;

  // get instance of the workflow
  WorkflowInstance instance = workflowRuntime.GetWorkflow(eventArgs.InstanceId);

  if (!bTransactional)
  {
    try
    {
      // workaround to detect WorkflowStatus
      instance.Start();
    }
    catch (Exception ex)
    {
      // WorkflowStatus != Created (therefore we can go ahead and use it)
      Trace.WriteLine(ex.Message);
    }
  }

  //WorkflowMessageEventHandler2 handler2 =
  //   new WorkflowMessageEventHandler2(interfaceType, eventName, instance);
  //handler2.EventHandler(sender, eventArgs);

  SendEventMessage(interfaceType, eventName, sender, eventArgs, instance);
  return instance;
}

Note, it would be nice having a public WorkflowMessageEventHandler class from the System.Workflow.Activities namespace, which would significantly simplify this implementation. I created one for this purpose - see the helpers.cs file, but it doesn't work within the transaction scope (I will figure out this issue and update it). For now, we can use a reflection to invoke a public method on the internal class as it is shown in the following code snippet:

C#
private static void SendEventMessage(Type interfaceType, string eventName,
  object sender, ExternalDataEventArgs eventArgs, WorkflowInstance instance)
{
 // get the type class
 Type type = 
    Type.GetType("System.Workflow.Activities.WorkflowMessageEventHandler, ...");

 // create instance
 object[] args1 =
  new object[3] {eventType, eventType.GetEvent(eventName), instance.WorkflowRuntime};
 object handler = Activator.CreateInstance(type, bindingAttr1, null, args1, cultureInfo);

 // invoke method
 object args2 = new object[2] { sender, eventArgs };
 type.InvokeMember("EventHandler", bindingAttr2, null, handler, args2);
}

WorkflowFireEventAttribute

This is a plumbing class to insert a custom invoker in the operation contract to perform dispatching an event message from the WCF into the WF paradigms in a loosely coupled manner.

The WorkflowFireEventAttribute class can be initiated by the following properties:

  • EventType - type of the Workflow Interface attributed by ExternalDataExchange (mandatory property)
  • EventName - name of the Workflow event (default event name is the name of the operation contract)
  • WorkflowFireEventOption - option of the operation invoker to pre or post process service method
    • None - no service method is invoking (default option)
    • Before - the workflow event is fired before the service method is invoked
    • After - the workflow event is firing after the service method has been invoked

The usage of the above properties is shown in the following examples, where the IFireEventTest interface is mapped to two Workflow interfaces with the same event name:

C#
[ServiceBehavior]
public class ServiceTest1 : IFireEventTest
{
    [WorkflowFireEvent(EventType = typeof(IWorkflowSimpleEvents))]
    public void Approve(object sender, ExternalDataEventArgs e) { }

    [WorkflowFireEvent(EventType = typeof(IWorkflowEvents))]
    public void Reject(object sender, MyEventArgs e) { }

    [WorkflowFireEvent(EventName = "Reject", EventType = 
                    typeof(IMyWorkflowEvents))]
    public void MyReject(object sender, MyEventArgs e) { }
}

or as shown in the next sample, how we can handle dispatching an event message within the operation method or pre-processing a firing workflow event:

C#
[ServiceBehavior]
public class ServiceTest2 : IFireEventTest
{
  public void Approve(object sender, ExternalDataEventArgs e, 
                        string workflowType)
  {
    // for test purpose
    MyObject mo = e.WorkItem as MyObject;
    Console.WriteLine("Operation Approve - {0}", mo.Name);

    // deliver message to the workflow instance
    WorkflowHelper.DispatchEvent(typeof(IWorkflowSimpleEvents), 
                        "Approve", sender, e);
  }

  [WorkflowFireEvent(Option = WorkflowFireEventOption.After,
    EventName = "Reject", EventType = typeof(IWorkflowEvents))]
  public void Reject(object sender, MyEventArgs e)
  {
    // for test purpose
    Console.WriteLine("Operation Reject - {0}", e.TaskName);
  }
}

As I mentioned earlier, the WCF connectivity enables the usage of any binding between the event source (client) and service. The following code snippet shows an example of the service endpoint configuration by config file:

XML
<service name="ServiceHost.ServiceTest1">
  <endpoint
    address="net.tcp://localhost:11111/Workflow"
    binding="netTcpBinding"
    contract="InterfaceContract.IFireEventTest"/>
</service>

<service name="ServiceHost.ServiceTest3">
  <endpoint
    address="net.msmq://localhost/private/rk.workflow_test"
    binding="netMsmqBinding"
    bindingConfiguration="PoisonBinding"
    contract="InterfaceContract.IFireEventTest"/>
</service>

Correlated events

In the case of using the multiple workflow event sinks driven by the same interface contract, the event(s) must be correlated with a specific parameter value to create a unique workflow queue name in the same workflow instance. The solution described in this article supports this feature and it is fully transparent to the workflow event sink HandleExternalEventActivity (HEEA). This feature is also implemented also in the WorkflowMessageEventHandler2 class.

The following code snippet shows a simple event Reject correlated on the parameter taskName:

C#
[ExternalDataExchange]
[CorrelationParameter("taskName")]
public interface IWorkflowEvents
{
    [CorrelationInitializer]
    void Initializer(string taskName);

    [CorrelationAlias("taskName", "TaskName")]
    event EventHandler<MyEventArgs> Reject;
}

The Correlated event requires having the Local Service layer to perform a method attributed as CorrelationInitializer. This method must be called in prior of the HEEA within the specific logical correlation token scope to inform the Local Service about the correlation value.

C#
public class MyLocalService : IWorkflowEvents
{
    public void Initializer(string taskName)
    {
        Console.WriteLine("Initializer - {0}", taskName);
    }

    public event EventHandler<MyEventArgs> Reject;
}

Note, in our loosely coupled connectivity, the correlation value can be sent by the event source or it can be pre-processed in the operation method on the WCF Service.

Attaching WorkflowRuntime

The WorkflowRuntime can be attached to the WCF Service host via its extension. For this case, I have created a WSServiceHostExtension class with two constructors. The first one, will store an existing WorkflowRuntime reference (for instance: created by Orcas WorkflowServices) and the second one will create a WorkflowRuntime from the config file.

The following code snippet shows an example of the creating WorkflowRuntime based on the config file and attaching it to the WCF host:

C#
using (System.ServiceModel.ServiceHost host =
   new System.ServiceModel.ServiceHost(typeof(ServiceTest3)))
{
 // Service extension for WF
 WFServiceHostExtension extension = 
    new WFServiceHostExtension("WorkflowRuntimeConfig");

 // Add the Extension to the ServiceHost collection
 host.Extensions.Add(extension);

 host.Open();

 Console.WriteLine("Press any key to stop server...");
 Console.ReadLine();

 host.Close();
}

Example of the workflow runtime configuration:

Image 3

Note, the SqlWorkflowPersistenceService must be included in the config file.

Test

The FireWorkflowEvent solution is divided into two folders such as WorkflowEventService - the hosting implementation and WorkflowFireEventAttribute. This assembly must be included in the application appDomain where the WCF and WF are hosted. The other folder is for test purpose only in order to demonstrate the usage of the WorkflowFireEventAttribute. This folder contains few projects such as ConsoleApplications for generating an event, InterfaceContracts for WCF and WF, Logger for simple logging of WCF messages on the console screen, ServiceHost project to host WCF services and WorkflowRuntime and the last project - WorkflowLibrary of the 3 workflows (state machine, sequential and xoml activated).

Image 4

Let's start testing. I assume that your environment has the netfx 3.0 and SqlExpress installed, included a workflow stuff.

Test 1. Disconnectable Transactional Event

This test demonstrates the connectivity between the event source and event sink (WCF - Workflow) via transactional MSMQ. Delivering an event message from end to end in transactional (disconnectable) manner enables building robust event driven architectures.

The following are major steps how to plumb WCF and WF models together:

Step 1. Workflow

In this step, we need to create an Interface contract for ExternalDataExchange with EventHandler method(s). In our example test, there is simple event Approve with a workflow ExternalDataEventArgs. Once we have this contract, the event sink such as HandleExternalEventActivity (HEEA) can be assigned. Note, it is not necessary to create a Local Service implementation.

The following picture shows these steps - interface contract, HEEA properties and test workflow:

Image 5

That's all for the Workflow. The following steps are WCF only.

Step 2. WCF Service

First of all, we need to create a Service (Interface) contract to consume a Workflow event(s). The following code snippet shows a simple contract for consuming two events - Reject and Approve. In this test, only Approve event will be fire it.

C#
[ServiceContract]
public interface IFireEventTest
{
  [OperationContract(IsOneWay = true)]
  void Reject(object sender, MyEventArgs e);

  [OperationContract(IsOneWay = true)]
  void Approve(object sender, ExternalDataEventArgs e);
}

Now, we can create a service and decorate a specific event for its integration with the Workflow. In this test, we ask the operation invoker for an event post-processing (the event is fired Before, see the option) and plumbing with the IWorkflowSimpleEvents contract. The operation is setup for transactional processing with auto committing.

C#
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class ServiceTest3 : IFireEventTest
{

 [WorkflowFireEvent(Option = WorkflowFireEventOption.Before,
   EventType = typeof(IWorkflowSimpleEvents))]
 [OperationBehavior(TransactionAutoComplete = 
            true, TransactionScopeRequired = true)]
 public void Approve(object sender, ExternalDataEventArgs e)
 {
   /*
   if(MessageBox.Show("Abort ...", "Approve", MessageBoxButtons.YesNo)==
                            DialogResult.Yes)
   {
      throw new Exception("Transaction has been aborted manually");
   }
   */
 }

 [WorkflowFireEvent(EventType = typeof(IWorkflowEvents))]
 public void Reject(object sender, MyEventArgs e) { }
}

Before hosting this service in the appDomain, we have to create a service configuration section. The following code snippet shows part of the ServiceHost.exe.config file for endpoint and netMsmqBinding.

XML
<services>
  <service name="ServiceHost.ServiceTest3">
    <endpoint
      address="net.msmq://localhost/private/rk.workflow_test"
      binding="netMsmqBinding"
      bindingConfiguration="PoisonBinding"
      contract="InterfaceContract.IFireEventTest"/>
  </service>
</services>

<bindings>
  <netMsmqBinding>
    <binding name="PoisonBinding"
      receiveErrorHandling="Drop"
      receiveRetryCount="0"
      exactlyOnce="true"
      useSourceJournal="true"
      useActiveDirectory="false">
      <security mode="None" />
    </binding>
  </netMsmqBinding>
</bindings>

Now, we can host the above service in the appDomain - see the ServiceHost project, program.cs file. After that, our Workflow has been exposed via WCF service to receive a message for the Approve event.

Step 3. WCF Client

This step shows an example of how the client can generate an Approve event in the transactional manner. For test purposes, the workflow2.xoml is created and persisted in advance of the firing event. The rest of the work is straightforward such as creating the channel with FireWorkflowEvent configuration, event argument with application specific event interest and sending an Approve message. Note, the message will be sent to the MSMQ queue, when the transaction scope is committed.

C#
using (TransactionScope ts = 
    new TransactionScope(TransactionScopeOption.RequiresNew))
{
  // unload workflow (for test purpose)
  using(XmlReader xmlReader=
    XmlReader.Create(@"..\..\..\WorkflowLibrary\Workflow2.xoml"))
  {
      wi = wr.CreateWorkflow(xmlReader);
  }
  wi.Unload();

  using (ChannelFactory<IFireEventTest> factory =
     new ChannelFactory<IFireEventTest>("FireWorkflowEvent"))
  {
    IFireEventTest channel = factory.CreateChannel();

    // eventArgs
    ExternalDataEventArgs eventArgs = 
        new ExternalDataEventArgs(wi.InstanceId);
    eventArgs.WaitForIdle = false;
    eventArgs.WorkItem = MyObject.Create(sender, ii);

    // fire workflow event
    channel.Approve(sender, eventArgs);

    // close this channel
    factory.Close();
  }

  ts.Complete();
}

Now, we are ready to make an actual test.

Step 4. Run Test

Launch the ConsoleApplication2 program. The following screen snippet shows a process of creating and unloading Workflow2. Using the included Logger, the console screen will show an event message envelope sent to the WCF service (and forwarding to the Workflow).

Image 6

Before launching the ServiceHost program, the message can be checked in the private rk.workflow_test queue. We don't have a binding setup for timeout delivery and receiving a message by service, therefore we can launch the service at any time.

The following picture shows the console screen snippet for ServiceHost program. We can see how many endpoints are in this host, receive event message and also how this message goes through the Workflow2. Based on the workflow definition, the workflow will be completed after receiving this message.

The test can be repeated for another workflow instance by pressing any key on the ConsoleApplication2 screen.

Image 7

One more thing. In the Step 2, we have commented a post-processing of the firing event. Let's uncomment it and run the service host one more time. Each Approve event call will be interrupted by the following dialog within the transaction scope:

Image 8

Based on your choice, we can simulate an Abort scenario. Note, the message is dropped from the MSMQ queue, but if you are running on Vista, the netMsmqBinding can be re-configured for using a poison queue.

That's all for this test and let's look at another examples of the WorkflowFireEventAttribute.

Test 2. Firing Correlated Events

This test demonstrates a correlated Reject event where the correlated parameter is taskName. Each workflow correlation scope has a responsibility to initialize this parameter before waiting for Reject event. For this purpose, the workflow is invoking the CallExternalMethodActivity and passing an initialize value of the correlated parameter to the host application, therefore the Local Service must be created and register in the WorkflowRuntime.

Image 9

Image 10

To test this scenario, the ServiceHost program must be launched prior to the ConsoleApplication1 program. Note, this test uses an application specific MyEventArgs derived from the ExternalDataEventArgs in the event message, therefore we have to add this type into the <system.runtime.serialization> section for both config files (service and client):

XML
<system.runtime.serialization>
 <dataContractSerializer>
  <declaredTypes>
   <add type="System.Workflow.Activities.ExternalDataEventArgs, 
                        System.Workflow.Activities,
       Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
     <knownType type ="InterfaceContract.MyObject, InterfaceContract"/>
   </add>
   <add type="InterfaceContract.MyEventArgs, InterfaceContract">
     <knownType type ="InterfaceContract.MyObject, InterfaceContract"/>
   </add>
  </declaredTypes>
 </dataContractSerializer>
</system.runtime.serialization>

Let's do one more test for StateMachine Workflow.

Test 3. Firing Simple State Machine Event

The following picture shows a simple example of the State Machine with one state waiting for the Approve event within the Timeout limit. The incoming Approve event will recycle a state and re-trigger a timeout limit.

Image 11

Image 12

Image 13

The interface contracts are very simple and require having only a Null (no body) WCF Service.

To test this scenario, the ServiceHost program must be launched prior to the ConsoleApplication program.

Note, the test ServiceHost program is configured to run all test client console programs, therefore it can be run for an endless loop test.

Appendix

Generic WCF Service

The purpose of the Generic service is to host a universal service for simple events with a typical event signature such as sender and ExternalDataEventArgs parameters. The application specific object can be passed via the WorkItem property of the ExternalDataEventArgs object to the Workflow.

The following code snippet shows complete implementation of the generic service for exposing Workflow via WCF:

C#
[ServiceContract]
public interface IFireWorkflowEvent
{
    [OperationContract(Action = "*", IsOneWay = true)]
    void FireWorkflowEvent(Message message);
}

[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class WorkflowEventService : IFireWorkflowEvent
{
    public void FireWorkflowEvent(Message message)
    {
        WorkflowHelper.FireEvent(message);
    }
}

As you can see, there is no operation interceptor and WorkflowFireEventAttribute for mapping the interface contracts. The concept and the limitation of this solution is based on implicitly mapping a soap message to the Workflow interface contract defined by name of the event and interface type. The name of the operation contract can be easy mapped to the workflow event name. The second mapping requirements such as an event type is not straightforward. I decided (for now) to use a namespace of the WCF interface contract.

Ok, now we can look at how the WorkflowHelper.FireEvent will map an incoming message to the workflow:

C#
public static void FireEvent(Message message)
{
  System.Xml.XmlDictionaryReader reader = message.GetReaderAtBodyContents();

  // event contract
  Type interfaceType = Type.GetType(reader.NamespaceURI);
  string eventName = reader.Name;

  // body of the event message
  reader.ReadStartElement();
  string sender = reader.ReadElementContentAsString("sender", 
                        reader.NamespaceURI);
  DataContractSerializer xs =
            new DataContractSerializer(typeof(ExternalDataEventArgs), 
        "e", reader.NamespaceURI);
  ExternalDataEventArgs eventArgs = 
        (ExternalDataEventArgs)xs.ReadObject(reader);
  while (reader.NodeType != XmlNodeType.EndElement) { reader.Read(); }
  reader.ReadEndElement();

  // deliver message to the workflow queue
  DispatchEvent(interfaceType, eventName, sender, eventArgs);
}

The FireEvent method deserializes a message body to obtain the first two eventhandler arguments such as sender and e. The event contract, as I mentioned above, is obtained from the method name and namespace. Having these parameters, the DispatchEvent is called to dispatch an event message to the Workflow queue the same way as it has been described earlier. The following code snippet shows an example of the WCF interface contract for this generic WCF service:

C#
[ServiceContract(Namespace="InterfaceContract.IWorkflowSimpleEvents, 
                        InterfaceContract")]
public interface IFireEventTest2
{
    [OperationContract(IsOneWay = true)]
    void Approve(object sender, ExternalDataEventArgs e);
}

How is it working?

Well, after the WorkflowEventService has been hosted in the appDomain with a WorkflowRuntime, we are ready to receive an event message via the endpoint. The following config snippet shows this generic configuration. Of course, we can create many kind of bindings:

XML
<service name="RKiss.WorkflowEventService.WorkflowEventService">
  <endpoint
    address="net.tcp://localhost:11110/Workflow"
    binding="netTcpBinding"
    contract="RKiss.WorkflowEventService.IFireWorkflowEvent"/>
</service>

Therefore, the client (eg. ConsoleApplication) will send an event message based on the WCF interface contract (eg. IFireEventTest2) to the above endpoint, forwarding to the generic service ( Action = "*") operation FireWorkflowEvent. Once we have a message in the method, we process it as an event message, otherwise it will throw an exception.

Correlated Events in the State Machine

In the case when the multiple event driven activities (HEEA) are waiting for the same event in the same State, the correlation feature can help it. The following picture shows how the correlation parameter can be initialized in the StateInitializationActivity. I didn't include this test, therefore try to modify the Workflow1 for this case and test it with a ConsoleApplication1 program.

Image 14

Conclusion

This article described a WCF/WF integration for firing workflow events. The concept is based on the loosely coupled connectivity between the WCF and WF interface contracts. It is a straightforward and transparent integration without requirements to modify a workflow and building a custom Local Service for no correlated events.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here