Search code examples
c#workflow-foundation-4workflow-foundation

Wrap an existing workflow in a try/catch activity at run-time with WF4.5


WF4.5 usage seems pretty scares these days, so to all WF4.5 experts out there, please take a minute to read this and see if you can help me out.

I'm trying to add a try/catch activity at run-time around an existing workflow activity.

Why you ask? Well I need to ensure my workflow never crashes as I'm using data in all my various activities which I need to return whether an unhandled exception occured or not, but OnUnhandledException does not return any values.

I've tried to implement using imperative coding and by creating an actual activity but I cannot figure out for the live of me on how to pass the arguments from the try/catch activity which is created in a dynamic activity to the workflow that's part of the try section.

Here's the try/catch function:

    public static Activity TryCatchWrap(Activity runtimeActivity)
    {
        var dataObject = new InOutArgument<DataObject>();

        var ex = new DelegateInArgument<Exception> { Name = "Ex" };

        Activity workflowTryCatch = new DynamicActivity()
        {
            Properties =
            {
                new DynamicActivityProperty
                {
                    Name = "DataObject",
                    Type = typeof(InOutArgument<DataObject>),
                    Value = dataObject
                }
            },
            Implementation = () => new Sequence
            {
                Activities =
                {
                    new WriteLine
                    {
                        Text = new InArgument<string>((ctx) => "Hello1 : " +
                               dataObject.Get(ctx).ApplicationName)
                    },
                    runtimeActivity
                }
            }
        };

        return workflowTryCatch;
    }

As you can see, I create a TryCatch activity in the DynamicActivity, create a property call DataObject and I set its Implementation to runtimeActivity.

My runtimeActivity activity, which is also a Dynamic Activity and contains a Flowchart activity (and more activities) is loaded from a XAML files using:

var activity = ActivityXamlServices.Load(
    fileName,
    new ActivityXamlServicesSettings()
    {
       CompileExpressions = false,
    });

What I need is to pass the DataObject from the TryCatch activity to the runtime activity which is provided in the 'try' section of the TryCatch.

How can I achieve this. I've tried so many combinations, that are just too many to list, not to mention the various errors. I've read countless articles, but to no avail, but I'm obviously missing something.

If I use the workflow designer, I've got my required scenario up and running in seconds, thought it is slightly different as I'm adding the TryCatch as the first element in my FlowChart, then within the TryCatch, I set the Trysection to whatever workflow I need.

If there is a different way to achieve this, I'm opened to any suggestions.

Any help would be greatly appreciated as I'm a complete standstill.

Thanks.

UPDATE-1:

I've just another it another way, where I'm extracting my flowchart out of my DynamicActivity programmatically. Once I have the flowchart, I then extract the StartNode and try to replace it with a new one which includes the try/catch and where the Try has been set to whatever the action originally associated with the StartNode was but this is still not working.

        var fullWorkflow = WorkflowInspectionServices.GetActivities(workflow);
        var flowchart = (Flowchart)fullWorkflow.First();
        var startNode = (FlowStep)flowchart.StartNode;
        var startNodeAction = startNode.Action;

        flowchart.StartNode = null;

        var ex = new DelegateInArgument<Exception> { Name = "Exception" };

        var tryCatch = new TryCatch();
        var catchException = new Catch<Exception>();
        var catchActivityAction = new ActivityAction<Exception>();

        catchActivityAction.Argument = ex;
        catchActivityAction.Handler = new Assign<Exception>()
        {
            To = new OutArgument<Exception>(new
            VisualBasicReference<Exception>("DataObject.Exception")),
            Value = new InArgument<Exception>(
            new VisualBasicValue<Exception>("Exception"))
        };

        catchException.Action = catchActivityAction;

        tryCatch.Try = startNodeAction;
        tryCatch.Catches.Add(catchException);

        startNode.Action = tryCatch;
        flowchart.StartNode = startNode;

        Activity workflowTryCatch = new DynamicActivity()
        {
            DisplayName = "HelloWorld",
            Properties =
            {
                new DynamicActivityProperty
                {
                    Name = "DataObject",
                    Type = typeof(InOutArgument<DataObject>),
                    //Value = argDataObject
                }
            },
            Implementation = () => flowchart
        };

I'm getting the following errors at run-time:

An unhandled exception of type 'System.Activities.InvalidWorkflowException' occurred in 
System.Activities.dll

Additional information: The following errors were encountered while processing the 
workflow tree:

'HelloWorld': The private implementation of activity '1: HelloWorld' has 
the following validation error:   Compiler error(s) encountered processing
expression "DataObject.Exception".

Type 'DataObject' is not defined.

'HelloWorld': The private implementation of activity '1: HelloWorld' has 
the following validation error:   Compiler error(s) encountered processing
expression "DataObject.ApplicationName".

Type 'DataObject' is not defined.

Any ideas/suggestions?

Thanks.


Solution

  • I eventually figured it out by myself!! Bleeping painful if you ask me and I'm not 100% sure if this is the correct way to go about it, but now I'm officially creating a DynamicActivity to which I'm passing my complex InOutArgument which in turns adds a TryCatch activity which triggers whatever Flowchart I pass to it.

    If any unhandled exception occur, these will be caught and set in my complex object i.e. DataObject.

    The important part for me is whether or not the workflow ran successfully or not, it will always return my complex object with whatever information was captured in the specific Flowchart.

    Now having said that, it works great but I haven't tested the performances but hopefully, it won't degrade too much.

    Here's how I achieved it. I create the following function:

        public static Activity CreateNew(Flowchart flowchart)
        {
            var inProperty = new DynamicActivityProperty
            {
                Name = "DataObject",
                Type = typeof(InOutArgument<DataObject>)
            };
    
            var ex = new DelegateInArgument<Exception> { Name = "exception" };
    
            var tryCatch = new TryCatch();
            var tryCatchException = new Catch<Exception>()
            {
                Action = new ActivityAction<Exception>()
                {
                    Argument = ex,
                    Handler =
                    new Sequence
                    {
                        Activities =
                        {
                            new Assign<Exception>
                            {
                                To = new VisualBasicReference<Exception>() 
                                {ExpressionText = "DataObject.Exception"},
                                Value = new InArgument<Exception>(ex)
                            },
                            new WriteLine()
                            {
                                Text = new InArgument<string>(new VisualBasicValue<string>() 
                               { ExpressionText = "DataObject.Exception.Message"})
                            }
                        }
                    }
                }
            };
    
            var activity = new DynamicActivity()
            {
                Implementation = () => new Flowchart
                {
                    StartNode = new FlowStep
                    {
                        Action = tryCatch
                    }
                },
                Properties = { inProperty }
            };
    
            tryCatch.Try = flowchart;
            tryCatch.Catches.Add(tryCatchException);
    
            AddVbSetting(activity);
    
            return activity;
        }
    

    The above will run, but the flowchart I pass to it contains one or more imported namespaces. I thought it would work as is, but that's not the case.

    I had to re-add them to the new activity. For now, the AddVbSetting only imports/adds the namespace of my complex object and it works fine, but there may be scenarios where you'll need other namespaces to be added, so this method will need to be expanded but hopefully, this will be straight forward enough.

    The code for the AddVbSettings is:

        private static void AddVbSetting(Activity activity)
        {
            var settings = new VisualBasicSettings
            {
                ImportReferences =
                {
                    new VisualBasicImportReference
                    {
                        Assembly = typeof(DataObject).Assembly.GetName().Name,
                        Import = typeof(DataObject).Namespace
                    }
                }
            };
            VisualBasic.SetSettings(activity, settings);
        }
    

    I've spent a huge amount of time on something that should have been simple and while looking at it, it seems obvious now, putting all the parts together was actually a nightmare!! It took (I feel really stupid saying this!) days to figure out and reading countless articles, god know how many prototypes written, not to mention exceptions after exceptions!!

    I hope if anyone out there is still using WF that it will be of some help to you and save you the headaches it caused me! Glad it's finally over... well until tomorrow :) anyway!

    PS: The final solution is a flowchart within a flowchart which isn't probably need it and I'll probably modify this tomorrow. I just thought I'd mention it before someone highlighted this as a major flaw!