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….

Comments (2)

  1. tilovell09 says:

    ZOMG someone has linked to one of my posts. :p

    And there you can see CacheMetadata in action, instead of trying to follow my meandering train of thought.

    http://www.dotnetconsult.co.uk/weblog2/PermaLink,guid,c3394709-1aaf-486a-8d1f-51050ce94ecc.aspx

    Tim

  2. Over time a few folks have asked on the forum for information about writing rehosted visual workflow debugger applications (a.k.a. workflow simulator, a.k.a. visual workflow tracking…). Many of these folks have already seen the Visual Workflow Tracking sample (MSDN) , and even read at least one of Kushal’s related blog posts ( “Debugging in Workflow 4.0” , “VisualWorkflowTracking aka Workflow Simulator” , and “Visual Workflow Tracking with Step Service” ). But since it’s still a reasonably popular subject to ask questions on it seems like a good time to rehash it, and add a little information on very closely related subjects.   1 – General Limitations At first glance (at the pictures) the Visual Workflow Tracking samples might appear to show a debugging experience extremely similar to the workflow debugging experience Visual Studio. Now actually it is different, and while it is true that the two scenarios rely on some of the same code, one significant difference to be aware of is that Visual Studio is literally debugging the activities as you run because it has information about which activity is executing gained because it is attaching as a debugger . This is immediately different from the Visual Workflow Tracking sample which is working based on a completely different source of information about workflow execution, namely data from the Workflow 4.0 ‘Tracking‘ feature (for a very quick intro on the Tracking feature see here , but please note that it refers to Beta 1 of .Net 4.0, not the final release. Also see the MSDN description which refers to the actual release). This means you might not be able to easily implement every debugging feature you are familiar with such as breakpoints, or if you can implement them that they may end up needing to behave slightly different to the Visual Studio behavior. Now most parts of the sample code are useful no matter you get information about the currently executing state of the workflow – if you get creative it could be something completely different from workflow tracking. However, there is one more limitation of the sample code, which many people will want to overcome. The sample itself contains a workflow which executed live and in the same process as the visual workflow tracker. In many cases customers are interested in doing something which is not the same, what they actually want to do is allow a) visualizing a workflow which ran in a different process (e.g. on a server machine) or b) replay of a workflow which finished executing some time in the past. The impact of this is that instead of working from an Activity object , which is available as part of the TrackingRecord (on the derived subclasses) received by Participants, as the initial input into figuring out which activity is executing and needs displaying in the debugger, instead one must work with whatever Activity tracking data the TrackingParticipant saves to a database or log, which would typically include not the Activity object itself (this wouldn’t do what we want), instead it would usually include information such as an Activity ID.   2 – Activity IDs OK, that’s great but what is an Activity ID? Well, it’s actually a special  string. Here’s a concrete example that I totally made up and might even look wrong: “0.1.2.1.4” The important part of the example which isn’t wrong is that an Activity ID is just a series of numbers – numbers of significance! The numbers are in fact a path in your Activity tree. This is basically just like a file system path, “C:foobartempreadme.txt”, so it refers to a unique entity within a tree, and a given Activity ID always refers to a particular Activity as long as you are talking about the same exact workflow. Yes, I need to clarify that. It has to be the same exact workflow (workflow structure, not workflow instance) for two reasons. First reason: Look at the Activity ID string again. “0.1.2.1.4” is really, when interpreted as a suffix instead of a complete string, jus

Skip to main content