NativeActivity - CacheMetadata for Fun and Profit

[Cache. Profit. I guess I’m going on 50.]

After reading Basic Activity Scheduling and starting to write subclasses of NativeActivity, you may start running into a bunch of really weird or downright incomprehensible error messages. Some samples to whet your appetite:

Activity '1: CodeActivity1' cannot access this variable because it is declared at the scope of activity '1: CodeActivity1'. An activity can only access its own implementation variables.

Variable '' of type 'System.Int32' cannot be used. Please make sure it is declared in an Activity or SymbolResolver.

The following errors were encountered while processing the workflow tree:
'CodeActivity1': The activity with display name 'CodeActivity1' is attempting to reference itself. An activity cannot be its own parent.

The following errors were encountered while processing the workflow tree:
'CodeActivity1': The activity 'CodeActivity1' cannot be referenced by activity 'CodeActivity1' because the latter is not in another activity's implementation. An activity can only be referenced by the implementation of an activity which specifies that activity as a child or import. Activity 'CodeActivity1' is declared by activity 'CodeActivity1'.

Hungry? OK. Now what the heck is going on?

Cache your metadata! (Eat your vegetables!)

All these error messages are the runtime’s heavy-handed way of limiting the way your activities work to something it can understand. How does it understand what your activity does? Well you have to tell it, by providing the runtime with metadata. But what, for example, information are you actually telling the runtime?

Here’s the first and most important thing

  • I might want to invoke these child activities, which are somewhat accessible to the outside world so I might not know completely what they do.
  • I might want to invoke these child activities, which I totally own - they can’t see anything outside me.

Actually we’ll use special names for these: in the first case such children get called public children. In the second case such children get called implementation children.

You will see a lot of examples of Activities with public children in the WF 4.0 framework, here are a few: Sequence, If, While, Flowchart. What do all these activities have in common? You provide the children.

You should know some examples of Activities with implementation children too: every time you create an activity in XAML using the workflow designer, all the activities you added inside that activity are implementation children. And of course it’s possible familiar activities have some invisible implementation children that we just never know about

Aside from using CacheMetadata to declare child activities, you can also

  • Declare variables (public or implementation)
  • Declare arguments
  • Implement custom validation rules, so you too can have nifty validation errors show up in the Workflow Designer
  • Declare you require an extension
  • Register a default extension provider
The default implementation

Do the WF 4.0 built-in activities override CacheMetadata()? Of course! There’s a good reason, which is performance! But do we have to? The Basic Activity Scheduling post (again) didn’t override NativeActivity.CacheMetadata(), and it still worked...

NativeActivity.CacheMetadata does have a default implementation, and it goes something like this:

For each public Property of type Activity on this type, declare it as a public child activity.
For each public Property of type Variable on this type, declare it as a public variable.
For each public Property of type InArgument on this type, declare it as an argument.

All of this code works by reflection on your class. Reflection is slow. This is why the framework always overrides CacheMetadata, and a reason you might need to override CacheMetadata in a 'real world' application.

So if you had to reinvent the wheel, and write your own custom Sequence activity, you probably don’t want to call base.CacheMetadata():

protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            //NOT calling base.CacheMetadata(); even though it might do exactly what we want
             foreach(Activity x in this.Children) metadata.AddChild(x);
        }

Variables

The real fun starts when you start thinking variables are the way to pass data around your workflow, and you get into variable visibility rules, and all those public/implementation declarations start to have an effect….