C# 脚本引擎RulesEngine的使用详解
当编写应用程序时,经常性需要花费大量的时间与精力处理业务逻辑,往往业务逻辑的变化需要重构或者增加大量代码,对开发测试人员很不友好。
之前在这篇文章说过,可以使用脚本引擎来将我们需要经常变化的代码进行动态编译执行,自由度非常大,不过对应的需要资源也多。如果只是针对非常具体业务逻辑的变化,可以尝试使用RulesEngine对程序进行操作。
下文使用了官方示例且部分内容翻译自说明文档
简介
RulesEngine是微软推出的规则引擎,规则引擎在很多企业开发中有所应用,是处理经常变动需求的一种优雅的方法。个人任务,规则引擎适用于以下的一些场景:
- 输入输出类型数量比较固定,但是执行逻辑经常变化;
- switch条件经常变化,复杂switch语句的替代;
- 会变动的,具有多种条件或者规则的业务逻辑;
- 规则自由度不要求特别高的场景。(这种情况建议使用脚本引擎)
RulesEngine的规则使用JSON进行存储,通过lambda表达式方式表述规则(Rules)。
安装很方便,直接使用nuget进行安装:
install-pacakgeRulesEngine
规则定义
需要有Rules,有WorkflowName,然后还有一些属性。
[ { "WorkflowName":"Discount", "Rules":[ { "RuleName":"GiveDiscount10", "SuccessEvent":"10", "ErrorMessage":"Oneormoreadjustrulesfailed.", "ErrorType":"Error", "RuleExpressionType":"LambdaExpression", "Expression":"input1.country==\"india\"ANDinput1.loyalityFactor<=2ANDinput1.totalPurchasesToDate>=5000ANDinput2.totalOrders>2ANDinput3.noOfVisitsPerMonth>2" } ] } ]
除了标准的RuleExpressionType,还可以通过定义Rules嵌套多个条件,下面是Or逻辑。
{ "RuleName":"GiveDiscount30NestedOrExample", "SuccessEvent":"30", "ErrorMessage":"Oneormoreadjustrulesfailed.", "ErrorType":"Error", "Operator":"OrElse", "Rules":[ { "RuleName":"IsLoyalAndHasGoodSpend", "ErrorMessage":"Oneormoreadjustrulesfailed.", "ErrorType":"Error", "RuleExpressionType":"LambdaExpression", "Expression":"input1.loyalityFactor>3ANDinput1.totalPurchasesToDate>=50000ANDinput1.totalPurchasesToDate<=100000" }, { "RuleName":"OrHasHighNumberOfTotalOrders", "ErrorMessage":"Oneormoreadjustrulesfailed.", "ErrorType":"Error", "RuleExpressionType":"LambdaExpression", "Expression":"input2.totalOrders>15" } ] }
示例
可以从官方的代码库中下载示例,定义了上述规则,就可以直接开始用了。示例描述了这么一个应用场景:
根据不同的客户属性,提供不同的折扣。由于销售的情况变化较快,提供折扣的规则也需要经常变动。因此比较适用于规则引擎。
publicvoidRun() { Console.WriteLine($"Running{nameof(BasicDemo)}...."); //创建输入 varbasicInfo="{\"name\":\"hello\",\"email\":\"abcy@xyz.com\",\"creditHistory\":\"good\",\"country\":\"canada\",\"loyalityFactor\":3,\"totalPurchasesToDate\":10000}"; varorderInfo="{\"totalOrders\":5,\"recurringItems\":2}"; vartelemetryInfo="{\"noOfVisitsPerMonth\":10,\"percentageOfBuyingToVisit\":15}"; varconverter=newExpandoObjectConverter(); dynamicinput1=JsonConvert.DeserializeObject(basicInfo,converter); dynamicinput2=JsonConvert.DeserializeObject (orderInfo,converter); dynamicinput3=JsonConvert.DeserializeObject (telemetryInfo,converter); varinputs=newdynamic[] { input1, input2, input3 }; //加载规则 varfiles=Directory.GetFiles(Directory.GetCurrentDirectory(),"Discount.json",SearchOption.AllDirectories); if(files==null||files.Length==0) thrownewException("Rulesnotfound."); varfileData=File.ReadAllText(files[0]); varworkflowRules=JsonConvert.DeserializeObject >(fileData); //初始化规则引擎 varbre=newRulesEngine.RulesEngine(workflowRules.ToArray(),null); stringdiscountOffered="Nodiscountoffered."; //执行规则 List
resultList=bre.ExecuteAllRulesAsync("Discount",inputs).Result; //处理结果 resultList.OnSuccess((eventName)=>{ discountOffered=$"Discountofferedis{eventName}%overMRP."; }); resultList.OnFail(()=>{ discountOffered="Theuserisnoteligibleforanydiscount."; }); Console.WriteLine(discountOffered); }
输入
输入一般来说是IEnumerable
varnestedInput=new{ SimpleProp="simpleProp", NestedProp=new{ SimpleProp="nestedSimpleProp", ListProp=newList{ newListItem { Id=1, Value="first" }, newListItem { Id=2, Value="second" } } } };
命名空间
和脚本引擎一样,默认规则引擎只能访问System的命名空间。如果需要使用到稍微复杂一些的类型,可以自己定义类型或者函数。比如定义一个这样的函数:
publicstaticclassUtils { publicstaticboolCheckContains(stringcheck,stringvalList) { if(String.IsNullOrEmpty(check)||String.IsNullOrEmpty(valList)) returnfalse; varlist=valList.Split(',').ToList(); returnlist.Contains(check); } }
需要使用的时候,先将类传递给RulesEngine:
varreSettingsWithCustomTypes=newReSettings{CustomTypes=newType[]{typeof(Utils)}}; varengine=newRulesEngine.RulesEngine(workflowRules.ToArray(),null,reSettingsWithCustomTypes);
然后就可以直接在表达式中使用了。
"Expression":"Utils.CheckContains(input1.country,\"india,usa,canada,France\")==true"
规则参数
默认情况下,规则的输入使用的是类似input1input2这样的形式,如果想直观一点,可以使用RuleParameter来进行封装具体的参数类型。
RuleParameterruleParameter=newRuleParameter("NIP",nestedInput); varresultList=bre.ExecuteAllRulesAsync(workflow.WorkflowName,ruleParameter).Result;
本地变量
如果表达式比较复杂的情况下,可以使用本地变量来进行分段处理,这对调试来说会比较方便。
本地变量的关键字为localParams,可以将中间的内容简单理解成varname=expression
{ "name":"allow_access_if_all_mandatory_trainings_are_done_or_access_isSecure", "errorMessage":"Pleasecompleteallyourtraining(s)togetaccesstothiscontentoraccessitfromasecuredomain/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也比较简洁,上手非常简单。
以上就是C#脚本引擎RulesEngine的使用详解的详细内容,更多关于C#脚本引擎RulesEngine的资料请关注毛票票其它相关文章!