I am building a rule checking algorithm that using expression tree, for the type generated using reflection.emit namespace. For some reason i am not able to get it working.
Getting the exception "System.ArgumentException: 'Instance property 'Parameter1' is not defined for type 'System.Object''"
I did try creating dynamic type using expandoObject
instead of reflection.emit
, it did not work
I appreciate it if anybody can help me to solve the problem. Code sample is given.
using System;
using System.Linq;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
namespace RuleEngine
{
public class RuleEngine
{
public static IEnumerable<RuleOutput> RunRule<T>(IEnumerable<RuleCondition> rules, object targetEntity)
{
IEnumerable<RuleOutput> ruleOutput = null;
foreach (var rule in rules)
{
var ruleDetails = rule.RuleConditionDetails;
rule.RuleConditionDetails.ForEach((item) =>
{
item.ConditionOperatorToApply = GetExpressionOperator(item.ConditionOperator);
});
var compiledRules = CompileRule<T>(rule.RuleConditionDetails);
var foundMatchingRule = compiledRules.All(ruleTemp => ruleTemp(targetEntity));
if (foundMatchingRule)
{
ruleOutput = rule.RuleConditionOutput;
break;
}
}
return ruleOutput;
}
private static List<Func<object, bool>> CompileRule<T>(List<RuleConditionDetails> ruleConditionDetailsList)
{
var compiledRules = new List<Func<object, bool>>();
ruleConditionDetailsList.ForEach(ruleConditionDetails =>
{
var parameterExpression = Expression.Parameter(typeof(T));
var binaryExpression = BuildBinaryExpression<T>(ruleConditionDetails, parameterExpression);
compiledRules.Add(Expression.Lambda<Func<object, bool>>(binaryExpression, parameterExpression).Compile());
});
return compiledRules;
}
private static Expression BuildBinaryExpression<T>(RuleConditionDetails ruleConditionDetails, ParameterExpression parameterExpression)
{
Expression expression = null;
var leftOperand = MemberExpression.Property(parameterExpression, ruleConditionDetails.RuleParameter);
ExpressionType expressionType;
Type operandType = null;
MethodInfo methodInfo = null;
if (ExpressionType.TryParse(ruleConditionDetails.ConditionOperatorToApply, out expressionType))
{
operandType = typeof(T).GetProperty(ruleConditionDetails.RuleParameter).PropertyType;
var rightOperand = Expression.Constant(Convert.ChangeType(ruleConditionDetails.ParameterValue, operandType));
expression = Expression.MakeBinary(expressionType, leftOperand, rightOperand);
}
else
{
if (ruleConditionDetails.ConditionOperatorToApply == "Contains")
{
ruleConditionDetails.ParameterValue = ruleConditionDetails.ParameterValue.ToString().Split(',').ToList();
operandType = Type.GetType("System.Collections.Generic.List`1[System.String]");
methodInfo = operandType.GetMethod(ruleConditionDetails.ConditionOperatorToApply);
var rightOperand = Expression.Constant(Convert.ChangeType(ruleConditionDetails.ParameterValue, operandType));
expression = Expression.Call(rightOperand, methodInfo, leftOperand);
}
}
return expression;
}
private static string GetExpressionOperator(string expressionOperator)
{
string tempOperator = null;
switch (expressionOperator)
{
case "==":
tempOperator = "Equal";
break;
case ">":
tempOperator = "GreaterThan";
break;
case ">=":
tempOperator = "GreaterThanOrEqual";
break;
case "<":
tempOperator = "LessThan";
break;
case "<=":
tempOperator = "LessThanOrEqual";
break;
case "!=":
tempOperator = "NotEqual";
break;
case "IN":
tempOperator = "Contains";
break;
default:
tempOperator = expressionOperator;
break;
}
return tempOperator;
}
}
public class RuleCondition
{
public int RuleId { get; set; }
public List<RuleConditionDetails> RuleConditionDetails { get; set; }
public List<RuleOutput> RuleConditionOutput { get; set; }
}
public class RuleConditionDetails
{
public string RuleParameter { get; set; }
public string ConditionOperator { get; set; }
public object ParameterValue { get; set; }
public string ConditionOperatorToApply { get; set; }
}
public class RuleOutput
{
public string RuleParameter { get; set; }
public object ParameterValue { get; set; }
}
class Program
{
static void Main(string[] args)
{
Dictionary<string, object> keyValues = new Dictionary<string, object>();
keyValues.Add("Parameter1", "XYZ");
keyValues.Add("Parameter2", 999);
var rules = GetRuleCondtions();
IList<PropertyDescriptor> propertyDescriptors = new List<PropertyDescriptor>();
foreach (var item in keyValues)
{
var valueType = item.Value == null ? typeof(string) : item.Value.GetType();
var propertyDescriptor = new PropertyDescriptor(item.Key, valueType);
propertyDescriptors.Add(propertyDescriptor);
}
DynamicClassBuilder dynamicClassBuilder = new DynamicClassBuilder("RuleEngineInputData", propertyDescriptors);
var ruleInputDataObject = dynamicClassBuilder.CreateObject();
RuleEngine.RunRule<Object>(rules, ruleInputDataObject);
Console.ReadKey();
}
private static IList<RuleCondition> GetRuleCondtions()
{
IList<RuleCondition> conditons = new List<RuleCondition>();
conditons.Add(new RuleCondition()
{
RuleConditionDetails = new List<RuleConditionDetails>()
{
new RuleConditionDetails()
{
RuleParameter = "Parameter1",
ConditionOperator = "==",
ParameterValue = "XYZ"
},
new RuleConditionDetails()
{
RuleParameter = "Parameter2",
ConditionOperator = "==",
ParameterValue = "999"
},
},
});
return conditons;
}
}
// Code to create type at runtime
public class DynamicClassBuilder
{
AssemblyName asemblyName = null;
IEnumerable<PropertyDescriptor> propertyDescriptors = null;
public DynamicClassBuilder(string ClassName, IEnumerable<PropertyDescriptor> propertyDescriptors)
{
this.asemblyName = new AssemblyName(ClassName);
this.propertyDescriptors = propertyDescriptors;
}
public Type CreateType()
{
// Create the type builder for creating the class
TypeBuilder typeBuilder = this.GetDynamicTypeBuilder();
// Create the constructor inside the class
CreateConstructor(typeBuilder);
// Create the properties inside the class
foreach (var propertyDescriptor in propertyDescriptors)
{
CreateProperty(typeBuilder, propertyDescriptor.PropertyName, propertyDescriptor.PropertyType);
}
// Create class
Type type = typeBuilder.CreateTypeInfo();
return type;
}
public object CreateObject()
{
// Create the type builder for creating the class
TypeBuilder typeBuilder = this.GetDynamicTypeBuilder();
// Create the constructor inside the class
CreateConstructor(typeBuilder);
// Create the properties inside the class
foreach (var propertyDescriptor in propertyDescriptors)
{
CreateProperty(typeBuilder, propertyDescriptor.PropertyName, propertyDescriptor.PropertyType);
}
// Create class
Type type = typeBuilder.CreateTypeInfo();
return Activator.CreateInstance(type);
}
private TypeBuilder GetDynamicTypeBuilder()
{
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(this.asemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
TypeBuilder typeBuilder = moduleBuilder.DefineType(this.asemblyName.FullName
, TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout
, null);
//typeBuilder.SetParent(typeof(RuleEngineInputDataBase));
return typeBuilder;
}
private void CreateConstructor(TypeBuilder typeBuilder)
{
typeBuilder.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
}
private void CreateProperty(TypeBuilder typeBuilder, string propertyName, Type propertyType)
{
FieldBuilder fieldBuilder = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
MethodBuilder getPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
ILGenerator getILGenerator = getPropMthdBldr.GetILGenerator();
getILGenerator.Emit(OpCodes.Ldarg_0);
getILGenerator.Emit(OpCodes.Ldfld, fieldBuilder);
getILGenerator.Emit(OpCodes.Ret);
MethodBuilder setMethodBuilder = typeBuilder.DefineMethod("set_" + propertyName,
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
null, new[] { propertyType });
ILGenerator setILGenerator = setMethodBuilder.GetILGenerator();
Label modifyProperty = setILGenerator.DefineLabel();
Label exitSet = setILGenerator.DefineLabel();
setILGenerator.MarkLabel(modifyProperty);
setILGenerator.Emit(OpCodes.Ldarg_0);
setILGenerator.Emit(OpCodes.Ldarg_1);
setILGenerator.Emit(OpCodes.Stfld, fieldBuilder);
setILGenerator.Emit(OpCodes.Nop);
setILGenerator.MarkLabel(exitSet);
setILGenerator.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getPropMthdBldr);
propertyBuilder.SetSetMethod(setMethodBuilder);
}
public object SetPropertyValues(object accessor, IDictionary<string, object> ruleInputData)
{
Type accessorType = accessor.GetType();
foreach (PropertyInfo propertyInfo in accessorType.GetProperties())
{
object propertyValue = null;
if (ruleInputData.TryGetValue(propertyInfo.Name, out propertyValue))
{
if (propertyInfo.CanRead)
{
propertyValue = Convert.ChangeType(propertyValue, propertyInfo.PropertyType);
propertyInfo.SetValue(accessor, propertyValue);
}
}
}
return accessor;
}
}
}
I have found few issues. First of all, methods should not be generic. You set T
to object
anyway. Instead you should get entity type from ruleInputDataObject
(ruleInputDataObject.GetType()
).
Another problem is the way you're creating lambda in CompileRule
method. You pass want to have lambda with Func<object, bool>
signature and access properties from generated type. This won't work unless you cast parameterExpression
to entity type.
Here is fixed code:
namespace RuleEngine
{
public class PropertyDescriptor
{
public string PropertyName { get; }
public Type PropertyType { get; }
public PropertyDescriptor(string propertyName, Type propertyType)
{
PropertyName = propertyName;
PropertyType = propertyType;
}
}
public class RuleEngine
{
public static IEnumerable<RuleOutput> RunRule(IEnumerable<RuleCondition> rules, object targetEntity)
{
Type entityType = targetEntity.GetType();
IEnumerable<RuleOutput> ruleOutput = null;
foreach (var rule in rules)
{
var ruleDetails = rule.RuleConditionDetails;
rule.RuleConditionDetails.ForEach((item) =>
{
item.ConditionOperatorToApply = GetExpressionOperator(item.ConditionOperator);
});
var compiledRules = CompileRule(rule.RuleConditionDetails, entityType);
var foundMatchingRule = compiledRules.All(ruleTemp => ruleTemp(targetEntity));
if (foundMatchingRule)
{
ruleOutput = rule.RuleConditionOutput;
break;
}
}
return ruleOutput;
}
private static List<Func<object, bool>> CompileRule(List<RuleConditionDetails> ruleConditionDetailsList,
Type entityType)
{
var compiledRules = new List<Func<object, bool>>();
ruleConditionDetailsList.ForEach(ruleConditionDetails =>
{
var parameterExpression = Expression.Parameter(typeof(object));
var entityExpression = Expression.Convert(parameterExpression, entityType);
var binaryExpression = BuildBinaryExpression(ruleConditionDetails, entityExpression, entityType);
compiledRules.Add(
Expression.Lambda<Func<object, bool>>(binaryExpression, parameterExpression).Compile());
});
return compiledRules;
}
private static Expression BuildBinaryExpression(RuleConditionDetails ruleConditionDetails,
Expression parameterExpression, Type entityType)
{
Expression expression = null;
var leftOperand = MemberExpression.Property(parameterExpression, ruleConditionDetails.RuleParameter);
ExpressionType expressionType;
Type operandType = null;
MethodInfo methodInfo = null;
if (ExpressionType.TryParse(ruleConditionDetails.ConditionOperatorToApply, out expressionType))
{
operandType = entityType.GetProperty(ruleConditionDetails.RuleParameter).PropertyType;
var rightOperand =
Expression.Constant(Convert.ChangeType(ruleConditionDetails.ParameterValue, operandType));
expression = Expression.MakeBinary(expressionType, leftOperand, rightOperand);
}
else
{
if (ruleConditionDetails.ConditionOperatorToApply == "Contains")
{
ruleConditionDetails.ParameterValue =
ruleConditionDetails.ParameterValue.ToString().Split(',').ToList();
operandType = Type.GetType("System.Collections.Generic.List`1[System.String]");
methodInfo = operandType.GetMethod(ruleConditionDetails.ConditionOperatorToApply);
var rightOperand =
Expression.Constant(Convert.ChangeType(ruleConditionDetails.ParameterValue, operandType));
expression = Expression.Call(rightOperand, methodInfo, leftOperand);
}
}
return expression;
}
private static string GetExpressionOperator(string expressionOperator)
{
string tempOperator = null;
switch (expressionOperator)
{
case "==":
tempOperator = "Equal";
break;
case ">":
tempOperator = "GreaterThan";
break;
case ">=":
tempOperator = "GreaterThanOrEqual";
break;
case "<":
tempOperator = "LessThan";
break;
case "<=":
tempOperator = "LessThanOrEqual";
break;
case "!=":
tempOperator = "NotEqual";
break;
case "IN":
tempOperator = "Contains";
break;
default:
tempOperator = expressionOperator;
break;
}
return tempOperator;
}
}
public class RuleCondition
{
public int RuleId { get; set; }
public List<RuleConditionDetails> RuleConditionDetails { get; set; }
public List<RuleOutput> RuleConditionOutput { get; set; }
}
public class RuleConditionDetails
{
public string RuleParameter { get; set; }
public string ConditionOperator { get; set; }
public object ParameterValue { get; set; }
public string ConditionOperatorToApply { get; set; }
}
public class RuleOutput
{
public string RuleParameter { get; set; }
public object ParameterValue { get; set; }
}
class Program
{
static void Main(string[] args)
{
Dictionary<string, object> keyValues = new Dictionary<string, object>();
keyValues.Add("Parameter1", "XYZ");
keyValues.Add("Parameter2", 999);
var rules = GetRuleCondtions();
IList<PropertyDescriptor> propertyDescriptors = new List<PropertyDescriptor>();
foreach (var item in keyValues)
{
var valueType = item.Value == null ? typeof(string) : item.Value.GetType();
var propertyDescriptor = new PropertyDescriptor(item.Key, valueType);
propertyDescriptors.Add(propertyDescriptor);
}
DynamicClassBuilder dynamicClassBuilder =
new DynamicClassBuilder("RuleEngineInputData", propertyDescriptors);
var ruleInputDataObject = dynamicClassBuilder.CreateObject();
RuleEngine.RunRule(rules, ruleInputDataObject);
Console.ReadKey();
}
private static IList<RuleCondition> GetRuleCondtions()
{
IList<RuleCondition> conditons = new List<RuleCondition>();
conditons.Add(new RuleCondition()
{
RuleConditionDetails = new List<RuleConditionDetails>()
{
new RuleConditionDetails()
{
RuleParameter = "Parameter1",
ConditionOperator = "==",
ParameterValue = "XYZ"
},
new RuleConditionDetails()
{
RuleParameter = "Parameter2",
ConditionOperator = "==",
ParameterValue = "999"
},
},
});
return conditons;
}
}
// Code to create type at runtime
public class DynamicClassBuilder
{
AssemblyName asemblyName = null;
IEnumerable<PropertyDescriptor> propertyDescriptors = null;
public DynamicClassBuilder(string ClassName, IEnumerable<PropertyDescriptor> propertyDescriptors)
{
this.asemblyName = new AssemblyName(ClassName);
this.propertyDescriptors = propertyDescriptors;
}
public Type CreateType()
{
// Create the type builder for creating the class
TypeBuilder typeBuilder = this.GetDynamicTypeBuilder();
// Create the constructor inside the class
CreateConstructor(typeBuilder);
// Create the properties inside the class
foreach (var propertyDescriptor in propertyDescriptors)
{
CreateProperty(typeBuilder, propertyDescriptor.PropertyName, propertyDescriptor.PropertyType);
}
// Create class
Type type = typeBuilder.CreateTypeInfo();
return type;
}
public object CreateObject()
{
// Create the type builder for creating the class
TypeBuilder typeBuilder = this.GetDynamicTypeBuilder();
// Create the constructor inside the class
CreateConstructor(typeBuilder);
// Create the properties inside the class
foreach (var propertyDescriptor in propertyDescriptors)
{
CreateProperty(typeBuilder, propertyDescriptor.PropertyName, propertyDescriptor.PropertyType);
}
// Create class
Type type = typeBuilder.CreateTypeInfo();
return Activator.CreateInstance(type);
}
private TypeBuilder GetDynamicTypeBuilder()
{
AssemblyBuilder assemblyBuilder =
AssemblyBuilder.DefineDynamicAssembly(this.asemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
TypeBuilder typeBuilder = moduleBuilder.DefineType(this.asemblyName.FullName
, TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout
, null);
//typeBuilder.SetParent(typeof(RuleEngineInputDataBase));
return typeBuilder;
}
private void CreateConstructor(TypeBuilder typeBuilder)
{
typeBuilder.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName);
}
private void CreateProperty(TypeBuilder typeBuilder, string propertyName, Type propertyType)
{
FieldBuilder fieldBuilder =
typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
PropertyBuilder propertyBuilder =
typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
MethodBuilder getPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName,
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType,
Type.EmptyTypes);
ILGenerator getILGenerator = getPropMthdBldr.GetILGenerator();
getILGenerator.Emit(OpCodes.Ldarg_0);
getILGenerator.Emit(OpCodes.Ldfld, fieldBuilder);
getILGenerator.Emit(OpCodes.Ret);
MethodBuilder setMethodBuilder = typeBuilder.DefineMethod("set_" + propertyName,
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
null, new[] {propertyType});
ILGenerator setILGenerator = setMethodBuilder.GetILGenerator();
Label modifyProperty = setILGenerator.DefineLabel();
Label exitSet = setILGenerator.DefineLabel();
setILGenerator.MarkLabel(modifyProperty);
setILGenerator.Emit(OpCodes.Ldarg_0);
setILGenerator.Emit(OpCodes.Ldarg_1);
setILGenerator.Emit(OpCodes.Stfld, fieldBuilder);
setILGenerator.Emit(OpCodes.Nop);
setILGenerator.MarkLabel(exitSet);
setILGenerator.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getPropMthdBldr);
propertyBuilder.SetSetMethod(setMethodBuilder);
}
public object SetPropertyValues(object accessor, IDictionary<string, object> ruleInputData)
{
Type accessorType = accessor.GetType();
foreach (PropertyInfo propertyInfo in accessorType.GetProperties())
{
object propertyValue = null;
if (ruleInputData.TryGetValue(propertyInfo.Name, out propertyValue))
{
if (propertyInfo.CanRead)
{
propertyValue = Convert.ChangeType(propertyValue, propertyInfo.PropertyType);
propertyInfo.SetValue(accessor, propertyValue);
}
}
}
return accessor;
}
}
}