Code Monkey home page Code Monkey logo

rulesengine's Introduction

Rules Engine

build Coverage Status Nuget download

Overview

Rules Engine is a library/NuGet package for abstracting business logic/rules/policies out of a system. It provides a simple way of giving you the ability to put your rules in a store outside the core logic of the system, thus ensuring that any change in rules don't affect the core system.

Installation

To install this library, download the latest version of NuGet Package from 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 given and they can be stored in any store as deemed appropriate like Azure Blob Storage, Cosmos DB, Azure App Configuration, Entity Framework, SQL Servers, file systems etc. For RuleExpressionType LambdaExpression, the rule is written as a lambda expressions.

An example rule:

[
  {
    "WorkflowName": "Discount",
    "Rules": [
      {
        "RuleName": "GiveDiscount10",
        "SuccessEvent": "10",
        "ErrorMessage": "One or more adjust rules failed.",
        "ErrorType": "Error",
        "RuleExpressionType": "LambdaExpression",
        "Expression": "input1.country == \"india\" AND input1.loyaltyFactor <= 2 AND input1.totalPurchasesToDate >= 5000"
      },
      {
        "RuleName": "GiveDiscount20",
        "SuccessEvent": "20",
        "ErrorMessage": "One or more adjust rules failed.",
        "ErrorType": "Error",
        "RuleExpressionType": "LambdaExpression",
        "Expression": "input1.country == \"india\" AND input1.loyaltyFactor >= 3 AND input1.totalPurchasesToDate >= 10000"
      }
    ]
  }
]

You can inject the rules into the Rules Engine by initiating an instance by using the following code -

var rulesEngine = new RulesEngine(workflow);

Here, workflow is a list of deserialized objects based on the schema explained above Once initialised, the Rules Engine needs to execute the rules for a given input. This can be done by calling the method ExecuteAllRulesAsync:

List<RuleResultTree> 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, which itself may consist of a list of class instances.

The response will contain a list of RuleResultTree which gives information if a particular rule passed or failed.

Note: A detailed example showcasing how to use Rules Engine is explained in Getting Started page of Rules Engine Wiki.

A demo app for the is available at this location.

Basic

A simple example via code only is as follows:

List<Rule> rules = new List<Rule>();

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);

var workflows = new List<Workflow>();

Workflow exampleWorkflow = new Workflow();
exampleWorkflow.WorkflowName = "Example Workflow";
exampleWorkflow.Rules = rules;

workflows.Add(exampleWorkflow);

var bre = new RulesEngine.RulesEngine(workflows.ToArray());

Entity Framework

Consuming Entity Framework and populating the Rules Engine is shown in the EFDemo class with Workflow rules populating the array and passed to the Rules Engine, The Demo App includes an example RulesEngineDemoContext using SQLite and could be swapped out for another provider.

var wfr = db.Workflows.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

The rules can be stored in any store and be fed to the system in a structure which adheres to the schema of WorkFlow model.

A wrapper needs to be created over the Rules Engine package, which will get the rules and input message(s) from any store that your system dictates and put it into the Engine. The wrapper then handles the output using appropriate means.

Note: To know in detail of the workings of Rules Engine, please visit How it works section in Rules Engine Wiki.

3rd Party Tools

RulesEngine Editor

There is an editor library with it's own NuGet Package written in Blazor, more information is in it's repo https://github.com/alexreich/RulesEngineEditor.

Live Demo

https://alexreich.github.io/RulesEngineEditor

This can also be installed as a standalone PWA and used offline.

With Sample Data

https://alexreich.github.io/RulesEngineEditor/demo

Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.


For more details please check out Rules Engine Wiki.

rulesengine's People

Contributors

abbasc52 avatar aleks-ivanov avatar alexreich avatar anu20890 avatar ashishprasad avatar ayhandoslu avatar banyc avatar bavardha avatar cking27 avatar cosive-james-cooper avatar david-moreira avatar dependabot[bot] avatar dishantmunjal avatar jafin avatar joshidp avatar kulshekhar avatar marodev avatar microsoft-github-policy-service[bot] avatar mutuduxf avatar okolobaxa avatar peeveen avatar sddkorg avatar toddmeinershagen avatar uwepitsch avatar yogeshpraj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rulesengine's Issues

Dependency Injection into ReSetting Custom Types

Hi all,

Is there a method where I can inject dependencies into the custom types used in resettings? I would like to inject items that can be utilized to call external resources.

 public static class Utils
    {
        public static bool CheckContains(string check, string valList , IConfiguration configuration)
        {

            var someSetting = configuration["someSetting"];

            if (String.IsNullOrEmpty(check) || String.IsNullOrEmpty(valList))
                return false;

            var list = valList.Split(',').ToList();
            return list.Contains(check);
        }
    }

First call of Utils.GetTypedObject is very slow

We have an entity with 182 fields, which can contain different data types in the fields.

Translate the entity to a IDictionary<string, object>, run it through Utils.GetTypedObject which is slow the first time.

static IDictionary<string, object> GetEntityValues(Entity entity)
{
	ExpandoObject entityExpandoObject = new ExpandoObject();
	IDictionary<string, object> entityValues = entityExpandoObject;

	foreach (Field field in entity.Fields)
	{
		entityValues.Add(field.FieldType.Id, field.Data);
	}

	return entityValues;
}

// 10 items
List<Entity> items = GetItems();

// Doesn't make any difference, so it's not a particular item
//items.Reverse();

// Just in case for testing
await Task.Delay(TimeSpan.FromSeconds(5));

foreach (Entity item in items)
{
	IDictionary<string, object> entityValues = GetEntityValues(item);
	Stopwatch stopwatch = Stopwatch.StartNew();
	object itemEntityTyped = Utils.GetTypedObject(entityValues);
	Console.WriteLine($"GetTypedObject3 [{item.Id}]: {stopwatch.ElapsedMilliseconds}ms");
}

Results:

GetTypedObject: 824ms
GetTypedObject: 157ms
GetTypedObject: 149ms
GetTypedObject: 144ms
GetTypedObject: 134ms
GetTypedObject: 145ms
GetTypedObject: 186ms
GetTypedObject: 158ms
GetTypedObject: 191ms
GetTypedObject: 133ms

The first time it always is around 4 times slower. Is Utils.GetTypedObject still needed? Is there any way to let it use a Dictionary instead, or is there some other way we can improve the performance?

I know my work laptop isn't that fast, Intel Core i5-6200U, but it should be faster to convert 182 fields.

Executing the rules itself always is fast, at least no problems there. But, we currently only use like entity.Field == true.

I've created a separate reproduce solution for it. Which strangely is faster than the above stats.

ConsoleApp_20201028_1431_GetTypedObjectReproduce.zip

Started!
Loading values from file...
Loaded values from file
Wait 5 seconds...
Run 0, GetTypedObject: 66ms
Run 1, GetTypedObject: 0ms
Run 2, GetTypedObject: 0ms
Run 3, GetTypedObject: 0ms
Run 4, GetTypedObject: 0ms
Run 5, GetTypedObject: 0ms
Run 6, GetTypedObject: 0ms
Run 7, GetTypedObject: 0ms
Run 8, GetTypedObject: 0ms
Run 9, GetTypedObject: 0ms
Finished!

No applicable method exists in type

Same project, new issues. This time I've got the source code and the project linked to my solution, so I'm using everything out of the master branch.

My rule:

[
  {
    "WorkflowName": "ConsItemPricePurchEntity",
    "Rules": [
      {
        "RuleName": "VendorPartyNumberExists",
        "SuccessEvent": "10",
        "WarningMessage": "Vendor party number does not exist in Dynamics",
        "ErrorType": "Warning",
        "RuleExpressionType": "LambdaExpression",
        "Expression": "utils.ElementNameExists(input1.ELEMENTNAME)"
      },
      {
        "RuleName": "BuildingTypeIdExists",
        "SuccessEvent": "15",
        "WarningMessage": "BuildingTypeId doesn't exist in Dynamics",
        "ErrorType": "Warning",
        "RuleExpressionType": "LambdaExpression",
        "Expression": "utils.BuildingTypeIdExists(input1.BUILDINGTYPEID)"
      }
    ]
  }
]
results = engine.ExecuteRule(rule.WorkflowName,
    new RuleParameter(nameof(input1),input1),
    new RuleParameter(nameof(utils),utils));

I get this output for the rules:
No applicable method 'ElementNameExists' exists in type 'Utils'
No applicable method 'BuildingTypeIdExists' exists in type 'Utils'

I'm getting this whether I wrap everything in RuleParameter or not. I am, however, getting more consistent errors by making sure my input object is the first parameter.

The methods are there...

image

Using ReSettings doesn't appear to work

I have coded a custom class like the wiki example provided for using ReSettings and I get an exception:
No applicable method 'LengthGreaterThan' exists in type 'Utils'

I'm coding in VisualStudio & C#. Not sure how to get it to work.

The class is:
namespace ValidationFunction
{
public static class Utils
{
public static bool LengthGreaterThan(string s, int len)
{
return str.Length > len;
}
}
}

It's setup as
var reSettingsWithCustomTypes = new ReSettings { CustomTypes = new Type[] { typeof(Utils) } };
var bre = new RulesEngine.RulesEngine(workflowRules.ToArray(), null, reSettingsWithCustomTypes);

The rule contains: "Expression": "input1.CompanyName != null && Utils.LengthGreaterThan(input1.CompanyName, 6) == true"

why not support regular expressions ?

I wanna use this library in my project, but I found Lambda expression not support regular expression ?
Is there any suggestion for me ?

Thanks a lot

LocalParams seems to be broken in nested Rule

I have a simple class that is my input into the RulesEngine.

public class WindowsVersion
{
    public int MajorVersionNumber { get; set; }
    public int MinorVersionNumber { get; set; }
}

I setup my inputs using the following...

var input1 = new WindowsVersion { MajorVersionNumber = 10, MinorVersionNumber = 0 };
var inputs = new dynamic[]
    {
        input1
    };

Using the following JSON defining 2 workflows, executing WorkflowTest1 throws a System.Linq.Dynamic.Core.Exceptions.ParseException with message No property or field 'versionFloat' exists in type 'WindowsVersion'. Executing WorkflowTest2 executes without exception and returns the correct results. Is this a bug, a limitation, or is there something else I need to do for localParams support within rules nested beneath Or or And groups?

[
    {
        "WorkflowName": "WorkflowTest1",
        "Rules": [
            {
                "RuleName": "Test AND Group",
                "SuccessEvent": "OS is supported",
                "ErrorMessage": "OS is not not supported",
                "ErrorType": "Error",
                "Operator": "And",
                "Rules": [
                    {
                        "RuleName": "Windows 8 through Windows 10",
                        "SuccessEvent": "OS is supported",
                        "ErrorMessage": "OS is not supported",
                        "ErrorType": "Error",
                        "RuleExpressionType": "LambdaExpression",
                        "localParams": [
                            {
                                "name": "versionFloat",
                                "expression": "float.Parse(string.Format(\"{0}.{1}\", input1.majorVersionNumber.ToString(), input1.minorVersionNumber.ToString()))"
                            }
                        ],
                        "Expression": "versionFloat >= 6.2 AND versionFloat <= 10.0"
                    }
                ]
            }
        ]
    },
    {
        "WorkflowName": "WorkflowTest2",
        "Rules": [
            {
                "RuleName": "Windows 8 through Windows 10",
                "SuccessEvent": "OS is supported",
                "ErrorMessage": "OS is not supported",
                "ErrorType": "Error",
                "RuleExpressionType": "LambdaExpression",
                "localParams": [
                    {
                        "name": "versionFloat",
                        "expression": "float.Parse(string.Format(\"{0}.{1}\", input1.majorVersionNumber.ToString(), input1.minorVersionNumber.ToString()))"
                    }
                ],
                "Expression": "versionFloat >= 6.2 AND versionFloat <= 10.0"
            }
        ]
    }
]

Rules visual editor

Exist any project to create a visual editor to rules engine, like Bot Framework Composer ?

Thanks
Best regards
Carbonete

Enable all features for nested Rules

Currently certain features are limited to only top level Rules. Expectation is to allow all features to be available even when Rules are nested

Issue with Object Type Changing

I have a object example of dynamic object called Orders and a list of Products with ProductID and Gross and Net. I have the Gross and the Net set set as Double and add the different products to the the order. If the first product has the Gross or the Net as 999 instead of 999.99 it is trying to make the entire List of products be an Int64 instead of Double and the rest of the validation for the other products in the list are double it fails saying:

System.ArgumentException: 'Object of type 'System.Double' cannot be converted to type 'System.Int64'.'

Any ideas how I can force the values to stay being double?

Thanks,
Tim

No applicable method '<method>' exists in type '<static class>'

var bre = new RulesEngine.RulesEngine(workflowRules.ToArray(), null, new ReSettings { CustomTypes = new Type[] { typeof(AppHelpers) } });

When using CustomTypes, the LambdaExpressionBuilder generates an error when calling the ParseLambda method. The AppHelpers class is a public, static class and the methods in the class are all declared static just as it explains in the Wiki. There are some instances where I was able to get this to work, but most of the time it failes with the error message in the header of this issue post.

Local params of rule are not getting refreshed

Steps to repro:

  1. Define workflow that has rules. Define local param in the rule.
  2. Execute the workflow once.
  3. Change the local param expression.
  4. Remove / clear workflow
  5. Add workflow again
  6. Execute the workflow again.

The rule engine is not using the new/modified local params part of the rule in the workflow.

Signed Version of RulesEngine Assembly

My Project is a signed one, when I tried installing RulesEngine from Nuget I am not able to use it.
When I run the application and try to run the RulesEngine, I am getting below error.

Could not load file or assembly 'RulesEngine, Version=2.1.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. A strongly-named assembly is required. (Exception from HRESULT: 0x80131044)

Configurable behaviour when no input property match a rule lamda expression within the workflow

Using the sample workflow in the docs, assuming I appended the following rule to the collection:

{ "RuleName": "GiveDiscount45CurrentYearPurchasers", "SuccessEvent": "45", "ErrorMessage": "One or more adjust rules failed.", "ErrorType": "Error", "RuleExpressionType": "LambdaExpression", "Expression": "input1.loyalityFactor > 3 AND input1.currentYearPurchaseCount > 1" }

When input1 does not contain the property "currentYearPurchaseCount", we get an exception

Operator '>' incompatible with operand types 'Object' and 'Int32' (at index 62) Unhandled exception. Operator '>' incompatible with operand types 'Object' and 'Int32' (at index 62)

Do you think there should be a configurable setting to either

  • Skip that rule
  • Preferably have "Pass", "Fail", and an additional "UnEvaluated" Result.

I'm prototyping a credit scoring solution at the moment, and sometimes, for instance, the application does not have a "Business Address Validity" Score. It implies that I have to give it an arbitrary value to make it skip the configured rule when the input is not available.

Thoughts?

ExpandoObject property doesn't exist

I'm trying something different based on something you mentioned in the last issue I posted, so I thought I'd post this in a separate thread.

I need to do some OData lookups; as a result, my Utils object is now non-static and has a constructor so I can cache some things, etc.. This object is called utils and my ExpandoObject is input1.

When I run the rules engine, my code looks like this:

foreach (var entity in entities)
{
    var serializer = new DynamicTableEntityJsonSerializer();
    var converter = new ExpandoObjectConverter();

    //serialize the entity as JSON, then deserialize as an ExpandoObject
    var data = serializer.Serialize(entity);
    dynamic input1 = JsonConvert.DeserializeObject<ExpandoObject>(data, converter);

    foreach (var rule in workflowRules)
    {
        List<RuleResultTree> results;
        try
        {
            results = engine.ExecuteRule(rule.WorkflowName, new RuleParameter("utils",utils),new RuleParameter("input1",input1));
        }
        catch (Exception e)
        {
            log.LogError($"[{tableName}] {e.GetType()}: {e.Message}\r\n{e.StackTrace}");
            throw new ApplicationException($"An exception was thrown in the rules engine at row {row} for {tableName}. Check the rules and try again.");
        }

        foreach (var result in results)
        {
            if (result.IsSuccess) continue;

            var sb = new StringBuilder();
            sb.AppendLine($"{rule.WorkflowName}.{result.Rule} encountered an error in row {row}: {result.ExceptionMessage}");
            var msg = result.GetMessages();
            foreach (var warn in msg.WarningMessages)
            {
                sb.AppendLine($"Warning: {warn}");
            }

            foreach (var errorMessage in msg.ErrorMessages)
            {
                sb.AppendLine($"Error: {errorMessage}");
            }
            log.LogError(sb.ToString());
            log.LogError($"processing for {tableName} will now stop due to errors.");
            throw new ApplicationException($"Processing for {tableName} stopped because rule {result.Rule.RuleName} failed to pass.");
        }
    }

    row++;
}

Currently, I'm getting an error that says ConsItemPricePurchEntity.RulesEngine.Models.Rule encountered an error in row 1: No property or field 'ELEMENTNAME' exists in type 'ExpandoObject'. When I look, the ELEMENTNAME` field is there with a non-null value.

So two questions:

  • is it allowable to pass in a utility object in this manner?
  • is this exception part of an error?

Thanks!

Identify Specific Failure Events

While trying to use this project, I was trying to identify which rule failed evaluation. Unlike the OnSuccess extension, the OnFailure does allow for the same functionality.

resultList.OnSuccess((eventName) =>
resultList.OnFailure((???) =>

Is there a way another way to accomplish this?

Thanks

result as input

allow more complex rules to be defined by taking the result of an evaluated message and feeding it back into the rules engine with the same/different determined by the value and/or triggering some defined async action i.e. HTTP POST

Introduce Actions on Rule evaluation

Currently RulesEngine only returns true or false. We are planning to introduce Actions which will provide a way to extend the existing functionality.

Along with the functionality we will provide the following Actions out-of-box:

  • An action to return a result based on an expression evaluation
  • An action to invoke other rule(Rule chaining) with ability to use output of current rule (Deferred)

Addition feature requirement:

  • Allow registering custom actions

Ignore case when comparing string values

Hi when I tested the demo the string values are not ignore case comparision. I'm using "RuleExpressionType": "LambdaExpression".
for example
if I have an expression
"Expression": "input1.country== "india""
input1.country.Contains("ind")
pass the country value india the result will be true. however if change the case to INDIA or India the result is giving false.
Is there a way I can say to rule engine to use ignore case when comparing strings

Error when getting a false value

I have the following workflow rule(s) defined:

[
  {
    "WorkflowName": "ConsVendCertificateEntity",
    "Rules": [
      {
        "RuleName": "VendorPartyNumberCheck",
        "SuccessEvent": "10",
        "ErrorMessage": "VendorPartyNumber doesn't exist in Dynamics",
        "ErrorType": "Error",
        "RuleExpressionType": "LambdaExpression",
        "Expression": "Utils.ValueExists(\"ConsVendCertificateEntity\",\"VendorPartyNumber\",input1.VENDORPARTYNUMBER) == true"
      },
      {
        "RuleName": "VendorCertificateType",
        "SuccessEvent": "15",
        "WarningMessage": "VendorCertificateType isn't General Liability or Workmen's Compensation",
        "ErrorType": "Warning",
        "RuleExpressionType": "LambdaExpression",
        "Expression": "Utils.CheckContains(input1.VENDCERTIFICATETYPENAME,\"General Liability,Workmen's Compensation\") == true"
      }
    ]
  }
]

I loop through records in an Azure Storage table to validate data in them. The Utils.CheckContains method is from the Wiki documentation. My code (abbreviated) looks like this:

                foreach (var rule in workflowRules)
                {
                    List<RuleResultTree> results;
                    try
                    {
                        results = engine.ExecuteRule(rule.WorkflowName, input1);
                    }
                    catch (Exception e)
                    {
                        log.LogError($"[{tableName}] {e.GetType()}: {e.Message}\r\n{e.StackTrace}");
                        throw new ApplicationException($"An exception was thrown in the rules engine at row {row} for {tableName}. Check the rules and try again.");
                    }
               }

When I get through the first record, it passes both rules. When I get to the second record, it fails the VendorCertificateType rule because my record has Workmen's Compensation spelled wrong. It's not null. But instead of getting a rule failure, I get this exception:

ArgumentNullException: Value cannot be null. (Parameter 'input')
   at System.Text.RegularExpressions.Regex.Matches(String input)
   at System.Text.RegularExpressions.Regex.Matches(String input, String pattern, RegexOptions options, TimeSpan matchTimeout)
   at System.Text.RegularExpressions.Regex.Matches(String input, String pattern)
   at RulesEngine.RulesEngine.FormatErrorMessages(IEnumerable`1 result)
   at RulesEngine.RulesEngine.ExecuteRuleByWorkflow(String workflowName, RuleParameter[] ruleParameters)
   at RulesEngine.RulesEngine.ValidateWorkflowAndExecuteRule(String workflowName, RuleParameter[] ruleParams)
   at RulesEngine.RulesEngine.ExecuteRule(String workflowName, RuleParameter[] ruleParams)
   at RulesEngine.RulesEngine.ExecuteRule(String workflowName, Object[] inputs)
   at SM.Integration.Repos.BRE.RulesProcessor.ProcessRules() in C:\path\to\my\code\RulesProcessor.cs:line 88

The Utils.CheckContains code, for reference

        public static bool CheckContains(string check, string valList)
        {
            if (string.IsNullOrEmpty(check) || string.IsNullOrEmpty(valList))
                return false;

            var list = valList.Split(',').ToList();
            return list.Contains(check);
        }

ActionResult class avaliable without using directive

With version 3.0.0 classes ActionResult and ActionRuleResult are readily available in projects referencing RulesEngine NuGet package without the using directive.
These classes can be used throughout the project as if they were in the same namespace. This means there is no need to specify namespace name when referencing these classes.

The same also happens in every project that references projects containing RulesEngine NuGet package.
The main issue is with ActionResult classes from other namespaces, since "globally available" RulesEngine ActionResult is always used over other ActionResult classes (except when they are fully qualified).
There is no IntelliSense or compiler warning.
This becomes a breaking issue with ASP.NET Core controllers which can use ActionResult base classes as a return type.

Unit testing the rules logic

Hi All, This JSON based rules engine with lambda expression support is great. I can understand that we will be to write Integration tests for the rules configurations. But how can we have Unit tests for the Lamba expression which we write in the rules expression ?. If not unit tested, would not be error prone. ?

Return string instead or bool

Hello,
Having a rule engine based on Json is very great, but fmho it is limited by the fact that the results are limited to static values: SuccessEvent and Actions.
Would it be possible to dynamically assign the SuccessEvent string from inside ReSettings for example? And leave the interpretation to the developer then? We could return whatever we want, another Json for example.
Taking back the example with the "Discount to be applied to a customer", we could return different discount depending on the context or even the name of another function or rule to be called. It gives the developer lots of flexibility.
Thank you

Executing a rule within a rule

First, Thank you for this awesome project. I was looking for a lightweight rules engine and this is exactly what i'm looking for.

I apologize in advance if my question is not clear.

I have a bunch of rules that depend on the value of other rules. Here is an example:

Let's say i have a bunch of rules that evaluate some formulas:

Rule1 expression: input.x + input.y
Rule2 expression: input.x / input.y * 5
Rule3 expression: Rule1(input) + input.x
...etc

How can i express Rule3 Expression in the json file?

Suggestion to improve WiKi about ReSetting and custom methods

Hi everyone. I think that could be helpful for others that are starting with the lib to know how to register and use custom class. So then I've copied and edited the Wiki part about Settings to show how to do it.

Thanks.


ReSettings

This model is a list of custom types. While, lambda expressions are quite powerful, there is a limit to what they can do because of the fact that the methods that a lambda expression can do are limited to System namespace of .Net framework.

To use more complex and custom classes and have some logics which are way too complex to be written into a lambda expression, these settings come into picture. If the user wants to create a custom method to run for the rule to be used, these settings help them.

Example –

You can create a public class called Utils and include a method in it which check contains in a list.

using System;
using System.Linq;

namespace RE.HelperFunctions
{
    public static class Utils
    {
        public static bool CheckContains(string check, string valList)
        {
            if (String.IsNullOrEmpty(check) || String.IsNullOrEmpty(valList))
                return false;

            var list = valList.Split(',').ToList();
            return list.Contains(check);
        }
    }
}

And this can be then used in lambda expression in a very simple manner like this –

"Expression": "Utils.CheckContains(input1.country, \"india,usa,canada,France\") == tru

To use the custom class when evaluating the rules:

  1. Register the class:
  2. Then pass settings through rules engine.
var reSettingsWithCustomTypes = new ReSettings { CustomTypes = new Type[] { typeof(Utils) } };
new RulesEngine.RulesEngine(workflowRules.ToArray(), null, reSettingsWithCustomTypes);

Performance issue

I did some performance test on nestedinputdemo:

            foreach (var workflow in workflowRules)
            {
                List<RuleResultTree> resultList = bre.ExecuteRule(workflow.WorkflowName, nestedInput);
            }

            Stopwatch sw = new Stopwatch();
            sw.Start();

            for (int i = 0; i < 10000; i++)
            {
                foreach (var workflow in workflowRules)
                {
                    List<RuleResultTree> resultList = bre.ExecuteRule(workflow.WorkflowName, nestedInput);
                }
            }

            sw.Stop();
            Console.WriteLine($"{sw.ElapsedMilliseconds}");

it shows it need 40ms to finish the 30000 times rule execution.
correction: it shows it need 120ms to finish the 30000 times rule execution ( 40ms is 10000 times ).
Is there any way to make it faster?

Using RuleParameter have strange effect

I like the idea of having an engine that can parse expressions like we are used to in C#!

But I see that the RuleParameter, which anybody should use to have the right input names in the expression is causing problems, and I don't have any idea why it is not behaving as expected.

Example program:

class Program
{
	static void Main(string[] args)
	{
		Console.WriteLine("Started!");

		Sample1();
		Sample2();
		Sample3();
		Sample4();
		Sample5();
		Sample6();

		Console.WriteLine("Finished!");
		Console.ReadKey(intercept: true);
	}

	public static void Sample1()
	{
		RulesEngine.RulesEngine rulesEngine = GetRulesEngineWithRules();

		dynamic entity = new ExpandoObject();
		entity.Field1 = "Value1";
		entity.Field2 = 10;

		List<RuleResultTree> resultList = rulesEngine.ExecuteRule("Workflow", entity);

		ShowResult("Sample1 (ExpandoObject, no RuleParam)", resultList);
	}

	public static void Sample2()
	{
		RulesEngine.RulesEngine rulesEngine = GetRulesEngineWithRules();

		dynamic entity = new ExpandoObject();
		entity.Field1 = "Value1";
		entity.Field2 = 10;

		List<RuleResultTree> resultList = rulesEngine.ExecuteRule("Workflow", new RuleParameter("input1", entity));

		ShowResult("Sample2 (ExpandoObject, RuleParam)", resultList);
	}

	public static void Sample3()
	{
		RulesEngine.RulesEngine rulesEngine = GetRulesEngineWithRules();

		Dictionary<string, object> entity = new Dictionary<string, object>
		{
			{ "Field1", "Value1" },
			{ "Field2", 10 }
		};

		List<RuleResultTree> resultList = rulesEngine.ExecuteRule("Workflow", entity);

		ShowResult("Sample3 (Dictionary<string, object>, no RuleParam)", resultList);
	}

	public static void Sample4()
	{
		RulesEngine.RulesEngine rulesEngine = GetRulesEngineWithRules();

		Dictionary<string, object> entity = new Dictionary<string, object>
		{
			{ "Field1", "Value1" },
			{ "Field2", 10 }
		};

		List<RuleResultTree> resultList = rulesEngine.ExecuteRule("Workflow", new RuleParameter("input1", entity));

		ShowResult("Sample4 (Dictionary<string, object>, RuleParam)", resultList);
	}

	public static void Sample5()
	{
		RulesEngine.RulesEngine rulesEngine = GetRulesEngineWithRules();

		ExpandoObject entity = new ExpandoObject();
		IDictionary<string, object> dictionary = entity;
		dictionary.Add("Field1", "Value1");
		dictionary.Add("Field2", 10);

		List<RuleResultTree> resultList = rulesEngine.ExecuteRule("Workflow", entity);

		ShowResult("Sample5 (ExpandoObject filled through Dictionary<string, object>, no RuleParam)", resultList);
	}

	public static void Sample6()
	{
		RulesEngine.RulesEngine rulesEngine = GetRulesEngineWithRules();

		ExpandoObject entity = new ExpandoObject();
		IDictionary<string, object> dictionary = entity;
		dictionary.Add("Field1", "Value1");
		dictionary.Add("Field2", 10);

		List<RuleResultTree> resultList = rulesEngine.ExecuteRule("Workflow", new RuleParameter("input1", entity));

		ShowResult("Sample6 (ExpandoObject filled through Dictionary<string, object>, RuleParam)", resultList);
	}

	private static RulesEngine.RulesEngine GetRulesEngineWithRules()
	{
		Rule rule1 = new Rule { RuleName = "Rule1", Expression = "input1.Field1 = \"Value1\"", ErrorType = ErrorType.Error, RuleExpressionType = RuleExpressionType.LambdaExpression };
		Rule rule2 = new Rule { RuleName = "Rule2", Expression = "input1.Field2 > 5", ErrorType = ErrorType.Error, RuleExpressionType = RuleExpressionType.LambdaExpression };
		RulesEngine.RulesEngine rulesEngine = new RulesEngine.RulesEngine(new string[] { }, null);

		rulesEngine.AddWorkflow(new WorkflowRules
		{
			WorkflowName = "Workflow",
			Rules = new List<Rule>
			{
				rule1,
				rule2
			}
		});

		return rulesEngine;
	}

	private static void ShowResult(string name, List<RuleResultTree> resultList)
	{
		Console.WriteLine($"{name}:{Environment.NewLine}{string.Join(Environment.NewLine, resultList.Select(r => $"{r.Rule.RuleName} - {(r.IsSuccess ? r.IsSuccess.ToString() : r.ExceptionMessage)}"))}{Environment.NewLine}");
	}
}

Output:

Sample1 (ExpandoObject, no RuleParam):
Rule1 - True
Rule2 - True

Sample2 (ExpandoObject, RuleParam):
Rule1 - No property or field 'Field1' exists in type 'ExpandoObject'
Rule2 - No property or field 'Field2' exists in type 'ExpandoObject'

Sample3 (Dictionary<string, object>, no RuleParam):
Rule1 - True
Rule2 - Operator '>' incompatible with operand types 'Object' and 'Int32'

Sample4 (Dictionary<string, object>, RuleParam):
Rule1 - True
Rule2 - Operator '>' incompatible with operand types 'Object' and 'Int32'

Sample5 (ExpandoObject filled through Dictionary<string, object>, no RuleParam):
Rule1 - True
Rule2 - True

Sample6 (ExpandoObject filled through Dictionary<string, object>, RuleParam):
Rule1 - No property or field 'Field1' exists in type 'ExpandoObject'
Rule2 - No property or field 'Field2' exists in type 'ExpandoObject'

I would have expected that all samples would return true for both rules.

Only changing List<RuleResultTree> resultList = rulesEngine.ExecuteRule("Workflow", entity); to List<RuleResultTree> resultList = rulesEngine.ExecuteRule("Workflow", new RuleParameter("input1", entity)); messes it up. For me as a user I would have expected that it would behave exactly the same, because it already was "input1", and now I explicitly named it like "input1".

And the the Dictionary<string, object> (Sample3/4) I would have expected it would understand the underlying types.

Also, please provide some examples of this feature. The main problem using this nice project is the lack of examples.

Thanks in advance!

Outdated IRulesEngine interface - RulesEngine v2

Hello @abbasc52
I was about to use your engine, while maintaining the use of interface provided, but I noticed, the IRulesEngine seems to be outdated, the contract does not state everything that should make the RulesEngine implementation work integrally.
The RulesEngine is able to be instantiated with no workflows, but add new workflows with the AddWorkflow method. So at least the interface should reflect that.

What Happened:
Attempted to instantiate the RulesEngine implementation of the interface while using the "no workflows" constructor. Not able to add workflows.
image

Currently, if you abide by the interface you'll end up having to construct a new RulesEngine object everytime you need to use different workflows.

Proposed Solution:
IRulesEngine should also contain:

  • AddWorkflow
  • ClearWorkflows
  • RemoveWorkflow

Current workaround:
Well... using the concrete implementation... that has full functionality exposed. 😆

examples

it seems like the examples section is lacking. the wiki seems to be thin too. what would it take to see some examples? specifically how others are using this system?

Strange performance fluctuations

After #65 was closed and have removed Utils.GetTypedObject we still have large performance fluctuations.

Code:

IDictionary<string, object> entityValues = GetEntityValues(entity);
Stopwatch stopwatch = Stopwatch.StartNew();
List<RuleResultTree> resultList = rulesEngine.ExecuteRule(WorkflowCode, new RuleParameter("entity", entityValues));
Log(LogLevel.Information, $"[{entity.Id}] ExecuteRule: {stopwatch.ElapsedMilliseconds}ms");

We could have multiple rules for a single entity to check if it validates. Currently only 2 rules containing only expressions like entity.Field == true.

Log:

Information [37840] ExecuteRule: 1ms
Information [37840] ExecuteRule: 1ms
Information [37840] ExecuteRule: 206ms
Information [37840] ExecuteRule: 1970ms
Information [37840] ExecuteRule: 1938ms
Information [37840] ExecuteRule: 1896ms
Information [37840] ExecuteRule: 1ms
Information [37840] ExecuteRule: 1ms
Information [37840] ExecuteRule: 1ms
Information [37840] ExecuteRule: 1ms
Information [37840] ExecuteRule: 1910ms
Information [37840] ExecuteRule: 1ms

This is a very bad run, many times 1ms, but also 4 times almost 2 seconds. Any idea what could be fluctuating?

I couldn't find a memory leak when running it a million times.

Empty RuleParameter is required if there is a rule has undefined identifier in Expression

Step to reproduce:

  • Here is the workflow rules:
    • Noted that the second rule "AvailableIf" contains two more expression identifiers: productStock and customer
[
  {
    "WorkflowName": "DisplayProductAvailability",
    "WorkflowRulesToInject": null,
    "Rules": [
      {
        "RuleName": "AlwaysAvailable",
        "Operator": null,
        "ErrorMessage": "test",
        "ErrorType": "Warning",
        "RuleExpressionType": "LambdaExpression",
        "WorkflowRulesToInject": null,
        "Rules": null,
        "LocalParams": null,
        "Expression": "input.Name == \"Normal product\"",
        "SuccessEvent": null
      },
      {
        "RuleName": "AvailableIf",
        "Operator": "AndAlso",
        "ErrorMessage": "Not Available",
        "ErrorType": "Warning",
        "RuleExpressionType": null,
        "WorkflowRulesToInject": null,
        "Rules": [
          {
            "RuleName": "InStock",
            "Operator": null,
            "ErrorMessage": "Out of stock",
            "ErrorType": "Warning",
            "RuleExpressionType": "LambdaExpression",
            "WorkflowRulesToInject": null,
            "Rules": null,
            "LocalParams": null,
            "Expression": "input.Quantity < productStock.Quantity",
            "SuccessEvent": null
          },
          {
            "RuleName": "VisibleToCustomer",
            "Operator": null,
            "ErrorMessage": "Product is not available at customer location",
            "ErrorType": "Warning",
            "RuleExpressionType": "LambdaExpression",
            "WorkflowRulesToInject": null,
            "Rules": null,
            "LocalParams": null,
            "Expression": "productStock.Location == customer.Location",
            "SuccessEvent": null
          }
        ],
        "LocalParams": null,
        "Expression": null,
        "SuccessEvent": "In stock and visible to customer"
      }
    ]
  }
]
  • Execute the workflow with one RuleParameter:
dynamic input = new ExpandoObject();
input.Name = "Normal product";
var ruleParameters = new[]
   {
      new RuleParameter( nameof( input ), input )
   };
RulesEngine.ExecuteRule("DisplayProductAvailability", ruleParameters );

Result: An exception is thrown: "Target object is not an ExpandoObject"
My expection is that the RulesEngine run successfully and the RuleResult should be success for rule name "AlwaysAvailable"

  • Execute the workflow with two RuleParameters:
dynamic input = new ExpandoObject();
input.Name = "Normal product";
var ruleParameters = new[]
   {
      new RuleParameter( "empty", "" ), // empty rule parameter and not related to any rules expressions
      new RuleParameter( nameof( input ), input )
   };
RulesEngine.ExecuteRule("DisplayProductAvailability", ruleParameters );

Result: RulesEngine execute successfully, the first rule name is returned "AlwaysAvailable"

Custom method question

I have a simple custom class & method: Utils.Equals(string a, string b)
and calling it in an "Expression": "Utils.Equals(input1.Thing1, "\something") == true"

and get an exception from the rules engine. I have tried adding (string)input1.Thing1 to the expression
but it doesn't work. I'm not sure how to convert input1.Thing1 into a string. I believe the input.Thing1
is a dynamic object so the rules engine can't match it to a string type?
I'm just trying to understand how the custom methods will work and need a working example.

I have also seen others trying to pass an Array of objects as an argument so one can
check if it contains an item. Can an example be provided?

Runnable sample app

The sample app provided in this repository is quite difficult to get running.

When you open it (the csproj), it already begins complaining about the missing SDK or framework:
image

I think it is because it is not part of a solution. But even when you add it to a solution already containing a .NET Core Console App you run into more problems. And that is the missing nuget package(s). Also, it is not possible to add them, because it somehow does not understand it.

So. Take 3, copying over the files to the current console app. Even that did not automatically work. It complains about multiple entry points in the project. So I had to even change more things to get it working.

Also there is too little info about building a (dynamic) input, without JSON as the 'input' for the inputs.

I finally found out. Please provide a full out-of-the-box working example app. Thanks!

Also there is no info about the "Operator" property, I guess it is not implemented/used? Also some more info about the inputs would be nice. What type is expected? Would be nice if that info is also on the readme/wiki, and not only in the example app.

How to write Expressions for an Array in input source?

Hi,

I have a requirement to execute a condition on an array in input source and return true for all or any passed.
Example:
Input Object:
{
"company":"ABC Limited",
"branches": [
{ "city": "London", "Region":"Europe" },
{ "city": "Paris", "Region":"Europe" },
{ "city": "Germany", "Region":"Europe" },
]
}

How do I write the LamdaExpression for condition like below:

"input.branches.All(b => b == 'Europe')"

Support for CheckListContains and CheckObjectContains in ExpressionUtils

Hi Sir,
My name is srikanth. I have been following this project and i think it is great work. Thanks for coming up with such concept. I had few things to ask can you please help answer them.
In your class ExpressionUtils can you please add support to these two methods.

public static bool CheckListContains(IEnumerable list, string value, string prop = null)
{
if (list == null || list.Count() == 0)
return false;
if (prop == null)
return list.Contains(value);
else
{
foreach (object singleObj in list)
{
if (singleObj != null)
{
PropertyInfo property = singleObj.GetType().GetProperty(prop);
if (property != null)
{
var _val = property.GetValue(singleObj);
if (_val.ToString().Equals(value))
{
return true;
}
}
}
}
return false;

        }
    }

    public static bool CheckObjectContains(object obj, string value, string prop = null)
    {
        return CheckListContains(new List<object> { obj }, value, prop);
    }

As well Iam currently building an analytics product . Would like use your project for business work rules. Can i pick up the code drop and make it part of the project or should i be only going through nuget. I would also be happy to contribute and help add some features support as part of this curve. Please let me know.

Improve error when target in expression is not found

When you call the code below:

private static RulesEngine.RulesEngine GetRulesEngineWithRules()
{
	Rule rule1 = new Rule { RuleName = "Rule1", Expression = "entity.Field1 = \"Value1\"", ErrorType = ErrorType.Error, RuleExpressionType = RuleExpressionType.LambdaExpression };
	Rule rule2 = new Rule { RuleName = "Rule2", Expression = "entity.Field2 > 5", ErrorType = ErrorType.Error, RuleExpressionType = RuleExpressionType.LambdaExpression };
	RulesEngine.RulesEngine rulesEngine = new RulesEngine.RulesEngine(new string[] { }, null);

	rulesEngine.AddWorkflow(new WorkflowRules
	{
		WorkflowName = "Workflow",
		Rules = new List<Rule>
		{
			rule1,
			rule2
		}
	});

	return rulesEngine;
}

private static void Sample1()
	RulesEngine.RulesEngine rulesEngine = GetRulesEngineWithRules();

	dynamic entity = new ExpandoObject();
	entity.Field1 = "Value1";
	entity.Field2 = 10;

	List<RuleResultTree> resultList = rulesEngine.ExecuteRule("Workflow", entity);
}

It will throw the following error:
System.Reflection.TargetInvocationException InvalidOperationException: Target object is not an ExpandoObject

At first you think the entity object is not and ExpandoObject, but it is. But because the rule is searching for the object entity which it couldn't find, because there is no RuleParameter called entity, it probably receives 'null' or so, and it throws a too generic error.

A better error would be Object 'entity' used in expression not found in parameters

Error when trying to use custom methods

Hi!

I'm facing the following issue when I'm trying to use custom methods.

On my class I create the rules engine like that:

private readonly ReSettings reSettings = new ReSettings {
    CustomTypes = new Type[] { typeof(RulesEngineUtils) }
};

My class RulesEngineUtils

public static class RulesEngineUtils
{
	public const string DefaultListSeparator = ",";

	public static bool HasFinancialProduct(string financialProducts, string financialProduct)
	{
		if (string.IsNullOrEmpty(financialProducts))
			throw new ArgumentException(nameof(HasFinancialProduct), nameof(financialProducts));

		if (string.IsNullOrEmpty(financialProduct))
			throw new ArgumentException(nameof(HasFinancialProduct), nameof(financialProduct));

		var financialProcutsList = financialProducts.Split(DefaultListSeparator);
		return financialProcutsList?.Any(x => x.Equals(financialProduct, StringComparison.InvariantCultureIgnoreCase)) ?? false;
	}
}

And the way that I'm using this all:

var contex = new {
	decisionParams.Context.OperationType,
	decisionParams.Context.Seller,
	FinancialProduct = string.Join(RulesEngineUtils.DefaultListSeparator, decisionParams.Context.FinancialProduct)
};

var rulesEngine = new RulesEngine.RulesEngine(workflowRules.ToArray(), null, this.reSettings);

var resultList = rulesEngine.ExecuteRule(decisionParams.WorkflowName, contex);

This is an example of my payload:

{
  "WorkflowName": "general-rules",
  "Rules": [
    {
      "RuleName": "active",
      "Operator": "Or",
      "Rules": [
        {
          "RuleName": "first",
          "SuccessEvent": "true",
          "RuleExpressionType": "LambdaExpression",
          "Expression": "OperationType == \"Som12\" and Seller == \"Som133\""
        },
        {
          "RuleName": "second",
          "SuccessEvent": "true",
          "RuleExpressionType": "LambdaExpression",
          "Expression": "OperationType == \"Som11\" and Seller == \"Som12\""
        }
      ]
    },
    {
      "RuleName": "mandatory",
      "Operator": "Or",
      "Rules": [
        {
          "RuleName": "firstMd",
          "SuccessEvent": "true",
          "RuleExpressionType": "LambdaExpression",
          "Expression": "OperationType == \"Som2\""
        },
        {
          "RuleName": "secondMd",
          "SuccessEvent": "true",
          "RuleExpressionType": "LambdaExpression",
          "Expression": "OperationType ==  \"Som1\""
        }
      ]
    },
    {
      "RuleName": "cacheable",
      "Operator": "Or",
      "Rules": [
        {
          "RuleName": "firstCh",
          "SuccessEvent": "true",
          "RuleExpressionType": "LambdaExpression",
          "Expression": "RulesEngineUtils.HasFinancialProduct(FinancialProduct, \"Some\")"
        }
      ]
    },
    {
      "RuleName": "timeout",
      "Operator": "Or",
      "Rules": [
        {
          "RuleName": "forstTm",
          "SuccessEvent": "3",
          "RuleExpressionType": "LambdaExpression",
          "Expression": "RulesEngineUtils.HasFinancialProduct(FinancialProduct, \"Some\")"
        },
        {
          "RuleName": "secondTm",
          "SuccessEvent": "10",
          "RuleExpressionType": "LambdaExpression",
          "Expression": "RulesEngineUtils.HasFinancialProduct(FinancialProduct, \"Some\")"
        }
      ]
    }
  ]
}

I did debug the lib and the exception is throwing here:
RulesEngine.ExpressionBuilders.LambdaExpressionBuilder.BuildExpressionForRule
At: var body = (BinaryExpression)e.Body;

internal override Expression<Func<RuleInput, RuleResultTree>> BuildExpressionForRule(Rule rule, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp)
        {
            try
            {
                var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) };
                var e = DynamicExpressionParser.ParseLambda(config, typeParamExpressions.ToArray(), null, rule.Expression);
                var body = (BinaryExpression)e.Body;
                return Helpers.ToResultTreeExpression(rule, null, body, typeParamExpressions, ruleInputExp);
            }
            catch (Exception ex)
            {
                var binaryExpression = Expression.And(Expression.Constant(true), Expression.Constant(false));
                var exceptionMessage = ex.Message;
                return Helpers.ToResultTreeExpression(rule, null, binaryExpression, typeParamExpressions, ruleInputExp, exceptionMessage);
            }           
        }

And the full stack trace was:

InnerException: null
Message: Unable to cast object of type 'System.Linq.Expressions.MethodCallExpression2' to type 'System.Linq.Expressions.BinaryExpression'.
StackTrace: at RulesEngine.ExpressionBuilders.LambdaExpressionBuilder.BuildExpressionForRule(Rule rule, IEnumerable`1 typeParamExpressions, ParameterExpression ruleInputExp) in C:\Users\alexandre_borges\Code\myonpocs\RulesEngine\src\RulesEngine\RulesEngine\ExpressionBuilders\LambdaExpressionBuilder.cs:line 33


Other information:

I'm using:
.Net Core 3.1
Visual Studio: 2019 - 16.5.4

I also have tried to use the ExpressionUtils.CheckContains but I've got the same issue
Could anyone help me with that?

Thanks a lot

Ability to check rules without Workflow

It would be nice to check for a single rule.

private static void Sample1()
{
	RulesEngine.RulesEngine rulesEngine = new RulesEngine.RulesEngine(new string[] { }, null);

	dynamic entity = new ExpandoObject();
	entity.Field1 = "Value1";
	entity.Field2 = 10;

	Rule rule1 = new Rule { RuleName = "Rule1", Expression = "input1.Field1 = \"Value1\"", ErrorType = ErrorType.Error, RuleExpressionType = RuleExpressionType.LambdaExpression };
	RuleResultTree ruleResult = rulesEngine.ExecuteRule(rule1, entity);
}

Also, it looks like ExecuteRule actually should be named ExecuteWorkflow, and that we should have a real ExecuteRule you can supply a Rule to which returns true/false or RuleResultTree :)

My current workaround is just adding a workflow with the name of "Workflow" and call it with rulesEngine.ExecuteRule("Workflow", entity)

Return all outcomes of the rules for a single row of data

I was hoping to use the engine to validate fields in a class to determine if all fields passed a set of rules. An example would be is the length of field 1 is > 2, another rule would be the value of field 2 is > 5. I was hoping that if rule 2 failed then I would be able to capture it on the OnFail method. It seems that if rule 1 passes it does not call the OnFail method for rule 2, Is this the correct behavior?

Here is a sample rule.

[
  {
    "WorkflowName": "NonEdi_Header",
    "Rules": 
	[
      {
        "RuleName": "lengthrule",
        "SuccessEvent": "10",
        "ErrorMessage": "wut",
        "ErrorType": "Error",
        "RuleExpressionType": "LambdaExpression",
        "Expression": "input1.ClaimCode == \"300065847701\" "
		},
       {
        "RuleName": "classid",
        "SuccessEvent": "100",
        "ErrorMessage": "Case id not equal 1 ",
        "ErrorType": "Error",
        "RuleExpressionType": "LambdaExpression",
        "Expression": "input1.ClientId  == \"12\" "
      }
    ]
  }
]

Issue with nuget package, same code work in basedemo project but not when create new project and add nuget package

I wrote custom code create workflow rules and rule definitions. The code works perfectly fine when I add it to the base demo project that is part of RulesEngine repo howeve the same code throws exceptions if I create a new console and add the latest nuget package and run.

Exception I get is

Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpression1' to type 'System.Linq.Expressions.BinaryExpression'.

public void Run()
{
List ruleDefintions = new List();
ruleDefintions.Add(new Rule
{
RuleName = "test",
RuleExpressionType = RuleExpressionType.LambdaExpression,
Expression = "input1.GRADE.Contains("720")"
});
var workflowRules = new List
{
new WorkflowRules
{
WorkflowName = "test"
}
};
workflowRules[0].Rules = ruleDefintions;
var item = new ExpandoObject();
AddProperty(item, "GRADE", "720-1");
var inputs1 = new[]
{
item
};
var customUtils = new ReSettings { CustomTypes = new[] { typeof(ExpressionUtils) } };
var bre = new RulesEngine.RulesEngine(workflowRules.ToArray(), null, customUtils);

        List<RuleResultTree> resultList = bre.ExecuteRule("test", inputs1);
        Console.WriteLine(resultList.Count);
    }

    /// <summary>
    /// Adds properties to expando object.
    /// </summary>
    /// <param name="parameterInput"> The parameter input.</param>
    /// <param name="propertyName">The property name.</param>
    /// <param name="propertyValue">The property value.</param>
    private static void AddProperty(ExpandoObject parameterInput, string propertyName, object propertyValue)
    {
        // ExpandoObject supports IDictionary so we can extend it like this
        var expandoDict = parameterInput as IDictionary<string, object>;
        if (expandoDict.ContainsKey(propertyName))
        {
            expandoDict[propertyName] = propertyValue;
        }
        else
        {
            expandoDict.Add(propertyName, propertyValue);
        }
    }

Evaluation of rules

Hi, I've downloaded this great library and been playing with it. While running the DemoApp, specifically the Discount rule example (basic), I found that if you define the test case to match for the GiveDiscount30 rule, it always returns that the matched and applied rule is the GiveDiscount25.

If I set this info for my test case, I always get that the applied rule is GiveDiscount25:

        var basicInfo = "{\"name\": \"hello\",\"email\": \"[email protected]\",\"creditHistory\": \"good\",\"country\": \"canada\",\"loyalityFactor\": 4,\"totalPurchasesToDate\": 50000}";
        var orderInfo = "{\"totalOrders\": 6,\"recurringItems\": 2}";
        var telemetryInfo = "{\"noOfVisitsPerMonth\": 20,\"percentageOfBuyingToVisit\": 15}";

The GiveDiscount25 rule is set to (same as give in this repository):

"Expression": "input1.country != "india" AND input1.loyalityFactor >= 2 AND input1.totalPurchasesToDate >= 10000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 5"

And the GiveDiscount30 rule is set to:

"Expression": "input1.loyalityFactor > 3 AND input1.totalPurchasesToDate >= 50000 AND input1.totalPurchasesToDate <= 100000 AND input2.totalOrders > 5 AND input3.noOfVisitsPerMonth > 15"

The only way I get it to apply the GiveDiscount30 is if I set the country to "india" or if define it before the GiveDiscount25 in the Discount.json file.

Am I doing anything wrong or this is the right behavior?

Thank you very much for your support!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.