This is 1 of a 2 part posting on external windows workflow correlated communication. The first post will focus on windows workflow today without the use of Aware Server. After reading this post you should be able to plug in a communication service that supports correlation into your windows workflow instances.
The second post will build off the sample built here and demonstrate what Aware Server does to help make this easier in a server environment.
Workflow External Communication Service
The Windows Workflow runtime defines a way for the hosting application to communicate back and forth with a running workflow instance via an ExternalDataExchange service. To perform this you start by creating a normal .NET interface.
| interface IOrderWorkflowService { event EventHandler<OrderEventArgs> OrderCreated; event EventHandler<OrderEventArgs> OrderShipped; } |
Next you adorn your interface with ExternalDataExchange attribute located in the System.Workflow.Activities namespace.
| [ExternalDataExchange] interface IOrderWorkflowService |
Any events your fire from you workflow service must have EventArgs that derive from ExternalDataEventArgs and must be Serializable.
| /// <summary> /// /// </summary> [Serializable] class OrderEventArgs : ExternalDataEventArgs { private Guid orderId; public Guid OrderId { get { return orderId; } set { orderId = value; } } /// <summary> /// /// </summary> /// <param name="instanceId"></param> /// <param name="orderId"></param> public OrderEventArgs(Guid instanceId, Guid orderId) : base(instanceId) { this.orderId = orderId; } } |
Correlation is built in the Windows Workflow engine via several attributes. The workflow runtime uses these correlation attributes to ensure an event is fired on the right activity inside a running worklow instance.
| [CorrelationParameter("OrderId")] [ExternalDataExchange] interface IOrderWorkflowService { [CorrelationInitializer] [CorrelationAlias("OrderId", "e.OrderId")] event EventHandler<OrderEventArgs> OrderCreated; [CorrelationAlias("OrderId", "e.OrderId")] event EventHandler<OrderEventArgs> OrderShipped; } |
The CorrelationParameter attribute defines what the correlation parameter should be named and will be used in with all the other correlation attributes. In this case we named our parameter “OrderId”. You can have N number of these attributes. The combination of the parameters must form a unique key.
The CorrelationAlias attribute defines a mapping between the parameter “OrderId” to the path inside the OrderEventArgs which is “e.OrderId”. Why “e.” you ask? Well if you right click on the EventHandler class and select “Go to Definition” you will see that the delegate’s signature is as follows:
| public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e); |
Notice the second parameter named “e”. Hence once our OrderEventArgs is replaced in the delegate “e.OrderId” would get us to the OrderId property on the OrderEventArgs.
| ...EventHandler<OrderEventArgs>(object sender, OrderEventArgs e); |
The last attribute of concern is the CorrelationInitializer. This attribute is very important and must be on at least one event or on method in your interface. When either an event or method is called with this attribute applied the workflow runtime will establish the correlation for that particular workflow instance and bind it to the “OrderId” thus marrying the OrderId to the workflow instance. In our example above we opted to fire the created event once an order workflow instance is started to initiate the correlation.
Next you must provide an implementation of this interface in a local class.
| class OrderWorkflowService : IOrderWorkflowService { public event EventHandler<OrderEventArgs> OrderCreated; public event EventHandler<OrderEventArgs> OrderShipped; public void RaiseOrderCreated(Guid instanceId, Guid orderId) { RaiseEvent(this.OrderCreated, instanceId, orderId); } public void RaiseOrderShipped(Guid instanceId, Guid orderId) { RaiseEvent(this.OrderShipped, instanceId, orderId); } private void RaiseEvent(EventHandler<OrderEventArgs> eventHandler, Guid instanceId, Guid orderId) { if (eventHandler != null) { eventHandler(null, new OrderEventArgs(instanceId, orderId)); } } } |
After the implementation is complete the Workflow Runtime needs to know about this service specifically the ExternalDataExchangeService because it will wire up all the events on the implementation class to ensure that when someone fires the normal CLR event that it gets put in the workflow instance’s scheduler queue and processed. This code goes in the program.cs file if you are using a console workflow application.
| ExternalDataExchangeService service = new ExternalDataExchangeService(); workflowRuntime.AddService(service); service.AddService(new OrderWorkflowService()); |
Sample Workflow
To use the service in the workflow drag on a HandleExternalEventActivity and configure it to point at our IOrderWorkflowService and the OrderCreated event.

Notice the CorrelationToken property above. This is the key to using correlation in our workflow. Type in any string you want like “OrderToken” for example and then select the scope to apply this token to. We want this token to be available to any activity in the workflow so we select Workflow1.

Lastly drag another HandleExternalEventActivity onto the workflow and configure to point at the OrderShipped event.

Select our OrderToken as the value for CorrelationToken thus coupling the Created and Shipped events together.

Test Program
Back over in the program.cs file of your console workflow test application add the following code after the workflow instance has been started. This code will fire the initial OrderCreated event thus initializing the correlation for that particular workflow instance.
| WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(ExternalWorkflowCommunications.Workflow1)); instance.Start(); OrderWorkflowService orderService = workflowRuntime.GetService<OrderWorkflowService>(); orderService.RaiseOrderCreated(instance.InstanceId, orderId); |
Back over in the program.cs file add a handler to the WorkflowRuntime.WorkflowIdled event and execute the following code forcing the OrderShipped event to be fired.
| workflowRuntime.WorkflowIdled += delegate(object sender, WorkflowEventArgs e) { OrderWorkflowService orderService2 = workflowRuntime.GetService<OrderWorkflowService>(); orderService2.RaiseOrderShipped(e.WorkflowInstance.InstanceId, orderId); }; |
Run the console application and notice our events were fired via the following code.
| private void HandleCreated_Invoked(object sender, ExternalDataEventArgs e) { Console.WriteLine("Order Created"); } private void HandleShipped_Invoked(object sender, ExternalDataEventArgs e) { Console.WriteLine("Order Shipped"); } |

Notice that we need the instance id to fire any of these events to ensure that they get to the appropriate instance. In a stateless server environment we will need to have the instance id in memory as we do in this sample. From most clients we would have the order id, but not the instance id. The next post will demonstrate how Aware Server handles this external workflow communication and how it tracks all the correlation automatically for you. This coupled with Aware Server’s side-by-side versioning allows you to have 2 different versions of business process running and Aware will ensure that external events are not only fired to the right workflow instance, but to the right version as well.
Below is the full listing of program.cs.
| Guid orderId = Guid.NewGuid(); using(WorkflowRuntime workflowRuntime = new WorkflowRuntime()) { ExternalDataExchangeService service = new ExternalDataExchangeService(); workflowRuntime.AddService(service); service.AddService(new OrderWorkflowService()); workflowRuntime.StartRuntime(); AutoResetEvent waitHandle = new AutoResetEvent(false); workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) {waitHandle.Set();}; workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e) { Console.WriteLine(e.Exception.Message); waitHandle.Set(); }; workflowRuntime.WorkflowIdled += delegate(object sender, WorkflowEventArgs e) { OrderWorkflowService orderService2 = workflowRuntime.GetService<OrderWorkflowService>(); orderService2.RaiseOrderShipped(e.WorkflowInstance.InstanceId, orderId); }; WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(ExternalWorkflowCommunications.Workflow1)); instance.Start(); OrderWorkflowService orderService = workflowRuntime.GetService<OrderWorkflowService>(); orderService.RaiseOrderCreated(instance.InstanceId, orderId); waitHandle.WaitOne(); |
Currently rated 4.0 by 4 people
- Currently 4/5 Stars.
- 1
- 2
- 3
- 4
- 5