beaucrawford.net

Give me data or give me death

About the author

Author Name is someone.
E-mail me Send mail

Recent comments

Don't show

Authors

Tags

Don't show

    Disclaimer

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

    © Copyright 2010

    Hacking into the Windows Workflow Rules Engine

    On my current gig we have been toying with the idea of creating a rules engine to a control a simple work flow that centers on Microsoft Message Queue.  The main idea is to build a very simple UI that allows a Business user to enter an expression based off of a Type in our system, such as ICustomer.   We would give the user some sort of “Poor Man’s Intellisense” to help them with the syntax but, other than that, they would be on their own – just keep pressing keys until you have a valid expression (we would wrap the expression evaluation call in a try/catch).

    My first thought was to use the LINQ Dynamic Query API to allow a user to enter a statement as a string and then simply call some code that looks something like this:

    var lambda = DynamicExpression.ParseLambda(typeof(ICustomer), typeof(bool), txtExpression.Text.Trim());
    var result = (bool)lambda.Compile().DynamicInvoke(instance);

    The statement, in this case, would evaluate to a Boolean value (the conditional part of the Rule evalulation).  That looked very promising and definitely would have fit the bill for the vast majority of our needs. I wanted something more though – namely the ability to use extension methods as part of the expression and to also be able to execute method invocation statements.  I was not having any luck in modifying the LINQ Dynamic Query code to consistently do what I needed to for these items.  That led me to look elsewhere.

    I had previously heard about using the WF Rules Engine outside of WF itself at the Twin Cities Code Camp a few months ago.  It’s a pretty simple concept really and I remember thinking at the time that it looked very promising.  In an effort to pay this problem its due diligence I investigated WF in more detail.  Turns out it’s pretty easy to interact with the Rule object model directly:
    var thisType = typeof(IShipment);
    
    var currentInstance = new Shipment();
    
    var ruleSet = new RuleSet("Test");            
    var ruleValidation = new RuleValidation(thisType, null);
    
    Rule rule = new Rule("Rule 1");
    rule.ReevaluationBehavior = RuleReevaluationBehavior.Never;
    rule.Condition = Parser.ParseCondition("ShipmentDate > DateTime.Now.AddDays(-30)", ruleValidation);
    rule.ThenActions.AddRange(Parser.ParseStatementList("Queue.Send(\"StagingQueue\", this)", ruleValidation).ToArray());
    
    ruleSet.Rules.Add(rule);
    
    RuleExecution exec = new RuleExecution(new RuleValidation(currentInstance.GetType(), null), currentInstance);
    ruleSet.Execute(exec);

    The crux of my problem is obviously how to parse an arbitrarily complex expression into an an instance of RuleExpressionCondition.  If you look at the above code you will notice some references to a static class named “Parse”.  This, unfortunately, is a class that I had to create.  I noticed that WF uses the System.Workflow.Activities.Rules.Design.RuleSetDialog Windows Form to maintain a RuleSet.  I cracked this Type open in Reflector in an effort to see how they were taking the strings entered on the UI and converting them to expressions.  As I expected, the functionality for doing this is all internal.  Bummer.  Well, for now, I decided to use a little Full Trust reflection to create a wrapper around some Reflection calls:

    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Workflow.Activities.Rules;
    
    namespace WFRulesSample
    {
        public static class Parser
        {
            private const string TypeName = "System.Workflow.Activities.Rules.Parser, System.Workflow.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35";
    
            public static RuleExpressionCondition ParseCondition(string expression, RuleValidation ruleValidation)
            {
                return ExecuteMethod("ParseCondition", new object[] { ruleValidation }, new object[] { expression }) as RuleExpressionCondition;
            }
    
            public static List< RuleAction > ParseStatementList(string expression, RuleValidation ruleValidation)
            {
                return ExecuteMethod("ParseStatementList", new object[] { ruleValidation }, new object[] { expression }) as List< RuleAction >;
            }
    
            private static object ExecuteMethod(string name, object[] ctorParameters, object[] methodParameters)
            {
                var type = Type.GetType(TypeName);
                var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(RuleValidation) }, null);
                var instance = ctor.Invoke(ctorParameters);
                var method = instance.GetType().GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                return method.Invoke(instance, methodParameters);
            }
        }
    }

    It’s doubtful that the above code will ever see the light of day for this project.  I just found it interesting.

    Caveat – yes, the above code is probably not great in terms of performance.  There are numerous ways you could speed it up with the use of some DynamicMethod calls.  Good luck and happy hacking.


    Categories: Reflection | C#
    Posted by Beau on Saturday, March 14, 2009 1:03 AM
    Permalink | Comments (3) | Post RSSRSS comment feed

    Comments

    bloggingabout.net

    Sunday, April 05, 2009 12:23 PM

    Pingback from bloggingabout.net

    Rule engine with WYSIWYG rule serialization - Vagif Abilov's blog on .NET

    sunny us

    Saturday, May 23, 2009 5:28 PM

    Thanks great info...

    запознанства ro

    Thursday, July 16, 2009 6:59 AM

    thanks for that code info.