OdeToCode IC Logo

More on Managing Windows Workflow Events in ASP.NET

Wednesday, January 24, 2007

In the last post I pointed out problems you can experience trying to handle workflow events in an ASP.NET page. Before we get to a working solution, let's take a look at another pitfall.

We know the default WF scheduling service will select a thread from the CLR thread pool to execute workflows asynchronously. Generally speaking, this isn't a good approach for server side applications because we can tie up too many threads. Instead, we use the ManualWorkflowSchedulerService. The manual scheduler lets us run workflows on the same thread that is processing the HTTP request. We just need to call RunWorkflow on the service whenever we need a workflow to execute.

It's tempting to think the manual scheduler can make life easier since we don't have to worry about threads. For instance, let's suppose we want any unhandled faults originating from inside a workflow to bring the current request to a screeching halt. We want unhandled exceptions! When a workflow faults and throws an exception, WF will catch the exception and raise a WorkflowTerminated event. This event will fire on the same thread as the request that ran the faulty workflow.

Knowing that we want to end requests with an error, we could try to use global.asax like the following. After all, if we throw an exception on the current request thread, we should create the yellow screen of death (assuming the Page doesn't have an exception handler). Note: I'm using global.asax just to make this simple.

void Application_Start(object sender, EventArgs e)
{
    _workflowRuntime =
new WorkflowRuntime("wfConfiguration");

    _workflowRuntime.WorkflowTerminated +=
        
new EventHandler<WorkflowTerminatedEventArgs>
            (_workflowRuntime_WorkflowTerminated);

    _workflowRuntime.StartRuntime();

}
  
void _workflowRuntime_WorkflowTerminated
        (
object sender, WorkflowTerminatedEventArgs e)
{
    
throw e.Exception;        
}
        
private static WorkflowRuntime _workflowRuntime;

The problem is that the WorkflowRuntime is dedicated to its job. The runtime is tasked with telling all the event subscribers that a workflow terminated, and it is not going to let some unhandled exception prevent the rest of the subscribers from missing events. The WF runtime swallows our exception. See Ken Getz's article for how to use GetInvocationList to achieve this behavior.

How can we communicate the exception back to the Page? Consider the following code.

void _workflowRuntime_WorkflowTerminated
        (
object sender, WorkflowTerminatedEventArgs e)
{
    
HttpContext.Current.Items["WorkflowResult"] = e.Exception;
}

Now the web form can pull the exception out of the HTTP request context. Problem solved!

Oh, but wait - we are spiraling out of control. Now the ASP.NET developer has to remember to start workflows with the manual scheduler, and check the request context for exceptions, and use such and such a communication service to raise events to the workflow.

If you are going to the trouble to use Windows Workflow in an ASP.NET application, then you have an architectural responsibility to abstract away these problematic and sometimes dangerous scenarios to the point that the ASP.NET developer doesn't even know there is a workflow driving the process. Bring a mediator to the party.