Tinkering with the Rules OM

Ok it's been a long time since I posted. Time for some code now.

I was thinking what to write about and Don's post on CodeDom Quirks made me think about CodeDom and Rules. As Don said, the WF rules engine uses CodeDom for its expression model. Just to show you that the RuleSet Editor really does make life easy when it comes to authoring rules, I thought I'd tinker with the OM and show you how the code for a simple Rule might look like if you were using the OM instead.

I just defined a really simple Target Object I am going to write the rules against - a Person class with Name and Age properties. Then, let's say I want to write a simple rule on the target object: If Name is "Anna" then Age = 16 (I know I'm not too creative with this example :)

This is the code just to create that Rule. In the code below, MyPerson is of type Person and is defined as a field in my class. This is the field that the condition and the action refers to

            RuleExpressionCondition condition = new RuleExpressionCondition("TestName");

            CodeFieldReferenceExpression cfre = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(Program)), "MyPerson");
            CodePropertyReferenceExpression cpreName = new CodePropertyReferenceExpression(cfre, "Name");
            CodeBinaryOperatorExpression cboeNameIsAnna = new CodeBinaryOperatorExpression(cpreName, CodeBinaryOperatorType.ValueEquality, new CodePrimitiveExpression("Anna"));
            condition.Expression = cboeNameIsAnna;

            CodePropertyReferenceExpression cpreAge = new CodePropertyReferenceExpression(cfre, "Age");
            CodeAssignStatement casAgeIsSixteen = new CodeAssignStatement(cpreAge, new CodePrimitiveExpression(16));

            RuleStatementAction rsq = new RuleStatementAction(casAgeIsSixteen);
            List<RuleAction> actions = new List<RuleAction>(1);
            actions.Add(rsq);

            Rule r = new Rule("If_Name_Is_Anna_Then_Age_Is_Sixteen", condition, actions);

Top-down, when you create a Rule, you pass in a name, a condition, then actions and optionally else actions. In this particular case, I am only passing 1 then action (Age = 16) and I have a simple condition (Name == "Anna")

Ok, so once you have a Rule, if you want to execute this Rule, you put it in a RuleSet.

            RuleSet rs = new RuleSet("RuleSet1");
            rs.Rules.Add(r);

Now, if you want to execute the RuleSet, here is the code for doing that. When you use the RuleSet editor dialog, we do some error checking for you automatically to make sure your RuleSet is created correctly. However, when you created your RuleSet using the OM, you'd want to make sure it is valid by calling Validate on the RuleSet. You need to pass in a RuleValidation object which holds the validation state. Note, I passed in null for the TypeProvider.

            RuleValidation rv = new RuleValidation(typeof(Person), null);
            MyPerson = new Person(name, age); // initialize your type just like I initialized my object on which I want the rules executed against

            RuleExecution re = new RuleExecution(rv, MyPerson);
            if (rs.Validate(rv))
            {
                rs.Execute(re);
            }
            else
            {
                Console.WriteLine("There were errors in validation: " + rv.Errors.Count);
                foreach (ValidationError error in rv.Errors)
                {
                    Console.WriteLine(error.ErrorText);
                }
            }
           
So. I think I made my point. It is cumbersome to use the OM to write these rules - thanks to the RuleSetEditor, you just type in code like it's C# and we internally create the CodeDom for you and write out the expression tree as a .rules file. And thanks to the PolicyActivity, where once you associate a RuleSet with it, the RuleSet is executed for you when the PolicyActivity is executed. So, you dont need to write even the code that calls Execute.

Ok, but you may be thinking, what if I want to use Rules outside of WF, then I dont have the PolicyActivity and all that, so I do I have to do all this? The answer is kind of, but not really. The RuleSetDialog is re-hostable. Jurgen wrote the ExternalRuleSet ToolKit sample to basically show that you can host this dialog outside of a WF and use the dialog to author the rules. Now, once you have the RuleSet, it's easy to execute it since the code to execute was pretty simple - it's only creating the RuleSet thats cumbersome.

Here is some code that shows how to execute a RuleSet from the serialized .rules file. That .rules file is just an XML file which has the whole CodeDom tree. You can use the OM as I showed above, and then use the WorkflowMarkupSerializer class to write out the rules into a .rules file ; or you can use the ExternalRuleSet ToolKit sample to author your rules and it writes out a .rules file. Once you have that, you can use the code below to get to the RuleSet, and then call the RuleEngine directly.

            WorkflowMarkupSerializer s = new WorkflowMarkupSerializer();
            XmlReaderSettings settings = new XmlReaderSettings();
            XmlReader reader = XmlReader.Create("Workflow1.rules", settings);
            RuleDefinitions defs = s.Deserialize(reader) as RuleDefinitions;
            RuleEngine engine = new RuleEngine(defs.RuleSets[0], typeof(Test));  // Test is my Target Object type, this would be whatever your target type is
            Test test = new Test(); // Create your target object type, in my case, Test
            engine.Execute(test);

Enough said!