<|start_filename|>src/SimpleRules/SimpleRulesEngineExtensions.cs<|end_filename|>
﻿using System;
using System.Linq;
using System.Reflection;
using SimpleRules.Attributes;
using System.Collections.Generic;

namespace SimpleRules
{
    public static class SimpleRulesEngineExtensions
    {
        public static IEnumerable<ValidationResult> Validate<TConcrete>(this SimpleRulesEngine engine, List<TConcrete> src)
            where TConcrete : class
        {
            return engine.Validate(src);
        }

        public static ValidationResult Validate<TConcrete>(this SimpleRulesEngine engine, TConcrete src)
            where TConcrete : class
        {
            return engine.Validate(new List<TConcrete> { src }).First();
        }

        public static bool HasRuleAttributes(this Type type)
        {
            return type.GetProperties(publicPropFlags)
                       .SelectMany(p => p.GetCustomAttributes<BaseRuleAttribute>(false))
                       .Any();
        }

        public static bool HasRuleMetadataAttribute(this Type type)
        {
            return type.GetCustomAttributes<RuleMetadataAttribute>(false)
                       .Any();
        }

        public static Type FindRuleMetadataType(this Type type)
        {
            var ruleMetaAttr = type.GetCustomAttribute<RuleMetadataAttribute>();
            return ruleMetaAttr.MetaFor;
        }
        
        public static IEnumerable<Tuple<BaseRuleAttribute, PropertyInfo>> GetRules(this Type srcType)
        {
            var attrList = srcType.GetProperties(publicPropFlags)
                                  .SelectMany(c => c.GetCustomAttributes<BaseRuleAttribute>(), (prop, attr) => new { prop, attr })
                                  .Select(p => new Tuple<BaseRuleAttribute, PropertyInfo>(p.attr, p.prop));
            return attrList;
        }

        public static IEnumerable<Tuple<BaseRuleAttribute, PropertyInfo>> GetRules(this Type srcType, Type metadataType)
        {
            var srcProperties = srcType.GetProperties(publicPropFlags);
            var metaProperties = metadataType.GetProperties(publicPropFlags);
            var matchedMetaProperties = srcProperties
                                           .Select(p => p.GetMatchingPropertyInfo(metaProperties))
                                           .Where(r => r != null);            
            var attrList = matchedMetaProperties
                            .SelectMany(c => c.GetCustomAttributes<BaseRuleAttribute>(), (prop, attr) => new { prop, attr })
                            .Select(p => new Tuple<BaseRuleAttribute, PropertyInfo>(p.attr, p.prop));
            return attrList;
        }

        private static PropertyInfo GetMatchingPropertyInfo(this PropertyInfo srcProperty, IEnumerable<PropertyInfo> metaProperties)
        {
            return metaProperties.SingleOrDefault(s => 
                        s.Name == srcProperty.Name && 
                        (s.PropertyType == srcProperty.PropertyType || 
                        s.PropertyType == typeof(object)));
        }
        
        public static bool All(this List<ValidationResult> results)
        {
            return results.All(r => r.IsError);
        }

        public static bool Any(this List<ValidationResult> results)
        {
            return results.Any(r => r.IsError);
        }

        public static Tuple<Type, Type> GetMetadataType(this Dictionary<Type, Type> typeMetaDictionary, Type srcType)
        {
            if (typeMetaDictionary.ContainsKey(srcType))
                return new Tuple<Type, Type>(srcType, typeMetaDictionary[srcType]);

            if (srcType.HasRuleMetadataAttribute())
                return new Tuple<Type, Type>(srcType, srcType.FindRuleMetadataType());

            if (srcType.HasRuleAttributes())
                return new Tuple<Type, Type>(srcType, srcType);

            return null;
        }

        public static PropertyInfo FindEntityKeyPropertyInfo(this Tuple<Type, Type> ruleMetaMap)
        {
            if (ruleMetaMap.Item1 == ruleMetaMap.Item2)
            {
                var propertyWithEntityKeyAttr = ruleMetaMap.Item1
                                                           .GetProperties(publicPropFlags)
                                                           .FirstOrDefault(p => p.GetCustomAttributes<EntityKeyAttribute>().Any());
                if (propertyWithEntityKeyAttr != null)
                    return propertyWithEntityKeyAttr;
                else
                    return null;
            }
            var propertyWithEntityKeyAttrMeta = ruleMetaMap.Item2
                                                       .GetProperties(publicPropFlags)
                                                       .FirstOrDefault(p => p.GetCustomAttributes<EntityKeyAttribute>().Any());
            if (propertyWithEntityKeyAttrMeta != null)
            {
                var matchedProperty = ruleMetaMap.Item1.GetProperties().FirstOrDefault(p => p.Name == propertyWithEntityKeyAttrMeta.Name && p.PropertyType == propertyWithEntityKeyAttrMeta.PropertyType);
                if (matchedProperty != null)
                {
                    return matchedProperty;
                }
            }
            return null;
        }

        public static IEnumerable<Tuple<BaseRuleAttribute, PropertyInfo>> GetRules(this Tuple<Type, Type> metaMapping)
        {
            if (metaMapping.Item1 == metaMapping.Item2)
                return metaMapping.Item1.GetRules();
            return metaMapping.Item1.GetRules(metaMapping.Item2);
        }

        public static object GetEntityKey<TConcrete>(this Dictionary<Type, PropertyInfo> entityKeyCache, TConcrete item)
        {
            if (entityKeyCache.ContainsKey(typeof(TConcrete)))
            {
                var propertyInfo = entityKeyCache[typeof(TConcrete)];
                var keyValue = propertyInfo.GetValue(item);
                return keyValue;
            }
            return null;
        }

        private static BindingFlags publicPropFlags = BindingFlags.Instance | BindingFlags.Public;
    }
}


<|start_filename|>src/SimpleRules/Exceptions/HandlerNotFoundException.cs<|end_filename|>
﻿using System;

namespace SimpleRules.Exceptions
{
    public class HandlerNotFoundException : Exception
    {
        public HandlerNotFoundException(string attrName, string attrType)
            : base($"Unable to find a handler for the attribute {attrName} ({attrType})")
        {
        }
    }
}


<|start_filename|>src/SimpleRules/Attributes/SimpleRuleAttribute.cs<|end_filename|>
﻿using System.Linq.Expressions;

namespace SimpleRules.Attributes
{
    public abstract class SimpleRuleAttribute : BaseRuleAttribute
    {
        public abstract ExpressionType SupportedType { get; }
        public abstract string OtherPropertyName { get; }
        public abstract object ConstantValue { get; }
        public abstract bool CanBeNull { get; }
        public abstract RuleType RuleType { get; }
    }
}


<|start_filename|>src/SimpleRules/StringExtensions.cs<|end_filename|>
﻿using System.Text;

namespace SimpleRules
{
    public static class StringExtensions
    {
        public static string AddSpaces(this string text, bool preserveAcronyms = false)
        {
            if (string.IsNullOrWhiteSpace(text))
                return string.Empty;
            var newText = new StringBuilder(text.Length * 2);
            newText.Append(text[0]);
            for (int i = 1; i < text.Length; i++)
            {
                if (char.IsUpper(text[i]))
                    if ((text[i - 1] != ' ' && !char.IsUpper(text[i - 1])) ||
                        (preserveAcronyms && char.IsUpper(text[i - 1]) &&
                         i < text.Length - 1 && !char.IsUpper(text[i + 1])))
                        newText.Append(' ');
                newText.Append(text[i]);
            }
            return newText.ToString();
        }
    }
}


<|start_filename|>test/SimpleRules.Tests/RuleAttributeIdentificationTests.cs<|end_filename|>
﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using SimpleRules.Attributes;
using SimpleRules.Generic;
using System;
using System.Collections.Generic;
using System.Linq;

namespace SimpleRules.Tests
{
    [TestClass]
    public class RuleAttributeIdentificationTests
    {
        [TestMethod]
        public void CanIdentifyClassWithRuleAttributes()
        {
            var srcType = typeof(Employee);
            var result = srcType.HasRuleAttributes();
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void CanIdentifyClassWithoutRuleAttributes()
        {
            var srcType = typeof(EmployeeWithExternalMeta);
            var result = srcType.HasRuleAttributes();
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void CanIdentifyClassWithRuleMetadataAttribute()
        {
            var srcType = typeof(EmployeeWithMetaAttribute);
            var result = srcType.HasRuleMetadataAttribute();
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void CanIdentifyTheAssociatedRuleMetaType()
        {
            var srcType = typeof(EmployeeWithMetaAttribute);
            var result = srcType.FindRuleMetadataType();
            Assert.IsNotNull(result);
            Assert.AreEqual(typeof (EmployeeMetadata), result);
        }

        [TestMethod]
        public void CanIdentifyClassWithoutRuleMetadataAttribute()
        {
            var srcType = typeof(Employee);
            var result = srcType.HasRuleMetadataAttribute();
            Assert.IsFalse(result);
        }
        
        [TestMethod]
        public void CanIdentifyRulesUsingAttributes()
        {
            var srcType = typeof(Employee);
            var rules = srcType.GetRules().ToList();
            Assert.AreEqual(7, rules.Count);
            var greaterThanAttrs = rules.Select(r => r.Item1).Where(r => r.Is<GreaterThanAttribute>());
            Assert.AreEqual(3, greaterThanAttrs.Count());
            var greaterThanEqualAttrs = rules.Select(r => r.Item1).Where(r => r.Is<GreaterThanOrEqualAttribute>());
            Assert.AreEqual(1, greaterThanEqualAttrs.Count());
            var notEqualAttrs = rules.Select(r => r.Item1).Where(r => r.Is<NotEqualToAttribute>());
            Assert.AreEqual(3, notEqualAttrs.Count());
        }

        [TestMethod]
        public void CanIdentifyRulesUsingMetadataType()
        {
            var srcType = typeof(EmployeeWithMetaAttribute);
            var metaType = typeof(EmployeeMetadata);
            var rules = srcType.GetRules(metaType).ToList();
            Assert.AreEqual(7, rules.Count);
            var greaterThanAttrs = rules.Select(r => r.Item1).Where(r => r.Is<GreaterThanAttribute>());
            Assert.AreEqual(3, greaterThanAttrs.Count());
            var greaterThanEqualAttrs = rules.Select(r => r.Item1).Where(r => r.Is<GreaterThanOrEqualAttribute>());
            Assert.AreEqual(1, greaterThanEqualAttrs.Count());
            var notEqualAttrs = rules.Select(r => r.Item1).Where(r => r.Is<NotEqualToAttribute>());
            Assert.AreEqual(3, notEqualAttrs.Count());
        }

        [TestMethod]
        public void CanIdentifyMetadataTypeIfPresentFromRegistration()
        {
            var concreteType = typeof (Employee);
            var metaType = typeof (EmployeeMetadata);
            var dictionary = new Dictionary<Type, Type>();
            dictionary.Add(concreteType, metaType);

            var requiredMetadata = dictionary.GetMetadataType(concreteType);
            Assert.IsNotNull(requiredMetadata);
            Assert.AreEqual(metaType, requiredMetadata.Item2);
        }

        [TestMethod]
        public void CanIdentifyMetadataTypeWithMetaAttribute()
        {
            var concreteType = typeof(EmployeeWithMetaAttribute);
            var metaType = typeof(EmployeeMetadata);
            var dictionary = new Dictionary<Type, Type>();

            var requiredMetadata = dictionary.GetMetadataType(concreteType);
            Assert.IsNotNull(requiredMetadata);
            Assert.AreEqual(metaType, requiredMetadata.Item2);
        }

        [TestMethod]
        public void CanIdentifyMetadataTypeInline()
        {
            var concreteType = typeof(Employee);
            var dictionary = new Dictionary<Type, Type>();

            var requiredMetadata = dictionary.GetMetadataType(concreteType);
            Assert.IsNotNull(requiredMetadata);
            Assert.AreEqual(concreteType, requiredMetadata.Item1);
            Assert.AreEqual(concreteType, requiredMetadata.Item2);
        }

        [TestMethod]
        public void CanIdentifyMetadataAsNullIfNotPresent()
        {
            var concreteType = typeof(User);
            var dictionary = new Dictionary<Type, Type>();

            var requiredMetadata = dictionary.GetMetadataType(concreteType);
            Assert.IsNull(requiredMetadata);
        }

        [TestMethod]
        public void CanIdentifyEntityKeyAttributeIfPresent()
        {
            var concreteType = typeof (User);
            var metaType = typeof (UserMetadata);
            var ruleMetaMap = new Tuple<Type, Type>(concreteType, metaType);
            var entityProp = ruleMetaMap.FindEntityKeyPropertyInfo();
            Assert.IsNotNull(entityProp);
            var expectedProp = concreteType.GetProperty("Id");
            Assert.AreEqual(expectedProp.Name, entityProp.Name);
            Assert.AreEqual(expectedProp.PropertyType, entityProp.PropertyType);
            Assert.AreEqual(expectedProp.DeclaringType, entityProp.DeclaringType);
        }

        [TestMethod]
        public void CanIdentifyEntityKeyAttributeIfPresentWithoutMeta()
        {
            var concreteType = typeof(Course);
            var ruleMetaMap = new Tuple<Type, Type>(concreteType, concreteType);
            var entityProp = ruleMetaMap.FindEntityKeyPropertyInfo();
            Assert.IsNotNull(entityProp);
            var expectedProp = concreteType.GetProperty("Id");
            Assert.AreEqual(expectedProp.Name, entityProp.Name);
            Assert.AreEqual(expectedProp.PropertyType, entityProp.PropertyType);
            Assert.AreEqual(expectedProp.DeclaringType, entityProp.DeclaringType);
        }

        [TestMethod]
        public void CanIdentifyNullEntityKeyAttribute()
        {
            var concreteType = typeof(Employee);
            var ruleMetaMap = new Tuple<Type, Type>(concreteType, concreteType);
            var entityProp = ruleMetaMap.FindEntityKeyPropertyInfo();
            Assert.IsNull(entityProp);
        }
    }

    public class Employee
    {
        [GreaterThan("", constantValue: 0)] 
        public int Id { get; set; }
        [NotEqualTo("", RuleType.Error)]
        [NotEqualTo(null, RuleType.Error)]
        public string Name { get; set; }
        [NotEqualTo("", RuleType.Error)]
        public string Ssn { get; set; }
        [GreaterThan("LastPayDate", RuleType.Warning)]
        public DateTime StartDate { get; set; }
        [GreaterThan("StartDate", RuleType.Error, canBeNull: true)]
        public DateTime? TermDate { get; set; }
        [GreaterThanOrEqual("StartDate", RuleType.Warning)]
        public DateTime LastPayDate { get; set; }
    }

    [RuleMetadata(typeof(EmployeeMetadata))]
    public class EmployeeWithMetaAttribute
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Ssn { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime? TermDate { get; set; }
        public DateTime LastPayDate { get; set; }
    }

    public class EmployeeMetadata
    {
        [GreaterThan("", constantValue: 0)]
        public object Id { get; set; }
        [NotEqualTo("", RuleType.Error)]
        [NotEqualTo(null, RuleType.Error)]
        public string Name { get; set; }
        [NotEqualTo("", RuleType.Error)]
        public string Ssn { get; set; }
        [GreaterThan("LastPayDate", RuleType.Warning)]
        public DateTime StartDate { get; set; }
        [GreaterThan("StartDate", RuleType.Error, canBeNull: true)]
        public DateTime? TermDate { get; set; }
        [GreaterThanOrEqual("StartDate", RuleType.Warning)]
        public DateTime LastPayDate { get; set; }
    }

    public class EmployeeWithExternalMeta
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Ssn { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime? TermDate { get; set; }
        public DateTime LastPayDate { get; set; }
    }

    public static class TestExtensions
    {
        public static bool Is<T>(this BaseRuleAttribute attribute)
            where T: BaseRuleAttribute
        {
            return attribute != null && attribute is T;
        }
    }
}


<|start_filename|>sample/SimpleRulesEngineSample/Models/ValidationResultsModel.cs<|end_filename|>
﻿using System.Collections.Generic;

namespace SimpleRulesEngineSample.Models
{
    public class ValidationResultsModel
    {
        public IEnumerable<string> UserValidationResults { get; set; }
        public IEnumerable<string> RegistrationValidationResults { get; set; }
        public IEnumerable<string> ActivityValidationResults { get; set; }
        public IEnumerable<string> StudentValidationResults { get; set; }
        public int Key { get; set; }
    }
}


<|start_filename|>src/SimpleRules/Attributes/RuleMetadataAttribute.cs<|end_filename|>
﻿using System;

namespace SimpleRules.Attributes
{
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class RuleMetadataAttribute : Attribute
    {
        private readonly Type _type;

        public RuleMetadataAttribute(Type type)
        {
            _type = type;
        }

        public Type MetaFor
        {
            get
            {
                return _type;
            }
        }
    }
}


<|start_filename|>sample/SimpleRulesEngineSample/Entities/Student.cs<|end_filename|>
﻿using System;
using SimpleRules.Generic;
using SimpleRulesEngineSample.Rules;
using SimpleRules.Attributes;

namespace SimpleRulesEngineSample.Entities
{
    public class Student
    {
        [EntityKey]
        [GreaterThan("", constantValue: 100)]
        public int Id { get; set; }
        [MatchRegex(@"(^\d{3}-?\d{2}-?\d{4}$|^XXX-XX-XXXX$)")]
        public string Ssn { get; set; }
        [EmailMatchRegex]
        public string EmailAddress { get; set; }
        [LessThan("StartDate")]
        public DateTime RegistrationDate { get; set; }
        public DateTime StartDate { get; set; }
        [LessThan("StartDate", canBeNull: true)]
        public DateTime? EndDate { get; set; }
        [LessThan("RegistrationDate")]
        [LessThan("StartDate")]
        [LessThan("EndDate")]
        public DateTime DateOfBirth { get; set; }
        [RangeRule(2, 4)]
        public int EnrolledCount { get; set; }
        [UsPhoneNumberRegex]
        public string Contact { get; set; }
    }
}


<|start_filename|>src/SimpleRules/ExpressionGenerationExtensions.cs<|end_filename|>
﻿using System;
using SimpleRules.Attributes;
using SimpleRules.Contracts;
using System.Collections.Generic;
using System.Reflection;
using System.Linq.Expressions;
using System.Linq;
using SimpleRules.Exceptions;

namespace SimpleRules
{
    public static class ExpressionGenerationExtensions
    {
        public static EvaluatedRule ProcessRule<TConcrete>(this Dictionary<Type, IHandler> handlerMapping, 
                                                        BaseRuleAttribute attribute, PropertyInfo targetProp)
        {
            var handler = handlerMapping.FindHandler(attribute);
            return handler.GenerateEvaluatedRule<TConcrete>(attribute, targetProp);
        }

        public static IHandler FindHandler(this Dictionary<Type, IHandler> handlerMapping, BaseRuleAttribute attribute)
        {
            var handler = handlerMapping.Values.SingleOrDefault(h => h.Handles(attribute));
            if (handler == null)
            {
                var attrType = attribute.GetType();
                throw new HandlerNotFoundException(attrType.Name, attrType.FullName);
            }
            return handler;
        }

        public static BinaryExpression CreateBinaryExpression(this List<Expression> expressions)
        {
            var aggregatedExpr = expressions.Aggregate((e1, e2) =>
            {
                return Expression.Or(e1, e2);
            });
            return (BinaryExpression) aggregatedExpr;
        }

        public static BinaryExpression CreateBinaryExpression(this Expression expr, PropertyInfo propertyInfo)
        {
            var nullableExpr = Expression.Convert(expr, propertyInfo.PropertyType);
            var nullConst = Expression.Constant(null);
            return Expression.MakeBinary(ExpressionType.Equal, nullableExpr, nullConst);
        }

        public static BinaryExpression CreateBinaryExpression(this Expression leftExpr, 
                                                                   Expression rightExpr, 
                                                                   ExpressionType exprType, 
                                                                   PropertyInfo propertyInfo,
                                                                   PropertyInfo otherPropInfo = null)
        {
            var isNullable = propertyInfo.IsNullable() || (otherPropInfo != null ? otherPropInfo.IsNullable() : true);
            var propType = propertyInfo.PropertyType;
            var leftExprFinal = isNullable ? Expression.Convert(leftExpr, propertyInfo.PropertyType) : leftExpr;
            var rightExprFinal = isNullable ? Expression.Convert(rightExpr, propertyInfo.PropertyType) : rightExpr;
            return Expression.MakeBinary(
                        exprType,
                        leftExprFinal,
                        rightExprFinal
                   );
        }

        public static bool IsNullable(this PropertyInfo propertyInfo)
        {
            return propertyInfo.PropertyType.IsGenericType() && 
                   propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>);
        }
    }
}


<|start_filename|>sample/SimpleRulesEngineSample/Entities/User.cs<|end_filename|>
﻿using SimpleRules.Generic;

namespace SimpleRulesEngineSample.Entities
{
    public class User
    {
        public string Username { get; set; }
        public string Password { get; set; }
        [EqualTo("Password")]
        public string ConfirmPassword { get; set; }
        [EmailMatchRegex]
        public string EmailAddress { get; set; }
        [UsPhoneNumberRegex]
        public string PhoneNumber { get; set; }
    }
}


<|start_filename|>src/SimpleRules/ValidationResult.cs<|end_filename|>
﻿using System.Linq;
using System.Collections.Generic;

namespace SimpleRules
{
    public class ValidationResult
    {
        public ValidationResult()
        {
            this.Errors = new List<string>();
            this.Warnings = new List<string>();
        }

        public object Key { get; set; }
        public List<string> Errors { get; set; }
        public List<string> Warnings { get; set; }
        public bool IsError
        {
            get
            {
                return Errors.Any();
            }
        }
        public bool HasWarnings
        {
            get
            {
                return Warnings.Any();
            }
        }
        public bool HasOnlyWarnings
        {
            get
            {
                return HasWarnings && !IsError;
            }
        }
        public bool HasOnlyErrors
        {
            get
            {
                return IsError && !HasWarnings;
            }
        }

        public void Add(string message, RuleType ruleType)
        {
            if (ruleType == RuleType.Error)
                this.Errors.Add(message);
            else
                this.Warnings.Add(message);
        }
    }
}


<|start_filename|>src/SimpleRules/Handlers/SimpleRuleHandler.cs<|end_filename|>
﻿using SimpleRules.Attributes;
using SimpleRules.Contracts;
using SimpleRules.Exceptions;
using SimpleRules.Generic;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace SimpleRules.Handlers
{
    public class SimpleRuleHandler : IHandler
    {
        public EvaluatedRule GenerateEvaluatedRule<TConcrete>(BaseRuleAttribute attribute, PropertyInfo targetProp)
        {
            var expressions = new List<Expression>();
            var messages = new List<string>();
            var relationalAttr = attribute as RelationalOperatorAttribute;
            var srcType = typeof (TConcrete);
            var input = Expression.Parameter(srcType, "i");
            var leftExpr = Expression.PropertyOrField(input, targetProp.Name);
            var isNullable = targetProp.IsNullable();
            if (relationalAttr.CanBeNull)
            {
                if (isNullable)
                {
                    var nullConstant = leftExpr.CreateBinaryExpression(targetProp);
                    expressions.Add(nullConstant);
                    messages.Add("It can also be null.");
                }
                else
                {
                    throw new NotNullablePropertyException(targetProp.Name);
                }
            }
            if (relationalAttr.ConstantValue != null)
            {
                var constantValue = Expression.Constant(relationalAttr.ConstantValue);
                BinaryExpression finalExpr = leftExpr.CreateBinaryExpression(constantValue, relationalAttr.SupportedType, targetProp);                
                expressions.Add(finalExpr);
                messages.Add(string.Format("Or {0} can be {1} {2}.", targetProp.Name.AddSpaces(), relationalAttr.SupportedType.ToString().AddSpaces(), relationalAttr.ConstantValue));
            }
            if (!string.IsNullOrEmpty(relationalAttr.OtherPropertyName))
            {
                var otherPropInfo = srcType.GetProperty(relationalAttr.OtherPropertyName);
                var rightExpr = Expression.PropertyOrField(input, relationalAttr.OtherPropertyName);
                var propExpr = leftExpr.CreateBinaryExpression(rightExpr, relationalAttr.SupportedType, targetProp, otherPropInfo);
                expressions.Add(propExpr);
                messages.Add(string.Format("{0} should be {1} the {2}.", targetProp.Name.AddSpaces(), relationalAttr.SupportedType.ToString().AddSpaces(), relationalAttr.OtherPropertyName.AddSpaces()));
            }
            var orExpr = expressions.CreateBinaryExpression();
            var lambdaExpr = Expression.Lambda(orExpr, input);
            messages.Reverse();
            return new EvaluatedRule
            {
                MessageFormat = string.Join(" ", messages),
                Expression = lambdaExpr,
                RuleType = relationalAttr.RuleType
            };
        }

        public bool Handles(BaseRuleAttribute attribute)
        {
            return typeof(RelationalOperatorAttribute).IsAssignableFrom(attribute.GetType());
        }
    }
}


<|start_filename|>src/SimpleRules/Compatibility/NETSTANDARD/TypeExtensions.cs<|end_filename|>
﻿#if NETSTANDARD
using System;
using System.Reflection;
using System.Collections.Generic;

namespace SimpleRules
{
    public static class TypeExtensions
    {
        public static bool IsInterface(this Type type)
        {
            return type.GetTypeInfo().IsInterface;
        }

        public static bool IsGenericType(this Type type)
        {
            return type.GetTypeInfo().IsGenericType;
        }

        public static Assembly GetAssembly(this Type type)
        {
            return type.GetTypeInfo().Assembly;
        }

        public static IEnumerable<T> GetCustomAttributes<T>(this Type type, bool inherit = false)
            where T:Attribute
        {
            return type.GetTypeInfo().GetCustomAttributes<T>(false);
        }

        public static T GetCustomAttribute<T>(this Type type, bool inherit = false)
            where T : Attribute
        {
            return type.GetTypeInfo().GetCustomAttribute<T>(false);
        }

        public static bool IsAssignableFrom(this Type type, Type type2)
        {
            return type.GetTypeInfo().IsAssignableFrom(type2);
        }
    }
}
#endif


<|start_filename|>sample/SimpleRulesEngineSample/Entities/Registration.cs<|end_filename|>
﻿using System;
using SimpleRules.Generic;

namespace SimpleRulesEngineSample.Entities
{
    public class Registration
    {
        public DateTime StartDate { get; set; }
        public DateTime? EndDate { get; set; }
    }

    public class RegistrationMetadata
    {
        public DateTime StartDate { get; set; }
        [GreaterThan("StartDate", canBeNull: true)]
        public DateTime? EndDate { get; set; }
    }
}


<|start_filename|>src/SimpleRules/Compatibility/GENERAL/TypeExtensions.cs<|end_filename|>
﻿#if !NETSTANDARD && !NET_4_6_1 && !NET_4_5 && !NET_4_5_1 && !NET_4_5_2
using System;
using System.Reflection;

namespace SimpleRules
{
    public static class TypeExtensions
    {
        public static bool IsInterface(this Type type)
        {
            return type.IsInterface;
        }

        public static bool IsGenericType(this Type type)
        {
            return type.IsGenericType;
        }

        public static Assembly GetAssembly(this Type type)
        {
            return type.Assembly;
        }
    }
}
#endif


<|start_filename|>src/SimpleRules/Generic/EmailMatchRegexAttribute.cs<|end_filename|>
﻿namespace SimpleRules.Generic
{
    public class EmailMatchRegexAttribute : MatchRegexAttribute
    {
        public EmailMatchRegexAttribute()
            : base(@"\A(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\Z")
        {
        }
    }
}


<|start_filename|>sample/SimpleRulesEngineSample/Controllers/HomeController.cs<|end_filename|>
﻿using Microsoft.AspNetCore.Mvc;
using SimpleRules;
using SimpleRulesEngineSample.Entities;
using SimpleRulesEngineSample.Models;
using System;

namespace SimpleRulesEngineSample.Controllers
{
    public class HomeController : Controller
    {
        private readonly SimpleRulesEngine _rulesEngine;

        // see Startup.cs - ConfigureServices
        public HomeController(SimpleRulesEngine rulesEngine)
        {
            _rulesEngine = rulesEngine;
        }

        public IActionResult Index()
        {
            var model = new ValidationResultsModel();
            var user = new User
                {
                    Username = "kanant",
                    Password = "<PASSWORD>",
                    ConfirmPassword = "<PASSWORD>",
                    EmailAddress = "kanant",
                    PhoneNumber = "12345"
                };
            model.UserValidationResults = _rulesEngine.Validate<User>(user).Errors;
            var registration = new Registration
                {
                    StartDate = DateTime.Now.AddDays(-3),
                    EndDate = DateTime.Now.AddDays(-6)
                };
            model.RegistrationValidationResults = _rulesEngine.Validate<Registration>(registration).Errors;
            var activity = new Activity
                {
                    StartDate = DateTime.Now.AddDays(-3),
                    EndDate = DateTime.Now.AddDays(-6),
                    Capacity = 25
                };
            model.ActivityValidationResults = _rulesEngine.Validate<Activity>(activity).Errors;
            var student = new Student
            {
                Id = 1,
                Ssn = "12345",
                EmailAddress = "jdoe",
                RegistrationDate = DateTime.Now,
                StartDate = DateTime.Now.AddDays(-1),
                DateOfBirth = DateTime.Now,
                EnrolledCount = 5,
                Contact = "98412",
                EndDate = DateTime.Now.AddDays(3)
            };
            var validationResult = _rulesEngine.Validate<Student>(student);
            model.StudentValidationResults = validationResult.Errors;
            model.Key = (int)validationResult.Key;

            return View(model);
        }

        public IActionResult Error()
        {
            return View();
        }
    }
}


<|start_filename|>sample/SimpleRulesEngineSample/Views/Home/Index.cshtml<|end_filename|>
﻿@model SimpleRulesEngineSample.Models.ValidationResultsModel

@{
    ViewData["Title"] = "Home Page";
}

<style type="text/css">
    pre, code {
        background: #272822;
        font-family: monospace;
        border: 0px;
    }
</style>

<br />

<div>
    <strong>SimpleRules.Net</strong> is a rules engine that is simple enough to use and extend! It will be evident once you see
    all the examples below!
</div>

<div>
    Given the following class with a validation rule set,
</div>
<br />

<pre>
<code class="csharp">
    public class User
    {
        public string Username { get; set; }
        public string Password { get; set; }
        [EqualTo("Password")]
        public string ConfirmPassword { get; set; }
        [EmailMatchRegex]
        public string EmailAddress { get; set; }
        [UsPhoneNumberRegex]
        public string PhoneNumber { get; set; }
    }
    </code>
</pre>

<div>
    When the following instance of <i>User</i> is provided as the input:
</div>
<br />

<pre>
    <code class="csharp">
    var user = new User
        {
            Username = "kanant",
            Password = "<PASSWORD>",
            ConfirmPassword = "<PASSWORD>",
            EmailAddress = "kanant",
            PhoneNumber = "12345"
        };
    </code>>
</pre>

<br />

<div>
    The validation results are:
</div>
<br />

<div class="well">
<ul>
        @foreach (var item in Model.UserValidationResults)
        {
<li>@item</li>
        }
    </ul>
</div>
<hr />

<div>
    Given the following class with corresponding class for metadata,
</div>
<br />

<pre>
    <code class="csharp">
    public class Registration
    {
        public DateTime StartDate { get; set; }
        public DateTime? EndDate { get; set; }
    }
    public class RegistrationMetadata
    {
        public DateTime StartDate { get; set; }
        [GreaterThan("StartDate", canBeNull: true)]
        public DateTime? EndDate { get; set; }
    }
    </code>
</pre>

<div>
    When the following instance of <i>Registration</i> is provided as the input:
</div>
<br />

<pre>
    <code class="csharp">
    var registration = new Registration
            {
                StartDate = DateTime.Now.AddDays(-3),
                EndDate = DateTime.Now.AddDays(-6)
            };
    </code>>
</pre>

<br />

<div>
    The validation results are:
</div>
<br />

<div class="well">
<ul>
        @foreach (var item in Model.RegistrationValidationResults)
        {
<li>@item</li>
        }
    </ul>
</div>
<div>
    Metadata registration is done during the initial setup in Startup.cs, as shown below:
</div>
<br />

<pre>
<code class="csharp">
    public void ConfigureServices(IServiceCollection services)
    {
        // ...
        var simpleRulesEngine = new SimpleRulesEngine()
                                    .RegisterMetadata&lt;Registration, RegistrationMetadata&gt;();
        services.AddSingleton(typeof (SimpleRulesEngine), simpleRulesEngine);
        // ...
    }
    </code>
</pre>
<hr />

<div>
    Given the following class with corresponding class for metadata,
</div>
<br />

<pre>
<code class="csharp">
    public class Activity
    {
        public DateTime StartDate { get; set; }
        public DateTime? EndDate { get; set; }
        public int Capacity { get; set; }
    }
    public class ActivityMetadata
    {
        public DateTime StartDate { get; set; }
        [GreaterThan("StartDate", canBeNull: true)]
        public DateTime? EndDate { get; set; }
        [RangeRule(10, 20)]
        public int Capacity { get; set; }
    }
    </code>
</pre>

<div>
    When the following instance of <i>Activity</i> is provided as the input:
</div>
<br />

<pre>
    <code class="csharp">
    var activity = new Activity
        {
            StartDate = DateTime.Now.AddDays(-3),
            EndDate = DateTime.Now.AddDays(-6),
            Capacity = 25
        };
    </code>>
</pre>

<br />

<div>
    The validation results are:
</div>
<br />

<div class="well">
<ul>
        @foreach (var item in Model.ActivityValidationResults)
        {
<li>@item</li>
        }
    </ul>
</div>

<div>
    Metadata registration is done during the initial setup in Startup.cs, as shown below. Also, notice that custom rules are 
    registered using the <strong>DiscoverHandlers</strong> extension.
</div>
<br />

<pre>
<code class="csharp">
    public void ConfigureServices(IServiceCollection services)
    {
        // ...
        var simpleRulesEngine = new SimpleRulesEngine()
                                        .DiscoverHandlers(new [] { typeof(Startup) })
                                        .RegisterMetadata&lt;, RegistrationMetadata&gt;()
                                        .RegisterMetadata&lt;, ActivityMetadata&gt;();
        services.AddSingleton(typeof (SimpleRulesEngine), simpleRulesEngine);
        // ...
    }
    </code>
</pre>
<br />

<div>
    Given below is the implementation of the custom <strong>RangeRule</strong>
</div>
<br />

<pre>
<code class="csharp">
    public class RangeRuleAttribute : BaseRuleAttribute
    {
        public RangeRuleAttribute(int minValue, int maxValue)
        {
            MinValue = minValue;
            MaxValue = maxValue;
        }
        public int MinValue { get; set; }
        public int MaxValue { get; set; }
    }
    public class RangeRuleHandler : IHandler
    {
        public EvaluatedRule GenerateEvaluatedRule&lt;TConcrete&gt;(BaseRuleAttribute attribute, PropertyInfo targetProp)
        {
            var rangeAttr = attribute as RangeRuleAttribute;
            var input = Expression.Parameter(typeof(TConcrete), "a");
            var propName = targetProp.Name;
            var comparison = Expression.And(
                    Expression.GreaterThan(Expression.PropertyOrField(input, propName), Expression.Constant(rangeAttr.MinValue)),
                    Expression.LessThan(Expression.PropertyOrField(input, propName), Expression.Constant(rangeAttr.MaxValue))
                );
            var lambda = Expression.Lambda(comparison, input);
            return new EvaluatedRule
            {
                MessageFormat = string.Format("{0} should be between {1} and {2}", propName, rangeAttr.MinValue, rangeAttr.MaxValue),
                RuleType = RuleType.Error,
                Expression = lambda
            };
        }
        public bool Handles(BaseRuleAttribute attribute)
        {
            return typeof(RangeRuleAttribute).IsAssignableFrom(attribute.GetType());
        }
    }
    </code>
</pre>

<div>
    And finally a class with a number of validation rules!
</div>

<br />

<pre>
    <code class="csharp">
    public class Student
    {
        public int Id { get; set; }
        [MatchRegex("(^\d{3}-?\d{2}-?\d{4}$|^XXX-XX-XXXX$)")]
        public string Ssn { get; set; }
        [EmailMatchRegex]
        public string EmailAddress { get; set; }
        [LessThan("StartDate")]
        public DateTime RegistrationDate { get; set; }
        public DateTime StartDate { get; set; }
        [LessThan("StartDate", canBeNull: true)]
        public DateTime? EndDate { get; set; }
        [LessThan("RegistrationDate")]
        [LessThan("StartDate")]
        [LessThan("EndDate")]
        public DateTime DateOfBirth { get; set; }
        [RangeRule(2, 4)]
        public int EnrolledCount { get; set; }
        [UsPhoneNumberRegex]
        public string Contact { get; set; }
    }
    </code>
</pre>

<br />

<div>
    Validated with the following instance:
</div>

<br />

<pre>
    <code class="csharp">
    var student = new Student
    {
        Id = 1,
        Ssn = "12345",
        EmailAddress = "jdoe",
        RegistrationDate = DateTime.Now,
        StartDate = DateTime.Now.AddDays(-1),
        DateOfBirth = DateTime.Now,
        EnrolledCount = 5,
        Contact = "98412",
        EndDate = DateTime.Now.AddDays(3)
    };
    </code>
</pre>

<br />

<div>
    Validation results for the item with key @Model.Key is:
</div>

<br />

<div class="well">
    <ul>
        @foreach (var item in Model.StudentValidationResults)
        {
            <li>@item</li>
        }
    </ul>
</div>


<|start_filename|>src/SimpleRules/Generic/NotEqualToAttribute.cs<|end_filename|>
﻿using System;
using System.Linq.Expressions;

namespace SimpleRules.Generic
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
    public class NotEqualToAttribute : RelationalOperatorAttribute
    {
        public NotEqualToAttribute(string otherProp, object constantValue = null, bool canBeNull = false, RuleType ruleType = RuleType.Error) : base(otherProp, constantValue, canBeNull, ruleType) { }

        public override ExpressionType SupportedType
        {
            get
            {
                return ExpressionType.NotEqual;
            }
        }
    }
}


<|start_filename|>src/SimpleRules/Generic/MatchRegexAttribute.cs<|end_filename|>
﻿using SimpleRules.Attributes;

namespace SimpleRules.Generic
{
    public class MatchRegexAttribute : RegexRuleAttribute
    {
        private readonly string _regex;

        public MatchRegexAttribute(string regex)
        {
            _regex = regex;
        }

        public override string RegularExpression
        {
            get
            {
                return _regex;
            }
        }
    }
}


<|start_filename|>src/SimpleRules/Attributes/BaseRuleAttribute.cs<|end_filename|>
﻿using System;

namespace SimpleRules.Attributes
{
    public abstract class BaseRuleAttribute : Attribute
    {
    }
}


<|start_filename|>src/SimpleRules/Generic/RelationalOperatorAttribute.cs<|end_filename|>
﻿using System;
using SimpleRules.Attributes;

namespace SimpleRules.Generic
{
    public abstract class RelationalOperatorAttribute : SimpleRuleAttribute
    {
        private readonly string _otherProp;
        private readonly object _constantValue;
        private readonly bool _canBeNull;
        private readonly RuleType _ruleType;

        public RelationalOperatorAttribute(string otherProp, object constantValue = null, bool canBeNull = false, RuleType ruleType = RuleType.Error)
        {
            _otherProp = otherProp;
            _constantValue = constantValue;
            _canBeNull = canBeNull;
            _ruleType = ruleType;
        }

        public override string OtherPropertyName
        {
            get
            {
                return _otherProp;
            }
        }

        public override object ConstantValue
        {
            get
            {
                return _constantValue;
            }
        }

        public override bool CanBeNull
        {
            get
            {
                return _canBeNull;
            }
        }

        public override RuleType RuleType
        {
            get
            {
                return _ruleType;
            }
        }
    }
}


<|start_filename|>src/SimpleRules/Generic/LessThanOrEqualToAttribiute.cs<|end_filename|>
﻿using System;
using System.Linq.Expressions;

namespace SimpleRules.Generic
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
    public class LessThanOrEqualToAttribute : RelationalOperatorAttribute
    {
        public LessThanOrEqualToAttribute(string otherProp, object constantValue = null, bool canBeNull = false, RuleType ruleType = RuleType.Error) : base(otherProp, constantValue, canBeNull, ruleType) { }

        public override ExpressionType SupportedType
        {
            get
            {
                return ExpressionType.LessThanOrEqual;
            }
        }
    }
}


<|start_filename|>src/SimpleRules/Generic/UsPhoneNumberRegexAttribute.cs<|end_filename|>
﻿namespace SimpleRules.Generic
{
    public class UsPhoneNumberRegexAttribute : MatchRegexAttribute        
    {
        public UsPhoneNumberRegexAttribute()
            : base(@"^\([2-9]{3}\)\s?[0-9]{3}-[0-9]{4}$")
        {
        }
    }
}


<|start_filename|>src/SimpleRules/SimpleRulesEngine.cs<|end_filename|>
﻿using System;
using System.Linq;
using System.Reflection;
using SimpleRules.Contracts;
using SimpleRules.Exceptions;
using System.Collections.Generic;

namespace SimpleRules
{
    public class SimpleRulesEngine
    {
        private readonly Dictionary<Type, EvaluatedRule[]> typeRulesCache = new Dictionary<Type, EvaluatedRule[]>();
        private readonly Dictionary<Type, PropertyInfo> entityKeyCache = new Dictionary<Type, PropertyInfo>();
        private readonly Dictionary<Type, Type> typeMetaCache = new Dictionary<Type, Type>();
        private readonly Dictionary<Type, IHandler> handlerMapping = new Dictionary<Type, IHandler>();

        public SimpleRulesEngine()
        {
            AddDefaultHandlers();
        }
        
        public IEnumerable<ValidationResult> Validate<TConcrete>(List<TConcrete> src)
            where TConcrete : class
        {
            var rules = GetRules<TConcrete>();            
            return src.Select(s =>
            {
                var validationResult = new ValidationResult { Key = entityKeyCache.GetEntityKey(s) };
                foreach (var rule in rules)
                {
                    var compiledExpression = GetFunc<TConcrete>(rule.Delegate);
                    if (!compiledExpression(s))
                        validationResult.Add(rule.MessageFormat, rule.RuleType);
                }
                return validationResult;
            });
        }

        public SimpleRulesEngine RegisterMetadata<TConcrete, TMeta>()
            where TConcrete : class
            where TMeta : class
        {
            var concreteType = typeof (TConcrete);
            var metaType = typeof (TMeta);
            if (typeMetaCache.ContainsKey(concreteType))
                throw new DuplicateMetadataRegistrationException(concreteType.Name, metaType.Name);
            typeMetaCache.Add(concreteType, metaType);
            return this;
        }

        public SimpleRulesEngine RegisterCustomRule<Rhandler>()
            where Rhandler : IHandler, new()
        {
            if (!handlerMapping.ContainsKey(typeof (Rhandler)))
                handlerMapping.Add(typeof(Rhandler), new Rhandler());
            return this;
        }

        public SimpleRulesEngine DiscoverHandlers(params Type[] assemblyMarkers)
        {
            var discoveredHandlerTypes = assemblyMarkers.FindHandlerTypesInAssemblies();
            foreach (var handler in discoveredHandlerTypes)
            {
                if (!handlerMapping.ContainsKey(handler))
                    handlerMapping.Add(handler, handler.CreateInstance());
            }
            return this;
        }

        private EvaluatedRule[] GetRules<TConcrete>()
        {
            var type = typeof (TConcrete);

            if (typeRulesCache.ContainsKey(type))
                return typeRulesCache[type];

            var metaDataType = typeMetaCache.GetMetadataType(type);
            if (metaDataType == null)
                throw new Exception(string.Format("Unable to identify rule metadata for the entity: {0}", type.FullName));

            var entityKeyProp = metaDataType.FindEntityKeyPropertyInfo();
            if (entityKeyProp != null)
            {
                entityKeyCache.Add(type, entityKeyProp);
            }

            var rulePropertyMap = metaDataType.GetRules()
                                              .Select(m => handlerMapping.ProcessRule<TConcrete>(m.Item1, m.Item2))
                                              .ToArray();
            typeRulesCache[type] = rulePropertyMap;
            return rulePropertyMap;
        }

        private void AddDefaultHandlers()
        {
            this.DiscoverHandlers(typeof (SimpleRulesEngine));
        }

        private static Func<TConcrete, bool> GetFunc<TConcrete>(Delegate funcDelegate)
        {
            return (Func<TConcrete, bool>)funcDelegate;
        }
    }
}


<|start_filename|>src/SimpleRules/RuleType.cs<|end_filename|>
﻿namespace SimpleRules
{
    public enum RuleType
    {
        Error,
        Warning
    }
}


<|start_filename|>test/SimpleRules.Tests/RuleGenerationTests.cs<|end_filename|>
﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using SimpleRules.Attributes;
using SimpleRules.Exceptions;
using SimpleRules.Generic;
using System;
using System.Collections.Generic;
using System.Linq;

namespace SimpleRules.Tests
{
    [TestClass]
    public class RuleGenerationTests
    {
        [TestMethod]
        public void CanGenerateSimpleRule()
        {
            var users = new List<User>
            {
                new User { Id = 1001, RegistrationDate = DateTime.Now, LastLoginDate = DateTime.Now.AddDays(-1) }
            };
            var results = new SimpleRulesEngine()
                                .RegisterMetadata<User, UserMetadata>()
                                .Validate<User>(users);
            Assert.IsNotNull(results);
            Assert.AreEqual(1, results.Count());
            var first = results.First();
            Assert.AreEqual(1001, first.Key);
            Assert.AreEqual(1, first.Errors.Count);
            Assert.AreEqual("Last Login Date should be Greater Than the Registration Date.", first.Errors[0]);
        }

        [TestMethod]
        [ExpectedException(typeof(DuplicateMetadataRegistrationException))]
        public void CanThrowExceptionForDuplicateMetdataRegistration()
        {
            var users = new List<User>
            {
                new User { Id = 1001, RegistrationDate = DateTime.Now, LastLoginDate = DateTime.Now.AddDays(-1) }
            };
            var results = new SimpleRulesEngine()
                                .RegisterMetadata<User, UserMetadata>()
                                .RegisterMetadata<User, UserMetadata>()
                                .Validate<User>(users);
            Assert.IsNotNull(results);
            Assert.AreEqual(1, results.Count());
        }

        [TestMethod]
        public void CanGenerateSimpleRuleWithWarnings()
        {
            var users = new List<User>
            {
                new User { Id = 1001, RegistrationDate = DateTime.Now, LastLoginDate = DateTime.Now.AddDays(-1) }
            };
            var results = new SimpleRulesEngine()
                                .RegisterMetadata<User, UserMetadataWithWarning>()
                                .Validate<User>(users);
            Assert.IsNotNull(results);
            Assert.AreEqual(1, results.Count());
            var first = results.First();
            Assert.AreEqual(1001, first.Key);
            Assert.AreEqual(1, first.Warnings.Count);
            Assert.AreEqual("Last Login Date should be Greater Than the Registration Date.", first.Warnings[0]);
        }

        [TestMethod]
        [ExpectedException(typeof(NotNullablePropertyException))]
        public void CanIdentifyNotNullablePropWithCanBeNullSetWithConstant()
        {
            var jobs = new List<JobWithoutNull>
           { 
                new JobWithoutNull { Capacity = 10 }
            };
            var validationResultsEnumerator = new SimpleRulesEngine()
                                            .Validate<JobWithoutNull>(jobs);
            var validationResults = validationResultsEnumerator.ToList();

        }

        [TestMethod]
        public void CanIdentifyNullablePropWithCanBeBullSetWithConstant()
        {
            var jobs = new List<JobWithNull>
            {
                new JobWithNull { Capacity = null }
            };
            var validationResults = new SimpleRulesEngine()
                                            .Validate<JobWithNull>(jobs);
            Assert.AreEqual(1, validationResults.Count());
            Assert.AreEqual(0, validationResults.First().Errors.Count());
            jobs = new List<JobWithNull>
            {
                new JobWithNull { Capacity = 25, MaxCapacity = 15 }
            };
            Assert.AreEqual(1, validationResults.Count());
            Assert.AreEqual(0, validationResults.First().Errors.Count());
            jobs = new List<JobWithNull>
            {
                new JobWithNull { Capacity = 40, MaxCapacity = 45 }
            };
            Assert.AreEqual(1, validationResults.Count());
            Assert.AreEqual(0, validationResults.First().Errors.Count());
        }

        [TestMethod]
        public void CanGenerateMessagesconsideringNullAndConstantStatus()
        {
            var engine = new SimpleRulesEngine();
            var jobs = new List<JobWithNull>
            {
                new JobWithNull { MaxCapacity = 15, Capacity = 40 }
            };
            var validationResults = engine.Validate<JobWithNull>(jobs);
            Assert.AreEqual(1, validationResults.Count());
            Assert.AreEqual("Capacity should be Less Than the Max Capacity. Or Capacity can be Less Than 30. It can also be null.", validationResults.First().Errors[0]);
            var jobsWithConst = new List<JobWithOnlyConstant>
            {
                new JobWithOnlyConstant { MaxCapacity = 15, Capacity = 40 }
            };
            var validationResultsConst = engine.Validate<JobWithOnlyConstant>(jobsWithConst);
            Assert.AreEqual(1, validationResultsConst.Count());
            Assert.AreEqual("Capacity should be Less Than the Max Capacity. Or Capacity can be Less Than 30.", validationResultsConst.First().Errors[0]);
            var jobsWithNull = new List<JobWithOnlyNull>
            {
                new JobWithOnlyNull { MaxCapacity = 15, Capacity = 40 }
            };
            var validationResultsNull = engine.Validate<JobWithOnlyNull>(jobsWithNull);
            Assert.AreEqual(1, validationResultsNull.Count());
            Assert.AreEqual("Capacity should be Less Than the Max Capacity. It can also be null.", validationResultsNull.First().Errors[0]);
        }

        [TestMethod]
        public void CanGenerateRegexRules()
        {
            var codes = new List<Code>
            {
                new Code { Value = "abc" }
            };
            var validationResults = new SimpleRulesEngine()
                                        .Validate<Code>(codes);
            Assert.AreEqual(1, validationResults.Count());
            Assert.AreEqual(1, validationResults.First().Errors.Count());
            Assert.AreEqual("Value does not match the expected format", validationResults.First().Errors[0]);
            codes = new List<Code>
            {
                new Code { Value = "123" }
            };
            validationResults = new SimpleRulesEngine()
                                        .Validate<Code>(codes);
            Assert.AreEqual(1, validationResults.Count());
            Assert.AreEqual(0, validationResults.First().Errors.Count());
        }

        [TestMethod]
        public void CanIdentifyNullableOtherProperty()
        {
            var students = new List<Student>
            {
                new Student
                {
                    StartDate = DateTime.Now.AddDays(-5),
                    EndDate = DateTime.Now,
                    DateOfBirth = DateTime.Now.AddDays(1)
                }
            };
            var validationResults = new SimpleRulesEngine()
                                        .Validate<Student>(students);
            Assert.AreEqual(1, validationResults.Count());
            var first = validationResults.First();
            Assert.AreEqual(1, first.Errors.Count());
            Assert.AreEqual("Date Of Birth should be Less Than the End Date.", first.Errors[0]);
        }
    }

    public class Student
    {
        public int Id { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime? EndDate { get; set; }
        [LessThan("EndDate")]
        public DateTime DateOfBirth { get; set; }
    }

    public class User
    {
        public int Id { get; set; }
        public DateTime RegistrationDate { get; set; }
        public DateTime LastLoginDate { get; set; }
    }

    public class UserMetadata
    {
        [EntityKey]
        public int Id { get; set; }

        public DateTime RegistrationDate { get; set; }

        [GreaterThan("RegistrationDate")]
        public DateTime LastLoginDate { get; set; }
    }

    public class UserMetadataWithWarning
    {
        [EntityKey]
        public int Id { get; set; }

        public DateTime RegistrationDate { get; set; }

        [GreaterThan("RegistrationDate", ruleType: RuleType.Warning)]
        public DateTime LastLoginDate { get; set; }
    }

    public class Course
    {
        [EntityKey]
        public int Id { get; set; }
    }

    public class JobWithNull
    {
        [LessThan("MaxCapacity", canBeNull: true, constantValue: 30)]
        public int? Capacity { get; set; }
        public int MaxCapacity { get; set; }
    }

    public class JobWithoutNull
    {
        [LessThan("MaxCapacity", canBeNull: true, constantValue: 30)]
        public int Capacity { get; set; }
        public int MaxCapacity { get; set; }
    }

    public class JobWithOnlyConstant
    {
        [LessThan("MaxCapacity", constantValue: 30)]
        public int? Capacity { get; set; }
        public int MaxCapacity { get; set; }
    }

    public class JobWithOnlyNull
    {
        [LessThan("MaxCapacity", canBeNull: true)]
        public int? Capacity { get; set; }
        public int MaxCapacity { get; set; }
    }

    public class Code
    {
        [MatchRegex(@"\d+")]
        public string Value { get; set; }
    }
}


<|start_filename|>sample/SimpleRulesEngineSample/Entities/Activity.cs<|end_filename|>
﻿using SimpleRules.Generic;
using SimpleRulesEngineSample.Rules;
using System;

namespace SimpleRulesEngineSample.Entities
{
    public class Activity
    {
        public DateTime StartDate { get; set; }
        public DateTime? EndDate { get; set; }
        public int Capacity { get; set; }
    }

    public class ActivityMetadata
    {
        public DateTime StartDate { get; set; }
        [GreaterThan("StartDate", canBeNull: true)]
        public DateTime? EndDate { get; set; }
        [RangeRule(10, 20)]
        public int Capacity { get; set; }
    }
}


<|start_filename|>Pack_Nuget.bat<|end_filename|>
rmdir /Q /S NuGet
dotnet pack src\SimpleRules --configuration release --output NuGet


<|start_filename|>sample/SimpleRulesEngineSample/Rules/RangeRule.cs<|end_filename|>
﻿using SimpleRules;
using SimpleRules.Attributes;
using SimpleRules.Contracts;
using System.Linq.Expressions;
using System.Reflection;

namespace SimpleRulesEngineSample.Rules
{
    public class RangeRuleAttribute : BaseRuleAttribute
    {
        public RangeRuleAttribute(int minValue, int maxValue)
        {
            MinValue = minValue;
            MaxValue = maxValue;
        }

        public int MinValue { get; set; }
        public int MaxValue { get; set; }
    }

    public class RangeRuleHandler : IHandler
    {
        public EvaluatedRule GenerateEvaluatedRule<TConcrete>(BaseRuleAttribute attribute, PropertyInfo targetProp)
        {
            var rangeAttr = attribute as RangeRuleAttribute;
            var input = Expression.Parameter(typeof(TConcrete), "a");
            var propName = targetProp.Name;
            var comparison = Expression.And(
                    Expression.GreaterThan(Expression.PropertyOrField(input, propName), Expression.Constant(rangeAttr.MinValue)),
                    Expression.LessThan(Expression.PropertyOrField(input, propName), Expression.Constant(rangeAttr.MaxValue))
                );
            var lambda = Expression.Lambda(comparison, input);
            return new EvaluatedRule
            {
                MessageFormat = string.Format("{0} should be between {1} and {2}", propName.AddSpaces(), rangeAttr.MinValue, rangeAttr.MaxValue),
                RuleType = RuleType.Error,
                Expression = lambda
            };
        }

        public bool Handles(BaseRuleAttribute attribute)
        {
            return typeof(RangeRuleAttribute).IsAssignableFrom(attribute.GetType());
        }
    }
}


<|start_filename|>src/SimpleRules/Exceptions/NotNullablePropertyException.cs<|end_filename|>
﻿using System;

namespace SimpleRules.Exceptions
{
    public class NotNullablePropertyException : Exception        
    {
        public NotNullablePropertyException(string propName)
            : base($"Property {propName} is not nullable. So canBeNull cannot be set to true")
        {
        }
    }
}


<|start_filename|>src/SimpleRules/Attributes/RegexRuleAttribute.cs<|end_filename|>
﻿using System.Text.RegularExpressions;

namespace SimpleRules.Attributes
{
    public abstract class RegexRuleAttribute : BaseRuleAttribute
    {
        public abstract string RegularExpression { get; }
    }
}


<|start_filename|>src/SimpleRules/Exceptions/DuplicateMetadataRegistrationException.cs<|end_filename|>
﻿using System;

namespace SimpleRules.Exceptions
{
    public class DuplicateMetadataRegistrationException : Exception
    {
        public DuplicateMetadataRegistrationException(string typeName, string metaTypeName)
            : base($"An attempt to register {typeName}, {metaTypeName} again was detected")
        {

        }
    }
}


<|start_filename|>src/SimpleRules/EvaluatedRule.cs<|end_filename|>
﻿using System;
using System.Linq.Expressions;

namespace SimpleRules
{
    public class EvaluatedRule
    {
        private Delegate _delegate;

        public string MessageFormat { get; set; }
        public LambdaExpression Expression { get; set; }
        public RuleType RuleType { get; set; }
        public Delegate Delegate
        {
            get
            {
                if (_delegate == null)
                    _delegate = Expression.Compile();
                return _delegate;
            }
        }
    }
}


<|start_filename|>test/SimpleRules.Tests/HandlerIdentificationTests.cs<|end_filename|>
﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using SimpleRules.Attributes;
using SimpleRules.Contracts;
using SimpleRules.Exceptions;
using SimpleRules.Generic;
using SimpleRules.Handlers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace SimpleRules.Tests
{
    [TestClass]
    public class HandlerIdentificationTests
    {
        [TestMethod]
        public void CanIdentifyAppropriateHandler()
        {
            var handlerMapping = new Dictionary<Type, IHandler>();
            handlerMapping.Add(typeof(SimpleRuleHandler), new SimpleRuleHandler());

            var type = typeof(Employee);
            var ruleAttributes = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                     .SelectMany(p => p.GetCustomAttributes<BaseRuleAttribute>())
                                     .ToList();
            ruleAttributes.ForEach(attr =>
            {
                var handler = handlerMapping.FindHandler(attr);
                Assert.IsNotNull(handler);
                Assert.AreEqual(typeof(SimpleRuleHandler), handler.GetType());
            });
        }

        [TestMethod]
        [ExpectedException(typeof(HandlerNotFoundException))]
        public void CanThrowExceptionsForUnkownAttributes()
        {
            var handlerMapping = new Dictionary<Type, IHandler>();
            var type = typeof (Employee);
            var ruleAttributes = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                     .SelectMany(p => p.GetCustomAttributes<BaseRuleAttribute>())
                                     .ToList();
            var handler = handlerMapping.FindHandler(ruleAttributes.First());
        }

        [TestMethod]
        public void CanGenerateEvaluatedRuleForGreaterThanAttribute()
        {
            var handler = new SimpleRuleHandler();
            var targetProp = typeof (Registration).GetProperty("EndDate");
            var greaterThanAttribute = new GreaterThanAttribute("StartDate");
            var evaluatedRule = handler.GenerateEvaluatedRule<Registration>(greaterThanAttribute, targetProp);
            Assert.IsNotNull(evaluatedRule);
            Assert.AreEqual("End Date should be Greater Than the Start Date.", evaluatedRule.MessageFormat);
            Assert.IsNotNull(evaluatedRule.Expression);
        }

        [TestMethod]
        public void CanIdentifyCustomHandlersRegistered()
        {
            var activities = new List<Activity>
            {
                new Activity { Id = 1, StartDate = DateTime.Parse("04/01/2017"), EndDate = DateTime.Parse("03/30/2017"), Capacity = 45, Name = "Cricket" }
            };
            var validationResults = new SimpleRulesEngine()
                                        .RegisterCustomRule<RangeRuleHandler>()
                                        .Validate<Activity>(activities);
            Assert.AreEqual(1, validationResults.Count());
            var validationResult = validationResults.First();
            Assert.AreEqual(2, validationResult.Errors.Count);
            Assert.AreEqual("Capacity should be between 10 and 30", validationResult.Errors[1]);
            Assert.AreEqual("End Date should be Greater Than the Start Date.", validationResult.Errors[0]);
        }

        [TestMethod]
        public void CanIdentifyCustomHandlersUsingDiscovery()
        {
            var activities = new List<Activity>
            {
                new Activity { Id = 1, StartDate = DateTime.Parse("04/01/2017"), EndDate = DateTime.Parse("03/30/2017"), Capacity = 45, Name = "Cricket" }
            };
            var validationResults = new SimpleRulesEngine()
                                            .DiscoverHandlers(new[] { typeof(Marker) })
                                            .Validate<Activity>(activities);
            Assert.AreEqual(1, validationResults.Count());
            var validationResult = validationResults.First();
            Assert.AreEqual(2, validationResult.Errors.Count);
            Assert.AreEqual("Capacity should be between 10 and 30", validationResult.Errors[1]);
            Assert.AreEqual("End Date should be Greater Than the Start Date.", validationResult.Errors[0]);
        }

        [TestMethod]
        public void CanDealWithMultipleCallsToDiscoverHandlers()
        {
            var activities = new List<Activity>
            {
                new Activity { Id = 1, StartDate = DateTime.Parse("04/01/2017"), EndDate = DateTime.Parse("03/30/2017"), Capacity = 45, Name = "Cricket" }
            };
            var validationResults = new SimpleRulesEngine()
                                            .DiscoverHandlers(new[] { typeof(Marker) })
                                            .DiscoverHandlers(new[] { typeof(Marker) })
                                            .Validate<Activity>(activities);
            Assert.AreEqual(1, validationResults.Count());
            var validationResult = validationResults.First();
            Assert.AreEqual(2, validationResult.Errors.Count);
            Assert.AreEqual("Capacity should be between 10 and 30", validationResult.Errors[1]);
            Assert.AreEqual("End Date should be Greater Than the Start Date.", validationResult.Errors[0]);
        }
    }

    public class Marker
    {

    }

    public class Registration
    {
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
    }

    public class Activity
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime StartDate { get; set; }
        [GreaterThan("StartDate")]
        public DateTime EndDate { get; set; }
        [RangeRule(10, 30)]
        public int Capacity { get; set; }
    }

    public class RangeRuleAttribute : BaseRuleAttribute
    {
        public RangeRuleAttribute(int minValue, int maxValue)
        {
            MinValue = minValue;
            MaxValue = maxValue;
        }

        public int MinValue { get; set; }
        public int MaxValue { get; set; }
    }

    public class RangeRuleHandler : IHandler
    {
        public EvaluatedRule GenerateEvaluatedRule<TConcrete>(BaseRuleAttribute attribute, PropertyInfo targetProp)
        {
            var rangeAttr = attribute as RangeRuleAttribute;
            var input = Expression.Parameter(typeof(TConcrete), "a");
            var propName = targetProp.Name;
            var comparison = Expression.And(
                    Expression.GreaterThan(Expression.PropertyOrField(input, propName), Expression.Constant(rangeAttr.MinValue)),
                    Expression.LessThan(Expression.PropertyOrField(input, propName), Expression.Constant(rangeAttr.MaxValue))
                );
            var lambda = Expression.Lambda(comparison, input);
            return new EvaluatedRule
            {
                MessageFormat = string.Format("{0} should be between {1} and {2}", propName, rangeAttr.MinValue, rangeAttr.MaxValue),
                RuleType = RuleType.Error,
                Expression = lambda
            };
        }

        public bool Handles(BaseRuleAttribute attribute)
        {
            return typeof(RangeRuleAttribute).IsAssignableFrom(attribute.GetType());
        }
    }
}


<|start_filename|>src/SimpleRules/Handlers/RegexRuleHandler.cs<|end_filename|>
﻿using SimpleRules.Attributes;
using SimpleRules.Contracts;
using System.Reflection;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Linq;

namespace SimpleRules.Handlers
{
    public class RegexRuleHandler : IHandler
    {
        public EvaluatedRule GenerateEvaluatedRule<TConcrete>(BaseRuleAttribute attribute, PropertyInfo targetProp)
        {
            var regexAttr = attribute as RegexRuleAttribute;
            var input = Expression.Parameter(typeof(TConcrete), "i");
            var ctor = typeof(Regex).GetConstructors().SingleOrDefault(c => c.GetParameters().Count() == 1);
            var method = typeof(Regex).GetMethods()
                                      .SingleOrDefault(m => m.Name == "IsMatch" && 
                                                            !m.IsStatic && 
                                                            m.GetParameters().Count() == 1);
            var leftExpr = Expression.PropertyOrField(input, targetProp.Name);
            var block = Expression.Block(
                            Expression.Call(
                                Expression.New(
                                    ctor, 
                                    Expression.Constant(regexAttr.RegularExpression)), method, leftExpr)
                        );
            var expression = Expression.Lambda(block, input);
            return new EvaluatedRule
            {
                RuleType = RuleType.Error,
                MessageFormat = string.Format("{0} does not match the expected format", targetProp.Name.AddSpaces(), regexAttr.RegularExpression),
                Expression = expression
            };
        }

        public bool Handles(BaseRuleAttribute attribute)
        {
            return typeof(RegexRuleAttribute).IsAssignableFrom(attribute.GetType());
        }
    }
}


<|start_filename|>src/SimpleRules/Contracts/IHandler.cs<|end_filename|>
﻿using SimpleRules.Attributes;
using System.Reflection;

namespace SimpleRules.Contracts
{
    public interface IHandler
    {
        bool Handles(BaseRuleAttribute attribute);
        EvaluatedRule GenerateEvaluatedRule<T>(BaseRuleAttribute attribute, PropertyInfo targetProp);
    }
}


<|start_filename|>src/SimpleRules/ReflectionExtensions.cs<|end_filename|>
﻿using System;
using System.Linq;
using SimpleRules.Contracts;
using System.Collections.Generic;

namespace SimpleRules
{
    public static class ReflectionExtensions
    {
        public static IEnumerable<Type> FindHandlerTypesInAssemblies(this Type[] assemblyMarkerTypes)
        {
            var assemblies = assemblyMarkerTypes.Select(a => a.GetAssembly());
            var handlerTypes = assemblies
                                    .SelectMany(a => a.GetTypes())
                                    .Where(a => typeof(IHandler).IsAssignableFrom(a) && 
                                                !a.IsInterface());
            return handlerTypes;
        }

        public static IHandler CreateInstance(this Type type)
        {
            var instance = Activator.CreateInstance(type);
            return instance as IHandler;
        }
    }
}
