Programmatically Create Windows Workflow Rules
I am currently working with a client that is evaluating the Windows Workflow Rules Engine. One of their requirements is that they need to be able to create the rules programmatically through their own application. First we looked at the ability to host the Windows Workflow rules dialog boxes in our application. We can accomplish this through the RuleSetDialog object and the RuleConditionDialog object. Second we looked at how we could create our own UI and programmatically create the rules through an API. Based on the requirement of the application they have decided to use their own UI. So, the search began on how we were going to create these rules.
The Workflow Rules Engine API works through the CodeDom system. This makes a lot of sense since we are using code to create code - which is exactly what CodeDom was designed for.
I have used a sample fictitious rule that represents the type of rule that will be created through the UI. The rule we need to create (and the one used in the example below) is a rule that checks the model and year of a car along with the gas mileage and assigns a surcharge, if necessary.
In looking at the code below I have set up private variables to hold our data. This could have easily been a separate object (and will be in real life). Once I have my variables I need to setup the CodeDom objects which will reference the variables that I will be using in my rules.
The CodeDom objects that are supported in the rules object model are:
CodeThisReferenceExpression
CodeArrayIndexerExpression
CodeAssignStatement
CodeBinaryOperatorExpression
CodeCastExpression
CodeDirectionExpression
CodeExpressionStatement
CodeFieldReferenceExpression
CodeIndexerExpression
CodeMethodInvokeExpression
CodeMethodReferenceExpression
CodePrimitiveExpression
CodePropertyReferenceExpression
CodeTypeReference
CodeTypeReferenceExpression
Next, I start the rule creation code. I create a RuleSet object which I will add Rule objects to. After I create the Rule object then I create the conditions which will be added to the rule using the Rule.Condition property(handing it a RuleExpressCondition object). After that I will add the action using the Rule.ThenAction.Add Method (handing it a RuleStatementAction object). I can also add the Else action using the Rule.ElseActions.Add method. Before I can add the condition I need to create the code to represent the predicate parts that will be added to the condition. In the code this can be seen starting with the code to create the predicate for GasMileage. I create a CodeBinaryOperatorExpression object and then using the Left, Operator and Right properties to assembly the predicate. I continue this for each of the predicates I will need for this rule (in this case there are 3). I have shown in the code how to compare against an integer (with the GasMileage predicate), against a string (with the ModelYear predicate) as well as against an Enum type (with the CarCategory predicate). These predicates also show how to use the CodeBinaryOperatorType object with both the .ValueEquality and .LessThan operators. An interesting find is that there is no inequality operator. To find inequality you need to create the syntax as (CarCategory == "Sports") == false.
Once I have created the predicates that represent each of the parts of my rule I then need to join the predicates and create the condition which will be passed to a RuleExpressionCondition object. As seen in the code below I take the first two predicates; the ruleGasMileageTest object and using a BooleanAnd operator join to the ruleCarCategoryTest object. If I only had two predicates I would be done and could add the condition object to the rule (carChargeRule). Since I had three predicates I need to take the condition object created from the joining of the first two predicates and join them to the third predicate (ruleModelYearTest) to create the final condition (ruleCondition2) which will be added to the rule.
At this point we have the 'if' portion of the rule complete. We now need to create the 'then' portion. This is accomplished using the Rule.ThenAction.Add method as seen at the bottom of the code.
After you have looked at the code make sure to look at the image of the locals window which follows the code. This screen shot shows the rules code that all of this CodeDom code created.
public class ......
{
//Object Items
private string CarName = "Jaguar";
private string CarModel = "XK SLE";
private string ModelYear = "2009";
private CarCategory carCategory = CarCategory.SportsCar;
private int GasMileage = 12;
private double SurchargeRate = 0;
private void BuildRuleSet()
{
RuleSet surchargeRuleSet = new RuleSet("SurchargeRuleSet");
// Define property and activity reference expressions through CodeDom functionality
CodeThisReferenceExpression thisRef = new CodeThisReferenceExpression();
CodeFieldReferenceExpression CarNameRef = new CodeFieldReferenceExpression(thisRef, "CarName");
CodeFieldReferenceExpression CarCategoryRef = new CodeFieldReferenceExpression(thisRef, "carCategory");
CodeTypeReferenceExpression CategoryEnumRef = new CodeTypeReferenceExpression(typeof(CarCategory));
CodeFieldReferenceExpression ModelYearRef = new CodeFieldReferenceExpression(thisRef, "ModelYear");
CodeFieldReferenceExpression SurchargeRef = new CodeFieldReferenceExpression(thisRef, "SurchargeRate");
CodeFieldReferenceExpression GasMileageRef = new CodeFieldReferenceExpression(thisRef, "GasMileage");
// IF GasMileage < 15 AND CarCategory = Sports AND ModelYear = 2009
// THEN Surcharge = $500
Rule carChargeRule = new Rule("CarChargeRule");
surchargeRuleSet.Rules.Add(carChargeRule);
// define first predicate: GasMileage < 15
CodeBinaryOperatorExpression ruleGasMileageTest = new CodeBinaryOperatorExpression();
ruleGasMileageTest.Left = GasMileageRef;
ruleGasMileageTest.Operator = CodeBinaryOperatorType.LessThan;
ruleGasMileageTest.Right = new CodePrimitiveExpression(15);
// define second predicate: CarCategory = Sports
CodeBinaryOperatorExpression ruleCarCategoryTest = new CodeBinaryOperatorExpression();
ruleCarCategoryTest.Left = CarCategoryRef;
ruleCarCategoryTest.Operator = CodeBinaryOperatorType.ValueEquality;
ruleCarCategoryTest.Right = new CodeFieldReferenceExpression(CategoryEnumRef, "SportsCar");
// define third predicate: ModelYear = 2009
CodeBinaryOperatorExpression ruleModelYearTest = new CodeBinaryOperatorExpression();
ruleModelYearTest.Left = ModelYearRef;
ruleModelYearTest.Operator = CodeBinaryOperatorType.ValueEquality;
ruleModelYearTest.Right = new CodePrimitiveExpression("2009");
// join the first two predicates into a single condition
CodeBinaryOperatorExpression ruleCondition = new CodeBinaryOperatorExpression();
ruleCondition.Left = ruleGasMileageTest;
ruleCondition.Operator = CodeBinaryOperatorType.BooleanAnd;
ruleCondition.Right = ruleCarCategoryTest;
// join the third predicate into the condition
CodeBinaryOperatorExpression ruleCondition2 = new CodeBinaryOperatorExpression();
ruleCondition2.Left = ruleCondition;
ruleCondition2.Operator = CodeBinaryOperatorType.BooleanAnd;
ruleCondition2.Right = ruleModelYearTest;
carChargeRule.Condition = new RuleExpressionCondition(ruleCondition2);
// add the action: Surcharge = 500
CodeAssignStatement ruleSurchargeAction = new CodeAssignStatement(SurchargeRef, new CodePrimitiveExpression(500));
carChargeRule.ThenActions.Add(new RuleStatementAction(ruleSurchargeAction));
// Add the ruleset
RuleDefinitions ruleDef = new RuleDefinitions();
ruleDef.RuleSets.Add(surchargeRuleSet);
// Set the RuleDefinitions on the workflow
this.SetValue(RuleDefinitions.RuleDefinitionsProperty, ruleDef);
}
public enum CarCategory
{
SportsCar = 0,
Sedan = 1,
SUV = 2,
.......
}