Tuesday, October 17, 2006 12:25 PM bart

WF - Introducing External Data Exchange, the CallExternalMethodActivity and Local Communications Services

Introduction

Workflows don't stand on their own; they need a host application to run in but in a lot of cases there's more: external services are needed to provide functionality to the workflow. You might think, let's just use a CodeActivity to call into some piece of functionality but that doesn't offer enough flexibility in most cases. Okay, you can work with an interface and parameterize the workflow, but still the workflow doesn't reveal its intentions visibly: a CodeActivity is an opaque container where the visualized world of workflows is traded for procedural code again.

Enter the Communication Services, part one. In this post, you'll learn how to exchange data with the workflow by hooking in a "external data exchange service".

CallExternalMethodActivity

At the core of external data exchange is the CallExternalMethodActivity. In a later post, we'll focus on HandleExternalEvent but for now, let's just rely on plain simple method calls. The scenario is the following:

  1. We need a workflow to get the job done (for reasons that may vary a lot, which have been discussed extensively already).
  2. It can't be 100% self-contained and has to rely on external services to process things, for example to make an order.
  3. This external service communication needs a big deal of genericity, so that it can be replaced by an alternative implementation when required. In other words, it needs to be contract-based.

(And no, it isn't a web service we want to call, because in that case we could rely on InvokeWebService.)

Demo

A workflow definition

Right, on to the demo with a somewhat simplified contract: let's call a simple calculator to get things done (I don't want to get the curse of the bad demo gods :-)). Start by creating a simple console-based workflow application:

Next, add a single CallExternalMethodActivity from the toolbox to the workflow definition as shown below:

This is what you should get to see when you set the properties correctly as outlined below. First, go to the code behind of the workflow and add three properties:

private int a;

public int
A
{
   get { return
a; }
   set { a = value
; }
}

private int
b;

public int
B
{
   get { return
b; }
   set { b = value
; }
}

private int
result;

public int
Result
{
   get { return
result; }
   set { result = value
; }
}

Now, add an interface to the project called ICalculator:

[ExternalDataExchange]
interface
ICalculator
{
   int Add(int a, int
b);
   int Subtract(int a, int
b);
   int Multiply(int a, int
b);
   int Divide(int a, int
b);
}

Notice the use of the ExternalDataExchangeAttribute, which lives in System.Workflow.Activities. This indicates the interface acts as the contract for external data exchange.

Now go back to the designer and set the CallExternalMethodActivity properties. Start by specifying the InterfaceType:

Next, set the MethodName to Add. Then, set the parameters a, b and (ReturnValue) that appear in the property grid to point to the properties A, B and Result respectively:

If you've done things correctly, this should result in the following:

Right, so now your workflow knows how to transform some input parameters into the corresponding output.

Letting the host know about data services

That's all what's needed for the workflow definition itself. On to the host now (Program.cs). First, we'll parameterize the workflow instantiation as follows:

Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add(
"A"
, 1);
parameters.Add(
"B"
, 2);

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(ExternalDataDemo.Workflow1
), parameters);
instance.Start();

Next, create some class that implements ICalculator. In this case this is very straightforward since we live in the same project as the workflow definition. In reality you'd have to reference the assembly with the interface definition of course. For the sake of the demo, a simple implementation as a nested class inside Program will meet our needs:

class CalculatorService : ICalculator
{
   #region
ICalculator Members

   public int Add(int a, int
b)
   {
      return
a + b;
   }

   public int Subtract(int a, int
b)
   {
      return
a - b;
   }

   public int Multiply(int a, int
b)
   {
      return
a * b;
   }

   public int Divide(int a, int
b)
   {
      return
a / b;
   }

   #endregion
}

Now it's time to hook in this service to the workflow runtime, so that instances can rely on it to get their jobs done. This is done in the Program class again, as follows:

ExternalDataExchangeService edx = new ExternalDataExchangeService();
workflowRuntime.AddService(edx);
//keep this order!
edx.AddService(new CalculatorService()); //keep this order!

It's important to keep the order of the last two lines, otherwise you'll end up with an exception. Also, import the System.Workflow.Activities namespace that contains the ExternalDataExchangeService class. This class acts as a container for all external data exchange services, i.e. implementations of interfaces that were annotated with the ExternalDataExchangeAttribute earlier on. You can only have one implementation of each service.

Finally, to get the result back, we'll change the WorkflowCompleted event handler as displayed below:

int res = 0;

AutoResetEvent waitHandle = new AutoResetEvent(false);
workflowRuntime.WorkflowCompleted +=
delegate(object sender, WorkflowCompletedEventArgs e)
{
   res = (
int) e.OutputParameters["Result"
];
   waitHandle.Set();
};

...

In here, res is a variable declared on top of the the Main method which gets printed out at the end of the method:

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(ExternalDataDemo.Workflow1), parameters);
instance.Start();

waitHandle.WaitOne();

Console.WriteLine(res);

The result when executing should be obvious :-).

Conclusion

Exchanging data with external parties should be apparent when looking at the workflow definition. Therefore, one has created the CallExternalMethodActivity to encapsulate such a call for external data. By doing so, external data exchange has the potential to become just yet another service, like tracking and persistence. In the next post, we'll talk about events. Enjoy!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,

Comments

No Comments