1、背景说明
1.1 规则引擎的使用场景
RulesEngine是Microsoft推出的一个规则引擎项目,用于系统中抽象出的业务逻辑/规则/策略。在医疗行业中经常涉及的功能就是知识库或CDSS,这个基本上就是各种各样的规则集合及提示。例如:两个药品之间会有配伍禁忌、相互作用,因此不能一起配液或同时使用;某药品的给药频率为一天一次(QD),不可为一天三次(TID)、一天四次(QID)等。而这种场合的特点就是输入信息基本不变,规则经常变(常常是增加)。现阶段增加意味着增加代码,发布版本。因此普通企业/软件的这种需求,特别适合规则引擎。
偶然在网上看到规则引擎,当天就研究了下,真的是太好用了!所以总结一下
1.2 demo的代码说明
我用的环境:win10企业版、VS2019社区版,.NET Framework 4.7.2,然后新建控制台程序
2、演示
首先从NuGet上下载并安装RulesEngine
。如图所示
2.1 入门demo演示
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using RulesEngine.Models;
namespace REDemo2
{
class Program
{
static async Task Main(string[] args)
{
//模拟用户的输入内容
var userInput = new UserInput
{
IdNo = null,
Age = 18
};
//定义规则
var rulesStr = @"[{
""WorkflowName"": ""UserInputWorkflow"",
""Rules"": [
{
""RuleName"": ""CheckAge"",
""ErrorMessage"": ""年龄必须大于18岁."",
""ErrorType"": ""Error"",
""RuleExpressionType"": ""LambdaExpression"",
""Expression"": ""Age > 18""
},
{
""RuleName"": ""CheckIDNoIsEmpty"",
""ErrorMessage"": ""身份证号不可以为空."",
""ErrorType"": ""Error"",
""RuleExpressionType"": ""LambdaExpression"",
""Expression"": ""IdNo != null""
}
]
}] ";
//反序列化Json格式规则字符串
var workflowRules = JsonConvert.DeserializeObject<List<WorkflowRules>>(rulesStr);
//初始化规则引擎
var rulesEngine = new RulesEngine.RulesEngine(workflowRules.ToArray());
//使用规则进行判断,并返回结果
List<RuleResultTree> resultList = await rulesEngine.ExecuteAllRulesAsync("UserInputWorkflow", userInput);
//返回结果并展示
foreach (var item in resultList)
{
Console.WriteLine("验证成功:{0},消息:{1}", item.IsSuccess, item.ExceptionMessage);
}
Console.ReadLine();
}
}
public class UserInput
{
public string IdNo { get; set; }
public int Age { get; set; }
}
}
代码的规则,只是简单的验证年龄必须大于18岁及身份证号不能为空。
运行以上代码,展示的结果如下:
可以通过以上的入门demo展示规则引擎的使用。
2.1.2 代码下载
样例代码下载。代码下载 提取码:NHZL
2.2 规则参数说明
先拿一个完整的样例说一下规则,如下图所示
整个规则分为如上图的三部分,这三者又是嵌套的关系。其中1和3是必须的,2可有可无。若没有2,则全部按默认处理。这三部分的定义如下
2.2.1 第一部分参数说明
第一部分的参数只有两个。两个都是必须项,具体定义如下
参数名称 |
类型 |
说明 |
必须 |
WorkflowName |
string |
定义被规则引擎识别的名称 |
是 |
Rules |
array |
具体的规则。规则可以进行多层嵌套 |
是 |
2.2.2 第二部分参数说明
第二部分官方文档中称之为Rule。具体定义如下
参数名称 |
类型 |
说明 |
必须 |
RuleName |
string |
规则的名称 |
是 |
Operator |
enum |
操作符.用于第3部分多个规则之间的关系。共有4种:And 、AndAlso 、Or 、OrElse
|
是 |
ErrorMessage |
string |
错误信息。用于返回的信息 |
否 |
ErrorType |
enum |
错误类型。共有两种:Warning 、Error
|
否 |
SuccessEvent |
string |
完成事件,默认为规则名称 |
否 |
Rules |
Rules |
规则数组,也就说可以嵌套 |
是 |
2.2.3 第三部分参数说明
第三部分参数在文档中称之为Leaf Rule
,意思就是最小的规则。也就是具体规则内容。
参数名称 |
类型 |
说明 |
必须 |
RuleName |
string |
规则的名称 |
是 |
Expression |
string |
表单式,具体的规则内容 |
是 |
RuleExpressionType |
enum |
虽然是枚举类型,但目前只有一种,只能填写LambdaExpression
|
是 |
ErrorMessage |
string |
提示的错误信息 |
否 |
ErrorType |
enum |
错误类型。共有两种:Warning 、Error
|
否 |
SuccessEvent |
string |
完成事件,默认为规则名称 |
否 |
以上定义的官方文档:官方文档链接
2.3 在Expression使用自定义判断
上面的例子在第三部分的参数(leaf rule)的Expression中,只能使用系统(system)自带的方法进行逻辑判断。即string自带的方法、int自带的方法。若想使用自定义方法,则需要进行额外的处理。
2.3.1 先编写业务逻辑判断方法
首先先编写业务逻辑判断方法,如下所示
public static class IdCardUtil
{
//此处使用了C#的方法扩展
public static int GetAgeByIdCard(this string str)
{
//假设进行了相关处理,得到结果
return 45;
}
//此处是正常的业务逻辑方法
public static int getAgeByIdCardNo(string str)
{
return 50;
}
}
2.3.2 通过ReSettings进行增加自定义类型
然后通过ReSettings增加自定义类型
private static readonly RulesEngine.Models.ReSettings reSettings = new RulesEngine.Models.ReSettings
{
CustomTypes = new[] { typeof(IdCardUtil) }
};
2.3.2 修改具体规则
//定义规则
var rulesStr = @"[{
""WorkflowName"": ""UserInputWorkflow"",
""Rules"": [
{
""RuleName"": ""CheckNestedSimpleProp"",
""ErrorMessage"": ""年龄必须小于18岁."",
""ErrorType"": ""Error"",
""RuleExpressionType"": ""LambdaExpression"",
""Expression"": ""IdCardUtil.getAgeByIdCardNo(IdNo) < 18"" //这是常用的方法。
""Expression"": ""IdNo.GetAgeByIdCard() < 18"" // 这个是使用C#自定义扩展方法。 这两句任选一个就可以。
},
{
""RuleName"": ""CheckNestedSimpleProp1"",
""ErrorMessage"": ""身份证号不可以为空."",
""ErrorType"": ""Error"",
""RuleExpressionType"": ""LambdaExpression"",
""Expression"": ""IdNo != null""
}
]
}] ";
规则中""Expression"": ""IdCardUtil.getAgeByIdCardNo(IdNo) < 18""
和""Expression"": ""IdNo.GetAgeByIdCard() < 18""
的效果是一样的,任选一个就OK了
2.3.3 将自定义类型进行注册
将自定义的内容进行注册
var rulesEngine = new RulesEngine.RulesEngine(workflowRules.ToArray(),null,reSettings); //添加reSettings内容
然后就可以正常运行了。
2.3.4 代码下载
源码下载。代码 提取码:NHZL
2.4 其他参数说明
2.4.1 RuleParameter(规则参数)
可以对输入内容可以使用RuleParameter来进行封装具体的参数类型。
//这是原始的输入
var zhenglininfo = new UserInput
{
IdNo = null,
Age = 18
};
//使用RuleParameter进行封装
RulesEngine.Models.RuleParameter ruleParameter = new RulesEngine.Models.RuleParameter("Test", zhenglininfo);
.....
//相应的修改
List<RulesEngine.Models.RuleResultTree> resultList = await rulesEngine.ExecuteAllRulesAsync("UserInputWorkflow", ruleParameter);
2.4.2 LocalParams(本地变量)
这个比较重要,这个会将多个条件进行拆分,并分别单独命名,然后使用这些命名进行逻辑判断。例如
var rulesStr = @"[{
""WorkflowName"": ""UserInputWorkflow"",
""Rules"": [
{
""RuleName"": ""CheckAge"",
""ErrorMessage"": ""年龄必须大于18岁."",
""ErrorType"": ""Error"",
""localParams"": [
{
""Name"": ""model1"",
""Expression"": ""Age!=0""
},
{
""Name"": ""model2"",
""Expression"": ""IdCardUtil.getAgeByIdCardNo(Test.IdNo) < 18""
}
],
""RuleExpressionType"": ""LambdaExpression"",
""Expression"": ""model1 AND model2""
},
{
""RuleName"": ""CheckIDNoIsEmpty"",
""ErrorMessage"": ""身份证号不可以为空."",
""ErrorType"": ""Error"",
""RuleExpressionType"": ""LambdaExpression"",
""Expression"": ""IdNo != null ""
}
]
}] ";
上面就是本地变量的规则样例,其中model2中,传入了Test.IdNo
,这就是用到了2.4.1的RuleParameter。
2.4.2.1 代码下载
LocalParams的代码下载。下载地址 提取码:NHZL
2.4.3 GlobalParams(全局变量)
GlobalParams(全局变量)是定义在workflow层面,并且可以在任何rule中使用。例如:
//Rule.json
{
"WorkflowName": "workflowWithGlobalParam",
"GlobalParams":[
{
"Name":"myglobal1",
"Expression":"myInput.hello.ToLower()"
}
],
"Rules":[
{
"RuleName": "checkGlobalEqualsHello",
"Expression":"myglobal1 == \"hello\""
},
{
"RuleName": "checkGlobalEqualsInputHello",
"Expression":"myInput.hello.ToLower() == myglobal1"
}
]
}
全局变量会在所有的rule中使用,下面的语句将返回true
//使用规则进行判断,并返回结果
var resultList = await rulesEngine.ExecuteAllRulesAsync("UserInputWorkflow", ruleParameter);
foreach (var item in resultList)
{
Console.WriteLine("验证成功:{0},消息:{1}", item.IsSuccess, item.ExceptionMessage);
}
2.4.3.2 方法2
var resultList = rulesEngine.ExecuteAllRulesAsync("UserInputWorkflow", ruleParameter).Result;
resultList.OnSuccess((eventName) => {
Console.WriteLine("{0}是ok!", eventName);
}).OnFail(() => {
Console.WriteLine("失败了!");
});
两个方法略有不同
2.5 后续学习
规则引擎主要是System.Linq.Dynamic.Core
,下一步继续学习下这块内容
2.6 参考资料
主要参考资料有:
1、官网资料。官网
2、波多尔斯基 的《C#规则引擎RulesEngine》
3、微软MVP精选 | .NET RulesEngine(规则引擎)