Every program answers two questions “what to do” and “when to do it.” Windows Workflow Foundation or WF is a part of the WinFX suite of tools which allows programmers to seperate the “what to do” and the “when to do it” and specifically allows you to declaratively model the “when to do it” allowing for easy creation of visual tools for working with the workflow logic. When using WF in this way one of the things you will need to learn to do is how to communicate back and forth between your WF code and your client code. This post will be covering the ExternalDataExchange service and how you can use it with built in activities to provide easy client\WF communications.
Background
Communication in WF is handled under the hood through queues which is out of the scope of this article but covered quite well in Essential Windows Workflow Foundation. For the purpose of this article we will be covering communication using built in WF services that take advantage of WF queuing abilities to communicate between client code and WF instances (WFIs) without you having to know the details. The way this works is show below.
Communication from the host (client) to the WFIs works by the host application calling into registered WF services to raise events that are then raised into WFIs. WFIs communicate with the host by calling methods on WF services which then turn around and call methods on the client. There are of course some details to work through here, like how to register services and how to wire up those services so that they can support this type of communication. The easiest way to do these things is to use the ExternalDataExchange service which is one of the core services that is included with WF. ExternalDataExchange allows you to register your own custom services with it and then provides all the plumbing needed to allow you to raise events from your services into workflows and allows your WFIs to call methods on your service for communicating back to your client code by mostly using interfaces and attributes. I will demonstrate this by showing the implementation of the ConsoleService.
ConsoleService
The ConsoleService is a contrived custom WF service that I’ve created that allows for writing to and reading from the console. The architecture of this service is shown below.
As you can see our Host Process will contain 3 components that will communicate with each other
- Console – System.Console class which will write to and read from the console
- Calls ConsoleService.RaiseReadLine() to pass
- WorkflowRuntime – this is the heart of WF and will be responsible for creating and running WFIs and then providing services for those WFIs to use. One of these services will be the ConsoleService.
- WorkflowInstance – a running workflow
For this implementation the we will register the ExternalDataExchange service with the WorkflowRuntime and then we will create our ConsoleService and register it with the ExternalDataExchange service to allow us to communicate through our service to and from the System.Console from WFIs. One thing to keep in mind is that there are several ways of hosting the WorkflowRuntime and covering those is out of the scope of this article. You would most likely not host the WorkflowRuntime in a console applicaiton in production code. Let’s start by starting Visual Studio and creating a new Sequential Workflow Console Application project from under the Workflow project templates in visual studio.
This will generate the following code which I’ve commented to describe what each piece does
class Program
{
static void Main(string[] args)
{
using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
{
// Thread wait handle used to block main thread until WFI thread completes
AutoResetEvent waitHandle = new AutoResetEvent(false);
// Wire up the WorkflowCompleted and WorkflowTerminated events on the WorkflowRuntime
// and Set() the wait handle to allow main thread to be notified when this WFI completes
workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) { waitHandle.Set(); };
workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
{
Console.WriteLine(e.Exception.Message);
waitHandle.Set();
};
// Create a workflow instance using the CreateWorkflow overload that allows creating a
// WFI from a .Net type
WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(Echo.Workflow1));
// Start the WFI
instance.Start();
// Wait for it to complete
waitHandle.WaitOne();
}
}
}
Next we need to create our ConsoleService. The key to being able to communicate back and forth with WFIs is implementing ExternalDataExchange attributed interface. Next add two interface, IReadLine and IWriteLine, to your project with the following definition. Make sure to add a reference to System.Workflow.Activities.
[ExternalDataExchange]
public interface IReadLine
{
event EventHandler<ReadLineEventArgs> LineRead;
void ReadLine();
}
[ExternalDataExchange]
public interface IWriteLine
{
void WriteLine(string text);
}
The ExternalDataExchange attribute is all that you need to add to be able to raise events into WFIs and to be able to call methods from a WFI to a client. Any methods or events declared on the interface are ready for WF. Next we need to implement interfaces in our ConsoleService. Add a new class to your project called ConsoleService and add the code shown below.
[Serializable]
public class ConsoleService : IWriteLine, IReadLine
{
#region IWriteLine
/// <summary>
/// Allows WF instances to write a line of text to the console
/// </summary>
///
<param name="text">Specfies text to be written out.</param>
public void WriteLine(string text)
{
Console.WriteLine(text);
}
#endregion // IWriteLine
#region IReadLine
/// <summary>
/// Raised to communicate that a line was read from the console and
/// be delievered in the ReadLineEventArgs.Text
/// </summary>
public event EventHandler<ReadLineEventArgs> LineRead;
/// <summary>
/// Will read a line of text from the console and then raise the LineReadEvent
/// </summary>
///
<param name="instanceId">Specfies instance ID of the WF instance that the LineRead event
/// will be raised on.</param>
public void ReadLine()
{
RaiseLineRead(WorkflowEnvironment.WorkflowInstanceId, Console.ReadLine());
}
/// <summary>
/// Allows WF clients to raise LineRead event for WF instances
/// </summary>
///
<param name="instanceId">Specifies the instanceId of the target WF instance</param>
///
<param name="text">Specifies line of text read from client</param>
private void RaiseLineRead(Guid instanceId, string text)
{
if (LineRead != null)
{
LineRead(this, new ReadLineEventArgs(instanceId, text));
}
}
#endregion // IReadLine
}
Fist thing to note is that any class that is used to communicate with WFIs needs to be serializable which is accomplished by adding the Serializable attribute. Also, As you can see ConsoleService implements both IReadLine and IWriteLine. WriteLine is pretty self explanitory. This method can now becalled from a WFI by using a CallExternalMethodActivity, but more on that later. ReadLine takes a little more work because it requires two-way communication which requires both a method (WFI to client) and an event (client to WFI). The WFI will indicate to the console service that it wants to read a line by calling ReadLine() as shown below.
public void ReadLine()
{
RaiseLineRead(WorkflowEnvironment.WorkflowInstanceId, Console.ReadLine());
}
This function grabs the currently running WFIs InstanceId using WorkflowEnvironment.WorkflowInstanceId and then passes as it to RaiseLineRead() along with the second argument which will be whatever text is read from the console using Console.ReadLine().
private void RaiseLineRead(Guid instanceId, string text)
{
if (LineRead != null)
{
LineRead(this, new ReadLineEventArgs(instanceId, text));
}
}
RaiseLineRead first checks to make sure that there are subscribers to the LineRead event which there will be as soon as the ConsoleService is registered with the WorkflowRuntime’s ExternalDataExchangeService. It then raises the event passing a this refernece as the sender and a new ReadLineEventArgs as the second argument which gets the target WFIs instanceId and the text that was written as the second argument. Now add a new class called ReadLineEventArgs to your project and add the following code.
[Serializable]
public class ReadLineEventArgs : ExternalDataEventArgs
{
private string m_text;
public string Text
{
get { return m_text; }
set { m_text = value; }
}
public ReadLineEventArgs(Guid instanceId, string text)
: base(instanceId)
{
Text = text;
}
}
Again we must make our class serializable using the Serializable attribute and then for this EventArgs to work with ExternalDataExchange we mush derive our custom Event args from ExternalDataEventArgs which is also in the System.Workflow.Activities namespace. When an event is raised against the ExternalDataExchange service, the service will use the instanceId which is passed to the constructor to deliever the event to the correct WFI. I’ve added a Text property to ReadLineEventArgs which will be populated with the text entered at the console and delievered to the WFI via an instance of ReadLineEventArgs. Next we need to add our service to the WorkflowRuntime.
Registering ConsoleService
Now we will update our host program to register the ConsoleService. The full host code is shown below.
class Program
{
static void Main(string[] args)
{
using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
{
// Thread wait handle used to block main thread until WFI thread completes
AutoResetEvent waitHandle = new AutoResetEvent(false);
// Wire up the WorkflowCompleted and WorkflowTerminated events on the WorkflowRuntime
// and Set() the wait handle to allow main thread to be notified when this WFI completes
workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) { waitHandle.Set(); };
workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
{
Console.WriteLine(e.Exception.Message);
waitHandle.Set();
};
// Add ExternalDataExchangeService for communications
ExternalDataExchangeService externalDataExchangeService
= new ExternalDataExchangeService();
workflowRuntime.AddService(externalDataExchangeService);
// Add ConsoleService to externalDataExchangeService service for commincation back
// to the client
ConsoleService consoleService = new ConsoleService();
externalDataExchangeService.AddService(consoleService);
// Create a workflow instance using the CreateWorkflow overload that allows creating a
// WFI from a .Net type
WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(Echo.Workflow1));
// Start the WFI
instance.Start();
// Wait for it to complete
waitHandle.WaitOne();
}
}
}
The code that registers that ConsoleService is shown below.
// Add ExternalDataExchangeService for communications ExternalDataExchangeService externalDataExchangeService = new ExternalDataExchangeService(); workflowRuntime.AddService(externalDataExchangeService); // Add ConsoleService to externalDataExchangeService service for commincation back // to the client ConsoleService consoleService = new ConsoleService(); externalDataExchangeService.AddService(consoleService);
First we create an ExternalDataExchange instance and add it to the workflowRuntime instance via the AddServiceMethod. Now that the externalDataExchange instance is added to the WorkflowRuntime’s registered services we can now create an instance of the ConsoleService and add it to the externalDataExchange instance which will make the ConsoleService available for use by the WFIs that are hosted by this WorkflowRuntime. We do this using externalDataExchange.AddService().
Creating the Echo Workflow
Next we will update Workflow1 which was created when we created our project to add activites that allow us to create a simple console echo program that will write out whatever text is written in. To get started open Workflow1.CS in the designer by double clicking it in solution explorer. Next drag and drop a CallExternalMethodActivity from the toolbox onto the designer’s surface and look at it’s properties in the property window.
As you can see in this image, the UI will display validation errors for you both on the activity on the designer surface as well as next to the properties that need to be addressed. For our demo we want to write out “Please enter text to echo: ” to the console and so we need to select our IWriteLine interface for the InterfaceType and WriteLine for the MethodName. To do this first build your project then click on right side of InterfaceType and a button ‘…” will be displayed. Click that button then browse to the IWriteLine interface and select it as shown below.
After selecting the InterfaceType clicking on the MethodName text box will display a drop down of available methods and from the list select WriteLine. Once you select WriteLine notice that the parameters are dynamically added to the property window and you can now set the text parameter of WriteLine. Go ahead and set that to Please enter text to echo: and then set a more user friendly name for this activity like PromptForEchoText. Your property window should now look like this.
Go ahead and run the demo by clicking CTRL+F5 and you should see your text displayed to the command line window. So now we have sucessfully communicated from the WFI to the client now lets try and bring some data back into the WFI. To do this add one more CallExternalMethodActivity to your workflow definition in the designer with
(Name)
ReadLine
InterfaceType
IReadLine
MethodName
ReadLine
Then add another activity but this time add a HandleExternalEventActivity and set the properties to:
(Name)
HandleLineRead
InterfaceType
IReadLine
EventName
LineRead
And now your WF definition should look like this.
What we have now will
- Ask for text
- Call a ConsoleService.ReadLine to send a Console.ReadLine to the console
- Handle the ConsoleService.ReadLine event that will be raised from the ConsoleService in response to step 2
So now you might be wondering where is the Console.ReadLine text and how can we write it back out. For that lets start by taking a look at dynamic properties that popped up on the HandleLineRead activity. In the group you will see e. This is ReadLineEventArgs which was the ExternalDataEventArgs derived class that contains a Text property and this is where the text that was read from the console is got to be now. That makes the next question how do we get this value to another CallExternalMethod so that we can write back out to the console. For that we will use property binding and bind to a new property on the base class.
Start by clicking the “…” button in the HandleReadLine.e text box to open the property binding dialog. Select the Bind to a new member tab and then set it like below.
Now add a new CallExternalMethod activity to the end of the workflow with the following settings.
(Name)
EchoText
InterfaceType
IWriteLine
MethodName
WriteLine
Next we will again use property binding to get ReadLineEventArg.Text written out to the console. To do this click the “…” button in EchoText.text to open the property bind dialog and then browse to ReadLineEventArg.Text as shown below.
Now run the program by clicking CTRL+F5.
Conclusion
Now you have the basics that are needed to communicate back and forth with clients through the ExternalDataExchange service. There are other options for communications including web services and WCF and you can roll your own with custom activites and services but ExternalDataExchange can be very handy and very quick to use.
Tags: Communication, ExternalDataExchange, WF 3.5










Great post, thanks a bunch! First one I found that shows how to use a WF commincation service.
A great topic as a followup on this might be how to create a custom activity that implements a sequence activity containing the CallExternalMethod and HandleExternalEvent activities.
Dear Author http://www.ryanvice.net !
I think, that you commit an error. Let’s discuss.
Big_iMan, please explain
I want to quote your post in my blog. It can?
And you et an account on Twitter?
Sure but please link back and sorry no Twitter…
Buy:Seroquel.Zocor.Nymphomax.Benicar.Prozac.Ventolin.Amoxicillin.Cozaar.SleepWell.Zetia.Wellbutrin SR.Female Cialis.Buspar.Lipothin.Acomplia.Female Pink Viagra.Lasix.Lipitor.Advair.Aricept….
CChannel http://dchannelbaf-l.BESTPARTSPLUS.INFO/tag/r2+CChannel+c/ : c…
CChannel…
Buy:Cialis.Tramadol.Maxaman.Viagra.Super Active ED Pack.Viagra Professional.Levitra.Cialis Soft Tabs.Cialis Super Active+.Viagra Soft Tabs.Viagra Super Force.Viagra Super Active+.Cialis Professional.Soma.Zithromax.Propecia.VPXL….
Team http://zwx.c1b.i34.co : Roster…
Team…
…
BUY FASHION. TOP BRANDS: GUCCI, DOLCE&GABBANA, BURBERRY, DIESEL, ICEBERG, ROBERTO CAVALLI, EMPORIO ARMANI, VERSACE…