Building a rule engine in c# (part 5: bringing it all together)

In the first four post of these series i showed a lot of code parts. I became some mails about that posts that show me that my descriptions are not as good as i thought. For that reason i decided to write that fifth post to bring it all together. First of all i decided to bring the source code of the posts together and so i created the ruleengine project at ruleengine.codeplex.com. Here are the links to the four previous posts and to the description of the expression evaluator.

Now i want to describe the tests i added to the ruleengine.codeplex.com project to show how to use ist. First i want to show the Person and Adress classed i use to validate the rules against:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public int Children { get; set; }
    public bool Married { get; set; }
    public Adresse Adresse_ { get; set; }
    private List<Adresse> adresses = new List<Adresse>();
    public List<Adresse> Adresses_ 
    { 
        get { return adresses; } 
        set { adresses = value; } 
    }
}

The Person class has some properties (Name, Age, Children, Married) and a collection of Adresses.

public class Adresse
{
    public string Street { get; set; }
    public int Plz { get; set; }
    public string City { get; set; }
    public bool ActiveState { get; set; }
}

The Adress class has some simple properties (Stree, Plz, City, ActiveState).
I will start with a simple test case to test a persons properties against a specific rule.

[TestMethod]
public void SimpleRuleLoaderPersonTest()
{
    Person person = new Person() { Name = "Mathias", Age = 36, Children = 2, Married = true };
    RuleLoader ruleLoader = new RuleLoader();
    // new Rule("Age", Operator.LessThanOrEqual, 50);
    Rule rule = ruleLoader.Load(2);
    RuleEngine.RuleEngine ruleEngine = new RuleEngine.RuleEngine();
    var ruleFunc = ruleEngine.CompileRule<Person>(rule);
    var result = ruleFunc(person);
    Assert.AreEqual(result, true);
}

That test defines a Person object and instanciates a RuleLoader object. That ruleLoader object would load the rules from the database but in my special case i wanted to use no database so the rule Loader is just a big switch statement that creates rules:

public class RuleLoader
{
    public Rule Load(int id)
    {            
        switch(id)
        {
            case 1 : 
                return new Rule("Name", Operator.NotEqual, "test");
            case 2 : 
                return new Rule("Age", Operator.LessThanOrEqual, 50);
            case 3: 
                return new Rule("Children", Operator.GreaterThan, 0);
            case 4: 
                return new Rule("City", Operator.Equal, "New York");
            case 5: return 
                new Rule("ActiveState", Operator.Equal, true);                
            case 6: 
                return new Rule("DecimalValue", Operator.GreaterThanOrEqual, 1);
            case 7: 
                return new Rule("DecimalValue", Operator.GreaterThanOrEqual, 1);
            case 8: 
                return new Rule("Married", Operator.Equal, true);
            default :
                return null;
        }
    }        
}

After that i use the RuleEngine to build a Func delegate out of the defined rule. In this case it is a Func of Person, bool delegate. This delegate gets invoked with the Person object as parameter and returns true or false. The second test shows how to use the RuleValidator class. That class has defined different ValidatorRule methodes to validate objects against rules.

[TestMethod]
public void SimpleRuleLoaderPersonValidateRulesAny()
{
    Person person1 = new Person() { 
        Name = "Mathias", Age = 36, Children = 2, Married = true };
    Person person2 = new Person() { 
        Name = "Anna", Age = 32, Children = 2, Married = false };
    RuleLoader ruleLoader = new RuleLoader();
    // new Rule("Age", Operator.LessThanOrEqual, 50);
    Rule rule1 = ruleLoader.Load(2);
    // new Rule("Children", Operator.GreaterThan, 0);
    Rule rule2 = ruleLoader.Load(3);
    RuleEngine.RuleEngine ruleEngine = new RuleEngine.RuleEngine();
    var ruleFuncs = ruleEngine.CombineRules<Person>(
        new Rule[] { rule1, rule2 });
    RuleValidator ruleValidator = new RuleValidator();
    var result = ruleValidator.ValidateRulesAny(
        new[] { person1, person2 }, ruleFuncs);
    Assert.AreEqual(result, true);
}

In this case we use the CombineRules method of the RuleEngine to create an array of Func delegates. That delegates and the two Person objects are overtaken to the ValidateRulesAny method of the RuleValidator. That method returns true if every person fulfills at least one rule. The next test shows how to use the RuleEngine to validate collections of objects against rules.

[TestMethod]
public void SimpleRuleLoaderPersonAddressValidateRulesAll()
{
    Person person = new Person() { 
        Name = "Mathias", Age = 36, Children = 2, Married = true };
    Adresse adresseOne = new Adresse() { 
        Street = "Teststreet1", Plz = 3030, City = "New York", ActiveState = true };
    Adresse adresseTwo = new Adresse() { 
        Street = "Teststreet2", Plz = 1010, City = "London", ActiveState = false };
    Adresse adresseThree = new Adresse() { 
        Street = "Teststreet3", Plz = 2020, City = "Paris", ActiveState = false };        
    person.Adresses_.Add(adresseOne);
    person.Adresses_.Add(adresseTwo);
    person.Adresses_.Add(adresseThree);
    RuleLoader ruleLoader = new RuleLoader();
    // new Rule("City", Operator.Equal, "New York");
    Rule firstAdressRule = ruleLoader.Load(4);
    // new Rule("ActiveState", Operator.Equal, true);      
    Rule secondAdressRule = ruleLoader.Load(5);
    RuleEngine.RuleEngine ruleEngine = new RuleEngine.RuleEngine();
    var firstAdressRuleFunc = 
        ruleEngine.CompileRule<Adresse>(firstAdressRule);
    var secondAdressRuleFunc = 
        ruleEngine.CompileRule<Adresse>(secondAdressRule);
    RuleValidator ruleValidator = new RuleValidator();
    bool result = ruleValidator.
        ValidateValuesAny(person.Adresses_, firstAdressRuleFunc);
    Assert.AreEqual(result, true);
    result = ruleValidator.
        ValidateValuesAny(person.Adresses_, secondAdressRuleFunc);            
    Assert.AreEqual(result, true);
}

Here the Person objects owns a List of Adress objects. The RuleValidator class has some methods that take a List and validates every element of that list against a rule.(ruleValidator.ValidateValuesAny(person.Adresses_, firstAdressRuleFunc) ). The next test shows how the ruleEngine sums up a property and validates this sum against two rules.

[TestMethod]
public void SimpleRuleLoaderPersonValidateRulesSum()
{
    Person person1 = new Person() { 
        Name = "Mathias", Age = 35, Children = 2 };
    Person person2 = new Person() { 
        Name = "Anna", Age = 32, Children = 2 };
    RuleLoader ruleLoader = new RuleLoader();
    // new Rule("Age", Operator.LessThanOrEqual, 50);
    Rule rule1 = ruleLoader.Load(2);
    // new Rule("Children", Operator.GreaterThan, 0);
    Rule rule2 = ruleLoader.Load(3);
    RuleValidator ruleValidator = new RuleValidator();
    var result = ruleValidator.ValidateRulesSum(
        new Person[] { person1, person2 },
        new Rule[] { rule1, rule2 });            
    Assert.AreEqual(result, false);
}

Here is use the ValidateRulesSum method. This method takes an array of objects (person1, person2) and an array of rules (rule1, rule2). It takes the first rule checks what property of the Person object this rule uses, sums up that property value of all overtaken objects and checks that sum against the rule. It returns true if the sum of all properties pass all rules. The next test shows the usage of the expression evaluator rules. In this case the Rules are expression rules ( like ” Name = ‘mathias’ “).

[TestMethod]
public void SimpleRuleLoaderPersonEvaluateExpression()
{
    Person person1 = new Person() 
        { Name = "Mathias", Age = 35, Children = 2 };
    ExpressionRuleLoader expressionRuleLoader = new ExpressionRuleLoader();
    // new Rule(" Name = 'mathias' ");
    Rule rule1 = expressionRuleLoader.Load(1);
    // new Rule(" Age = 35 ");
    Rule rule2 = expressionRuleLoader.Load(2);           
    RuleValidator ruleValidator = new RuleValidator();
    var result = ruleValidator.ValidateExpressionRules(person1, rule1);
    Assert.AreEqual(result, true);
    result = ruleValidator.ValidateExpressionRules(person1, rule2);
    Assert.AreEqual(result, true);
}

The RuleValidator class has methodes (e.g. ValidateExpressionRules) to validate the Expression Rules against objects. In this case the rules are ” Name = ‘Mathias’ ” and ” Age = 35 “. To look what the expression evaluator can afford and what not please look at my the implementing expression evaluator in c# post.
The source code of the expression evaluator is part of the ruleengine.codeplex.com project and can also be downloaded from simpleexpeval.codeplex.com.

Advertisements

implementing expression evaluator in c#

In this post i want to write about the implementation an expression evaluator. This expression evaluator evaluates simple c style expressions. I wrote it to implement defined rules like in my building a rule engine in c# post. A description about the use of the expression evaluator can be found at building a rule engine in c# (part 4: extending the rule engine to evaluate defined expressions). You can download the expression evaluator and the source code from (http://simpleexpeval.codeplex.com/). Now i want to show the use of the expression evaluator and then i will describe the implementation of the parts of the expression evaluator. That is the list of operations the expression evaluator supports:

  • open bracket ‘(‘ and close bracket ‘)’
  • addition ‘+’
  • subtraction ‘-‘
  • multiblication ‘*’
  • division ‘/’
  • modulo ‘%’
  • equality ‘=’
  • unequality ‘!=’
  • bigger than ‘>’
  • bigger than or equal ‘>=’
  • smaller than ‘<‘
  • smaller than or equal ‘<=’
  • logical and ‘&&’
  • logical or ‘||’

and the following data types:

  • integer ’10’
  • double ‘10.2’
  • string ‘”test”‘
  • boolean ‘true’ or ‘false’

Here is a example of the use of the expression evaluator.

Person person = new Person() { Name = "Mathias", Age = 36, Children = 2, Married = true };
var ruleText = " (Children = 2 && Married = true) || Age > 36 ";
Evaluator evaluator = new Evaluator();
var evaluatorResult = evaluator.Evaluate<Person>(ruleText, person);

As we see the expression evaluator takes a rule text and a object. In this case it is a person object with four properties. I use this properties in the rule text and the expression evaluator replaces the property name with the value of the overtaken object. That means that for the expression evaluator the rule text looks like ” (2 = 2 && true = true) || 36 > 36 “. Now the expression evaluator can evaluate that text and check if the expression is true or false. In this case it is true because 2 = 2 and true = true.
The expression evaluator consist of four parts. The Lexer that reads every character, the Parser that builds the expression parts the infix to postfix converter and the evaluator that takes the expression parts and evaluates it to a specific result. I want to start with a description of the lexer. It is a so called LL(k) lexer that means it left associative and starts with the first left character. The k means how many character the lexer can look ahead. In this case it is only one character. Here is the class diagram of the lexer.

SimpleExpressionEvaluatorLexer

In the Lexer class i have implementet the basis functionality for a LL(k) lexer. The ExpressionEvaluatorLexer class contains the specific implementation for the expression evaluator. The most importand part is the NextToken method that reads character after character and builds Tokens out of the characters. Here is the code for the NextToken method.

public override Token NextToken()
{
    while (lookahead[lookaheadIndex] != EOF)
    {
        switch (lookahead[lookaheadIndex])
        {
            case '.': ConsumeItem(); return new Token(TokenType.POINT, ".");
            case '+': ConsumeItem(); return Plus();
            case '-': ConsumeItem(); return Minus();
            case '*': ConsumeItem(); return Multibly();
            case '/': ConsumeItem(); return Divide();
            case '%': ConsumeItem(); return Modulo();
            case '<': ConsumeItem(); return Smaller();
            case '>': ConsumeItem(); return Greater();
            case '=': ConsumeItem(); return Equal();
            case '!': ConsumeItem(); return Unequal();
            case '"': ConsumeItem(); return String();
            case '\'': ConsumeItem(); return SingleQuoteString();
            case '|': ConsumeItem(); return Or();
            case '&': ConsumeItem(); return And();                    
            case '(': ConsumeItem(); return new Token(TokenType.OPENBRACKET, "(");
            case ')': ConsumeItem(); return new Token(TokenType.CLOSEBRACKET, ")");
            default:                        
                if (IsLetter())
                {
                    return Terminal();
                }
                if (IsNumber())
                {
                    return Number();
                }
                if (IsWhitespace())
                {
                    ConsumeItem();
                    continue;
                }
                throw new Exception("invalid character." + lookahead[lookaheadIndex]);
        }
    }
    return new Token(TokenType.EOF, "<EOF>");
}

As we see it is a loop that loops every character in the lookahead char array. If it finds a specific character for example a digit, the lexer assumes that it has an integer or an double. So i calls the Number method.

private Token Number()
{
    bool hasPoint = false;
    StringBuilder stringBuilder = new StringBuilder();
    while (IsNumber() || IsPoint() || IsExponent())
    {
        if (IsPoint())
        {
            hasPoint = true;
        }
        stringBuilder.Append(lookahead[lookaheadIndex]);
        ConsumeItem();
    }
    if (hasPoint)
        return new Token(TokenType.DOUBLE, stringBuilder.ToString());
    return new Token(TokenType.INTEGER, stringBuilder.ToString());
}

The Number method tries to find out if the text is a integer or a double and it returns an integer or an double Token with the value it read. The lexer does this for every character and the parser controls the lexer and tells it to read the next token. That means the lexer does not read every character in the character array, it reads it token by token. If it has identified one token it returns the control back to the parser. Here we see the class diagram of the parser.

SimpleExpressionEvaluatorParser

The ExpressionEvaluationParser reads the token from the lexer and builds a list of AbstractSyntaxTreeNodes. That nodes get rearranged by the InfixToPostfix class and then the result list is passed to the expression evaluator. That is necessary because the expression evaluator can handle operator precedence, that means that a multiblication is performed before and addition or substraction and that calculations are performed before comparison operations.

public AbstractSyntaxTreeNode BuildAST()
{                                    
    if (lookahead[lookaheadIndex].Type_ == TokenType.EQUAL)
    {
        return Equal();
    }
    else if (lookahead[lookaheadIndex].Type_ == TokenType.INTEGER)
    {
        return Integer(lookahead[lookaheadIndex].Text);
    }
    ...
public EqualNode Equal()
{
     EqualNode equalNode = new EqualNode();
     Match(TokenType.EQUAL);
     return equalNode;
}
public IntegerNode Integer(string value)
{
    IntegerNode integerNode = new IntegerNode();
    Match(TokenType.INTEGER);            
    integerNode.Value = int.Parse(value);
    return integerNode;
}

That is the parser code that perfoms the transformation from the tokens to the AbstractSyntaxTreeNodes. That means if it finds an Eqaul token it builds a EqualNode and consumes the next token, if it finds a Integer token it overtakes the value of the Integer, builds a IntegerNode with the overtaken value and consumes the next token with the match methode. The parser has a tranformation method for every token. We now have a list of AbstrancSyntaxTreeNodes that represent the overtaken text. After that we have to get the AbstractSyntaxTreeNodes in the correct order to handle operator precedence and open and closed brackets. The best way to do that is to convert the expression that is defined in infix into a postfix expression. The benefit of a postfix operation is that it is much easier to evaluate than an infix expression. All we need for the infix to postfix transformation is an operator stack. I will show an example for the transformation of a list of infix operations to a list of postfix operations. We start with the expression 5 + 3 * 2 – 1. To convert that infix expression to a postfix expression we take every part of the expression and tranform it. We need a stack for the operator precedence and a list that contains the postfix expressions after the transformation.

  • 5 => to List ( 5 )
  • + => to Operator Stack ( + )
  • 3 => to List ( 5, 3 )
  • * => to Operator Stack ( +, * )
  • 2 => to List ( 5, 3, 2 )
  • – => lower precedence than * on Stack => to List ( 5, 3, 2, * ) => Operator Stack ( +, – )
  • 1 => to List ( 5, 3, 2, *, 1 )
  • pop Operator Stack ( +, – ) => to List ( 5, 3, 2, *, 1, -, + )

Thats how the postfix list looks after the transformation: ( 5, 3, 2, *, 1, -, + )
Now we have one big benefit, we can take that list and evaluate it from the beginning to the end without mind of the operator precedence. But what is if we have brackets in out expression for example the expression ( 5 + 3 ) * 2. We can handle that also very easily.

  • ( => handle bracket, instanciate new Operator Stack
  • 5 => to List ( 5 )
  • + => to new Operator Stack ( + )
  • 3 => to List ( 5, 3 )
  • ) => close bracket pop Operator Stack ( + ) => to List ( 5, 3, + )
  • * => to Operator Stack ( * )
  • 2 => to List ( 5, 3, +, 2 )
  • pop Operator Stack ( * ) => to List ( 5, 3, +, 2, * )

As we see all brackets are gone and if we use postfix we dont need them anymore, if we evaluate that expression we can calculate the right result without any brackets.

What we now need to do is do define the operator precedence for all operators. That is necessary because at first the expression evaluator has to perform calculations (multiblication, division and modulo before addition and subtraction) then the expression evaluator has to check for comparison ( equal, unequal, greater than, smaller than). After he evaluated this it has to check for logical combinators (logical and, logical or). So that is the operator precedence of the expression evaluator.

  • *, /, % have the highest order
  • +, – have the second highest order
  • =, !=, >, >=, <, <= have the third highest order
  • &&, || have the lowest order

Here is the code for the ConvertInfixToPostfix expression that performs exact that transformation from an infix to a postfix expression.

public List<AbstractSyntaxTreeNode> ConvertInfixToPostfix(List<AbstractSyntaxTreeNode> postfixList)
{
    List<AbstractSyntaxTreeNode> returnList = new List<AbstractSyntaxTreeNode>();
    Stack<AbstractSyntaxTreeNode> operatorStack = new Stack<AbstractSyntaxTreeNode>();
    var position = 0;
    for (int i = position; i < postfixList.Count; i++)            
    {
        var item = postfixList[i];
        if (item is IntegerNode || item is DoubleNode || item is VariableNode ||
            item is BooleanNode || item is StringNode)
        {
            returnList.Add(item);
        }
        else if (item is OpenBracketNode)
        {
            i = ConvertInfixToPostfixBracket(i, postfixList, returnList);
        }
        else if (item is OrNode || item is AndNode)
        {
            while (operatorStack.Count > 0)
            {
                var abstractSyntaxTreeNode = operatorStack.Pop();
                returnList.Add(abstractSyntaxTreeNode);
            }
            operatorStack.Push(item);
        }
        else if (item is GreaterThenNode || item is GreaterThenOrEqualNode ||
            item is SmallerThenNode || item is SmallerThenOrEqualNode ||
            item is EqualNode || item is UnEqualNode)
        {
            if (operatorStack.Count() > 0 && (operatorStack.Peek() is MulNode || 
                operatorStack.Peek() is DivNode ||
                operatorStack.Peek() is ModuloNode))
            {
                AbstractSyntaxTreeNode node = operatorStack.Pop();
                returnList.Add(node);
            }
            else if (operatorStack.Count() > 0 && (operatorStack.Peek() is AddNode || 
                operatorStack.Peek() is SubNode))
            {
                AbstractSyntaxTreeNode node = operatorStack.Pop();
                returnList.Add(node);
            }
            operatorStack.Push(item);
        }
        else if (item is AddNode || item is SubNode)
        {
            if (operatorStack.Count() > 0 && (operatorStack.Peek() is MulNode || 
                operatorStack.Peek() is DivNode ||
                operatorStack.Peek() is ModuloNode))
            {
                AbstractSyntaxTreeNode node = operatorStack.Pop();
                returnList.Add(node);
            }
            operatorStack.Push(item);
        }
        else if (item is MulNode || item is DivNode || item is ModuloNode)
        {
            operatorStack.Push(item);
        }                
        position++;
    }
    while (operatorStack.Count > 0)
    {
        var abstractSyntaxTreeNode = operatorStack.Pop();
        returnList.Add(abstractSyntaxTreeNode);
    }
    return returnList;
}

As we see the code is very simple it loops all AbstractSyntaxTreeNodes in the overtaken list and if it finds a Integer, Double, String or Boolean AbstractSyntaxTreeNode it adds it to the return list. If it finds a add or sub Node it checks the stack. If it finds a mul, div or mod Node it pops the Node (that has a higher precedence than the add or sub Node) and adds it to the list, then it pushes the actual operator to the stack. It works simular for comparison operators ( equal, unequal … ) and for logical operators ( and, or). If it comes to a open bracket it calls the ConvertInfixToPostfixOpenBracket method. That method instanciates a new operator stack and works the same as the ConvertInfixToPostfix method. It calls it self recursive for nested brackets. Now we have brought our AbstractSyntaxTreeNodes in the correct order and we can overhand our postfix list to the expression evaluator. That evaluator takes one node after the other and evaluates them. For that evaluation we need a stack that takes the result of the calculations. For example if we want to calculate the result of the expression ( 5, 3, 2, *, 1, -, + )

  • 5 => to the Stack ( 5 )
  • 3 => to the Stack ( 5, 3 )
  • 2 => to the Stack ( 5, 3, 2 )
  • * => take first two parameter from the Stack ( 2, 3 ) and interchange them because they are in wrong order. ( 3, 2 ) => calculate the multiblication 3 * 2 = 6 => push result to Stack ( 5, 6 )
  • 1 => to the Stack ( 5, 6, 1 )
  • – => take first two parameter from the Stack ( 1, 6 ) and interchange them because they are in wrong order. ( 6, 1 ) => calcualte the subtraction 6 – 1 = 5 => push result to Stack ( 5, 5 )
  • + => take first two parameter from the Stack ( 5, 5 ) and interchange them because they are in wrong order. ( 5, 5 ) => calculate the addition 5 + 5 = 10 => push result to Stack

Now we can take the result from the Stack ( 10 ) => that is the result. Here is the code that shows the Evaluate method that takes the list of the AbstractSyntaxTreeNode and evaluates them.

public bool Evaluate<T>(List<AbstractSyntaxTreeNode> postfixList, 
    Dictionary<string, AbstractSyntaxTreeNode> symbolTable, T objectValue)
{
    foreach (var item in postfixList)
    {
        if (item is IntegerNode || item is DoubleNode || 
            item is BooleanNode || item is StringNode)
        {
            valueStack.Push(item);
        }
        else if (item is AddNode)
        {
            var secondoperand = valueStack.Pop();
            var firstoperand = valueStack.Pop();
            if (secondoperand is IntegerNode && firstoperand is IntegerNode)
            {
                 IntegerNode integerNode = new IntegerNode();
                 integerNode.Value = ((IntegerNode)firstoperand).Value + 
                      ((IntegerNode)secondoperand).Value;
                 valueStack.Push(integerNode);
            }
         }
         else if (item is MulNode)
         {
              var secondoperand = valueStack.Pop();
              var firstoperand = valueStack.Pop();
              if (secondoperand is IntegerNode && firstoperand is IntegerNode)
              {
                   IntegerNode integerNode = new IntegerNode();
                        integerNode.Value = ((IntegerNode)firstoperand).Value *
                   ((IntegerNode)secondoperand).Value;
                   valueStack.Push(integerNode);
              }
         }
         else if (item is EqualNode)
         {
              var secondoperand = valueStack.Pop();
              var firstoperand = valueStack.Pop();
              if (secondoperand is IntegerNode && firstoperand is IntegerNode)
              {
                   BooleanNode booleanNode = new BooleanNode();
                   booleanNode.Value = ((IntegerNode)firstoperand).Value == 
                        ((IntegerNode)secondoperand).Value;
                   valueStack.Push(booleanNode);
         }
}
...

The evaluator part is really easy, it uses on stack for the values and the calculation results. So it takes on AbstractSyntaxTreeNode after the other and if it is data it puts it to the stack, if it is an operator it performs the operation and puts the result to the stack. If it gets the following postfixlist ( IntegerNode(4), IntegerNode(5), IntegerNode(3), MulNode(*),AddNode(+)) it starts with pushing 4 to the stack, pushing 5 to the stack, pushing 3 to the stack. Poping 3 and 5 from the stack perform the multiblication and push 15 to the stack. Then it pops 15 and 4 from the stack, performs the addition and pushes 19 to the stack. So the result would be 15. The problem is that the Evaluate methode returns bool. That is because the expression evaluator can only perform expression the have true or false as result. So if you evaluate ( 4 + 5 * 3 ) you get an exception. You have to write ( 4 + 5 * 3 = 19 ) that is a correct expression that returns true or false. The expression evaluator was written to perform rules against the properties of objects (building a rule engine in c# (part 4: extending the rule engine to evaluate defined expressions)) and a rule has to return always true or false. You can download the source code from http://simpleexpeval.codeplex.com/.


Building a rule engine in c# (part 4: extending the rule engine to evaluate defined expressions)

In the first three posts of building a rule engine in c# building a rule engine in c#, building a rule engine in c# (part 2: extending the rule engine to handle collections), building a rule engine in c# (part 3: extending the rule engine to handle aggregations of collections) the definition of the rules are very basic and did not look very clear. So i decided to implement an expression evaluator. With this expression evaluator the definition of the rules becomes much clearer. I will show the difference in the next three pictures. The first one shows the definition of the rules how i described it in the past three posts.

ProcessingEngineDatabase

This second picture shows the definition with a expression evaluator.

ComplexProcessingRule

If we use a expression evaluator we can also use logical combinators (and, or) to combine the defined rules. The third picture shows how the processing rules would look like if they are combined.

ComplexProcessingRuleCombinator2

The problem with that processing rules is that in the first three post i used expression trees to generate the rules but i had the seperate parts of the rules and so i needed no parser. Now we have to develop a parser that parses the expression and an evaluator that checks the overtaken object against the parsed expression. In this post i will show how to use the expression evaluator i posted at codeplex (https://simpleexpeval.codeplex.com/) to validate a given object against the defined rule. In the additional blog post implementing an expression evaluator in c# i describes how the expression evaluator works. But in this post i will describe how to use it to define the rules you need to check your object again. The expression evaluator support the following operators:

  • open bracket ‘(‘ and close bracket ‘)’
  • addition ‘+’
  • subtraction ‘-‘
  • multiblication ‘*’
  • division ‘/’
  • modulo ‘%’
  • equality ‘=’
  • unequality ‘!=’
  • bigger than ‘>’
  • bigger than or equal ‘>=’
  • smaller than ‘<‘
  • smaller than or equal ‘<=’
  • logical and ‘&&’
  • logical or ‘||’

and the following data types:

  • integer ’10’
  • double ‘10.2’
  • string ‘”test”‘
  • boolean ‘true’ or ‘false’

This is a example how to use the expression evaluator with a defined person that is evaluated against a rule. That is the definition of the person class:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public int Children { get; set; }
    public bool Married { get; set; }
}

Now the defined rule text (” (Children = 2 && Married = true) || Age > 36 “) is evaluated against the defined person object.

Person person = new Person() { Name = "Mathias", Age = 36, Children = 2, Married = true };
RuleLoader ruleLoader = new RuleLoader();
Rule processingRule = ruleLoader.LoadProcessingRule(5);
//processingRule.ProcessingRule = " (Children = 2 && Married = true) || Age > 36 ";
Evaluator evaluator = new Evaluator();
var evaluatorResult = evaluator.Evaluate<Person>(ruleText, person);

The evaluatorResult is true because the person object has two children and is married. Here is a second example that shows that it is possible to use properties at any possition in the rule text.

Person person = new Person() { Name = "Mathias", Age = 36, Children = 2, Married = true };
RuleLoader ruleLoader = new RuleLoader();
Rule processingRule = ruleLoader.LoadProcessingRule(5);
//processingRule.ProcessingRule = " Name != 'test' && Children <= Age / 20 "; 
Evaluator evaluator = new Evaluator(); 
var evaluatorResult = evaluator.Evaluate<Person>(ruleText, person);

The evaluatorResult is also true because Age / 20 is smaller than Children and the name is unequal ‘test’. If you want to use the expression evaluation engine you can download it from codeplex (https://simpleexpeval.codeplex.com/) and add a reference in your visual studio project. Then you can use the Evaluator class to evaluate the defined rule text against an overtaken object. In the blog post implementing an expression evaluator in c# i describe the implementation of the expression evaluator and in the post building a rule engine in c# (part 5: bringing it all together) i try to give an overview of the usage of the ruleengine.codeplex.com project.