Showing posts with label runtime delegats. Show all posts
Showing posts with label runtime delegats. Show all posts

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   

Thursday, June 23, 2011

Build XML based Business Rule Engine in .NET 4 – Post 1 (Introduction and XML definition)

The requirement was simple that system should calculate Transaction Fee when distributing a product to external provider. We could have many providers and each have different rules to calculate Transaction Fee from sale price. The emphasis is on “Transaction Fee must be highly configurable”. How much *highly* configurable is contextual focus. It has to define limitation.
I thought of defining extensible lightweight XML rules so that end user can easily configure the rules or define new one of his choice. Later will develop a simple ‘Rule Engine’ to execute rules at runtime. I thought of ‘Forward Chaining’ where a distributor can have multiple rules to validate and each rule is combination of M conditions and N actions. If I could be able to have evaluated conditions - means to infer true or false at runtime then it will be achievement. Afterwards, I will go same way for actions. simple structure of a rule:

Rule => IF condition is true THEN execute action.

Let us define xml at first stage and use it at the end (its turn will not come until we are done other things for sure). 

<?xml version="1.0" encoding="utf-8" ?>
<Distributors>
<Amazon Key="Publizone_Price_Rule" Active="True">
  <Rule Name="transactionfee_is_5_percent">
    <Conditions LogicalOperation="AND">
      <Condition FieldName="NetProductPrice" Operation="GreaterOrEqual" Value="0"/>
    </Conditions>
    <Actions>
      <!--Calculate 5% of Net Price-->
      <Action Key="A1" FieldName="NetProductPrice" Operation="GreaterOrEqual" Data="5%" />
      <!--set value to TransactionFee property-->
      <Action FieldName="TransactionFee" Operation="EqualTo" Data="[A1]" />
    </Actions>
  </Rule>
  <!--If DRM is Watermarking (2) and TransactionFee < 4$ then TransactionFee = 4$-->
  <Rule Name="drm_is_watermarking">
    <Conditions LogicalOperation="AND">
      <Condition FieldName="DRM" Operation="Equals" Value="2"/>
      <Condition FieldName="TransactionFee" Operation="LessThan" Value="4"/>
    </Conditions>
    <Actions>
      <Action FieldName="TransactionFee" Operation="EqualTo" Value="4"/>
    </Actions>
  </Rule>
  <!--If DRM is hard DRMed (3) and TransactionFee < 8$  then TransactionFee = 8$-->
  <Rule Name="drm_is_digitaldrm">
    <Conditions LogicalOperation="AND">
      <Condition FieldName="DRM" Operation="Equals" Value="3"/>
      <Condition FieldName="TransactionFee" Operation="LessThan" Value="8"/>
    </Conditions>
    <Actions>
      <Action FieldName="TransactionFee" Operation="EqualTo" Value="8"/>
    </Actions>
  </Rule>
  <!--TransactionFee is always maximum 10$ -->
  <Rule Name="transactionfee_should_lessthan_10">
    <Conditions LogicalOperation="AND">
      <Condition FieldName="TransactionFee" Operation="GreaterThan" Value="10"/>
    </Conditions>
    <Actions>
      <Action FieldName="TransactionFee" Operation="EqualTo" Value="10"/>
    </Actions>
  </Rule>
</Amazon>

<iTune Key="iTune_Price_Rule" Active="True">
  <Rule Name="not_yet_defined">
  </Rule>
</iTune>
               
<GoogleBooks Key="GB_Price_Rule" Active="True">
  <Rule Name="not_yet_defined">
  </Rule>
</GoogleBooks>
</Distributors>

This is dummy a rule just as an example. Suppose Amazone will in their purchase return the following parts, per transaction.
1.       Exposed net price (i.e. the one that appears to the distributor)
2.       Transaction fee (5% of the above price, however min.  $2 by watermarked materials, min. $8 on hard DRM'ede materials, and always max. $10)
3.       "Genuine net price", ie. Exposed net price – Transaction fee

No rule definition for rest of the distributors like iTune and Google Books right now in XML.

In next post I will start building  following model (Keep it simple and stupid).

Want to read second post? click here to read>>


Your comments are greatly appreciated :