Reusable Rules in Code Effects
Code Effects business rules engine comes with a powerful feature that allows you to reuse any evaluation type rule in any other rule as if it were a simple field of the System.Boolean type.
Imagine that your organization uses a simple condition such as Age is greater than or equal to 21 in hundreds of its business rules. Obviously, if the value 21 changes tomorrow, your organization would need to edit, test, and redeploy all those rules with the new value. Having that condition as a reusable rule allows for much greater flexibility because any change to that rule is instantly adopted by all other rules that use it.
Code Effects supports reusable rules in two very different ways, depending on where your rules live.
With the Rule Repository, the Repository does the work. When a rule is created, its owner says whether other rules can reuse it. Only an evaluation type rule can be reusable. Once such a rule has a live version, the Repository offers it in the menus of the editor of every rule that is written for the same source object, and it leaves the rule being edited out of its own menus. You do not build the menus and you do not write the code that finds the called rules, because the Repository is the one that stores them. It also refuses what the editor alone would not: a call to a rule that is not available, a cycle, a call placed where the called rule's own object is not evaluated, and a version of a called rule that would stop the rules which call it from working. It checks these when a version is saved and again when a version is made live. A call runs the live version of the called rule at the moment your application creates its Evaluator, which is why an Evaluator that your application caches keeps the called rules as they were.
With the stand-alone Rule Editor, your application does the work. The editor is a web component, and the rules live in whatever storage you built for them, so only your application knows which rules exist. It must give the editor the list of rules to offer, and it must give both the editor and the Evaluator a method that retrieves a rule by its ID. The rest of this article describes how.
NOTE: The code samples and the pictures of the Rule Editor below are for the stand-alone editor. The Rule Repository handles reusable rules in its own way, as described above, and needs none of this code.
Reusable Rules in the Stand-Alone Editor
For example, consider the following source object:
using System;
using CodeEffects.Rule.Common.Attributes;
namespace TestProject
{
public class Patient
{
public string Name { get; set; }
public Gender Gender { get; set; } = Gender.Unknown;
[Field(DisplayName = "Date of Birth", DateTimeFormat = "MMMM dd, yyyy")]
public DateTime? DOB { get; set; }
[Method("Age", "Returns age of the patient")]
public int GetAge()
{
if(this.DOB == default) throw new Exception("DOB is not set");
else return DateTime.Now.Year - this.DOB.Value.Year;
}
}
public enum Gender
{
Male,
Female,
[ExcludeFromEvaluation]
Unknown
}
}
If we were to register Rule Editor on a web page with this Patient class as a source object, we could create an evaluation type rule that checks whether the patient is 21 or older, name that rule Legal Age, and save it:

Later, we could reuse that rule in other rules as if it were a Boolean field called Legal Age. The Rule Editor does not look for saved rules by itself. Your application gives it the list of rules that it can offer, as MenuItem objects with the ID of a rule, its name, and an optional description, the next time it loads the page:
using CodeEffects.Rule.Editor;
using CodeEffects.Rule.Editor.Models;
var rules = new List<MenuItem>
{
new MenuItem("11111111-0000-0000-0000-000000000001", "Legal Age", "True if the patient is 21 or older")
};
var editor = new Control
{
ID = "divEditor",
SourceType = typeof(Patient),
ToolBarRules = rules,
ContextMenuRules = rules
};
There are two collections because the menus have different jobs:
ToolBarRules fills the Rules menu of the Toolbar, which loads a rule for editing. It can hold rules of any type, and it is ignored if the toolbar is hidden.
ContextMenuRules fills the fields context menu, where the rules appear among the fields. It must hold only evaluation type rules. The editor does not check that for you, so you must not add an execution type rule to it.
With an Adaptive Source, the editor ignores both collections. The source provider supplies the rules instead: it returns a reusable rule from GetFieldsAndMethods as a Field with its IsRule property set to true, and it answers GetReusableRule with that field when the editor loads a rule that uses it.
The selection of this rule from the Rules menu for editing would look like this:

The rule also appears as an ordinary Boolean field in the context menu when you create other rules:

Notice that the Help String displays the description of our rule every time the mouse hovers over its name in either menu:

Now we can reuse our Legal Age rule in any other rule that uses the same source object:

This powerful feature allows you to easily encapsulate common logic into separate rules and reuse them across your entire organization.
The Rule XML of the rule above does not hold the Legal Age condition. It holds only a call to the rule by its ID, which the editor writes as a rule element with the ID of the reusable rule inside a condition:
<and>
<condition type="equal">
<rule id="11111111-0000-0000-0000-000000000001" />
<value type="System.Boolean">true</value>
</condition>
<condition type="startsWith">
<property name="Name" />
<value>J</value>
</condition>
</and>
Evaluating Rules That Use Rules
The Evaluator writes every rule that the main rule calls into it when it compiles the rule, so it needs the Rule XML of those rules. It looks for a called rule by its ID, first among the rules of the same Rule XML document (a ruleset can hold the called rules as well), and then it asks the resolver that you set in the RuleResolver property of EvaluationParameters. The resolver is a GetRuleDelegate, a method that receives a rule ID and returns the Rule XML of that rule, either as a whole document or as a single rule element:
using CodeEffects.Rule.Common.Models;
using CodeEffects.Rule.Engine;
var options = new EvaluationParameters { RuleResolver = YourRuleStorageService.LoadRuleXml };
var evaluator = new Evaluator<Patient>(mainRuleXml, options);
bool success = evaluator.Evaluate(patient);
Keep these in mind:
- The Evaluator asks once. It calls the resolver once for each called rule while it is being created, and it keeps the rules as they were at that moment. If you change Legal Age tomorrow, an
Evaluator that you created today still evaluates the old version. Create a new Evaluator when a rule that it uses has changed, or let your cache of evaluators expire.
- A missing rule is an error. If there is no resolver, or it has no rule with the requested ID and returns
null, the constructor of the Evaluator throws an EvaluationException with the control number GH7349.
- A called rule must be an evaluation type rule of the same source object. In the main rule it behaves as a
Boolean value.
Circular References
IMPORTANT! If used carelessly, reusable rules can lead to a nasty issue called circular references. Imagine the following three business rules:
Rule # 1
Check if Name is John and Rule # 3 is False
Rule # 2
Check if Email ends with .com and Rule # 1 is True
Rule # 3
Check if Zip code is 12345 or Rule # 2 is True
It's easy to see the circular references in these rules. What makes it worse is the fact that a rule author cannot see that by using Rule # 1 in Rule # 2, he's actually creating recursion.
Code Effects guards against circular references in three places.
The editor's menus. The Rule Editor never offers a rule inside itself. Once a rule with an ID is loaded, the editor leaves that rule out of the menus that offer reusable rules and out of the Execute... menu of inline rules, even if your application adds it to the menu's item collection or to InlineRules. This stops a rule from calling or executing itself directly, but not a longer cycle like the one above: the menus do not know which rules the other rules call.
The editor's validation, when it has a resolver. To find longer cycles, the editor needs the Rule XML of every rule that the current rule calls. Give it a GetRuleDelegate, a method that receives a rule ID and returns that rule's XML (or null if there is no such rule), in its RuleResolver property:
var editor = new CodeEffects.Rule.Editor.Control("divRuleEditor")
{
SourceType = typeof(Patient),
RuleResolver = YourRuleStorageService.LoadRuleXml
};
The GetRuleDelegate property of the Control class that older versions had still works, but it is obsolete. Use RuleResolver.
With the resolver set, rule validation walks every rule that the current rule calls, the rules those rules call, and so on. It looks for each rule first among the rules of the same Rule XML document, and then asks the resolver. The walk follows both kinds of calls: reusable rules used in conditions, and inline rules executed from THEN or ELSE, so it also finds a cycle that mixes the two. The walk runs after the rule has passed every other validation check, and the resolver is called each time the rule is validated, so cache your rules if they are expensive to load.
The rule is invalid if the walk finds either of these:
- A circular reference. The rule calls itself, directly or through other rules. Each element that calls a rule on the cycle is marked This rule causes or participates in a circular reference.
- A missing rule. The resolver returns
null for a rule that the current rule calls, directly or through other rules. Each element that leads to it is marked The rule this element calls could not be found or is not available.
An invalid rule's IsValid property is false, and GetRuleXml() refuses to return it by throwing an InvalidRuleException with the control number MK8125.
Without a resolver, the editor cannot see past the current rule, so neither problem is caught while the rule is written. Supply one whenever your rules can call other rules.
The Evaluator, always. The Evaluator does not depend on the editor. When it builds a rule that calls or executes itself, directly or through other rules, its constructor throws an EvaluationException with the control number GH7427; when it cannot find a called rule, the control number is GH7349.
In the Rule Repository, all of this is done for you: the Repository gives the editor its own resolver, so every rule is checked for cycles and missing rules when it is saved.
Rules That Execute Rules
An evaluation type rule is not the only kind of rule that one rule can use. An execution type rule can run another execution type rule from its THEN and ELSE sections. Those are called inline rules, and they are described in Inline Rules.