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

本文关键字:定义 规则 方法 最佳 许用户 用户 | 更新日期: 2023-09-27 18:26:03

我一直在研究规则引擎之类的东西,但我真的不知道从哪里开始。这更多的是为了实验,但我想在未来的工作中实现这样的东西。基本上,我有一个应用程序,其中用户提交一个表单,并用几个属性填充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;
}

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

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

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

  1. 一组属性、操作数和值
  2. 拒绝的原因

让我们来定义一下。我将保持简单,只处理等式,您可以定义其他等式:

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#风格的指导方针规定属性不应该是骆驼帽。。。但这完全取决于你:)

据我所知,您正在寻找某种用于业务规则的脚本系统。我发现这篇博客文章中提到了一些脚本环境。

您也可以像这里提到的那样动态创建程序集:https://stackoverflow.com/a/4181855/1229622.