OdeToCode IC Logo

Part I of “How To Screw Up HandleExternalEvent Activities in Windows Workflow”

Sunday, June 4, 2006

In learning Windows Workflow with Beta 2.2, I’ve managed to foul up events in every possible way. Well, perhaps not every possible way, I’m sure I’ll find a few more techniques as time goes on. Here are some pointers for anyone else who runs into problems raising events with WF.

First, it helps to have a firm grasp of how events reach a workflow. Events are one of the mechanisms we can use to communicate with a workflow instance. An event can tell a workflow that something interesting has finally happened, like when a check arrives to pay the balance of a past due account.

Unlike the events we use in Windows Forms and ASP.NET, workflow events don’t travel directly from us to the event listener (in this case a workflow instance). A workflow instance lives inside the motherly embrace of the workflow runtime, and we have to follow a protocol before the runtime will let the workflow instance come out and play. This is partly because the workflow instance we want to notify might be sleeping in database table. We can’t have a workflow in memory for three months waiting for an account to close.

The first part of the protocol is defining a contract to describe the events and types involved.

[ExternalDataExchange]
public interface IPaymentProcessingService
{
    
event EventHandler<PaymentProcessedEventArgs> PaymentProcessed;
}

[
Serializable]
public class PaymentProcessedEventArgs : ExternalDataEventArgs
{
    
public PaymentProcessedEventArgs(Guid instanceId, double amount)
        :
base(instanceId)
    {
        _amount = amount;
    }

    
private double _amount;
    
public double Amount
    {
        
get { return _amount; }
        
set { _amount = value; }
    }   
}

Here we’ve defined an interface containing the event we want to raise, and the event arguments. The following features are important to note:

  • We’ve decorated the interface with the ExternalDataExchangeAttribute
  • The event args class derives from ExternalDataEventArgs
  • The event args class is serializable.

When we register our service implementation with WF, the runtime will look for interfaces with the ExternalDataExchangeAttribute, and throw an exception if it does not find any (InvalidOperationException: Service does not implement an interface with the ExternalDataExchange attribute). WF uses the attribute to know when to create proxy listeners for the events in a service. The proxies can catch events and then route them to the correct workflow instance, possibly re-hydrating the instance after a long slumber inside a database table.

If the event args class does not derive from ExternalDataEventArgs, we will see an error during compilation. Activities have the ability to validate themselves and ensure we’ve set all the properties they need to function correctly at runtime. We use the HandleExternalEventActivity activity to listen for events in a workflow. We need to specify the interface and event name that the activity will listen for. If we don’t derive from the correct class, the error will read: “validation failed: The event PaymentProcessed has to be of type EventHandler where T derives from ExternalDataEventArgs“ (I think they meant to say “of type EventHandler”).

Finally, everything passing through the event has to be serializable. If not, we’ll see runtime exceptions. We’ll look at this exception (and others) in part II.