Dependency Injection with Windows Workflow Foundation 4 Introduction

Windows Workflow Foundation 4 offers a comprehensive set of tools to design, create and host your own workflows in your application. Normally when working with WF4 the only way to get your external dependencies into the workflow is by passing the dependencies as arguments to the workflow host such as WorkflowApplication, WorkflowInvoker or WorkflowServiceHost.

This might look like the following code illustrated (using ninject):


public sealed class Divide : CodeActivity
{
    [RequiredArgument]
    public InArgument<int> Dividend { get; set; }

    [RequiredArgument]
    public InArgument<int> Divisor { get; set; }

    [RequiredArgument]
    public InArgument<ICalculator> Calculator { get; set; }

    public OutArgument<int> Remainder { get; set; }
    public OutArgument<int> Result { get; set; }

    protected override void Execute(CodeActivityContext context)
    {
        var calculator = this.Calculator.Get(context);

        int quotient = calculator.Divide(Dividend.Get(context),Divisor.Get(context));
        int remainder = calculator.Reminder(Dividend.Get(context), Divisor.Get(context));

        Result.Set(context, quotient);
        Remainder.Set(context, remainder);
    }
}

var calculator = this.Kernel.Get<ICalculator>();
int dividend = 500;
int divisor = 36;

var inputs = new Dictionary<string, object>();
inputs.Add("Dividend", dividend);
inputs.Add("Divisor", divisor);
inputs.Add("Calculator", calculator);

WorkflowApplication wfApp = new WorkflowApplication(new Divide(), inputs);
wfApp.Run();

The same would be true for workflows executed in the WorkflowInvoker. For complex workflows this can become really messy and the dependencies would clutter the actual code which passes the workflow related input data into the host. But what if you would like to test drive your own activities in code and then build your whole workflow using code first or designer first approach? I will come to that later, keep this thought in mind!

To get a bit deeper into the problematic of DI with Windows Workflow Foundation 4 we must quickly look into the differences between the WorkflowApplication and the WorkflowInvoker:

Using WorkflowApplication you can perform the following tasks:

  1. Create a new workflow instance, or load a workflow instance from an instance store.
  2. Provide extensions to be used by activities within a workflow instance.
  3. Control the execution of a workflow instance.
  4. Resume a bookmark created by an activity within a workflow instance.
  5. Persist or unload a workflow instance.
  6. Be notified of workflow instance lifecycle events.

WorkflowInvoker contains both instance and static methods for invoking workflows synchronously, and instance methods for invoking workflows asynchronously.

WorkflowInvoker does not allow instance control such as persisting, unloading, or resuming bookmarks. If instance control is desired, use WorkflowApplication instead

Both the WorkflowApplication and the WorkflowInvoker offer an WorkflowExtensionManager which allows to register extensions which can be used by activities within a workflow instance. Extensions can for example be used to wait for a certain event and on a certain event resume the bookmark of an idle workflow. Normally the pattern of defining and adding extensions looks like the following:

// when inheriting from NativeActivity
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
	// Tell the runtime that we need this extension
	metadata.RequireExtension(typeof (SomeExtension));

	// Provide a Func<T> to create the extension if it does not already exist

	metadata.AddDefaultExtensionProvider(() => new SomeExtension());
}

protected override void Execute(NativeActivityContext context)
{
	// Get (which may create) the extension
	var extension = context.GetExtension<SomeExtension>();

	// do something on the extension
	extension.Do(/*whatever*/);
}

// or directly on Invoker or Application.

WorkflowApplication wfApp = new WorkflowApplication(new Divide(), inputs);
wfApp.Extensions.Add(new SomeExtension()); /* Registered as singleton */
wfApp.Extensions.Add(() => new SomeExtension()); /* Registered as transient */
wfApp.Run();

What happens if the extension itself had dependencies for example on configuration data or whatever? With the default approach we are quickly screwed because we have hard wired the creation of SomeExtension.

A final thought I want to give to you before ending this introduction blog post is the following. Imagine you have some kind of infrastructure component which creates for each request a new WorkflowApplication with a complex workflow hosted and runs it. The workflow execution time can vary and the infrastructure component needs to track the created workflow applications and on completion needs to remove them from the tracking collections. How would you unit test this without having to build up a real WorkflowApplication and run it?

I’ll try to ask the raised questions in my next blog post.

About the author

Daniel Marbach

2 comments

By Daniel Marbach

Recent Posts