diff --git a/demo/DemoApp/DemoApp.csproj b/demo/DemoApp/DemoApp.csproj index fb6e5d91..0b3c2182 100644 --- a/demo/DemoApp/DemoApp.csproj +++ b/demo/DemoApp/DemoApp.csproj @@ -16,7 +16,7 @@ PreserveNewest - PreserveNewest + Always diff --git a/demo/DemoApp/NestedInputDemo.cs b/demo/DemoApp/NestedInputDemo.cs index 9427af14..b8b7d0ba 100644 --- a/demo/DemoApp/NestedInputDemo.cs +++ b/demo/DemoApp/NestedInputDemo.cs @@ -51,7 +51,11 @@ public void Run() var fileData = File.ReadAllText(files[0]); var Workflows = JsonConvert.DeserializeObject>(fileData); - var bre = new RulesEngine.RulesEngine(Workflows.ToArray(), null); + var executionRules = new ReSettings(); + executionRules.NestedRuleExecutionMode = NestedRuleExecutionMode.Performance; + executionRules.CustomTypes = new Type[] { typeof(pep) }; + + var bre = new RulesEngine.RulesEngine(Workflows.ToArray(), executionRules); foreach (var workflow in Workflows) { var resultList = bre.ExecuteAllRulesAsync(workflow.WorkflowName, nestedInput).Result; @@ -63,8 +67,18 @@ public void Run() }); } + } + public class pep + { + public string Key; + public object Value; + public pep(string key, object value) + { + this.Key = key; + this.Value = value; + } } } } \ No newline at end of file diff --git a/demo/DemoApp/Program.cs b/demo/DemoApp/Program.cs index fe87de6d..4c792331 100644 --- a/demo/DemoApp/Program.cs +++ b/demo/DemoApp/Program.cs @@ -7,10 +7,10 @@ public static class Program { public static void Main(string[] args) { - new BasicDemo().Run(); - new JSONDemo().Run(); + //new BasicDemo().Run(); + //new JSONDemo().Run(); new NestedInputDemo().Run(); - new EFDemo().Run(); + //new EFDemo().Run(); } } } diff --git a/demo/DemoApp/Workflows/NestedInputDemo.json b/demo/DemoApp/Workflows/NestedInputDemo.json index 8c043843..1bdf901d 100644 --- a/demo/DemoApp/Workflows/NestedInputDemo.json +++ b/demo/DemoApp/Workflows/NestedInputDemo.json @@ -1,6 +1,20 @@ [ { "WorkflowName": "NestedInputDemoWorkflow1", + "GlobalParams": [ + { + "Name": "papa", + "Expression": "new object[] { \"1\", 4, null }" + }, + { + "Name": "pa", + "Expression": "papa[0]" + }, + { + "Name": "obj", + "Expression": "new pep[] { new pep(\"popo\" as string, pa as object) }" + } + ], "Rules": [ { "RuleName": "CheckNestedSimpleProp", diff --git a/src/RulesEngine/Actions/ActionContext.cs b/src/RulesEngine/Actions/ActionContext.cs index af66cdc8..fe28e3fe 100644 --- a/src/RulesEngine/Actions/ActionContext.cs +++ b/src/RulesEngine/Actions/ActionContext.cs @@ -41,6 +41,11 @@ public RuleResultTree GetParentRuleResult() return _parentResult; } + public bool ContainsKey(string name) + { + return _context.ContainsKey(name); + } + public bool TryGetContext(string name,out T output) { try diff --git a/src/RulesEngine/Actions/EvaluateRuleAction.cs b/src/RulesEngine/Actions/EvaluateRuleAction.cs index 032308e8..0017fb41 100644 --- a/src/RulesEngine/Actions/EvaluateRuleAction.cs +++ b/src/RulesEngine/Actions/EvaluateRuleAction.cs @@ -26,10 +26,11 @@ internal async override ValueTask ExecuteAndReturnResultAsync( var innerResult = await base.ExecuteAndReturnResultAsync(context, ruleParameters, includeRuleResults); var output = innerResult.Output as ActionRuleResult; List resultList = null; - if (includeRuleResults) + + if (includeRuleResults || output?.Results?.Count > 0) { resultList = new List(output?.Results ?? new List() { }); - resultList.AddRange(innerResult.Results); + if (innerResult.Results?.Count() > 0) resultList.AddRange(innerResult.Results); } return new ActionRuleResult { Output = output?.Output, diff --git a/src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs b/src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs index ac43d10c..9e36cd0d 100644 --- a/src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs +++ b/src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs @@ -37,7 +37,7 @@ private void PopulateMethodInfo() } public Expression Parse(string expression, ParameterExpression[] parameters, Type returnType) { - var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) }; + var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes), AllowNewToEvaluateAnyType = true }; return new ExpressionParser(parameters, expression, new object[] { }, config).Parse(returnType); } diff --git a/src/RulesEngine/HelperFunctions/Utils.cs b/src/RulesEngine/HelperFunctions/Utils.cs index 4ae9262a..bfe71fd0 100644 --- a/src/RulesEngine/HelperFunctions/Utils.cs +++ b/src/RulesEngine/HelperFunctions/Utils.cs @@ -12,6 +12,23 @@ namespace RulesEngine.HelperFunctions { public static class Utils { + /* valid only for netstandard 2.0 */ +#if NETSTANDARD2_0 || NETSTANDARD2_1 + + public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) + { + HashSet seenKeys = new HashSet(); + foreach (TSource element in source) + { + if (seenKeys.Add(keySelector(element))) + { + yield return element; + } + } + } +#endif + /* **************************** */ + public static object GetTypedObject(dynamic input) { if (input is ExpandoObject) diff --git a/src/RulesEngine/RuleCompiler.cs b/src/RulesEngine/RuleCompiler.cs index 2c3f33a6..c04086c4 100644 --- a/src/RulesEngine/RuleCompiler.cs +++ b/src/RulesEngine/RuleCompiler.cs @@ -83,7 +83,7 @@ private RuleFunc GetDelegateForRule(Rule rule, RuleParameter[] r var scopedParamList = GetRuleExpressionParameters(rule.RuleExpressionType, rule?.LocalParams, ruleParams); var extendedRuleParams = ruleParams.Concat(scopedParamList.Select(c => new RuleParameter(c.ParameterExpression.Name, c.ParameterExpression.Type))) - .ToArray(); + .DistinctBy(e => e.Name).ToArray(); RuleFunc ruleFn; @@ -118,25 +118,27 @@ private RuleExpressionParameter[] GetRuleExpressionParameters(RuleExpressionType foreach (var lp in localParams) { - try + if (!ruleExpParams.Any(e => e.ParameterExpression.Name == lp.Name)) { - var lpExpression = expressionBuilder.Parse(lp.Expression, parameters.ToArray(), null); - var ruleExpParam = new RuleExpressionParameter() { - ParameterExpression = Expression.Parameter(lpExpression.Type, lp.Name), - ValueExpression = lpExpression - }; - parameters.Add(ruleExpParam.ParameterExpression); - ruleExpParams.Add(ruleExpParam); - } - catch(Exception ex) - { - var message = $"{ex.Message}, in ScopedParam: {lp.Name}"; - throw new RuleException(message); + try + { + var lpExpression = expressionBuilder.Parse(lp.Expression, parameters.ToArray(), null); + var ruleExpParam = new RuleExpressionParameter() { + ParameterExpression = Expression.Parameter(lpExpression.Type, lp.Name), + ValueExpression = lpExpression + }; + parameters.Add(ruleExpParam.ParameterExpression); + ruleExpParams.Add(ruleExpParam); + } + catch (Exception ex) + { + var message = $"{ex.Message}, in ScopedParam: {lp.Name}"; + throw new RuleException(message, ex); + } } } } return ruleExpParams.ToArray(); - } /// @@ -250,7 +252,7 @@ private RuleFunc GetWrappedRuleFunc(Rule rule, RuleFunc e.Name); var result = ruleFunc(extendedInputs.ToArray()); return result; }; @@ -260,5 +262,7 @@ private RuleExpressionBuilderBase GetExpressionBuilder(RuleExpressionType expres { return _expressionBuilderFactory.RuleGetExpressionBuilder(expressionType); } + + } } diff --git a/src/RulesEngine/RulesEngine.cs b/src/RulesEngine/RulesEngine.cs index 1a7d62e2..de756255 100644 --- a/src/RulesEngine/RulesEngine.cs +++ b/src/RulesEngine/RulesEngine.cs @@ -103,8 +103,8 @@ public async ValueTask> ExecuteAllRulesAsync(string workflo { var sortedRuleParams = ruleParams.ToList(); sortedRuleParams.Sort((RuleParameter a, RuleParameter b) => string.Compare(a.Name, b.Name)); - var ruleResultList = ValidateWorkflowAndExecuteRule(workflowName, sortedRuleParams.ToArray()); - await ExecuteActionAsync(ruleResultList); + var ruleResultList = await ValidateWorkflowAndExecuteRule(workflowName, sortedRuleParams.ToArray()); + //await ExecuteActionAsync(ruleResultList); // Actions now are executed after each rule. return ruleResultList; } @@ -116,14 +116,21 @@ private async ValueTask ExecuteActionAsync(IEnumerable ruleResul { await ExecuteActionAsync(ruleResult.ChildResults); } - var actionResult = await ExecuteActionForRuleResult(ruleResult, false); - ruleResult.ActionResult = new ActionResult { - Output = actionResult.Output, - Exception = actionResult.Exception - }; + var actionResult = await ExecuteActionForRuleResult(ruleResult, true); + ruleResult.ActionResult = actionResult; } } + private async ValueTask ExecuteActionAsync(RuleResultTree ruleResult) + { + if (ruleResult.ChildResults != null) + { + await ExecuteActionAsync(ruleResult.ChildResults); + } + var actionResult = await ExecuteActionForRuleResult(ruleResult, true); + ruleResult.ActionResult = actionResult; + } + public async ValueTask ExecuteActionWorkflowAsync(string workflowName, string ruleName, RuleParameter[] ruleParameters) { var compiledRule = CompileRule(workflowName, ruleName, ruleParameters); @@ -250,13 +257,13 @@ public void RemoveWorkflow(params string[] workflowNames) /// input /// workflow name /// list of rule result set - private List ValidateWorkflowAndExecuteRule(string workflowName, RuleParameter[] ruleParams) + private async Task> ValidateWorkflowAndExecuteRule(string workflowName, RuleParameter[] ruleParams) { List result; if (RegisterRule(workflowName, ruleParams)) { - result = ExecuteAllRuleByWorkflow(workflowName, ruleParams); + result = await ExecuteAllRuleByWorkflow(workflowName, ruleParams); } else { @@ -329,13 +336,14 @@ private RuleFunc CompileRule(Rule rule, RuleParameter[] rulePara /// /// /// list of rule result set - private List ExecuteAllRuleByWorkflow(string workflowName, RuleParameter[] ruleParameters) + private async Task> ExecuteAllRuleByWorkflow(string workflowName, RuleParameter[] ruleParameters) { var result = new List(); var compiledRulesCacheKey = GetCompiledRulesKey(workflowName, ruleParameters); foreach (var compiledRule in _rulesCache.GetCompiledRules(compiledRulesCacheKey)?.Values) - { + { var resultTree = compiledRule(ruleParameters); + await ExecuteActionAsync(resultTree); result.Add(resultTree); } diff --git a/src/RulesEngine/RulesEngine.csproj b/src/RulesEngine/RulesEngine.csproj index 7ad5a926..c3d253a7 100644 --- a/src/RulesEngine/RulesEngine.csproj +++ b/src/RulesEngine/RulesEngine.csproj @@ -1,8 +1,9 @@ - net6.0;netstandard2.0 - 4.0.0 + netstandard2.0;netstandard2.1 + Library + 4.1.3 Copyright (c) Microsoft Corporation. LICENSE https://github.com/microsoft/RulesEngine @@ -10,20 +11,7 @@ Rules Engine is a package for abstracting business logic/rules/policies out of the system. This works in a very simple way by giving you an ability to put your rules in a store outside the core logic of the system thus ensuring that any change in rules doesn't affect the core system. https://github.com/microsoft/RulesEngine/blob/main/CHANGELOG.md BRE, Rules Engine, Abstraction - - - - true - - - true - true - snupkg - True - ..\..\signing\RulesEngine-publicKey.snk - True - true - true + true diff --git a/src/RulesEngine/install-nuget.bat b/src/RulesEngine/install-nuget.bat new file mode 100644 index 00000000..7ae39ed2 --- /dev/null +++ b/src/RulesEngine/install-nuget.bat @@ -0,0 +1 @@ +dotnet nuget push bin\Debug\RulesEngine.4.1.3.nupkg --interactive --source AvivaLabs -k \ No newline at end of file diff --git a/src/RulesEngine/nuget.config b/src/RulesEngine/nuget.config new file mode 100644 index 00000000..fa1c2425 --- /dev/null +++ b/src/RulesEngine/nuget.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file