Using WorkflowInvoker to Evaluate Custom Expressions

March 19th 2012 Workflow Foundation

Windows Workflow Foundation (WF) is an often overlooked part of .NET framework but its declarative approach to defining workflow logic can prove useful in spite of the steep learning curve and some unfortunate limitations. Once you get to know it, you might even come up with new creative ways of using it. Let's take a look at one such use case.

If you've at least tried out WF, you probably know that there is built in support for Visual Basic expressions which is being used by several activities. It doesn't take ingenuity to come up with a simple workflow which can be used to calculate a value of an expression.

Simple workflow for evaluating an expression

Using the simplest of the workflow hosting classes, it's easy to evaluate this expression inside your application for any input value:

private bool Invoke()
{
    var invoker = new WorkflowInvoker(new TimeTravel());
    var inputs = new Dictionary<string, object> { { "Speed", 80 } };
    var outputs = invoker.Invoke(inputs);
    return (bool)outputs["Result"];
}

A less known fact is that workflows can also be defined in code. Using this approach, it's easy to create a generic method for evaluating runtime defined expressions:

public TResult Evaluate<TArg, TResult>(TArg input, string expression)
{
    var vb = new VisualBasicValue<TResult>(expression);

    var wf = new DynamicActivity<TResult>
    {
        Properties = 
        {
            new DynamicActivityProperty
            {
                Name = "Input",
                Type = typeof(InArgument<TArg>)
            }
        },
        Implementation = () => new Assign<TResult>
        {
            Value = new InArgument<TResult>(vb),
            To = new ArgumentReference<TResult> { ArgumentName = "Result" }
        }
    };

    var inputs = new Dictionary<string, object>
    {
        { "Input", input }
    };

    return WorkflowInvoker.Invoke(wf, inputs);
}

The above code assumes that the input value will be referenced in the expression with the name Input. The generic arguments need not be only basic types but in this case it is necessary to import additional Visual Basic references. With only a minor change in the workflow definition a similar approach can be used for validating the expression when the user enters it:

public string Validate<TArg, TResult>(string expression)
{
    var vb = new VisualBasicValue<TResult>(expression);

    var wf = new DynamicActivity<TResult>
    {
        Properties = 
        {
            new DynamicActivityProperty
            {
                Name = "Input",
                Type = typeof(InArgument<TArg>)
            }
        },
        Implementation = () => new If()
        {
            Condition = new InArgument<bool>(false),
            Then = new Assign<TResult>
            {
                Value = new InArgument<TResult>(vb),
                To = new ArgumentReference<TResult> { ArgumentName = "Result" }
            }
        }
    };

    try
    {
        WorkflowInvoker.Invoke(wf);
    }
    catch (InvalidWorkflowException e)
    {
        return e.Message;
    }
    return null;
}

We are taking into account that the expression is getting compiled when WorkflowInvoker.Invoke is called. Since we don't have an instance of the input class available, we use the If statement to avoid evaluating the expression and causing a runtime exception. The only expected cause of the exception is therefore expression compilation error. The message returned by the method could even be parsed further to only include the actual error emitted by the compiler.

New features in .NET framework 4.5 (currently in beta) allow us to do even more:

  • It includes support not only for Visual Basic expressions but for C# expressions as well. CSharpValue<T> can be used just like VisualBasicValue<T>.
  • Both classes include additional GetExpressionTree method which can be used to retrieve and further process the expression tree parsed from the expression text. This offers many new possibilities, like modifying the expression to evaluate it without invoking the workflow or analyzing it to determine which input argument properties it actually depends on.

And no, GetExpressionTree method is not available in .NET 4 although the documentation is saying otherwise.

Copyright
Creative Commons License