Note that there are some explanatory texts on larger screens.

plurals
  1. POWF4: How do I evaluate an expression only known at runtime?
    text
    copied!<p>I am trying to create a simple WF4 activity that accepts a string that contains a VB.NET expression (from say the database), evaluates that string using the variables available in the current scope of the workflow and returns the result. Unfortunately, with the ways I've tried it, whether it be with a plain on <code>Activity</code> or a full-fledged <code>NativeActivity</code>, I keep hitting a wall.</p> <p>My first attempt was with a simple Activity, and I was able to make a simple class that evaluates an expression given some object as its input:</p> <pre><code>public class Eval&lt;T, TResult&gt; : Activity&lt;TResult&gt; { [RequiredArgument] public InArgument&lt;T&gt; Value { get; set; } public Eval(string predicate) { this.Implementation = () =&gt; new Assign&lt;TResult&gt; { Value = new InArgument&lt;TResult&gt;(new VisualBasicValue&lt;TResult&gt;(predicate)), To = new ArgumentReference&lt;TResult&gt;("Result") }; } public TResult EvalWith(T value) { return WorkflowInvoker.Invoke(this, new Dictionary&lt;string, object&gt;{ {"Value", value } }); } } </code></pre> <p>This woks nicely, and the following expression evaluates to 7:</p> <pre><code>new Eval&lt;int, int&gt;("Value + 2").EvalWith(5) </code></pre> <p>Unfortunately, I can't use it the way I want since the expression string is given as a constructor argument instead of as an <code>InArgument&lt;string&gt;</code>, so it can't be easily incorporated (dragged and dropped) into a workflow. My second attempt was to try and use <code>NativeActivity</code> to get rid of that pesky constructor parameter:</p> <pre><code>public class NativeEval&lt;T, TResult&gt; : NativeActivity&lt;TResult&gt; { [RequiredArgument] public InArgument&lt;string&gt; ExpressionText { get; set; } [RequiredArgument] public InArgument&lt;T&gt; Value { get; set; } private Assign Assign { get; set; } private VisualBasicValue&lt;TResult&gt; Predicate { get; set; } private Variable&lt;TResult&gt; ResultVar { get; set; } protected override void CacheMetadata(NativeActivityMetadata metadata) { base.CacheMetadata(metadata); Predicate = new VisualBasicValue&lt;TResult&gt;(); ResultVar = new Variable&lt;TResult&gt;("ResultVar"); Assign = new Assign { To = new OutArgument&lt;TResult&gt;(ResultVar), Value = new InArgument&lt;TResult&gt;(Predicate) }; metadata.AddVariable(ResultVar); metadata.AddChild(Assign); } protected override void Execute(NativeActivityContext context) { Predicate.ExpressionText = ExpressionText.Get(context); context.ScheduleActivity(Assign, new CompletionCallback(AssignComplete)); } private void AssignComplete(NativeActivityContext context, ActivityInstance completedInstance) { Result.Set(context, ResultVar.Get(context)); } } </code></pre> <p>I tried running <code>NativeEval</code> with the following:</p> <pre><code>WorkflowInvoker.Invoke(new NativeEval&lt;int, int&gt;(), new Dictionary&lt;string, object&gt; { { "ExpressionText", "Value + 2" }, { "Value", 5 } }); </code></pre> <p>But got the following exception:</p> <blockquote> <p>Activity '1: NativeEval' cannot access this variable because it is declared at the scope of activity '1: NativeEval'. An activity can only access its own implementation variables.</p> </blockquote> <p>So I changed <code>metadata.AddVariable(ResultVar);</code> to <code>metadata.AddImplementationVariable(ResultVar);</code> but then I got a different exception:</p> <blockquote> <p>The following errors were encountered while processing the workflow tree: 'VariableReference': The referenced Variable object (Name = 'ResultVar') is not visible at this scope. There may be another location reference with the same name that is visible at this scope, but it does not reference the same location.</p> </blockquote> <p>I tried using <code>.ScheduleFunc()</code> as described <a href="https://msmvps.com/blogs/theproblemsolver/archive/2011/04/05/scheduling-child-activities-with-input-parameters.aspx" rel="nofollow">here</a> to schedule a <code>VisualBasicValue</code> activity, but the result it returned was always <code>null</code> (but oddly enough no exceptions were thrown).</p> <p>I'm stumped. The metaprogramming model of WF4 seems much more difficult than the metaprogramming model of <code>System.Linq.Expressions</code>, which albeit difficult and often perplexing (like metaprogramming usually is), at least I was able to wrap my head around it. I guess it's because it has the added complexity of needing to represent a persistable, resumable, asynchronous, relocatable program, rather than just a plain old program.</p> <hr> <p><strong>EDIT:</strong> Since I don't think the issue I'm experiencing is caused by the fact that I'm trying to evaluate an expression that isn't hardcoded, the following alteration can be made to the <code>NativeActivity</code> that cause it to have a static expression:</p> <p>Replace</p> <pre><code>Predicate = new VisualBasicValue&lt;TResult&gt;(); </code></pre> <p>With</p> <pre><code>Predicate = new VisualBasicValue&lt;TResult&gt;("ExpressionText.Length"); </code></pre> <p>And remove the line</p> <pre><code>Predicate.ExpressionText = ExpressionText.Get(context); </code></pre> <p>Now even though with those lines the expression is static, I'm still getting the same errors.</p> <hr> <p><strong>EDIT2</strong>: <a href="http://blogs.msdn.com/b/tilovell/archive/2010/02/26/misadventures-in-cachemetadata-wrapping-an-inner-activity-in-code.aspx" rel="nofollow">This article</a> addressed the exception I was getting. I had to change both variable and child activity to be an "implementation", so this:</p> <pre><code>metadata.AddVariable(ResultVar); metadata.AddChild(Assign); </code></pre> <p>Changed to this:</p> <pre><code>metadata.AddImplementationVariable(ResultVar); metadata.AddImplementationChild(Assign); </code></pre> <p>And caused all the exceptions to go away. Unfortunately, it revealed that the following line does absolutely nothing:</p> <pre><code>Predicate.ExpressionText = ExpressionText.Get(context); </code></pre> <p>Changing the <code>ExpressionText</code> property of a <code>VisualBasicValue</code> during runtime has no effect. A quick check with ILSpy reveals why - the expression text is only evaluated and converted to an expression tree when <code>CacheMetadata()</code> is called, at which point the expression is not yet know, which is why I used the parameterless constructor which initialized and crystallized the expression to a no-op. I even tried saving the <code>NativeActivityMetadata</code> object I got in my own CacheMetadata overridden method and then use reflection to force a call to <code>VisualBasicValue</code>'s <code>CacheMetadata()</code>, but that just ended up throwing a different cryptic exception ("Ambiguous match found." of type AmbiguousMatchException).</p> <p>At this point it doesn't seem possible to fully integrate a dynamic expression into a workflow, exposing all the in-scope variables to it. I guess I'll have the method used in my <code>Eval</code> class within the <code>NativeEval</code> class.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload