WorkflowService Configuration Based Extensions

Note: This feature is now released in Microsoft.Activities v1.8.5

Activities can access extensions using the ActivityContext.GetExtension<T> method.

But how do extensions get added to the extensions collection? There are two options provided by WF4
  1. Add the extension to the WorkflowApplication.Extensions collection
  2. The activity can use the ActivityMetadata.AddDefaultExtensionProvider<T> property to provide a Func<T> which will create the extension if necessary

However, there are cases where either option is not sufficient.
  • WorkflowServices do not have a way for you to add to the WorkflowServiceHost.WorkflowExtensions collection
  • There may be cases where you want to provide a different extension than the activity would create for itself using the AddDefaultExtensionProvider.

Acceptance Tests

Add an abitrary extension

A workflow service uses activities which require an extension but do not provide a Default Extension Provider. The developer can at runtime specify zero or more extension types in web.config which will be created and added to the extensions collection

Dependency Injection via Extension

A developer who wants to use Dependency Injection uses a Common Service Locator which is added to the extensions collection. At runtime activities which require an extension can obtain it via the DI container of their choice.

Example

Suppose you have an activity which requires an extension. In CacheMetadata the activity indicates that it requires an extension by calling RequireExtension<T> or RequireExtension.

public class ActivityExtensionTest : CodeActivity<bool>
{
    protected override void CacheMetadata(CodeActivityMetadata metadata)
    {
        metadata.RequireExtension<TestExtension>();
        base.CacheMetadata(metadata);
    }

    protected override bool Execute(CodeActivityContext context)
    {
        var extension = context.GetExtension<TestExtension>();
        return extension != null;
    }
}

If you create a WorkflowService which uses this activity and try to load it you will get an error.

Test method Microsoft.Activities.Tests.WorkflowExtensionsBehaviorTest.WorkflowExtensionsBehaviorAddsExtension threw exception: 
System.Activities.ValidationException: An extension of type 'Microsoft.Activities.Tests.TestExtension' must be configured in order to run this workflow. 

Microsoft.Activities.ServiceModel.WorkflowExtensionsBehavior

This service behavior solves this problem by allowing you to define in configuration the extension types you want to create.

To use this behavior
  1. Install-Package Microsoft.Activities
  2. Modify your config file to add the necessary elements to provide the configuration

In this example, we are adding an extension Microsoft.Activities.Tests.TestExtension to the WorkflowService
  <system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior>
        <serviceDebug includeExceptionDetailInFaults="True" />
        <!-- The WorkflowExtensionsBehavior allows you to add extensions -->
        <workflowExtensions>
          <add extension="Microsoft.Activities.Tests.TestExtension, Microsoft.Activities.Tests" />
        </workflowExtensions>
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <extensions>
    <!-- Must register the extension behavior -->
    <behaviorExtensions>
      <add name="workflowExtensions" type="Microsoft.Activities.ServiceModel.WorkflowExtensionsElement, Microsoft.Activities, Version=1.8.4.711, Culture=neutral, PublicKeyToken=23b0c89d0d5ad43f"/>
    </behaviorExtensions>
  </extensions>
</system.serviceModel>



Last edited Jul 21, 2011 at 2:15 PM by ronjacobs, version 4

Comments

jgfunari Oct 25, 2011 at 3:10 PM 
I agree with CraigStuntz. If we could have a place where we can use a Composition Root using Dependency Injection it would make our lives much easier. Service Locator is more of anti-pattern now as far as I'm concerned. We are using DI containers in ASP.Net MVC and would love to use this same pattern within Windows Workflow.

ronjacobs Jul 14, 2011 at 5:28 PM 
@CraigStuntz - What if we created an acceptance test for this requirement? A test that shows how your idea can be done with the feature so completely you would be thrilled. I'm updating the spec with this.

CraigStuntz Jul 13, 2011 at 8:48 PM 
"Do you have any ideas about how I can make this better?" Yes, absolutely! If WF would support common DI containers out of the box, then I could focus on the specifics of my business problem and spend less time wondering how to hook this stuff up. E.g., I know conceptually what creating a custom WorkflowServiceHostFactory means, but would have to spend time with the docs to get one running in my actual service.

One way to do this would be via the http://commonservicelocator.codeplex.com/ project. What if I were given a single point in which to configure a DI container -- e.g., an event I could hook on the service -- which would let me configure a DI container, in the usual way for that container, say, Autofac (see http://code.google.com/p/autofac/wiki/ComponentCreation ). Or MEF. Whatever the user prefers. This is something I already know how to do. The key is that I should be able to ask the workflow context for an instance registered via Autofac/MEF/NInject/etc. without having to know anything else. So what if ActivityContext.GetExtension would ask the Common Service Locator for an implementation when it didn't already find one in the extensions collection? And what if it were correctly scoped for a WF service? Then the end user could continue using context.GetExtension, only they could set it up via whatever DI container they already use instead of having to write behaviors/hosts.

ronjacobs Jul 13, 2011 at 8:26 PM 
In order to add your extensions in statically checked code you have another option which is to create a custom WorkflowServiceHostFactory. This allows you to create the host and configure it. The downside is that you have to provide an SVC file or use config based activation to use your factory.

Do you have any ideas about how I can make this better?

CraigStuntz Jul 13, 2011 at 7:37 PM 
Well, that's an improvement over what we do today (write our own behavior) but would prefer a statically-typed configuration, and moving away from the service locator pattern. E.g., the "Composition Root" pattern. See Mark Seeman's book "Dependency Injection in .NET" for a longer explanation. I'd like to define my extensions in statically checked code, rather than a config file, and retrieve them with statically-typed properties, instead of getting runtime errors if I ask for the wrong type.