So I am building a MicroRuleEngine (Would love to see this take off as an OpenSource project) and I am running into a null reference Error When executing the compiled ExpressionTree and I am not exactly sure why. Rules against the simple properties work but going against Child Properties aka Customer.Client.Address.StreetName etc. do not work.
Below is the MicroRuleEngine
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
namespace Trial
{
public class MicroRuleEngine
{
public bool PassesRules<T>(List<Rule> rules, T toInspect)
{
bool pass = true;
foreach (var rule in rules)
{
var cr = this.CompileRule<T>(rule);
pass = pass && cr.Invoke(toInspect);
if (!pass)
return pass;
}
return pass;
}
public Func<T, bool> CompileRule<T>(Rule r)
{
var paramUser = Expression.Parameter(typeof(T));
Expression expr = BuildExpr<T>(r, paramUser);
// build a lambda function User->bool and compile it
return Expression.Lambda<Func<T, bool>>(expr, paramUser).Compile();
}
Expression BuildExpr<T>(Rule r, ParameterExpression param)
{
Expression propExpression;
Type propType;// typeof(T).GetProperty(r.MemberName).PropertyType;
ExpressionType tBinary;
if (r.MemberName.Contains('.'))
{
// support to be sorted on child fields.
String[] childProperties = r.MemberName.Split('.');
var property = typeof(T).GetProperty(childProperties[0]);
var paramExp = Expression.Parameter(typeof(T), "SomeObject");
propExpression = Expression.MakeMemberAccess(paramExp, property);
for (int i = 1; i < childProperties.Length; i++)
{
property = property.PropertyType.GetProperty(childProperties[i]);
propExpression = Expression.MakeMemberAccess(propExpression, property);
}
propType = propExpression.Type;
propExpression = Expression.Block(new[] { paramExp }, new[]{ propExpression });
}
else
{
propExpression = MemberExpression.Property(param, r.MemberName);
propType = propExpression.Type;
}
// is the operator a known .NET operator?
if (ExpressionType.TryParse(r.Operator, out tBinary))
{
var right = Expression.Constant(Convert.ChangeType(r.TargetValue, propType));
// use a binary operation, e.g. 'Equal' -> 'u.Age == 15'
return Expression.MakeBinary(tBinary, propExpression, right);
}
else
{
var method = propType.GetMethod(r.Operator);
var tParam = method.GetParameters()[0].ParameterType;
var right = Expression.Constant(Convert.ChangeType(r.TargetValue, tParam));
// use a method call, e.g. 'Contains' -> 'u.Tags.Contains(some_tag)'
return Expression.Call(propExpression, method, right);
}
}
}
public class Rule
{
public string MemberName { get; set; }
public string Operator { get; set; }
public string TargetValue { get; set; }
}
}
And This is the Test that is Failing
[TestMethod]
public void ChildPropertyRuleTest()
{
Container container = new Container()
{
Repository = "TestRepo",
Shipment = new Shipment() { OrderNumber = "555" }
};
MicroRuleEngine mr = new MicroRuleEngine();
var rules = new List<Rule>() { new Rule() { MemberName = "Shipment.OrderNumber", Operator = "Contains", TargetValue = "55" } };
var pases = mr.PassesRules<Container>(rules, container);
Assert.IsTrue(!pases);
}
So the error I was running into was all the examples I read in trying to find out how to access sub properties were using MemberAccess Expressions to walk down the properties and I found that using PropertyExpressions worked without a problem for the simple tests I have. Below is an update that is now working