Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Few improvements to suggest. #402

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion demo/DemoApp/DemoApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Workflows\NestedInputDemo.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

Expand Down
16 changes: 15 additions & 1 deletion demo/DemoApp/NestedInputDemo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ public void Run()
var fileData = File.ReadAllText(files[0]);
var Workflows = JsonConvert.DeserializeObject<List<Workflow>>(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;
Expand All @@ -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;
}
}
}
}
6 changes: 3 additions & 3 deletions demo/DemoApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}
14 changes: 14 additions & 0 deletions demo/DemoApp/Workflows/NestedInputDemo.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
5 changes: 5 additions & 0 deletions src/RulesEngine/Actions/ActionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ public RuleResultTree GetParentRuleResult()
return _parentResult;
}

public bool ContainsKey(string name)
{
return _context.ContainsKey(name);
}

public bool TryGetContext<T>(string name,out T output)
{
try
Expand Down
5 changes: 3 additions & 2 deletions src/RulesEngine/Actions/EvaluateRuleAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ internal async override ValueTask<ActionRuleResult> ExecuteAndReturnResultAsync(
var innerResult = await base.ExecuteAndReturnResultAsync(context, ruleParameters, includeRuleResults);
var output = innerResult.Output as ActionRuleResult;
List<RuleResultTree> resultList = null;
if (includeRuleResults)

if (includeRuleResults || output?.Results?.Count > 0)
{
resultList = new List<RuleResultTree>(output?.Results ?? new List<RuleResultTree>() { });
resultList.AddRange(innerResult.Results);
if (innerResult.Results?.Count() > 0) resultList.AddRange(innerResult.Results);
}
return new ActionRuleResult {
Output = output?.Output,
Expand Down
2 changes: 1 addition & 1 deletion src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

}
Expand Down
17 changes: 17 additions & 0 deletions src/RulesEngine/HelperFunctions/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
HashSet<TKey> seenKeys = new HashSet<TKey>();
foreach (TSource element in source)
{
if (seenKeys.Add(keySelector(element)))
{
yield return element;
}
}
}
#endif
/* **************************** */

public static object GetTypedObject(dynamic input)
{
if (input is ExpandoObject)
Expand Down
36 changes: 20 additions & 16 deletions src/RulesEngine/RuleCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ private RuleFunc<RuleResultTree> 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<RuleResultTree> ruleFn;

Expand Down Expand Up @@ -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();

}

/// <summary>
Expand Down Expand Up @@ -250,7 +252,7 @@ private RuleFunc<RuleResultTree> GetWrappedRuleFunc(Rule rule, RuleFunc<RuleResu
return resultFn(ruleParams);
}

var extendedInputs = ruleParams.Concat(scopedParams);
var extendedInputs = ruleParams.Concat(scopedParams).DistinctBy(e => e.Name);
var result = ruleFunc(extendedInputs.ToArray());
return result;
};
Expand All @@ -260,5 +262,7 @@ private RuleExpressionBuilderBase GetExpressionBuilder(RuleExpressionType expres
{
return _expressionBuilderFactory.RuleGetExpressionBuilder(expressionType);
}


}
}
30 changes: 19 additions & 11 deletions src/RulesEngine/RulesEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ public async ValueTask<List<RuleResultTree>> 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;
}

Expand All @@ -116,14 +116,21 @@ private async ValueTask ExecuteActionAsync(IEnumerable<RuleResultTree> 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<ActionRuleResult> ExecuteActionWorkflowAsync(string workflowName, string ruleName, RuleParameter[] ruleParameters)
{
var compiledRule = CompileRule(workflowName, ruleName, ruleParameters);
Expand Down Expand Up @@ -250,13 +257,13 @@ public void RemoveWorkflow(params string[] workflowNames)
/// <param name="input">input</param>
/// <param name="workflowName">workflow name</param>
/// <returns>list of rule result set</returns>
private List<RuleResultTree> ValidateWorkflowAndExecuteRule(string workflowName, RuleParameter[] ruleParams)
private async Task<List<RuleResultTree>> ValidateWorkflowAndExecuteRule(string workflowName, RuleParameter[] ruleParams)
{
List<RuleResultTree> result;

if (RegisterRule(workflowName, ruleParams))
{
result = ExecuteAllRuleByWorkflow(workflowName, ruleParams);
result = await ExecuteAllRuleByWorkflow(workflowName, ruleParams);
}
else
{
Expand Down Expand Up @@ -329,13 +336,14 @@ private RuleFunc<RuleResultTree> CompileRule(Rule rule, RuleParameter[] rulePara
/// <param name="workflowName"></param>
/// <param name="ruleParams"></param>
/// <returns>list of rule result set</returns>
private List<RuleResultTree> ExecuteAllRuleByWorkflow(string workflowName, RuleParameter[] ruleParameters)
private async Task<List<RuleResultTree>> ExecuteAllRuleByWorkflow(string workflowName, RuleParameter[] ruleParameters)
{
var result = new List<RuleResultTree>();
var compiledRulesCacheKey = GetCompiledRulesKey(workflowName, ruleParameters);
foreach (var compiledRule in _rulesCache.GetCompiledRules(compiledRulesCacheKey)?.Values)
{
{
var resultTree = compiledRule(ruleParameters);
await ExecuteActionAsync(resultTree);
result.Add(resultTree);
}

Expand Down
20 changes: 4 additions & 16 deletions src/RulesEngine/RulesEngine.csproj
Original file line number Diff line number Diff line change
@@ -1,29 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<Version>4.0.0</Version>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<OutputType>Library</OutputType>
<Version>4.1.3</Version>
<Copyright>Copyright (c) Microsoft Corporation.</Copyright>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageProjectUrl>https://github.com/microsoft/RulesEngine</PackageProjectUrl>
<Authors>Abbas Cyclewala</Authors>
<Description>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.</Description>
<PackageReleaseNotes>https://github.com/microsoft/RulesEngine/blob/main/CHANGELOG.md</PackageReleaseNotes>
<PackageTags>BRE, Rules Engine, Abstraction</PackageTags>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
</PropertyGroup>
<PropertyGroup Label="SourceLink">
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\..\signing\RulesEngine-publicKey.snk</AssemblyOriginatorKeyFile>
<DelaySign>True</DelaySign>
<Deterministic>true</Deterministic>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/RulesEngine/install-nuget.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dotnet nuget push bin\Debug\RulesEngine.4.1.3.nupkg --interactive --source AvivaLabs -k <PAT Token>
18 changes: 18 additions & 0 deletions src/RulesEngine/nuget.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="avivacred" value="https://pkgs.dev.azure.com/avivacred/_packaging/avivacred/nuget/v3/index.json" />
<add key="AvivaLabs" value="https://nuget.pkg.github.com/Aviva-Labs/index.json" />
<add key="ianido" value="https://nuget.pkg.github.com/ianido/index.json" />
</packageSources>
<packageSourceCredentials>
<AvivaLabs>
<add key="Username" value="token" />
<add key="ClearTextPassword" value="%AVIVA_GITHUB_PACKAGE_TOKEN%" />
</AvivaLabs>
<ianido>
<add key="Username" value="token" />
<add key="ClearTextPassword" value="%AVIVA_GITHUB_PACKAGE_TOKEN%" />
</ianido>
</packageSourceCredentials>
</configuration>