Tuesday, June 28, 2011

Build XML based Business Rule Engine in .NET 4 – Post 3 (write runtime condition predicate)

It is continuation of first post - << click here to read
and second post - < click here to read

Dynamicity is of great advantage. First of all let us move forward step wise, define delegate to a separate method;
rule.AddCondtion(po => po.NetProductPrice >= 0);
should tends to
rule.AddCondtion(IsPriceGreaterOrEqualToZero());

public static Predicate<PriceObject> IsPriceGreaterOrEqualToZero()
{
    return delegate(PriceObject po) { return po.NetProductPrice >= 0; };
}

Let us consider even more generic function to write. Point is every condition returns  Predicate<PriceObject> and internally the condition result is a Boolean variable (true/false). Why not create runtime generic delegate to handle Equals operator for example. We will make it compatible to other operators later.

public static Predicate<T> GetConditiPredicate<T>(string fieldName, string operatorName, string value)
{
    Type itemType = typeof(T);
    ParameterExpression predParam = Expression.Parameter(itemType, "po");
    PropertyInfo propertyInfo = itemType.GetProperty(fieldName);
    Expression left = Expression.Property(predParam, propertyInfo);
    Expression right = Expression.Constant(ObjTypeConverter.ChangeType(value, propertyInfo.PropertyType), propertyInfo.PropertyType);
    Expression equality = Expression.GreaterThanOrEqual(left, right);
    Func<T, bool> function = (Func<T, bool>)Expression.Lambda(equality, new[] { predParam }).Compile();
    return new Predicate<T>(function);
}

And caller can deliver a call like this

rule.AddCondtion(Dynamic.GetConditiPredicate<PriceObject>("DRM", "Equals", "2"));

Well compile and see if it works fine. It should :)

Now let us replace the statement with more general one….
//Expression equality = Expression.GreaterThanOrEqual(left, right);
Expression result = GetExpression(operatorName, left, right);
Func<T, bool> function = (Func<T, bool>)Expression.Lambda(result, new[] { predParam }).Compile();

And

public static Expression GetExpression(string operatorName, Expression left, Expression right)
{
    Expression result = null;
    switch (operatorName.ToLower())
    {
        case "equals":
            result = Expression.Equal(left, right);
            break;
        case "greaterorequal":
            result = Expression.GreaterThanOrEqual(left, right);
            break;
        case "lessorequal":
            result = Expression.LessThanOrEqual(left, right);
            break;
        case "greaterthan":
            result = Expression.GreaterThan(left, right);
            break;
        case "lessthan":
            result = Expression.LessThan(left, right);
            break;
        case "add":
            result = Expression.Add(left, right);
            break;
        case "subtract":
            result = Expression.Subtract(left, right);
            break;
        case "multiply":
            result = Expression.Multiply(left, right);
            break;
        case "divide":
            result = Expression.Divide(left, right);
            break;
        default:
            throw new Exception("Unknown Operator Name");
    }
    return result;
}
           
 This was simple to handle isolated conditions. How to handle Actions that may depend one another give us new dimension.

If want to play with Rule Engine source code - I extracted from my business case for demo purpose. download from skydrive. (FileName: ConfigurableRuleEngine.zip)
Comments please   

7 comments:

  1. Nice Post. Thanks for sharing this.
    Can I have the full sample project (source code) please?

    ReplyDelete
    Replies
    1. Done; Sorry for delay. I added the link 'download from skydrive' FileName: ConfigurableRuleEngine.zip - in the bottom of this post.

      /Basharat

      Delete
  2. very nice job my friend.

    ReplyDelete
  3. As Salamu Alaikum Bhai Basharat ,

    i am from Delhi,

    i was using your code ,
    and i found that it works for decimal types only
    if a DateTime Parameter in priceobject is used it does not support it and it is obvious because you have used decimal types for the engine in action ....

    can you give a solution for all Data types ??

    JazakAllahu Khair

    Hope All is good in Pakistan InshAllah !!!

    ReplyDelete
  4. And Bhai can u please give a application using mvc3 with aspx view engine ......


    JazakAllahu Khair

    ReplyDelete
  5. Hi Bashrat.Nice article.but i am unable to download the file from the above link.Also what is the LogicalOperation Operation .is the custom developed class(LogicalOperation) or predefined one,please confirm

    ReplyDelete
  6. Great & helpful article. I was trying to figure out how to define a custom action whereby it is a custom method I would like to invoke. Do you have an example of such a call?

    ReplyDelete