WF: Exception Handling with State Machine Workflow – Part 2

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.

Share This: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • del.icio.us

No Comments

Leave a Reply