Validation rule execution

◷ Reading Time: 8 minutes

Assumptions

Let’s say we have a simple model, such as the one shown below: 

Now we want to make some validation rules to:

  1. Detects
    1. Empty or null Name
    2. Empty or null Family
  2. Invalid email format
  3. Empty or null Line1 of address (if address exists)

Basic validation logic

The whole validation rule we are going to define will be grouped in to three logical sections:

  1. Basic validation: covers Name and Family detection
  2. Address validation: covers Address.Line1 detection
  3. Complete person test: combination of the two plus email detection

The first part we are tackling is to detect null or empty values for Name and Family. The rule would look like the logic shown below:

<Logic name="basic validation">
  <And name="NameAndFamilyCheck" message="Name and family is required">
    <Null value="Name" negate="true" />
    <Empty value="Name" negate="true" />
    <Null value="Family" negate="true" />
    <Empty value="Family" negate="true" />
  </And>
</Logic>

The next part is making another logic to validate the address, such as that shown in the following rule:

<Logic name="address validation">
  <And>
    <Null value="Line1" negate="true"/>
    <Empty value="Line1" negate="true"/>
  </And>
</Logic>

Then we put it all together with validation of the email format as a complete person test logic:

<Logic name="complete person test">
  <And>
    <Validate logic="basic validation" />
    <Regex pattern="^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$" value="Email" 
            message="Email field is not valid" tag="InvalidEmail" />
    <Validate name="person address" logic="address validation" value="Address" when="Address!=null"/>
  </And>
</Logic>

Putting logic into validation

<Validation name="PersonValidation">
  <Logic name="complete person test">
    <And>
      <Validate logic="basic validation" />
      <Regex pattern="^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$" value="Email" 
             message="Email field is not valid" tag="InvalidEmail" />
      <Validate name="person address" logic="address validation" value="Address" when="Address!=null"/>
    </And>
  </Logic>
  <Logic name="basic validation">
    <And name="NameAndFamilyCheck" message="Name and family is required">
      <Null value="Name" negate="true" />
      <Empty value="Name" negate="true" />
      <Null value="Family" negate="true" />
      <Empty value="Family" negate="true" />
    </And>
  </Logic>
  <Logic name="address validation">
    <And>
      <Null value="Line1" negate="true"/>
      <Empty value="Line1" negate="true"/>
    </And>
  </Logic>
</Validation>

How to execute validation

Step 1: Model and Execution plan

The application simply loads the model and creates the execution plan (Validator):

var engine = RuleEngine.FromXml(File.OpenRead("ValidationRules\\SampleValidationRule.xml"));

The engine instance maintains the execution plan life-cycle and runtime behavior. This section does not need to be repeated for the rest of the application execution. Once you have the execution plan of a validation rule it can be reused.

Step 2: Execution

This step is about executing the rule against an object:

var person = new Person()
            {
                Name = "John",
                Family = "k",
                Address = new Address() { Country = "AU", Line1 = "34 Thomas street", State = "VIC" }
            };

Now we are ready to execute the validation like this:

var result = eng.Run(new RunParameter("basic validation", person));

As you would expect, the value of the result. The outcome is true because the values for the Name and Family are provided. Now you execute the person complete test as shown in the following code:

var result = eng.Run(new RunParameter("complete person test", person));

The result.Outcome will be false. We know why because we have not provided the Email address that is checked as part of the complete test.

Retrieving execution feedback

When a validation run is executed, it stores the result in the engine’s result’s Notification property. It can be collected as shown in the following code:

foreach (var item in result.Notifications.Default.Notices)
                Console.WriteLine("{0}: {1}", item.Tag, item.Message);

When you run the code you can see the following output on the screen: InvalidEmail: Email field is not valid

Adding more detailed feedback

Now let’s see how we can grab more detailed feedback from the engine. Let’s create the following person object as well as the the basic validation:

A person with null Name and empty Family:

var person = new Person()
{
    Name = null,
    Family = "",
    Address = new Address() { Country = "AU", Line1 = "34 Thomas street", State = "VIC" }
};

And lets put more details messaging on the rule like this:

<Logic name="basic validation">
  <And name="NameAndFamilyCheck" message="Name and family is required">
    <Null value="Name" negate="true" message="Name null"/>
    <Empty value="Name" negate="true" message="Name empty"/>
    <Null value="Family" negate="true" message="Family null"/>
    <Empty value="Family" negate="true" message="Family empty"/>
  </And>
</Logic>

What has changed is now the Null and Empty rules have message level notification and they will add the result to the Notification of the engine to be collected from the application. When the application is run, you can see the following results on the screen: 
Name null
Name and family is required
As you may have noticed, the notifications for Family have not come through. That is because all of the checks are part of an And operation. To make the engine process all the child rules, you can add processAll=”true” to the And operator:

<Logic name="basic validation">
  <And name="NameAndFamilyCheck" message="Name and family is required" processAll="true">
    <Null value="Name" negate="true" message="Name null"/>
    <Empty value="Name" negate="true" message="Name empty"/>
    <Null value="Family" negate="true" message="Family null"/>
    <Empty value="Family" negate="true" message="Family empty"/>
  </And>
</Logic>

Then you will get the full result of the validation: 
Name null
Family empty
Name and family is required

The same processAll=”true” concept can be applied to the complete person test.

Final rule

<Validation name="PersonValidation">
  <Logic name="complete person test">
    <And processAll="true">
      <Validate logic="basic validation" />
      <Regex pattern="^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$" value="Email"
             message="Email field is not valid" tag="InvalidEmail" />
      <Validate name="person address" logic="address validation" value="Address" when="Address!=null"/>
    </And>
  </Logic>
  <Logic name="basic validation">
    <And name="NameAndFamilyCheck" message="Name and family is required" processAll="true">
      <Null value="Name" negate="true" message="Name null"/>
      <Empty value="Name" negate="true" message="Name empty"/>
      <Null value="Family" negate="true" message="Family null"/>
      <Empty value="Family" negate="true" message="Family empty"/>
    </And>
  </Logic>
  <Logic name="address validation">
    <And>
      <Null value="Line1" negate="true"/>
      <Empty value="Line1" negate="true"/>
    </And>
  </Logic>
</Validation>

Sample method

private void ValidateUsingValidationEngine()
{
    var eng = RuleEngine.FromXml(File.OpenRead("ValidationRules\\SampleValidationRule.xml"));
    var person = new Person()
    {
        Name = null,
        Family = "",
        Address = new Address() { Country = "AU", Line1 = "34 Thomas street", State = "VIC" }
    };
    var result = eng.Run(new RunParameter("basic validation", person));
    foreach (var item in result.Notifications.Default.Notices)
        Console.WriteLine(item.Message);
 
    result = eng.Run(new RunParameter("complete person test", person));
    foreach (var item in result.Notifications.Default.Notices)
        Console.WriteLine("{0}: {1}", item.Tag, item.Message);
}

Default Object

In the execution, a VariableContainer holds the data related to the context of the logic. A variable Container can also be type specific, which means it will expose the property values of an object.

When ValidationLogic uses a default object, that means the VariableContainer has all the properties of the default object and their values. This would help to write the logic just based on the properties name.

For example, VariableContainer of a Person has:

  • Birthday
  • Name
  • Family
  • Email
  • Address

Also, it has a parameter called $this.

this

On a Variable Container with a default object, $this refers to the actual object with which the container is registered.

For instance, in this example, the VariableContainer holds the property values of the object and the reference to the actual Person object is $this.

Next sections

In the next couple of articles, we will cover different aspects of modeling and executing the validation rules using Validation logic.

  1. Introduction to validation rules
  2. Validating hierarchy (Inheritance relation)
  3. Validating association (Aggregation, Composition)
  4. Validation rule execution and collecting results
  5. Pass extra input values to validation rules
  6. Extending validation conditions and actions
  7. How to apply rules under some conditions
  8. Referencing commonly used logic
  9. Sample for Order processing validation logic
Updated on April 6, 2020

Was this article helpful?

Related Articles