Sep 15 2010

Dependency injection options for Windows Workflow 4

Category: .NetRory Primrose @ 11:42

I’m a fan of Dependency Injection for all the benefits that it brings. Unfortunately dependency injection is not really supported with Windows Workflow.

Dependency Injection is a pattern in which dependencies are calculated outside an entity and provided to the entity for it to use. The Dependency Injection container is responsible for creating and managing these dependencies and injecting them onto the entity.

WF does not fully support this model. Any dependencies calculated outside a workflow must be provided to the workflow execution engine as a dictionary of input parameters. A Dependency Injection container can be used resolve the dependencies, however providing them to the workflow via the workflow engine is a manual process. This means that constructor, property and method injection are not supported as you cannot use a container to resolve or build up a workflow instance.

There are two ways that you can get dependencies to be available on a workflow. These are via arguments or via a custom extension/activity implementation.

There are a few considerations that dependency support in workflow should cater for. These are the amount of plumbing code required, validation of dependencies and support for persistence.

Dependencies via workflow arguments (the almost Dependency Injection pattern)

Using workflow arguments to provide dependencies is similar to property injection. Using handler classes is helpful for abstracting the workflows from callers of the assembly. Dependency Injection is used to create the handler with required dependencies. The handler then provides these dependencies to the workflow by manually pushing them through to the workflow execution as input parameters when a handler method is invoked. This produces decent amounts of manual code that pollutes the handler classes.

Workflows need to manually validate the arguments it has been provided. The workflow can’t make any assumptions about what has been provided to it so manual guard checks need to be added to the workflow. This is then further implementation in the workflow that requires testing.

Workflow arguments may not be serializable. The workflow isn’t responsible for creating the arguments and can’t guarantee that the arguments are serializable. Persisting a workflow to durable storage (SQL Server for example) would only be supported if all the dependencies were serializable. An exception would be thrown if a non-serializable argument was provided and the workflow was persisted and unloaded.

Pros

Cons

  • Workflow invocation code is messy
  • Explicit dependency validation required
  • Persistence support not guaranteed

Dependencies via custom extension/activity (the Service Locator pattern)

Using a custom extension or activity is more aligned with the service locator (anti-) pattern than dependency injection. A Dependency Injection container should still be used in this method to resolve the dependencies. This is because the Dependency Injection containers have the logic to create complex dependencies and typically have some aspects of instance lifetime management.

The first benefit of this method is that there is no plumbing code to provide these dependencies to the workflow engine. The dependencies will be requested by an activity inside the workflow execution and be resolved at that point (hence a service locator pattern).

Dependencies resolved inside the workflow execution will not require validation of the dependencies. A Dependency Injection container would normally throw an exception if a dependency can’t be resolved. If this is not the case then the custom logic involved can provide that validation.

Serialization of dependencies is still an issue with this method. The custom implementation can cater for this however and will be covered in the next post.

Any custom implementation in WF that uses this method should leverage a custom extension even if a custom activity is also used to provide the dependency to the parent workflow. This will allow the same dependency resolution logic to be available from any activity or extension running in the workflow execution. Using an extension is also a good way to abstract the Dependency Injection container from any custom activities that request a dependency.

Pros

  • Workflow invocation is clean
  • Implicit dependency validation

Cons

  • Slightly more coupled to Dependency Injection container
  • Persistence support not guaranteed, although can be fully supported

Conclusion

Most of my workflow experience has used workflow arguments to provide dependencies. I tend to be a purist so I prefer a true Dependency Injection implementation over a Service Locator implementation. I am however finding the advantages of using a custom extension/activity to resolve dependencies in WF4 far outweigh the disadvantages.

Persistence is the main disadvantage with the custom extension/activity method outlined above. The next post will provide an implementation that fully caters for persistence.

Tags: ,

Comments (4) -

3.
Andrew Heys Andrew Heys United Kingdom says:

Great set of articles, interesting stuff!

I just wanted to ask you to elaborate on your "Persistence support not guaranteed, although can be fully supported" if you have a minute?

I'm not too worried about coupling to Dependency Injection container but am interested how support for persistence can be achieved for custom extensions.

I haven't made a decision on approach one way or the other yet, need to understand a little more first!

Appreciate this is against your thinking so any pointers would be useful;)

Thx - Andy

BTW do you have a rough ETA for v1.1 of your toolbox?

4.
Rory Primrose Rory Primrose Australia says:

Hi Andy,

The set of posts in this series (www.neovolve.com/.../...solutione28093Wrap-up.aspx) provide the detail about how to support persistence for Dependency Injection in WF4. The most important post in the series to look at for persistence support is www.neovolve.com/.../...esolutione28093Part-2.aspx.

Persistence in this implementation is achieved by WF4 automatically serializing the set of InstanceHandler<T> class instances stored against the custom activity. This class defines the definition for an injected item. When WF4 persists the workflow, the custom activity will dispose any Dependency Injection instances and the workflow is serialized and persisted. The next time a Dependency Injection instance is referenced via the custom activity, the definition of the Dependency Injection instance is used to load the instance from the container again.

With respect to my comment "Persistence support not guaranteed, although can be fully supported", I think the point I was trying to make is that you need to be careful about how you handle a persistence scenario with Dependency Injection + WF. If a custom extension is the only place that understands your Dependency Injection instances then there is more work for the extension developer as persistence is a custom dev task in the extension. In my implementation, the understanding of the Dependency Injection instances (or more importantly, definitions) is stored against the custom activity. Storing the Dependency Injection information in the custom activity has the significant benefit that serialization of this information is automatic via WF4.

Does this answer your question?

As far as the toolkit goes, there are a few changes I would like to make but there are no show stoppers that I am aware of. I haven't had any feedback one way or the other. I hope to release it sometime in the next couple of weeks.

5.
Andrew Heys Andrew Heys United Kingdom says:

Hi Rory,

I clearly hadn't clicked 'Notify me...' and had not idea you had respondedFrown

Thanks for your response, I understand where you are coming from..

BTW, just downloaded 1.1 beta and am getting the following, any ideas?

Thx!


Error  1  XC1020: Build error occurred in the XAML MSBuild task: 'Could not load file or assembly 'file:///C:\Projects\Main\Source\Integration\bin\Neovolve.Toolkit.1.1\Neovolve.Toolkit.Contracts.dll' or one of its dependencies. Strong name signature could not be verified.  The assembly may have been tampered with, or it was delay signed but not fully signed with the correct private key. (Exception from HRESULT: 0x80131045)'  

6.
Rory Primrose Rory Primrose Australia says:

Hi Andrew,

Were you trying to load the contract assembly directly? Projects should not reference the contract assemblies. This should only be down indirectly via the Code Contract integration of Visual Studio (assuming it is installed).

Pingbacks and trackbacks (2)+

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading