October 16, 2005 spout

More Workflow Communciation Spelunking

After complaining about the inability to bind directly to event inputs, Dennis suggested I try the Workflow Communications Activity generator utility (wca.exe). If you point it at an assembly that contains interfaces decorated with the DataExchangeService attribute, it will generate typed invoke method and event sink activities for each method and event in the interface. To support this, I moved the communication interface, IOrderService, and types the interface depends on, i.e. Order and OrderEventArgs, to a separate library project and added the following post-build step:

"C:\Program Files\Microsoft SDks\Windows Workflow Foundation\wca.exe"
  "$(TargetPath)" /o:"$(SolutionDir)\EventSinkAndMethodInvoke2"

The reason I added this as a post-build step in the library project instead of as a pre-build stuff in the actual wf app, is because I want to have the types to program against whenever the library changes. However, either way, to get the new activities to show up on the toolbar, you have to build the application. Once I’d done that, I updated my workflow using the typed activities:

Unfortunately, just as I was capturing that screenshot, the Workflow Designer couldn’t find the activities, so it dropped them from my workflow without so much as a by your leave,” reminding me where much of the early WinForms Designer.

However, two nice things result from these generated types. The first is that my design-time experience, either in the designer or in the XOML, is improved because I don’t have to do a bunch of parameter binding:

<SequentialWorkflow
  x:Class="EventSinkAndMethodInvoke2.Workflow2"
  x:CompileWith="Workflow2.xoml.cs"
  ID="Workflow2"
  xmlns:x="Definition"
  xmlns="Activities">

  <ns0:CreateOrder
customer="Fabrikam"
    orderDescription="42&quot; Plasma TV"
    ID="createOrder1"
    MethodName="CreateOrder"
    InterfaceType="EventSinkAndMethodInvoke2.IOrderService"
    xmlns:ns0="IOrderService_Operations" />

  <ns1:OrderApproved
ID="orderApproved1"
    EventName="OrderApproved"
    InterfaceType="EventSinkAndMethodInvoke2.IOrderService"
    xmlns:ns1="IOrderService_Events" />

  <Code ExecuteCode="code1_ExecuteCode" ID="code1" />

  <Terminate
Error="*d2p1:ActivityBind(ID={orderApproved1};Path=Comment)"
ID="terminate1"
    xmlns:d2p1="ComponentModel" />

</SequentialWorkflow>

The other nice thing is that, because the typed event sink has properties that directly expose the event arguments, i.e. Comment and Order, instead of just via parameter bindings, I can bind to them in the b1 build of WF. This reduces my coupling, because the terminate activity doesn’t know where it’s getting it’s input, and it takes away that global variable” feel I had when I was binding parameters in and out of fields on the workflow itself. If I want to access the event sink’s typed properties directly, I can do so, as shown in the code activity’s handler:

public partial class Workflow2 : SequentialWorkflow {
  void code1_ExecuteCode(object sender, EventArgs e) {
    Console.WriteLine("Order: approved w/ comment= {0}", orderApproved1.Comment);
  }
}

I really like that typed activities are generated for me based on my data exchange service interfaces, but it’s still a manual, multi-stage process today. I’d prefer that it happened automatically, like typed resources and settings in VS05. If that can’t happen, I’d prefer to be able to bind directly to the parameter binding list that the generic event sink activity already knows about. At least that way, if there’s a problem, my workflow doesn’t get destroyed because of it.

The updated VS05b2, WF/WinFXb1 sample code is available for your enjoyment.