允许用户在C#中定义规则的最佳方法

时间:2013-08-01 18:36:32

标签: c# asp.net-mvc rules

我一直在研究规则引擎等,但我真的不知道从哪里开始。这更多是为了实验,但我希望将来能够实现这样的工作。基本上,我有一个应用程序,用户提交表单并使用多个属性填充POCO对象。我希望应用程序的管理员能够基于所述对象的属性定义规则并将它们存储在关系数据库中。提交表单后,我会根据用户定义的规则做出决定。例如,管理员可以进入应用程序并定义如下规则:

if (typeID == 4 && request.benchMarkScore < 10) {
    request.denied = true;
    request.denyReasons.Add("Score too low for this product");
}

这是我的POCO对象示例:

class Request
{
    public int benchMarkScore { get; set; }
    public int typeID { get; set; }
    public double rate { get; set; }
    public bool isEligable { get; set; }
    public bool denied { get; set; }
    public List<string> denyReasons { get; set; }
    public Dictionary<string, double> adjustments;
}

当然,我知道这是一个过于简化的示例,但我遇到过很多情况,我的用户可以在我的应用程序中受益于此功能。我不是在寻找一个完整的解决方案,而是想知道从哪里开始。

2 个答案:

答案 0 :(得分:2)

有很多方法可以解决这个问题。一个建议是利用反射本身,并允许管理员应用规则。我将保持这个简单,但规则将包括:

  1. 一堆属性,操作数和值
  2. 拒绝的理由。
  3. 所以让我们定义一下。我将保持这个简单,只是处理相等,你可以定义其他的:

    public enum Operand
    {
        Equals
    }
    

    现在,我们可以定义一个名为IRule的接口。我正在定义一个接口,以便将来可能会将特殊的,更复杂的规则放入其中。

    public interface IRule<TPOCO> where TPOCO : class
    {
        bool IsValid(TPOCO poco);
    }
    

    现在我们将定义我们的Rule类(注意:这不会处理索引属性):

    public class PropertyCompareRule : IRule<Request>
    {
        private sealed class PropertyCompare
        {
            public string PropertyName {get; set; }
            public Operand Operand {get; set; }
            public object Value {get; set;}
            public string Reason {get; set; }
        }
        private List<PropertyCompare> _comparers = new List<PropertyCompare>();
    
        public bool IsValid(Request poco)
        {
            bool isValid = true; // let's be optimistic!
            PropertyInfo[] properties = poco.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where((property) => property.GetIndexParameters().Length == 0 && property.CanRead).ToArray();
            foreach(var property in properties)
            {
                foreach(var comparer in _comparers)
                {
                    bool localIsValid;
                    if(comparer.PropertyName == property.Name)
                    {
                        object val = property.GetValue(poco, null);
                        switch(comparer.Operand)
                        {
                            case Operand.Equals:
                                {
                                    localIsValid = object.Equals(val, property.Value);
                                    break;
                                }
                        }
    
                        if(!localIsValid)
                        {
                            poco.denyReasons.Add(comparer.Reason);
                            isValid = false;
                        }
                    }
                }
            }
            return isValid;
        }
    
        public void AddComparer(string propertyName, Operand op, object value, string reason)
        {
            _comparers.Add(new PropertyCompare() { PropertyName = propertyName, Operand = op, Value = value, Reason = reason });
        }
    }
    

    您可以在数据库或其他此类存储中保留属性名称,操作数和值详细信息并不困难。假设我们充实了上面的枚举,我们可以想象:

    PropertyCompareRule rule = new PropertyCompareRule();
    rule.AddComparer("typeID", Operand.Equal, 4, "Reason 1");
    rule.AddComparer("benchMarkScore", Operand.LessThan, 10, "Reason 2");
    
    bool valid = rule.IsValid(somePocoInstance);
    

    编辑:一些注释

    1. 我使用localIsValid而非第一次机会拯救。如果需要,您可以更改此设置,但其想法是允许单个规则具有多个可拒绝点。这可能是也可能不是你想要的 - 但是很容易重构代码,以便在单个属性比较失败时挽救它。

    2. 这是一个挑剔,但一般来说C#style-guidlines规定属性不应该是驼峰式的......但这完全取决于你在一天结束时:)

      < / LI>

答案 1 :(得分:0)

据我了解,您正在寻找某种业务规则的脚本系统。我找到了这个blog post,其中提到了一些脚本环境。

您也可以动态创建装配体,如下所述:https://stackoverflow.com/a/4181855/1229622