An Activity Designer for InvokeAction

[Update! There are some bugs with the attached code, see the sequel post for discussion of bug fixes.]

My workmate Ramraj mentioned that a long way back (during earlier milestones of WF 4.0) something was invented for XAML called a property reference,which was a feature that in the workflow designer would enable creating custom XAML activities 'with holes’ for plugging in actions. More specifically, it enables authoring an InvokeAction<T> activity designer, so that an InvokeAction<T> activity in your workflow can invoke a certain ActivityAction<T> property which was set on your workflow.

This feature is kind of strange because it is hard to imagine how it could work using real CLR properties. Scenario:

- You have CLR property 'Action' on an InvokeAction activity.
- You want it to be exactly the same value as CLR property 'Action' on your custom Xaml Activity (which wraps the InvokeAction activity)
- There is no value of Action yet - so you can't just literally set the two properties to be the same value.
- You need one CLR to 'refer' to another one, as if overriding its getter to do

Action { get { return otherGuy.Action } }

But this seems impossible, because of course with normal CLR objects you can't selectively override properties in this way. Where a little magic is able to enter, and save the day, is the fact that we aren't restricted to dealing with actual CLR objects. The properties only need to be set to equal the same value when the workflow XAML definition for our custom activity is invoked - which, with a few ifs and buts, might be when the needed property value is already known (hint: there are some ways this can fall down).

So back to the original story - I heard this construct existed in the past as a prototype, but more recently (thanks to forum post here) I found out the functionality is actually alive and well in the 4.0 framework, and is even highlighted by one of the XAML samples!

It looks like this:

<Activity mc:Ignorable="sap" x:Class="Microsoft.Samples.Activities.ShowDateTimeAsAction" [namespaces omitted]>
  <x:Members>
    <x:Property Name="CustomAction" Type="ActivityAction(x:String)" />
  </x:Members>

  <InvokeAction x:TypeArguments="x:String" Argument="[str]" sap:VirtualizedContainerService.HintSize="200,82">
      <PropertyReference x:TypeArguments="ActivityAction(x:String)" PropertyName="CustomAction" />
  </InvokeAction>

</Activity>

The PropertyReference is tying the Action property (of type InvokeAction<string>), to the CustomAction property of the ShowDateTimeAsAction class.

So… the framework has support so that you can do all this through XAML. Which is cool. But nothing in XAML is cool enough without a designer experience...

If you want to dig deeper, there’s also a couple mysterious aspects to the above XAML.

-What’s this <PropertyReference> class?
-Why does the value of Action still appear to be null, after you deserialize it using WorkflowDesigner.Load()?
-Even though Action appears as null, the <PropertyReference> data survives loading, editing, and saving in the designer, and getting saved back to XAML?

So definitely something unusual is going on. It works by certain classes which do some custom XAML serialization/deserialization: ActivityPropertyReference, ActivityBuilder,and System.Xaml.AttachablePropertyServices.

Now as I really just want to know how we could use this in designer, the important point for me is that we can find the PropertyReference which has been loaded from XAML, and do something with it.

What we get when we load the above sample XAML (which had some bits cut out) is an object tree something like

-ActivityBuilder
+—Properties
+—Implementation
     +—Sequence
           +—Body
                +—…
                +—ActivityAction(with an attached property)
                     +—Action(=null)

where the attached property is
-ActivityPropertyReference
  +--TargetProperty=”Action”
  +--SourceProperty=”CustomAction”

We get/set the attached property using these APIs:

ActivityBuilder.GetPropertyReference(object target)
ActivityBuilder.SetPropertyReference(object target, ActivityPropertyReference value)

Now that we have this cool information, we should be able to make a designer for InvokeAction<T>, right? Right. Really. This is what it looks like in action:

image 

Download the attached code to play with it yourself. (P.S. I only tested it with InvokeAction<string> so far, let me know in the comments if you have issues....)

InvokeActionDesigner.zip