(WF4) Workflow 4.0 Hosting Extensions

[Update:
Dear Readers,
To those of you who found this post interesting, but still a little too vague on how exactly you can use extensions for workflow-host communications, how this ties in with bookmarking, or what might be involved in implementing your own custom Send/Receive semantics in a workflow activity, you may enjoy the sequel post 'Workflow 4.0 Hosting Extensions Redux']

Here's some really useful information for workflow 4.0. So useful, in fact, that it is covered by numerous other sources already (such as other people's blogs and the WF4 samples) – but! It still seems that this information is hard to find and in particular I have a hard time finding them! So it seems fair to post it just in case. :-)

 

A problem you might often see while designing custom activities is that the obvious ways of passing data to/from a workflow or activity only work when the workflow isn't really alive.

Example: Imagine that I wrote a send e-mail and wait-for-reply activity. It has inputs (e-mail address), outputs (reply), and can maybe throw an exception (e-mail address rejected). Additionally, it will go idle and persist for a long time. During this time it leaves a bookmark open for the workflow’s host to resume the workflow when a response email is received. However, in order for the host to know which bookmark to resume, your e-mail activity will probably need to pass some extra information to the host, describing the event for which the bookmark should be resumed. Neither InArgument nor OutArgument nor variable is highly suitable for this job.

OK.

So, the question is basically “How can I get extra information back from the workflow to the outside world (the host)?”

 

A second problem you might also see is the same problem but in the opposite direction: “How can I get extra information from outside world (in particular, the workflow host) into my activity, mid-execution of the activity?

 

The answer is using Hosting Extensions, aka Workflow Extensions, aka just Extensions. (I mentioned once a long time ago that you can access or declare extensions in a NativeActivity – now for some details.) There are a couple flavors in how you can set up an extension, but pretty much only one flavor of how to actually use an extension. You can do it in a CodeActivity or in a NativeActivity, and it looks something like this:

 

public sealed class CodeActivity1 : CodeActivity
    {
        protected override void Execute(CodeActivityContext context)
        {
            MyExtension extension = context.GetExtension<MyExtension>();
            string result = extension.DoSomething("Information");
        }
    }

 

So what does context.GetExtension<MyExtension>() actually return? This GetExtension<T>() function is a strongly typed version of the IServiceProvider pattern: it just returns your custom extension object, which you registered earlier, whatever you registered. Your extension can literally implement any functionality that you want it to, but there will exist only one such extension object per workflow instance, per T (at last in the usual case this is what you want) which is returned every time any activity in the workflow instance calls GetExtension<T>().

So, how did you set up MyExtension in the first place?

Well, there are two options.

1 - Go through the class System.Activities.Hosting.WorkflowInstanceExtensionManager directly. An instance of this type is available through WorkflowApplication.Extensions. This option can make sense if you are implementing (or customizing) the workflow host, and you want the host to set up the extensions before it starts running workflows.

2 - Your activity can register its own extension during CacheMetadata() by calling ActivityMetadata.AddDefaultExtensionProvider<T>(). This option makes sense if you are writing a custom activity class that should automatically set up its own extension no matter how it is being hosted. (This is useful if you don’t own the host, or if you have many extensions, many hosts, and it would be too much work registering the right extensions on every host.) An example of an activity in the framework which registers its own extnesion in this way is the Delay activity, which registers a type called TimerExtension.

 

Hungry for more? Well, let’s finally dig up the links to those related WF samples… (relative paths)

Basic\Services\AbsoluteDelay (a useful variant of framework’s delay activity)

Basic\Services\ChannelCache (using an extension to cache and reuse WCF channel objects, by the sound of it)

Basic\Execution\ControllingWorkflowApplications (readline workflow host scenario)

Basic\Persistence\PersistenceParticipants  (the persistance participants system seems to be based on the Hosting Extensions)

Enjoy!

Update: for a way to use Extensions in service hosted xaml (XAMLX) see Maurice's answer here: 'Using Workflow Extensions (context.GetExtension) in IIS-hosted workflows' (forum)