Monday 16 April 2012

Adding extensions to a hosted workflow

When hosting Workflows using WorkflowServiceHost or Windows Server AppFabric you may wish to add extensions to the runtime environment so that custom activities can access these extensions. Typically you’ll write custom activities to access some form of extension using a custom interface, and then provide an implementation of this interface at runtime.

When you are self hosting (using WorkflowInvoker or WorkflowApplication) it’s a simple matter of adding an object to the ‘host’, such as shown in the code below…

WorkflowInvoker invoker = new WorkflowInvoker(GetWorkflow());
invoker.Extensions.Add<IActivityLogger>(() => new ActivityLogger());

If you’re hosting using configuration using WorkflowServiceHost or AppFabric however it’s common to define everything in the web.config, however Workflow 4 doesn’t come with an out of the box mechanism to do this.


What I’d like to do is be able to configure up a bunch of extensions using the following in my web.config file…


<workflowExtensions>
<
extensions
>
<
add name="{something}"
extensionType="{Fully Qualifier Typename}"
{Optional Parameters}
/>
</
extensions
>
</
workflowExtensions
>

Now, if you’ve been using .NET for a while you might recognise this pattern – it’s very simple to the way you configure providers in ASP.NET, for things like membership, roles etc. I’ve done this deliberately – the provider model is a good pattern to use and fits this example well.


What I then need is something to read the set of extensions, and instantiate them for each hosted workflow. To do this you need a bit of WCF knowledge.


Behavior Extensions


A behavior extension allows us to inject some code into the WCF pipeline, and in this case I’m using one to instantiate objects defined in my <workflowExtensions> element. First up you need a class that derives from BehaviorExtensionElement as shown in the code below…


/// <summary>
///
Class which is used to define the configuration element for the
/// web.config, so we can add extensions to the workflow
/// </summary>
public class WorkflowExtensionsElement :
BehaviorExtensionElement
{
/// <summary>
///
Return the type of object that is created by this extension
/// </summary>
public override Type BehaviorType
{
get { return typeof(WorkflowExtensionsBehavior); }
}

/// <summary>
///
Construct the behavior
/// </summary>
/// <returns>
A behavior, created from the config in the web.config
</returns>
protected override object CreateBehavior()
{
List<WorkflowExtension> extensions = new List<WorkflowExtension>();

foreach (WorkflowExtensionConfigElement item in WorkflowExtensions)
extensions.Add(new WorkflowExtension(item.Name,
item.ExtensionType, item.Parameters));

return new WorkflowExtensionsBehavior(extensions);
}

///
Declare the extensions collection property.
[ConfigurationProperty("extensions", IsDefaultCollection = true)]
[ConfigurationCollection(typeof(WorkflowExtensionsCollection),
AddItemName = "add",
ClearItemsName = "clear",
RemoveItemName = "remove")]
public WorkflowExtensionsCollection WorkflowExtensions
{
get { return (WorkflowExtensionsCollection)base["extensions"]; }
}

}

This uses some other classes that are in the code download that read the appropriate information from the .config file. You then need to include some modifications to the .config file in order for this new behavior to be executed when reading the .config file…

  <system.serviceModel>

<
extensions
>
<behaviorExtensions
>
<
add name="workflowExtensions"
type="MSADC.Workflow.WorkflowExtensionsElement, MSADC.Workflow"
/>
</
behaviorExtensions
>
</
extensions>

This includes the extension such that we can then alter our behaviors element to include the list of extensions…

    <behaviors>
<
serviceBehaviors
>
<
behavior name=""
>
<
serviceMetadata httpGetEnabled="true"
/>
<
serviceDebug includeExceptionDetailInFaults="false"
/>
<
workflowExtensions
>
<
extensions
>
<
add name="logger"
extensionType="Demo.ActivityLogger, Demo"
/>
</
extensions
>
</
workflowExtensions
>

Extension Parameters


Now we have the ability to configure up an extension, how about additional parameters that the extension might need to use? Simple – the code that parses the <add> elements reads any additional attributes and pops these into a NameValueCollection, then during instantiation of the extension I check if the extension derives from the ProviderBase class. If so I simply call the Initialize() method, passing in the name of the provider and the NameValueCollection.


You could use this to pass in a database connection string name or other information – you just need to derive from ProviderBase and read the configuration parameters from the passed collection.


The Code


The download includes two projects, MSADC.Workflow and MSADC.Workflow.Common. I’ve refactored the code from my Hosting Dynamic Workflows example to move this into these libraries as there’s more to come and I need to keep this all in a sensible place. If you’re wondering about the name, MSADC is the shortened form of my company name (MS Application Development Consulting Ltd). I provide bespoke, deeply technical consulting on Windows Workflow 4 (and SIlverlight 4/5, WPF 4, WCF and lots of other parts of the .NET framework).


If you want some help from someone who knows what they’re talking about please get in touch!


The code is available as a NuGet package. Please click here to access the package. If you have any comments please contact me using the Contact Owners link from within NuGet.