Use workflow to evaluate dynamic expression

匿名 (未验证) 提交于 2019-12-03 02:38:01

问题:

I would like to pass an object and expression into a dynamically created workflow to mimic the Eval function found in many languages. Can anyone help me out with what I am doing wrong? The code below is a very simple example if taking in a Policy object, multiple its premium by 1.05, then return the result. It throws the exception:

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

'DynamicActivity': The private implementation of activity '1: DynamicActivity' has the following validation error: Value for a required activity argument 'To' was not supplied.

And the code:

using System.Activities; using System.Activities.Statements;  namespace ConsoleApplication1 {   class Program   {     static void Main(string[] args)     {         Policy p = new Policy() { Premium = 100, Year = 2016 };          var inputPolicy = new InArgument<Policy>();         var theOutput = new OutArgument<object>();          Activity dynamicWorkflow = new DynamicActivity()         {             Properties =             {                 new DynamicActivityProperty                 {                     Name="Policy",                     Type=typeof(InArgument<Policy>),                     Value=inputPolicy                 }             },             Implementation = () => new Sequence()             {                 Activities =                 {                     new Assign()                     {                          To =  theOutput,                          Value=new InArgument<string>() { Expression = "Policy.Premium * 1.05" }                     }                 }             }         };          WorkflowInvoker.Invoke(dynamicWorkflow);     } }    public class Policy   {       public int Premium { get; set; }       public int Year { get; set; }   } } 

回答1:

You can use Workflow Foundation to evaluate expressions, but it is far easier to use almost any other option.

The key issue at play with your code was that you were not trying to evaluate the expression (with either VisualBasicValue or CSharpValue). Assigning InArgument`1.Expression is an attempt to set the value - not to set the value to the result of an expression.

Keep in mind that compiling expressions is fairly slow (>10ms), but the resultant compiled expression can be cached for quick executions.

Using Workflow:

class Program {     static void Main(string[] args)     {         // this is slow, only do this once per expression         var evaluator = new PolicyExpressionEvaluator("Policy.Premium * 1.05");          // this is fairly fast          var policy1 = new Policy() { Premium = 100, Year = 2016 };         var result1 = evaluator.Evaluate(policy1);          var policy2 = new Policy() { Premium = 150, Year = 2016 };         var result2 = evaluator.Evaluate(policy2);          Console.WriteLine($"Policy 1: {result1}, Policy 2: {result2}");     }  }  public class Policy {     public double Premium, Year; }  class PolicyExpressionEvaluator {     const string          ParamName = "Policy",         ResultName = "result";      public PolicyExpressionEvaluator(string expression)     {         var paramVariable = new Variable<Policy>(ParamName);         var resultVariable = new Variable<double>(ResultName);         var daRoot = new DynamicActivity()         {             Name = "DemoExpressionActivity",             Properties =             {                 new DynamicActivityProperty() { Name = ParamName, Type = typeof(InArgument<Policy>) },                 new DynamicActivityProperty() { Name = ResultName, Type = typeof(OutArgument<double>) }             },             Implementation = () => new Assign<double>()             {                 To = new ArgumentReference<double>() { ArgumentName = ResultName },                 Value = new InArgument<double>(new CSharpValue<double>(expression))             }         };         CSharpExpressionTools.CompileExpressions(daRoot, typeof(Policy).Assembly);         this.Activity = daRoot;     }      public DynamicActivity Activity { get; }      public double Evaluate(Policy p)     {         var results = WorkflowInvoker.Invoke(this.Activity,              new Dictionary<string, object>() { { ParamName, p } });          return (double)results[ResultName];     } }  internal static class CSharpExpressionTools {     public static void CompileExpressions(DynamicActivity dynamicActivity, params Assembly[] references)     {         // See https://docs.microsoft.com/en-us/dotnet/framework/windows-workflow-foundation/csharp-expressions         string activityName = dynamicActivity.Name;         string activityType = activityName.Split('.').Last() + "_CompiledExpressionRoot";         string activityNamespace = string.Join(".", activityName.Split('.').Reverse().Skip(1).Reverse());         TextExpressionCompilerSettings settings = new TextExpressionCompilerSettings         {             Activity = dynamicActivity,             Language = "C#",             ActivityName = activityType,             ActivityNamespace = activityNamespace,             RootNamespace = null,             GenerateAsPartialClass = false,             AlwaysGenerateSource = true,             ForImplementation = true         };          // add assembly references         TextExpression.SetReferencesForImplementation(dynamicActivity, references.Select(a => (AssemblyReference)a).ToList());          // Compile the C# expression.           var results = new TextExpressionCompiler(settings).Compile();         if (results.HasErrors)         {             throw new Exception("Compilation failed.");         }          // attach compilation result to live activity         var compiledExpression = (ICompiledExpressionRoot)Activator.CreateInstance(results.ResultType, new object[] { dynamicActivity });         CompiledExpressionInvoker.SetCompiledExpressionRootForImplementation(dynamicActivity, compiledExpression);     } } 

Compare to the equivalent Roslyn code - most of which is fluff that is not really needed:

public class PolicyEvaluatorGlobals {     public Policy Policy { get; }      public PolicyEvaluatorGlobals(Policy p)     {         this.Policy = p;     } }  internal class PolicyExpressionEvaluator {     private readonly ScriptRunner<double> EvaluateInternal;      public PolicyExpressionEvaluator(string expression)     {         var usings = new[]          {             "System",             "System.Collections.Generic",             "System.Linq",             "System.Threading.Tasks"         };         var references = AppDomain.CurrentDomain.GetAssemblies()             .Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location))             .ToArray();          var options = ScriptOptions.Default             .AddImports(usings)             .AddReferences(references);          this.EvaluateInternal = CSharpScript.Create<double>(expression, options, globalsType: typeof(PolicyEvaluatorGlobals))             .CreateDelegate();     }      internal double Evaluate(Policy policy)     {         return EvaluateInternal(new PolicyEvaluatorGlobals(policy)).Result;     } } 

Roslyn is fully documented, and has the helpful Scripting API Samples page with examples.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!