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.";
//执行规则
ListresultList=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的资料请关注毛票票其它相关文章!