Part II of How To Screw Up HandleExternalEvent Activities in Windows Workflow

Sunday, June 4, 2006

We left off in Part I with a contract, an event args class, and all the metadata we need to raise events to a workflow. As Captain Hazelwood once said, what could possibly go wrong?

Let's start with an implementation of our payment processing contract. The code has a subtle problem, and will cause an exception.

class PaymentProcessingService : IPaymentProcessingService
{
    
public void ProcessPayment(Guid id, double amount)
    {
        
// ... do some work work work
        
        
// ... then raise an event to let everyone know
        PaymentProcessedEventArgs args;
        args =
new PaymentProcessedEventArgs(id, amount);  
      
        
EventHandler<PaymentProcessedEventArgs> evh;
        evh = PaymentProcessed;
        
if (evh != null)
            evh(
this,  args); // boom!
    }

    
public event EventHandler<PaymentProcessedEventArgs>
        PaymentProcessed;
}

The exception is an EventDeliveryFailedException, and the Message property will read like "Event PaymentProcessed on interface type IPaymentProcessingService for instance ID [GUID] cannot be delivered". The message doesn't yield any obvious clues, and we need to dig deeper to find more information.

If we look at the InnerException property, we get closer to an answer. The inner exception is an InvalidOperationException with the Message of "EventArgs not serializable". This exception is a little confusing, because we did make our EventArgs serializable! Notice in the dialog below, the current exception ($exception in the 2005 debugger) is wrapping yet another inner exception, with the precise cause of failure.

Debugger watch window on serialization exception

The value of the message is curt off, but says "Type PaymentProcessingService is not marked as serializable". It appears every parameter going into the event must be serializable, including the sender parameter. We pass a this reference, which points to our payment processing service. The workflow instance doesn't actually need a reference to our payment service (if it needs to invoke a method on the service, it can use the CallExternalEventActivity), so we can fix this problem by leaving the sender parameter as null or Nothing.

EventHandler<PaymentProcessedEventArgs> evh;
evh = PaymentProcessed;
if (evh != null)
    evh(
null,  args);

If you are seeing an event delivery failure, drill into the inner exceptions to find the exact type causing the problem. You event args might contain an object graph with an non-serializable type inside.

Setting Up The Workflow Runtime

I did jump ahead just a bit, because before the event will even throw an exception we need to configure the workflow runtime to handle events from our service. We do this by layering the ExternalDataExchangeService into the workflow runtime. The ExternalDataExchangeService manages the host's local communication services, like our payment processing service.

WorkflowRuntime workflowRuntime = new WorkflowRuntime();

ExternalDataExchangeService dataExchangeService;
dataExchangeService =
new ExternalDataExchangeService();
workflowRuntime.AddService(dataExchangeService);

PaymentProcessingService paymentProcessing;
paymentProcessing =
new PaymentProcessingService();
dataExchangeService.AddService(paymentProcessing);

// ...

Here is a version of the above code with a bug that took me quite some time to track down.

PaymentProcessingService paymentProcessing;
paymentProcessing =
new PaymentProcessingService();
workflowRuntime.AddService(paymentProcessing);
// this is WRONG!

We have to add our service to the ExternalDataExchangeService, and not directly to the workflow runtime. My service would fire an event, but nothing would happen. Looking at the event in the debugger showed a null value, meaning nobody was subscribing to the event. It's the ExternalDataExchangeService that reflects on the incoming service, looking for metadata like the ExternalDataExchange attribute, and subscribing to events.

Summary

Raising events to a workflow can be finicky. If the events appear to fire into the empty vacuum of space, make sure the ExternalDataExchanceService and the local communications services are properly configured and added to the runtime. If the workflow runtime is throwing exceptions, the exception will most likely wrap the juicy details in its InnerException property (which in turn might have an InnerException). Hopefully, these two tips can save someone some time.


Comments
Howard Monday, July 24, 2006
Thanks for these notes, I suspect this may be the cause of some problems I had back in May with beta2. I've scrapped that test and am re-writing it from scratch for .NET 3.0 CTP
Rafael L Wednesday, September 6, 2006
Does will have part III? I suggest to write how to get/set values in properties on invoke method of HandleExternalEventAcitivity.
Eva Tuesday, December 5, 2006
Thank you very much, Scott,

I've had a difficult time understanding why my event handlers were null, until I read your comment about attaching the service process to the ExternalDataExchangeService.

Your article saved me a huge amount of time.
:-)

Thanks,

Eva
gravatar deepu Friday, March 26, 2010
hi,

I have created the sample state machine workflow in asp.net,
with three states. the first state include handle external activity,code activity and call external activity,
and the second state has handle external event.
when start the workflow, it works fine at the initial state.
when i raise a event using the handle external event activity in second state,the event has the value null..
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!