• C#规则引擎RulesEngine


    当编写应用程序时,经常性需要花费大量的时间与精力处理业务逻辑,往往业务逻辑的变化需要重构或者增加大量代码,对开发测试人员很不友好。

    之前在这篇文章说过,可以使用脚本引擎来将我们需要经常变化的代码进行动态编译执行,自由度非常大,不过对应的需要资源也多。如果只是针对非常具体业务逻辑的变化,可以尝试使用RulesEngine对程序进行操作。

    下文使用了官方示例且部分内容翻译自说明文档

    简介

    RulesEngine是微软推出的规则引擎,规则引擎在很多企业开发中有所应用,是处理经常变动需求的一种优雅的方法。个人任务,规则引擎适用于以下的一些场景:

    • 输入输出类型数量比较固定,但是执行逻辑经常变化;
    • switch条件经常变化,复杂switch语句的替代;
    • 会变动的,具有多种条件或者规则的业务逻辑;
    • 规则自由度不要求特别高的场景。(这种情况建议使用脚本引擎)

    RulesEngine的规则使用JSON进行存储,通过lambda表达式方式表述规则(Rules)。

    安装很方便,直接使用nuget进行安装:

    install-pacakge RulesEngine
    

    规则定义

    需要有Rules,有WorkflowName,然后还有一些属性。

    [
      {
        "WorkflowName": "Discount",
        "Rules": [
          {
            "RuleName": "GiveDiscount10",
            "SuccessEvent": "10",
            "ErrorMessage": "One or more adjust rules failed.",
            "ErrorType": "Error",
            "RuleExpressionType": "LambdaExpression",
            "Expression": "input1.country == "india" AND input1.loyalityFactor <= 2 AND input1.totalPurchasesToDate >= 5000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
          }
        ]
      }
    ]
    

    除了标准的RuleExpressionType,还可以通过定义Rules嵌套多个条件,下面是Or逻辑。

    {
    "RuleName": "GiveDiscount30NestedOrExample",
    "SuccessEvent": "30",
    "ErrorMessage": "One or more adjust rules failed.",
    "ErrorType": "Error",
    "Operator": "OrElse",
    "Rules":[
        {
        "RuleName": "IsLoyalAndHasGoodSpend",
        "ErrorMessage": "One or more adjust rules failed.",
        "ErrorType": "Error",
        "RuleExpressionType": "LambdaExpression",
        "Expression": "input1.loyalityFactor > 3 AND input1.totalPurchasesToDate >= 50000 AND input1.totalPurchasesToDate <= 100000"
        },
        {
        "RuleName": "OrHasHighNumberOfTotalOrders",
        "ErrorMessage": "One or more adjust rules failed.",
        "ErrorType": "Error",
        "RuleExpressionType": "LambdaExpression",
        "Expression": "input2.totalOrders > 15"
        }
    ]
    }
    

    示例

    可以从官方的代码库中下载示例,定义了上述规则,就可以直接开始用了。示例描述了这么一个应用场景:

    根据不同的客户属性,提供不同的折扣。由于销售的情况变化较快,提供折扣的规则也需要经常变动。因此比较适用于规则引擎。

    public void Run()
    {
        Console.WriteLine($"Running {nameof(BasicDemo)}....");
        //创建输入
        var basicInfo = "{"name": "hello","email": "abcy@xyz.com","creditHistory": "good","country": "canada","loyalityFactor": 3,"totalPurchasesToDate": 10000}";
        var orderInfo = "{"totalOrders": 5,"recurringItems": 2}";
        var telemetryInfo = "{"noOfVisitsPerMonth": 10,"percentageOfBuyingToVisit": 15}";
    
        var converter = new ExpandoObjectConverter();
    
        dynamic input1 = JsonConvert.DeserializeObject<ExpandoObject>(basicInfo, converter);
        dynamic input2 = JsonConvert.DeserializeObject<ExpandoObject>(orderInfo, converter);
        dynamic input3 = JsonConvert.DeserializeObject<ExpandoObject>(telemetryInfo, converter);
    
        var inputs = new dynamic[]
            {
                input1,
                input2,
                input3
            };
        //加载规则
        var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "Discount.json", SearchOption.AllDirectories);
        if (files == null || files.Length == 0)
            throw new Exception("Rules not found.");
    
        var fileData = File.ReadAllText(files[0]);
        var workflowRules = JsonConvert.DeserializeObject<List<WorkflowRules>>(fileData);
        //初始化规则引擎
        var bre = new RulesEngine.RulesEngine(workflowRules.ToArray(), null);
    
        string discountOffered = "No discount offered.";
        //执行规则
        List<RuleResultTree> resultList = bre.ExecuteAllRulesAsync("Discount", inputs).Result;
        //处理结果
        resultList.OnSuccess((eventName) => {
            discountOffered = $"Discount offered is {eventName} % over MRP.";
        });
    
        resultList.OnFail(() => {
            discountOffered = "The user is not eligible for any discount.";
        });
    
        Console.WriteLine(discountOffered);
    }
    

    输入

    输入一般来说是IEnumerable<dynamic>或者是匿名类型,上面实例展示的是由json反序列化形成的dynamic类型,对于程序生成的数据,使用匿名类型更加方便。

    var nestedInput = new {
                    SimpleProp = "simpleProp",
                    NestedProp = new {
                        SimpleProp = "nestedSimpleProp",
                        ListProp = new List<ListItem>
                        {
                            new ListItem
                            {
                                Id = 1,
                                Value = "first"
                            },
                            new ListItem
                            {
                                Id = 2,
                                Value = "second"
                            }
                        }
                    }
    
                };
    

    命名空间

    和脚本引擎一样,默认规则引擎只能访问System的命名空间。如果需要使用到稍微复杂一些的类型,可以自己定义类型或者函数。比如定义一个这样的函数:

    public static class Utils
    {
        public static bool CheckContains(string check, string valList)
        {
            if (String.IsNullOrEmpty(check) || String.IsNullOrEmpty(valList))
                return false;
    
            var list = valList.Split(',').ToList();
            return list.Contains(check);
        }
    }
    

    需要使用的时候,先将类传递给RulesEngine:

    var reSettingsWithCustomTypes = new ReSettings { CustomTypes = new Type[] { typeof(Utils) } };
    var engine = new RulesEngine.RulesEngine(workflowRules.ToArray(), null, reSettingsWithCustomTypes);
    

    然后就可以直接在表达式中使用了。

    "Expression": "Utils.CheckContains(input1.country, "india,usa,canada,France") == true"
    

    规则参数

    默认情况下,规则的输入使用的是类似input1 input2这样的形式,如果想直观一点,可以使用RuleParameter来进行封装具体的参数类型。

    RuleParameter ruleParameter = new RuleParameter("NIP", nestedInput);
    var resultList = bre.ExecuteAllRulesAsync(workflow.WorkflowName, ruleParameter).Result;
    

    本地变量

    如果表达式比较复杂的情况下,可以使用本地变量来进行分段处理,这对调试来说会比较方便。

    本地变量的关键字为localParams,可以将中间的内容简单理解成var name = expression

    {
            "name": "allow_access_if_all_mandatory_trainings_are_done_or_access_isSecure",
            "errorMessage": "Please complete all your training(s) to get access to this content or access it from a secure domain/location.",
            "errorType": "Error",
            "localParams": [
              {
                "name": "completedSecurityTrainings",
                "expression": "MasterSecurityComplainceTrainings.Where(Status.Equals("Completed", StringComparison.InvariantCultureIgnoreCase))"
              },
              {
                "name": "completedProjectTrainings",
                "expression": "MasterProjectComplainceTrainings.Where(Status.Equals("Completed", StringComparison.InvariantCultureIgnoreCase))"
              },
              {
                "name": "isRequestAccessSecured",
                "expression": "UserRequestDetails.Location.Country == "India" ? ((UserRequestDetails.Location.City == "Bangalore" && UserRequestDetails.Domain="xxxx")? true : false):false"
              }
            ],
            "expression": "(completedSecurityTrainings.Any() && completedProjectTrainings.Any()) || isRequestAccessSecured "
          }
    

    总结

    使用规则引擎,可以将经常变动的业务逻辑独立摘出来,为我们编写动态、可拓展的程序提供了很大的便利。RulesEngine这个东西提供的API也比较简洁,上手非常简单。

    除非特殊说明,本作品由podolski创作,采用知识共享署名 4.0 国际许可协议进行许可。欢迎转载,转载请保留原文链接~喜欢的观众老爷们可以点下关注或者推荐~
  • 相关阅读:
    .net web开发经典图书总结
    Asp.net Web API实战
    扩展方法之二分查找
    在线转换图片文件等
    如何减少代码中的分支语句
    web插件化解决方案 开发分享
    .NET 4.5 MEF 基于约定的编程模型重典
    所有排序总结(内排序)
    生成zip文件
    open source ESB and integration platform
  • 原文地址:https://www.cnblogs.com/podolski/p/14380360.html
Copyright © 2020-2023  润新知