Building a Bug-tracking website with Windows Workflow Foundation – Part 3

In my previous post I designed the Bug-tracking workflow and created a custom activity for updating and persisting bugs.  In this post I’m going to try and put it all together.

How the Workflow integrates with the Website

It might be useful at this stage to discuss how the website will integrate with WF.  Since I’m using MVC my controllers usually depend upon a service layer.  This service layer in turn then depends upon the repository layer for loading and persisting data.  The service layer is useful for filtering data (loading the bugs for a specific user, for example) or validating data before saving it.  I would like to maintain this structure so I’m going to modify the BugService to depend on a workflow service (in addition to the BugRepository) for performing any workflow-related activities.

Let’s take a look at how we actually host and start the WF runtime.

Hosting the Workflow Foundation Runtime

Configuring and starting the WF runtime is a relatively simple exercise.  Again, we can do this configuration either from code or in a config file.  Since we will need to specify a connection string for workflow persistence (which is sure to change) I used a config file.

private readonly IUnityContainer container;
private WorkflowRuntime WorkflowRuntime { get; set; }

public WorkflowService(IUnityContainer container)
{
    this.container = container;

    WorkflowRuntime = new WorkflowRuntime("workflowConfiguration");

    WorkflowRuntime.WorkflowCompleted += WorkflowCompleted;
    WorkflowRuntime.WorkflowTerminated += WorkflowTerminated;
    WorkflowRuntime.WorkflowIdled += WorkflowIdled;

    AddServices();

    WorkflowRuntime.StartRuntime();
}

Here is the tricky part – we need to maintain a single instance of this runtime.  We also need to register the BugTrackingService and BugTransactionService with this single instance and be able to get a handle on the registered BugTrackingService to be able to raise the events when necessary.  Let’s first take a look at how we actually register these services with the runtime.  (Note that this is NOT the ideal way to do it)

private void AddServices()
{
    var dataExchangeService = new ExternalDataExchangeService();
    dataExchangeService.AddService(new BugTrackingService());

    WorkflowRuntime.AddService(dataExchangeService);
    WorkflowRuntime.AddService(new BugTransactionService());
}

While this approach would work, it breaks down on several fronts.  Firstly, we need a reference to the BugTrackingService and I would like to keep the Workflow service generic – it doesn’t need to know about the BugTrackingService or BugTransactionService.  This is where Unity comes into play.

Using Unity to configure the Workflow Foundation Runtime

I’m going to use Unity to register all the data exchange and transaction services I require and then add any registered service to the runtime.  We do the registration part in the Bootstrapper.

var bugTrackingService = new BugTrackingService();
container.RegisterInstance<IBugTrackingService>(bugTrackingService);
container.RegisterInstance<IExternalDataExchange>(typeof(BugTrackingService).FullName, bugTrackingService);
container.RegisterType<IPendingWork, BugTransactionService>(typeof(BugTransactionService).FullName, new ContainerControlledLifetimeManager());

container.RegisterType<IWorkflowService, WorkflowService>(new ContainerControlledLifetimeManager());

There are 2 things to note here.  Firstly, I am registering the BugTrackingService twice – once named and once unnamed.  This is because the ResolveAll method will only return named registrations – this is the method we will use when retrieving registered services in the runtime initialization.  Secondly, the ContainerControllerLifetimeManager is the correct way to specify that a registered class is a singleton.  (Unfortunately I cannot use this syntax for registering the BugTrackingService – the named and unnamed registrations will return different singleton instances.)

Now that the services are registered I can add all registered services to the runtime.

private void AddServices()
{
    var dataExchangeService = new ExternalDataExchangeService();
    WorkflowRuntime.AddService(dataExchangeService);

    foreach (var externalDataExchange in container.ResolveAll<IExternalDataExchange>())
    {
        dataExchangeService.AddService(externalDataExchange);
    }

    foreach (var pendingWorkService in container.ResolveAll<IPendingWork>())
    {
        WorkflowRuntime.AddService(pendingWorkService);
    }
}

Notice that the WorkflowService is now dependant on the Unity container itself – this is why I registered the container with itself in the first part of this series.

The Bug Service

As I mentioned, I’m going to implement the BugService to depend on the BugRepository as well as the WorkflowService (which is now a generic service for managing all workflow tasks).

public class BugService : IBugService
{
    private readonly IBugRepository bugRepository;
    private readonly IBugTrackingService bugTrackingService;
    private readonly IWorkflowService workflowService;

    public BugService(IBugRepository bugRepository, IBugTrackingService bugTrackingService, IWorkflowService workflowService)
    {
        this.bugRepository = bugRepository;
        this.bugTrackingService = bugTrackingService;
        this.workflowService = workflowService;
    }

    public IQueryable<Bug> GetBugs()
    {
        return bugRepository.GetBugs();
    }

    public void UpdateBug(Bug bug)
    {
        bugRepository.UpdateBug(bug);
    }

    public void AddBug(Bug bug)
    {
        var workflowId = workflowService.StartWorkflow(typeof(Workflow), WorkflowStatus.Running);

        bug.WorkflowInstanceId = workflowId;
        bugTrackingService.BugOpened(bug);

        workflowService.RunWorkflow(bug.WorkflowInstanceId, WorkflowStatus.Running);
    }

    public void CloseBug(Bug bug)
    {
        bugTrackingService.BugClosed(bug);
        workflowService.RunWorkflow(bug.WorkflowInstanceId, WorkflowStatus.Completed);
    }

    public BugState GetPossibleTransitions(Bug bug)
    {
        var possibleTransitions = BugState.None;
        foreach (var state in workflowService.GetStateMachineTransitions(bug.WorkflowInstanceId))
        {
           possibleTransitions |= (BugState)Enum.Parse(typeof(BugState), state.Replace("State", ""));
        }

        return possibleTransitions;
    }
}

Hopefully you should be able to see how the website will interact with the workflow (I have left out some of the transitions).

There are 2 methods that stand out here.  Firstly, the UpdateBug method allows us to update a bug without performing a workflow transition – this is useful when we change the details of a bug (such as the assigned user) without changing the state.  The second is GetPossibleTransitions – this avoids any hardcoding of possible transitions in the website.

Summary

I have skimmed over several of the details in running the workflow and retrieving the results.  Most of these concepts are expertly covered by Scott Allen and I felt no need to mention them.  I have simply tried to decrease coupling, improve the testability and improve the design to accommodate multiple workflows.

If you have any questions or comments either leave a comment below.