Inconsistent Validation of Extensions in WorkflowApplication

February 10th 2014 Workflow Foundation

While refactoring an application hosting workflow foundation runtime, I stumbled upon an inconsistent behavior in handling and validation of workflow extensions added to the host WorkflowApplication.

For those, not familiar with workflow extensions; they are specific classes that can provide external services to custom activities requiring them. They are added to the WorkflowApplication class hosting the workflow and can therefore serve as an interface enabling the activities to communicate with external resources. The activity can retrieve the instance of the extension when it executes and call its methods. It can also declare that it requires a specific extension. In this case a validation will be performed when starting the workflow. This will ensure that the activity will receive an instance of this extensions at runtime, otherwise the workflow wouldn't get started at all.

Let's illustrate it with a sample. First we'll define a minimal extension class:

public class ChildExtension
{ }

Then we'll create a custom activity requiring and retrieving an instance of that extension:

public class CustomActivity : NativeActivity
{
    protected override void Execute(NativeActivityContext context)
    {
        if (context.GetExtension<ChildExtension>() == null)
        {
            throw new NullReferenceException("No ChildExtension!");
        }
    }

    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
        metadata.RequireExtension<ChildExtension>();
        base.CacheMetadata(metadata);
    }
}

Now the workflow be defined and hosted in our application:

var wfActivity = new CustomActivity();
var wfApp = new WorkflowApplication(wfActivity);
wfApp.Extensions.Add(new ChildExtension());
wfApp.Run();

As you can see, an instance of our extension has been added to the host. Otherwise a ValidationException would be thrown when calling Run.

So far so good. The inconsistencies occur when we try to take advantage of IWorkflowInstanceExtension interface. If an extension implements it, it can serve as a parent extension, providing additional dependent extensions to the host. Let's create such an extension:

public class RootExtension : IWorkflowInstanceExtension
{
    public IEnumerable<object> GetAdditionalExtensions()
    {
        return new[] {new ChildExtension()};
    }

    public void SetInstance(WorkflowInstanceProxy instance)
    { }
}

Although there's not much documentation available about the interface, this should allow us to only add the parent extension to the host, while having the additional extensions added to the host automatically:

var wfActivity = new CustomActivity();
var wfApp = new WorkflowApplication(wfActivity);
wfApp.Extensions.Add(new RootExtension());
wfApp.Run();

If we do that, a ValidationException will be thrown when Run is called. Although this surprised me, I was even more surprised when I removed the call to RequireExtension from CustomActivity and everything worked just fine. The call to GetExtension returned the ChildExtension instance although an exception was thrown before the change, stating:

An extension of type 'Workflow.ChildExtension' must be configured in order to run this workflow.

This behavior seems inconsistent to me. I'd expect the exception not to be thrown in this case or at least GetExtension not returning the extension if it's not required. That's why I reported an issue to Microsoft Connect hoping for an official explanation or fix. I wrote this blog post as a warning to anyone else trying to take advantage of the above functionality, and of course to attract additional attention to the Connect issue. Feel free to vote for it.

Copyright
Creative Commons License