SoFunction
Updated on 2025-03-07

Detailed explanation of the use of C# scripting engine RulesEngine

When writing applications, it often takes a lot of time and effort to process business logic. Often, changes in business logic require refactoring or adding a lot of code, which is very unfriendly to development and testers.

BeforeThis articleAs mentioned, we can use the script engine to dynamically compile and execute the code that we need to change frequently, which has a lot of freedom, but the corresponding resources are also needed. If you are only targeting changes in very specific business logic, you can try using RulesEngine to operate on the program.

The official example is used below and some of the content is translated fromDescription

Introduction

RulesEngine is a rule engine launched by Microsoft. The rule engine is used in many enterprise development and is an elegant way to deal with frequent changing needs. For personal tasks, the rules engine is suitable for some of the following scenarios:

  • The number of input and output types is relatively fixed, but the execution logic often changes;
  • Switch conditions change frequently, and substitution for complex switch statements;
  • Business logic that will change and has multiple conditions or rules;
  • Scenes where the degree of freedom of rules does not require particularly high levels of freedom. (It is recommended to use the script engine in this case)

RulesEngine's rules are stored using JSON and express rules (Rules) through lambda expressions.

Installation is very convenient, just use nuget to install:

install-pacakge RulesEngine

Rule definition

There needs to be Rules, WorkflowName, and then some properties.

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

Except for the standardRuleExpressionType, you can also nest multiple conditions by defining Rules, below is the Or logic.

{
"RuleName": "GiveDiscount30NestedOrExample",
"SuccessEvent": "30",
"ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error",
"Operator": "OrElse",
"Rules":[
 {
 "RuleName": "IsLoyalAndHasGoodSpend",
 "ErrorMessage": "One or more adjust rules failed.",
 "ErrorType": "Error",
 "RuleExpressionType": "LambdaExpression",
 "Expression": " > 3 AND  >= 50000 AND  <= 100000"
 },
 {
 "RuleName": "OrHasHighNumberOfTotalOrders",
 "ErrorMessage": "One or more adjust rules failed.",
 "ErrorType": "Error",
 "RuleExpressionType": "LambdaExpression",
 "Expression": " > 15"
 }
]
}

Example

You can download the example from the official code base and define the above rules, and you can start using it directly. The example describes such an application scenario:

Different discounts are offered according to different customer attributes. As sales change rapidly, rules for providing discounts also need to change frequently. Therefore, it is more suitable for rule engines.

public void Run()
{
 ($"Running {nameof(BasicDemo)}....");
 //Create input var basicInfo = "{\"name\": \"hello\",\"email\": \"abcy@\",\"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 = &lt;ExpandoObject&gt;(basicInfo, converter);
 dynamic input2 = &lt;ExpandoObject&gt;(orderInfo, converter);
 dynamic input3 = &lt;ExpandoObject&gt;(telemetryInfo, converter);

 var inputs = new dynamic[]
  {
   input1,
   input2,
   input3
  };
 //Loading rules var files = ((), "", );
 if (files == null ||  == 0)
  throw new Exception("Rules not found.");

 var fileData = (files[0]);
 var workflowRules = &lt;List&lt;WorkflowRules&gt;&gt;(fileData);
 //Initialize the rule engine var bre = new ((), null);

 string discountOffered = "No discount offered.";
 //Execute the rules List&lt;RuleResultTree&gt; resultList = ("Discount", inputs).Result;
 //Processing results ((eventName) =&gt; {
  discountOffered = $"Discount offered is {eventName} % over MRP.";
 });

 (() =&gt; {
  discountOffered = "The user is not eligible for any discount.";
 });

 (discountOffered);
}

enter

Input is generallyIEnumerable<dynamic>Or anonymous type. The above example shows the dynamic type formed by json deserialization. For program-generated data, it is more convenient to use anonymous type.

var nestedInput = new {
    SimpleProp = "simpleProp",
    NestedProp = new {
     SimpleProp = "nestedSimpleProp",
     ListProp = new List<ListItem>
     {
      new ListItem
      {
       Id = 1,
       Value = "first"
      },
      new ListItem
      {
       Id = 2,
       Value = "second"
      }
     }
    }

   };

Namespace

Like script engines, the default rule engine can only access the System's namespace. If you need to use a slightly more complex type, you can define the type or function yourself. For example, define a function like this:

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

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

When you need to use it, first pass the class to RulesEngine:

var reSettingsWithCustomTypes = new ReSettings { CustomTypes = new Type[] { typeof(Utils) } };
var engine = new ((), null, reSettingsWithCustomTypes);

Then it can be used directly in the expression.

"Expression": "(, \"india,usa,canada,France\") == true"

Rule parameters

By default, the input of the rule uses a form similar to input1 input2. If you want to be more intuitive, you can use RuleParameter to encapsulate specific parameter types.

RuleParameter ruleParameter = new RuleParameter("NIP", nestedInput);
var resultList = (, ruleParameter).Result;

Local variables

If the expression is complex, you can use local variables to perform segmentation processing, which will be more convenient for debugging.

The keyword of the local variable is localParams, and the content in the middle can be simply understood as var name = expression

{
  "name": "allow_access_if_all_mandatory_trainings_are_done_or_access_isSecure",
  "errorMessage": "Please complete all your training(s) to get access to this content or access it from a secure domain/location.",
  "errorType": "Error",
  "localParams": [
   {
   "name": "completedSecurityTrainings",
   "expression": "((\"Completed\", ))"
   },
   {
   "name": "completedProjectTrainings",
   "expression": "((\"Completed\", ))"
   },
   {
   "name": "isRequestAccessSecured",
   "expression": " == \"India\" ? (( == \"Bangalore\" && =\"xxxx\")? true : false):false"
   }
  ],
  "expression": "(() && ()) || isRequestAccessSecured "
  }

Summarize

Using the rules engine, we can independently extract the frequently changing business logic, providing great convenience for us to write dynamic and scalable programs. The API provided by RulesEngine is also relatively simple and very easy to get started.

The above is the detailed explanation of the use of C# script engine RulesEngine. For more information about C# script engine RulesEngine, please follow my other related articles!