Download Example Source Code

This is part 2 of WF: Exception Handling with State Machine Workflow.

Solution Sample
In the previous article we discussed issues when handling exception in state machine workflow and specified that we can resolve the issue by using CallExternalMethod activity. Last time, I’ve explained the issue by giving RoseIsRose sample. In this solution, I’ve two project, one is state machine workflow library called RoseIsRoseWorkflow and another one is console application which is host for workflow named RoseIsRoseConsole.

The objective is if any exception happens in the state machine workflow, the workflow should propagate it to host.

For sake of simplicity, the workflow in the library named RoseWorkflow has only one state named “BudState”. This state has only one EventDrivenActivity called “onPluckFlowersEvent”. It has one HandleExternalEvent named “handlePluckFlowersEvent”.

The code for above workflow’s InterfaceType is following:

using System;
using System.Workflow.Activities;

namespace RoseIsRoseWorkflow
{
   [ExternalDataExchange]
   public interface IRoseWorkflowService
   {
       event EventHandler PluckFlowers;
       void DelegateException(Exception ex);
       void RaisePluckFlowersEvent(Guid instanceId);
   }
}

What about the fault handlers and what we need to redesign for propagating exception to host?

In IRoseWorkflowService, I have declared a method “DelegateException” which will be called by “CallExternalMethod” from the workflow. The “RaisePluckFlowersEvent” is used by host to invoke “PluckFlowers” event in IRoseWorkflowService which is actually handled at RoseWorkflow.

Before “callExternalMethodActivity1″, I have used one code handler named “onPluckFlowersEventFaultCodeHandler” for just displaying debug message. The code behind of RoseWorkflow.cs is

namespace RoseIsRoseWorkflow
{
    public sealed partial class RoseWorkflow : StateMachineWorkflowActivity
    {
        private object _sender;
        public object Sender
        {
            get { return _sender; }
            set { _sender = value; }
        }

        private Exception _delegatedException;
        public Exception DelegatedException
        {
            get { return _delegatedException; }
            set { _delegatedException = value; }
        }

        private ExternalDataEventArgs _eventArgs;
        public ExternalDataEventArgs EventArgs
        {
            get { return _eventArgs; }
            set { _eventArgs = value; }
        }

        public RoseWorkflow()
        {
            InitializeComponent();
        }

        private void handlePluckFlowersEvent_Invoked(object sender, ExternalDataEventArgs e)
        {
            _delegatedException = new Exception("In this stage, you cannot pluck flowers");
            throw _delegatedException;
        }

        private void onPluckFlowersEventFaultCodeHandler_ExecuteCode(object sender, EventArgs e)
        {
            FaultHandlerActivity faultHandler = ((Activity)sender).Parent as FaultHandlerActivity;
            Console.WriteLine("Handle exception in workflow itself.  Details: {0}",
                faultHandler.Fault.Message);
        }
    }
}

At the host side, I have implemented part of IRoseWorkflowService in RoseWorkflowService.cs:

using System;
using System.Workflow.Activities;
using RoseIsRoseWorkflow;

namespace RoseIsRoseConsole
{
    [Serializable]
    public class RoseWorkflowService : IRoseWorkflowService
    {
        #region IRoseWorkflowService Members

        public event EventHandler PluckFlowers;
        public Exception DelegatedException;

        public void DelegateException(Exception ex)
        {
            DelegatedException = ex;
        }       

        public void RaisePluckFlowersEvent(Guid instanceId)
        {
            if (PluckFlowers != null)
                PluckFlowers(null, new ExternalDataEventArgs(instanceId));
            while (DelegatedException == null) ;
            Console.WriteLine("Externally handled exception '{0}'.  Details: {1}",
                DelegatedException.GetType().Name, DelegatedException.Message);
            DelegatedException = null;
        }

        #endregion
    }
}

Note that “DelegateException” will be called by workflow. Whenever, it is getting called it assigned the exception details to the public field “DelegatedException”. In the “RaisePluckFlowersEvent” method, once it raised “PluckFlowers” event, the method going on a while until the “DelegatedException” is null. After that, it just handle the exception (either rethrow it to calling client or logged at host place).

The Program.cs is

namespace RoseIsRoseConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
            {
                AutoResetEvent waitHandle = new AutoResetEvent(false);
                workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) {waitHandle.Set();};
                workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
                {
                    Console.WriteLine(e.Exception.Message);
                    waitHandle.Set();
                };

                ExternalDataExchangeService dataExchange = new ExternalDataExchangeService();
                workflowRuntime.AddService(dataExchange);
                RoseWorkflowService roseWorkflowService = new RoseWorkflowService();
                dataExchange.AddService(roseWorkflowService);

                WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(RoseWorkflow));
                instance.Start();               

                roseWorkflowService.PluckFlowers += new EventHandler(roseWorkflowService_PluckFlowers);
                roseWorkflowService.RaisePluckFlowersEvent(instance.InstanceId);
                waitHandle.WaitOne();
            }
        }

        static void roseWorkflowService_PluckFlowers(object sender, ExternalDataEventArgs e)
        {
            Console.WriteLine("Handling PluckFlowers event");
        }
    }
}

The output:

Handling PluckFlowers event
Handle exception in workflow itself.  Details: In this stage, you cannot pluck flowers
Externally handled exception 'Exception'.  Details: In this stage, you cannot pluck flowers

For further reference, download the example project.

Tags Tags:
Categories: .NET
Posted By: udooz
Last Edit: 10 Jul 2008 @ 12 41 PM

EmailPermalinkComments (0)

Every day comes with some new wishes and challenges. Today my team struck up with handling in a state machine workflow. They digged the web for finding a solution, unluck.
Problem: How to rethrow an exception from a state machine workflow to the workflow runtime host?
Description: I have designed one of our product as the service interface layer (WCF) invokes some of the business functionalities through workflow. We can say “workflow enabled business functionalities”. This is a state machine workflow (I don’t find real enterprise level usage of sequential workflow, of course you can use it at UI level). We have instrumented (exception handling and logging) well the non-workflow enabled services and business functionalities. Whenever a request comes from the clients of these services (currently, we have only one ASP.NET client) to perform a business operation, each layer do their responsibilities well. In case of any failure, they respond to the client gently with all necessary details. However, we were not able to handle exception in our “workflow enabled business functionalities”. Laughing…! Initially, I was also felt that we have missed something. After a while, my team found that this is the issue with WF itself. Actually, you can handle exception within the workflow runtime, but how can you respond to your client there is a fault while executing their requested business functionality.

By its asynchronous nature of state machine workflow, even though WF provides rethrow activity, it cannot propagate upto the calling place which is the host of the workflow instance. This is common for any type of host such as Console, ASP.NET, etc.

How to reproduce:

1. Create a state machine workflow (for an example, RoseWorkflow) and add an event driven activity (onPluckFlowersEvent) on the initial state (Bud State).
2. In the onPluckFlowersEvent handling code, forcefully throw an exception (throw new Exception(“You cannot pluck flowers in this state”));. The value of the properties of this external event handler activity are:
Name: handlePluckFlowersEvent
InterfaceType: RoseIsRoseWorkflow.IRoseWorkflowService
EventName: PluckFlowers
e: Activity=RoseWorkflow, Path=EventArgs
sender: Activity=RoseWorkflow, Path=Sender
For both sender and e, I’ve declared two properties named “EventArgs” of type ExternalDataEventArgs and “Sender” of type object.

private void handlePluckFlowersEvent_Invoked(object sender, ExternalDataEventArgs e)
{
    // here _delegatedException is the member of   RoseWorkflow
    _delegatedException = new Exception("In this stage, you cannot pluck flowers");
    throw _delegatedException;
}

3. Add fault handler activity (onPluckFlowersEventFaultHandler) for the event and set its “FaultType” property as “System.Exception”.
4. If need, add normal code activity to handle the exception, like

private void onPluckFlowersEventFaultCodeHandler_ExecuteCode(object sender, EventArgs e)
{
    FaultHandlerActivity faultHandler = ((Activity)sender).Parent as FaultHandlerActivity;
    Console.WriteLine("Handle exception in workflow itself. Details: {0}", faultHandler.Fault.Message);
}

5. Add Throw activity, and set the following properties:
Fault: Activity=onPluckFlowersEventFaultHandler, Path=Fault
FaultType: Activity=onPluckFlowersEventFaultHandler, Path=FaultType
6. Host it into a console:

class Program
{
  static void Main(string[] args)
  {
    using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
    {
      AutoResetEvent waitHandle = new AutoResetEvent(false);
      workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) {waitHandle.Set();};
      workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
      {
        Console.WriteLine(e.Exception.Message);
        waitHandle.Set();
      };
      ExternalDataExchangeService dataExchange = new ExternalDataExchangeService();
      workflowRuntime.AddService(dataExchange);
      RoseWorkflowService roseWorkflowService = new RoseWorkflowService();
      dataExchange.AddService(roseWorkflowService);
      WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(RoseWorkflow));
      instance.Start();
      roseWorkflowService.PluckFlowers += new EventHandler(roseWorkflowService_PluckFlowers);
      try
      {
        roseWorkflowService.RaisePluckFlowersEvent(instance.InstanceId);}catch(Exception e){Console.WriteLine("from host. {0},{1}", e.GetType().Name, e.Message);
      }
      waitHandle.WaitOne();
    }
    Console.WriteLine("Press to quit");
    Console.ReadLine();
  }
  static void roseWorkflowService_PluckFlowers(object sender, ExternalDataEventArgs e)
  {
    Console.WriteLine("Handling PluckFlowers event");
  }
}

I used RoseWorkflowService as one of my local service which implement my IRoseWorkflowService which is used in my workflow.
IRoseWorkflowService:

[ExternalDataExchange]
public interface IRoseWorkflowService
{
  event EventHandler PluckFlowers;
  void RaisePluckFlowersEvent(Guid instanceId);
}

RoseWorkflowService:

[Serializable]
public class RoseWorkflowService : IRoseWorkflowService
{
  #region IRoseWorkflowService Members
  public event EventHandler PluckFlowers;

  public void RaisePluckFlowersEvent(Guid instanceId)
  {
    if (PluckFlowers != null)
      PluckFlowers(null, new ExternalDataEventArgs(instanceId));
  }
  #endregion
}

Now run the application. It never reaches to host place.
Solution (Workaround): You can resolve it through by “CallExternalMethod” activity. I will explain about this on next part with downloadable source code.

Tags Tags:
Categories: .NET
Posted By: udooz
Last Edit: 24 Jun 2008 @ 10 52 AM

EmailPermalinkComments (2)
\/ More Options ...
Change Theme...
  • Users » 1
  • Posts/Pages » 54
  • Comments » 39
Change Theme...
  • VoidVoid « Default
  • LifeLife
  • EarthEarth
  • WindWind
  • WaterWater
  • FireFire
  • LightLight