By chance, let me take out RulesEngine to complete a business. For the business, it is mainly to complete a business judgment of scalability (uncertain types and uncertain conditions, and the change of conditions may be continuously increased and modified). For example, after completing an achievement system, the administrator can create it. For achievements, there are one-time unlocking, daily, weekly, and reset at any time. Each time they reach Chengdu, they are triggered. In the face of the increase of achievement tasks, it is a headache for programmers to add and modify these achievement tasks every time. Well, you should have a simple understanding of this. Then follow the author to see how to use very little code to complete a simple dynamic logic processing in. NET.
RulesEngine overview
yes Microsoft A rule engine project is introduced for business logic / rules / policies abstracted from the system. In the process of our development, it is inevitable to deal with this repeated business logic. For this dynamic rule, it is an elegant way to use us to reduce the modification of our code or project.
How to use
At present, we can import the library in the form of nuget, as shown below:
dotnet add package RulesEngine
For rule configuration, you can directly use typed parameters. The author mainly aims to make it clear to you, so JSON configuration is used for demonstration.
//Deserialize Json format rule string var workflowRules = JsonConvert.DeserializeObject<List<WorkflowRules>>(rulesStr); var rulesEngine = new RulesEngine.RulesEngine(workflowRules.ToArray());
//Define rules var rulesStr = @"[{ ""WorkflowName"": ""UserInputWorkflow"", ""Rules"": [ { ""RuleName"": ""CheckAge"", ""ErrorMessage"": ""Age must be greater than 18."", ""ErrorType"": ""Error"", ""RuleExpressionType"": ""LambdaExpression"", ""Expression"": ""Age > 18"" }, { ""RuleName"": ""CheckIDNoIsEmpty"", ""ErrorMessage"": ""The ID number can not be empty.."", ""ErrorType"": ""Error"", ""RuleExpressionType"": ""LambdaExpression"", ""Expression"": ""IdNo != null"" } ] }] ";
As shown above, we have defined rule information. For this information, the author stores JSON data by default. Of course, you can store the following contents and split the following data structures into the database.
attribute | describe |
---|---|
RuleName | Rule name |
Properties | Rule attribute, get or set the custom attribute or tag of the rule |
Operator | Operator |
ErrorMessage | Error message |
Enabled | Gets and sets whether the rule is enabled |
RuleExpressionType | The regular expression type is LambdaExpression by default. Of course, there is only one such expression at present |
WorkflowRulesToInJect | Injection workflow rules |
Rules | rule |
LocalParams | Local parameters |
Expression | Expression tree |
Actions | |
SuccessEvent | Completion event, default to rule name |
Let's take a look at the results generated by the code. For this content, the author creates a class as follows:
public class UserInput { public string IdNo { get; set; } public int Age { get; set; } }
static async Task Main(string[] args) { var userInput = new UserInput { IdNo = null, Age = 18 }; //Deserialize Json format rule string 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("Validation succeeded:{0},Message:{1}",item.IsSuccess,item.ExceptionMessage); } Console.ReadLine(); }
The output results are as follows:
Validation succeeded: False,Message: age must be greater than 18. Validation succeeded: False,Message: the ID number can not be empty..
The return structure resultList is as follows:
{ "Rule":{ "RuleName":"CheckNestedSimpleProp","Properties":null,"Operator":null,"ErrorMessage":"Age must be greater than 18.", "ErrorType":"Error","RuleExpressionType":"LambdaExpression","WorkflowRulesToInject":null,"Rules":null,"LocalParams":null,"Expression":"Age > 18","Actions":null,"SuccessEvent":null},"IsSuccess":false,"ChildResults":null,"Inputs":{ "input1":{ "IdNo":null,"Age":18} }, "ActionResult":{ "Output":null,"Exception":null},"ExceptionMessage":"Age must be greater than 18.","RuleEvaluatedParams":[]}
Use extension method in expression tree
I believe you have a simple understanding of the use of the rule engine. Let's take another advanced version.
For example, I think that the age of input is not accurate, I want to calculate the age by ID number, then how do I operate? Under normal circumstances, we will pass the expansion method, then pass the ID number parameter to the processing program. After the processing program is completed, it will return to our age, and how do we operate it in this? Let's look down.
Adding custom types through ReSettings will extend methods, because the methods they can use are limited to [System namespace], so we need to add custom classes to the settings.
private static readonly ReSettings reSettings = new ReSettings { CustomTypes = new[] { typeof(IdCardUtil) } };
Amend the following:
var rulesEngine = new RulesEngine.RulesEngine(workflowRules.ToArray(), null, reSettings: reSettings);
var rulesStr = @"[{ ""WorkflowName"": ""UserInputWorkflow"", ""Rules"": [ { ""RuleName"": ""CheckNestedSimpleProp"", ""ErrorMessage"": ""Age must be less than 18."", ""ErrorType"": ""Error"", ""RuleExpressionType"": ""LambdaExpression"", ""Expression"": ""IdNo.GetAgeByIdCard() < 18"" }, { ""RuleName"": ""CheckNestedSimpleProp1"", ""ErrorMessage"": ""The ID number can not be empty.."", ""ErrorType"": ""Error"", ""RuleExpressionType"": ""LambdaExpression"", ""Expression"": ""IdNo != null"" } ] }] ";
The output results are as follows:
Validation succeeded: False,Message: age must be less than 18. Validation succeeded: True,Message:
Multi object combination condition
Next, we modify the previous rule content and add a class ListItem. After assigning the content, we create an anonymous type with two attributes, user and items. Finally, we make a logical judgment through our multi condition combination.
var rulesStr = @"[{ ""WorkflowName"": ""UserInputWorkflow"", ""Rules"": [ { ""RuleName"": ""CheckNestedSimpleProp"", ""ErrorMessage"": ""Value Value is not second."", ""ErrorType"": ""Error"", ""RuleExpressionType"": ""LambdaExpression"", ""Expression"": ""user.UserId==1 && items[0].Value==second"" } ] }] "; var userInput = new UserInput { UserId = 1, IdNo = "11010519491230002X", Age = 18 }; var input = new { user = userInput, items = new List<ListItem>() { new ListItem{ Id=1,Value="first"}, new ListItem{ Id=2,Value="second"} } };
The output results are as follows:
Validation succeeded: False,Message: Value Value is not second.
How?
For this, we should look at the principle according to the phenomenon. The internal dynamic tree is actually used System.Linq.Dynamic.Core , RulesEngine is built on this library and abstracted. It provides us with a rule engine. Let's try it System.Linq.Dynamic.Core.
We first query the set data and edit a condition string, as shown below:
var items = input.items.AsQueryable().Where("Id == 1").ToList(); foreach (var item in items) { Console.WriteLine($"Id: {item.Id},Value: {item.Value}"); }
Output results:
Id: 1,Value: first
Let's see how we implement it through the expression tree, as shown below:
Expression<Func<ListItem, bool>> predicate = x => x.Id == 1; //The input conditions are as follows var inputItem = new ListItem { Id = 1, Value = "second" }; if (inputItem.Id !=null) { predicate = predicate.And(x=>x.Id==inputItem.Id); } if (inputItem.Id != null) { predicate = predicate.And(x => x.Value == inputItem.Value); } public static class PredicateBuilder { public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) { var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>()); return Expression.Lambda<Func<T, bool>> (Expression.And(expr1.Body, invokedExpr), expr1.Parameters); } }
Normally, it's like the above. We splice conditions. I believe you can think through these conditions to determine what is suitable for you.
If dynamic query is used, the form is as follows:
var items = input.items.AsQueryable().Where("Id ==@0 && Value==@1",inputItem.Id,inputItem.Value).ToList();
Success failure event
For logic verification, since we want to do this, we must know whether it succeeded or failed. We can get the failure and success of logical verification not only through the IsSuccess of the object, but also through two events, as shown below:
var discountOffered = ""; resultList.OnSuccess((eventName) => { discountOffered = $"Success event:{eventName}."; }); resultList.OnFail(() => { discountOffered = "Failure event."; });
summary
If you are interested, you can have a look System.Linq.Dynamic.Core , because this project is still used for dynamic expression tree parsing. In addition, the project address is RulesEngine
https://github.com/hueifeng/BlogSample/tree/master/src/RulesEngineDemo