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

In my previous post I created the outline for the website and introduced Unity as the dependency injection framework.  In this post I’m going to design the actual workflow and show how to manage the persistence of the workflow as well as the domain objects.

I’m going to create an event-driven state-machine workflow.  This means the workflow will be driven by external events (the user clicking buttons on the website) and will be persisted to the database while waiting for further user input.

At this point it might be worth taking a step back and try to see how all of this will fit together.  Workflow Foundation (WF) allows us to persist information with a workflow instance – should we not simply add the bug object to the workflow instance and allow WF to manage all persistence for us?  Do we actually need a ‘Bugs’ table in the database?

While we could do away with having a table in the database, I wouldn’t recommend it.  We’re still going to be accessing bugs from a frontend (website), and I want to be able to select all bugs without using the workflow runtime.  This means the workflow will save our objects into the database and a bug object will have state which the workflow will have to update.  The bug object will also have a reference to a workflow instance.

Designing the Workflow

Before getting to the details of hosting and running the workflows, I’m going to create the workflow for Bug-tracking.

WorkflowDesign

Nothing major so far, I’ve simply defined the different states for the lifetime of a bug as well as the initial and final states.  Now I’m going to add the different transitions between these states.  To do this I first need to define an ExternalDataExchange interface.  This is simply an interface that contains events which will be raised in code to indicate to the workflow engine that an external event has occurred.  Later on we will provide an instance of this interface to the workflow runtime and WF will attach event handlers to all these events.

[ExternalDataExchange]
public interface IBugTrackingService
{
    event EventHandler<BugTrackingEventArgs> Opened;
    event EventHandler<BugTrackingEventArgs> Resolved;
    event EventHandler<BugTrackingEventArgs> Closed;
    event EventHandler<BugTrackingEventArgs> Deferred;
    event EventHandler<BugTrackingEventArgs> Assigned;
}
[Serializable]
public class BugTrackingEventArgs : ExternalDataEventArgs
{
    public Bug Bug { get; set; }

    public BugTrackingEventArgs(Guid instanceId, Bug bug) : base(instanceId)
    {
        Bug = bug;
    }
}

This is actually all we need.  Now we simply tell the workflow that this is the interface we’re using for raising external events and define which transitions are valid for the different states.

InitialStateEvents

HandleOpenedFromInitialEvent Here I’ve defined the event and state transition for the initial state (the other states are very similar).  Note that I haven’t added an activity to update and persist the bug to the database.  I will need to do this with a custom activity.

Create a Custom Activity for Persistence

I initially tried manually persisting the Bug object and running the workflow to perform the transition inside a transaction.  While this would work, this approach forces us to manually perform some of the workflow steps outside of the actual workflow (such as updating the state of the bug) and we wouldn’t really get the maximum benefit from using workflows.

What I really want is for the workflow to update my bug and handle all the persistence.  Keep in mind this includes persisting the bug object as well as the state of the workflow.  WF handles the persistence of the workflow instance and we need to plug the BugRepository into WF for persisting the bug object.

The custom activity is going to perform 2 tasks – updating the state of the bug and persisting the bug object.  This means we need to pass both the state and bug object to the custom activity.  We do this with dependency properties.  (If you find this part confusing take a look at Scott Allen’s article)

public static DependencyProperty BugProperty = DependencyProperty.Register("Bug", typeof(Bug), typeof(UpdateBugActivity));
public Bug Bug
{
    get
    {
        return (Bug) GetValue(BugProperty);
    }
    set
    {
        SetValue(BugProperty, value);
    }
}

public static DependencyProperty StateProperty = DependencyProperty.Register("State", typeof(string), typeof(UpdateBugActivity));
public string State
{
    get
    {
        return (string)GetValue(StateProperty);
    }
    set
    {
        SetValue(StateProperty, value);
    }
}

These 2 properties will be set from the workflow designer.  We will connect the Bug property to the object being passed in the BugTrackingEventArgs and the State property to the target state in the SetState activity.

We first need to save the BugTrackingEventArgs being passed by the event as a property inside our workflow instance.  Once you know how to do this it’s actually quite easy.  First, go to the properties tab on the HandleExternalEvent activity.

HandleExternalEventActivityProperties

We need to bind the event to a property in the workflow which we can then use in other activities.

BindingEventArgsToProperty

If you did it right you should have code inside your workflow that looks like this:

public partial class Workflow : StateMachineWorkflowActivity
{
    public BugTrackingEventArgs BugTrackingEventArgs = default(BugTrackingEventArgs);
}

Now we simply need to bind this property to the Bug argument on our custom activity.  Binding the State argument on our custom activity is a little different.  We could simply specify the required state value here, but a better idea is to bind to the target state in the SetState activity.  This way we are not duplicating the target state – we could change a transition to move to a different target state and we would only need to update the SetState activity.

BindStateProperty

I have now added the custom activity to the workflow and performed the necessary binding to the 2 dependency properties.  Let’s look at the actual code for updating and persisting the bug.

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
    if (Bug != null)
    {
        Bug.State = (BugState) Enum.Parse(typeof(BugState), State.Replace("State", ""));
        Bug.WorkflowInstanceId = WorkflowInstanceId;

        var txnService = executionContext.GetService<IBugTransactionService>();
        txnService.UpdateBug(Bug);
    }

    return ActivityExecutionStatus.Closed;
}

Transaction Service

The BugTransactionService is necessary to allow us to persist the bug in the same transaction as the workflow instance.  This transaction service will simply delegate persistence to our BugRepository class.  We will need to register an instance of this class with the workflow runtime.

public void Commit(Transaction transaction, ICollection items)
{
    foreach (Bug bug in items)
    {
        if (bug.Id == 0)
        {
            bugRepository.AddBug(bug);
        }
        else
        {
            bugRepository.UpdateBug(bug);
        }
    }
}

The only downside here is that we need to enable MSDTC.  This is because we can actually store the workflow instances in a different database to the database containing the Bugs table.  To enable MSDTC you simply need to start the DTC service.

In the next post I’m going to put it all together.  Hopefully you’ll be able to see the benefit of using workflows as well as a dependency injection framework like Unity.  I will publish all the code at the end of this series.