diff --git a/README.md b/README.md index b4d7c92..9126bfc 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,9 @@ Rules Engine is a library/NuGet package for abstracting business logic/rules/pol To install this library, please download the latest version of [NuGet Package](https://www.nuget.org/packages/RulesEngine/) from [nuget.org](https://www.nuget.org/) and refer it into your project. ## How to use it +There are several ways to populate workflows for the Rules Engine as listed below. -You need to store the rules based on the [schema definition](https://github.com/microsoft/RulesEngine/blob/main/schema/workflowRules-schema.json) given and they can be stored in any store as deemed appropriate like Azure Blob Storage, Cosmos DB, Azure App Configuration, SQL Servers, file systems etc. The expressions are supposed to be a [lambda expressions](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions). +You need to store the rules based on the [schema definition](https://github.com/microsoft/RulesEngine/blob/main/schema/workflowRules-schema.json) given and they can be stored in any store as deemed appropriate like Azure Blob Storage, Cosmos DB, Azure App Configuration, [Entity Framework](https://github.com/microsoft/RulesEngine#entity-framework), SQL Servers, file systems etc. The expressions are supposed to be a [lambda expressions](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions). An example rule could be - ```json @@ -52,7 +53,7 @@ Once done, the Rules Engine needs to execute the rules for a given input. It can ```c# List response = await rulesEngine.ExecuteAllRulesAsync(workflowName, input); ``` -Here, *workflowName* is the name of the workflow, which is *Discount* in the above mentioned example. And *input* is the object which needs to be checked against the rules. +Here, *workflowName* is the name of the workflow, which is *Discount* in the above mentioned example. And *input* is the object which needs to be checked against the rules, which itself may consist of a list of class instances. The *response* will contain a list of [*RuleResultTree*](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#ruleresulttree) which gives information if a particular rule passed or failed. @@ -61,6 +62,36 @@ _Note: A detailed example showcasing how to use Rules Engine is explained in [Ge _A demo app for the is available at [this location](https://github.com/microsoft/RulesEngine/tree/main/demo)._ +### Basic +A simple example via code only is as follows: +```c# +List rules = new List(); + +Rule rule = new Rule(); +rule.RuleName = "Test Rule"; +rule.SuccessEvent = "Count is within tolerance."; +rule.ErrorMessage = "Over expected."; +rule.Expression = "count < 3"; +rule.RuleExpressionType = RuleExpressionType.LambdaExpression; + +rules.Add(rule); + +workflowRule.Rules = rules; + +workFlowRules.Add(workflowRule); + +var bre = new RulesEngine.RulesEngine(workFlowRules.ToArray(), null); +``` + + +### Entity Framework +Consuming Entity Framework and populating the Rules Engine is shown in the [EFDemo class](https://github.com/microsoft/RulesEngine/blob/main/demo/DemoApp/EFDemo.cs) with Workflow rules populating the array and passed to the Rules Engine, The Demo App includes an example [RulesEngineDemoContext](https://github.com/microsoft/RulesEngine/blob/main/demo/DemoApp.EFDataExample/RulesEngineDemoContext.cs) using SQLite and could be swapped out for another provider. +```c# +var wfr = db.WorkflowRules.Include(i => i.Rules).ThenInclude(i => i.Rules).ToArray(); +var bre = new RulesEngine.RulesEngine(wfr, null); +``` +*Note: For each level of nested rules expected, a ThenInclude query appended will be needed as shown above.* + ## How it works ![](https://github.com/microsoft/RulesEngine/blob/main/assets/BlockDiagram.png) diff --git a/demo/DemoApp/BasicDemo.cs b/demo/DemoApp/BasicDemo.cs index 9e70e9f..9f5cab1 100644 --- a/demo/DemoApp/BasicDemo.cs +++ b/demo/DemoApp/BasicDemo.cs @@ -17,45 +17,51 @@ namespace DemoApp 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}"; + List workFlowRules = new List(); + WorkflowRules workflowRule = new WorkflowRules(); + workflowRule.WorkflowName = "Test Workflow Rule 1"; - var converter = new ExpandoObjectConverter(); + List rules = new List(); - dynamic input1 = JsonConvert.DeserializeObject(basicInfo, converter); - dynamic input2 = JsonConvert.DeserializeObject(orderInfo, converter); - dynamic input3 = JsonConvert.DeserializeObject(telemetryInfo, converter); + Rule rule = new Rule(); + rule.RuleName = "Test Rule"; + rule.SuccessEvent = "Count is within tolerance."; + rule.ErrorMessage = "Over expected."; + rule.Expression = "count < 3"; + rule.RuleExpressionType = RuleExpressionType.LambdaExpression; + rules.Add(rule); + + workflowRule.Rules = rules; + + workFlowRules.Add(workflowRule); + + var bre = new RulesEngine.RulesEngine(workFlowRules.ToArray(), null); + + dynamic datas = new ExpandoObject(); + datas.count = 1; var inputs = new dynamic[] - { - input1, - input2, - input3 - }; + { + datas + }; - var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "Discount.json", SearchOption.AllDirectories); - if (files == null || files.Length == 0) - throw new Exception("Rules not found."); + List resultList = bre.ExecuteAllRulesAsync("Test Workflow Rule 1", inputs).Result; - var fileData = File.ReadAllText(files[0]); - var workflowRules = JsonConvert.DeserializeObject>(fileData); + bool outcome = false; - var bre = new RulesEngine.RulesEngine(workflowRules.ToArray(), null); - - string discountOffered = "No discount offered."; - - List resultList = bre.ExecuteAllRulesAsync("Discount", inputs).Result; + //Different ways to show test results: + outcome = resultList.TrueForAll(r => r.IsSuccess); resultList.OnSuccess((eventName) => { - discountOffered = $"Discount offered is {eventName} % over MRP."; + Console.WriteLine($"Result '{eventName}' is as expected."); + outcome = true; }); resultList.OnFail(() => { - discountOffered = "The user is not eligible for any discount."; + outcome = false; }); - Console.WriteLine(discountOffered); + Console.WriteLine($"Test outcome: {outcome}."); } } } diff --git a/demo/DemoApp/JSONDemo.cs b/demo/DemoApp/JSONDemo.cs new file mode 100644 index 0000000..a97c8f5 --- /dev/null +++ b/demo/DemoApp/JSONDemo.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using RulesEngine.Models; +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.IO; +using static RulesEngine.Extensions.ListofRuleResultTreeExtension; + +namespace DemoApp +{ + public class JSONDemo + { + public void Run() + { + Console.WriteLine($"Running {nameof(JSONDemo)}...."); + 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(basicInfo, converter); + dynamic input2 = JsonConvert.DeserializeObject(orderInfo, converter); + dynamic input3 = JsonConvert.DeserializeObject(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>(fileData); + + var bre = new RulesEngine.RulesEngine(workflowRules.ToArray(), null); + + string discountOffered = "No discount offered."; + + List 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); + } + } +} diff --git a/demo/DemoApp/Program.cs b/demo/DemoApp/Program.cs index 039c5cf..fe87de6 100644 --- a/demo/DemoApp/Program.cs +++ b/demo/DemoApp/Program.cs @@ -8,6 +8,7 @@ namespace DemoApp public static void Main(string[] args) { new BasicDemo().Run(); + new JSONDemo().Run(); new NestedInputDemo().Run(); new EFDemo().Run(); }