If you've read Bruce Bukavics's "Pro WF - Windows Workflow in .NET 3.0", you may have seen some code for a Workflow Runtime Manager that wraps common steps to managing your Workflow Runtime such as launching workflows, adding services, retrieving output parameters and capturing exceptions from a workflow instance.
I expanded upon Bruce's implementation in two simple yet useful ways. First, I created an interface from it. I know, that's revolutionary! and nobody else would have ever though of that! *cough* *cough* Anyway, that's not the point of this post. I just needed to state that because I'm about to show you a clipping of the interface derived from Bruce's implementation.
public interface IWorkflowRuntimeManager
{
...
WorkflowInstanceWrapper StartWorkflow(Type workflowType, Dictionary<string, object> parameters);
...
}The problem with this is that I still have to set up that dictionary of parameters. And what if I want to launch this workflow from multiple locations in my code? I have to reinvent the wheel each time. And how do I ensure that everyone is supplying the correct set of input parameters and they're named the same? That's easy! With the addition of another interface.
using System;
using System.Collections.Generic;
namespace Ipc.Framework.Workflow.Hosting
{
/// <summary>
/// Provides an interface for starting a workflow using
/// <see cref="IWorkflowRuntimeManager"/>
/// </summary>
public interface IWorkflowStartInfo
{
/// <summary>
/// The type of workflow that the <see cref="IWorkflowRuntimeManager"/>
/// implementation should create and launch.
/// </summary>
Type WorkflowType { get; }
/// <summary>
/// The dictionary of parameters to feed to the new workflow instance.
/// </summary>
Dictionary<string, object> Parameters { get; }
}
}This interface basically describes how to build a key to start the car. I simply write a class that implements this interface for each workflow defined in my project. See an example implementation below.
public class HelpDeskCaseWorkflowStartInfo : IWorkflowStartInfo
{
private const string PARAM_CASEID = "CaseId";
private readonly Dictionary<string, object> _parameters;
public HelpDeskCaseWorkflowStartInfo(int caseId)
{
_parameters = new Dictionary<string, object>();
_parameters.Add(PARAM_CASEID, caseId);
}
#region IWorkflowStartInfo Members
public Type WorkflowType
{
get { return typeof (HelpDeskCaseWorkflow); }
}
public Dictionary<string, object> Parameters
{
get { return _parameters; }
}
#endregion
}And for the final step, let's expand the IWorkflowRuntimeManager interface as shown below. The two functions accomplish the same task, but the second method allows compile-time binding of parameters wherever it is used (you just have to do the work to make sure that the parameter names you use in the implementation are correct).
public interface IWorkflowRuntimeManager
{
...
WorkflowInstanceWrapper StartWorkflow(Type workflowType, Dictionary<string, object> parameters);
WorkflowInstanceWrapper StartWorkflow(IWorkflowStartInfo startInfo);
...
}
We therefore needed to make sure that we had as much code coverage as possible. Code coverage is simply the lines of code actually executed during the unit tests. Now 100% code coverage, while it means that every single line of code has been executed, does not by any means mean your code is bug-free. Code coverage does not take into account code you didn't write. For example if I have a function Add(int a, int b) that returns an integer and my method just does 'return a+b;', I've got a huge bug if a+b > Int.MaxValue. I could have had 100% code coverage and still missed that bug.