Validation rule execution

From FlexRule Wiki
Jump to: navigation, search

Assumptions

Let's say we have a simple model like
Person model
And 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 logic 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 detecting null or empty values for Name and Family. The rule would look like the logic:

  1. <Logic name="basic validation">
  2.   <And name="NameAndFamilyCheck" message="Name and family is required">
  3.     <Null value="Name" negate="true" />
  4.     <Empty value="Name" negate="true" />
  5.     <Null value="Family" negate="true" />
  6.     <Empty value="Family" negate="true" />
  7.   </And>
  8. </Logic>

And the next mart is making another logic to validate address like the following rule:

  1. <Logic name="address validation">
  2.   <And>
  3.     <Null value="Line1" negate="true"/>
  4.     <Empty value="Line1" negate="true"/>
  5.   </And>
  6. </Logic>

And then we put it all together with validating the email format as complete person test logic:

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

Putting logic into validation

  1. <Validation name="PersonValidation">
  2.   <Logic name="complete person test">
  3.     <And>
  4.       <Validate logic="basic validation" />
  5.       <Regex pattern="^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$" value="Email" 
  6.              message="Email field is not valid" tag="InvalidEmail" />
  7.       <Validate name="person address" logic="address validation" value="Address" when="Address!=null"/>
  8.     </And>
  9.   </Logic>
  10.   <Logic name="basic validation">
  11.     <And name="NameAndFamilyCheck" message="Name and family is required">
  12.       <Null value="Name" negate="true" />
  13.       <Empty value="Name" negate="true" />
  14.       <Null value="Family" negate="true" />
  15.       <Empty value="Family" negate="true" />
  16.     </And>
  17.   </Logic>
  18.   <Logic name="address validation">
  19.     <And>
  20.       <Null value="Line1" negate="true"/>
  21.       <Empty value="Line1" negate="true"/>
  22.     </And>
  23.   </Logic>
  24. </Validation>

How to execute validation

Step 1: Model and Execution plan

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

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

Engine instance maintains execution plan life-cycle and runtime behaviour. This section does not need to be repeated for the rest of 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:

  1. var person = new Person()
  2.             {
  3.                 Name = "John",
  4.                 Family = "k",
  5.                 Address = new Address() { Country = "AU", Line1 = "34 Thomas street", State = "VIC" }
  6.             };

And now we are ready to execute the validation like this:

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

As you expect the value of result.Outcome is true. Because in the values for the Bane and Family is provided. Now if you execute the person complete test like the following code:

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

The result.Outcome will be false. We know why, because we have not provided 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 like the following code:

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

And 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 lets see how we can grab more detail feedback from the engine. Lets create the following person object and the the basic validation as well:

A person with null Name and empty Family:

  1. var person = new Person()
  2. {
  3.     Name = null,
  4.     Family = "",
  5.     Address = new Address() { Country = "AU", Line1 = "34 Thomas street", State = "VIC" }
  6. };

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

  1. <Logic name="basic validation">
  2.   <And name="NameAndFamilyCheck" message="Name and family is required">
  3.     <Null value="Name" negate="true" message="Name null"/>
  4.     <Empty value="Name" negate="true" message="Name empty"/>
  5.     <Null value="Family" negate="true" message="Family null"/>
  6.     <Empty value="Family" negate="true" message="Family empty"/>
  7.   </And>
  8. </Logic>

What has changed is now the Null and Empty rules has message level notification and they will add the result to the Notification of engine to be collected from the application. When application is ran, you can see the following result on the screen:
Name null
Name and family is required
As you 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 And operator:

  1. <Logic name="basic validation">
  2.   <And name="NameAndFamilyCheck" message="Name and family is required" processAll="true">
  3.     <Null value="Name" negate="true" message="Name null"/>
  4.     <Empty value="Name" negate="true" message="Name empty"/>
  5.     <Null value="Family" negate="true" message="Family null"/>
  6.     <Empty value="Family" negate="true" message="Family empty"/>
  7.   </And>
  8. </Logic>

And 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 on complete person test.

Final rule

  1. <Validation name="PersonValidation">
  2.   <Logic name="complete person test">
  3.     <And processAll="true">
  4.       <Validate logic="basic validation" />
  5.       <Regex pattern="^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$" value="Email"
  6.              message="Email field is not valid" tag="InvalidEmail" />
  7.       <Validate name="person address" logic="address validation" value="Address" when="Address!=null"/>
  8.     </And>
  9.   </Logic>
  10.   <Logic name="basic validation">
  11.     <And name="NameAndFamilyCheck" message="Name and family is required" processAll="true">
  12.       <Null value="Name" negate="true" message="Name null"/>
  13.       <Empty value="Name" negate="true" message="Name empty"/>
  14.       <Null value="Family" negate="true" message="Family null"/>
  15.       <Empty value="Family" negate="true" message="Family empty"/>
  16.     </And>
  17.   </Logic>
  18.   <Logic name="address validation">
  19.     <And>
  20.       <Null value="Line1" negate="true"/>
  21.       <Empty value="Line1" negate="true"/>
  22.     </And>
  23.   </Logic>
  24. </Validation>

Sample method

  1. private void ValidateUsingValidationEngine()
  2. {
  3.     var eng = RuleEngine.FromXml(File.OpenRead("ValidationRules\\SampleValidationRule.xml"));
  4.     var person = new Person()
  5.     {
  6.         Name = null,
  7.         Family = "",
  8.         Address = new Address() { Country = "AU", Line1 = "34 Thomas street", State = "VIC" }
  9.     };
  10.     var result = eng.Run(new RunParameter("basic validation", person));
  11.     foreach (var item in result.Notifications.Default.Notices)
  12.         Console.WriteLine(item.Message);
  13.  
  14.     result = eng.Run(new RunParameter("complete person test", person));
  15.     foreach (var item in result.Notifications.Default.Notices)
  16.         Console.WriteLine("{0}: {1}", item.Tag, item.Message);
  17. }

Default Object

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

When ValidationLogic uses 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 Variable Container with a default object, $this refers to the actual object that the container is registered with.

For instance, in this example the VariableContainer holds the properties 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 aspect of modelling 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