Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Rule based object validator

4.91/5 (6 votes)
13 Jun 2012CPOL2 min read 17.1K  
Building a Rule Based Validation framework in C# and XML

Introduction

This article tries to build a Property based validator for .NET objects. We specify object test conditions and validation rules in the configuration and based on these rules identify which object satisfies the given criteria.

Background

We came across this problem when our operational team wanted us to quickly filter a list of object based on a very specific criteria (a property value in this case). Even though request was very specific for a single property, we realized that filter it in the code will not be a solution fit for our application as there could be new similar requests coming in future at a short notice. Hence we came with a solution to use XML Rule Based validator which could be used to fulfill such request with a neat configurable option without changing the code.

Using the code 

This is a sample piece of code and not intended to be used in a production system. It should be carefully extended and organized before using it for a real software.

Here is my XML file where I specify what are the entry criteria for the objects and how should they be validated.

Note: We are only doing property validation here. 

Rules.Xml
XML
<Rules>
  <Rule>
    <Condition>
      <Property Name="Property1" Value="Condition1" />
      <Property Name="Property2" Value="Condition1" />
    </Condition>
    <Validation>
      <Property Name="ValidateProperty" Operator="StartsWith" Value="TestProperty" />
    </Validation>
  </Rule>
  <Rule>
    <Condition>
      <Property Name="Property1" Value="Condition2" />
      <Property Name="Property2" Value="Condition1" />
    </Condition>
    <Validation>
      <Property Name="ValidateProperty" Operator="StartsWith" Value="test" />
    </Validation>
  </Rule>
</Rules>
Main.cs
C#
public class Property
{
    public string PropertyName;
    public string Operator;
    public string Value;
}

interface IRule
{
    bool ShouldValidate(object o);
    bool Validate(object o);
}
public class Rule: IRule
{
    public List<Property> Conditions = new List<Property>();
    public List<Property> Validators = new List<Property>();
    private XmlNode xmlNode;

    public Rule()
    {

    }

    public bool Validate(object p)
    {
        bool shouldValidate = ShouldValidate(p);

        if (shouldValidate)
        {
            return Validators.All(v =>
            {
                var pi = p.GetType().GetProperty(v.PropertyName);
                if (pi != null)
                {
                    var pValue = pi.GetValue(p) as string;
                    if (pValue != null)
                    {
                        switch (v.Operator)
                        {
                            case "StartsWith":
                                return pValue.StartsWith(v.Value);
                            default:
                                return false;
                        }
                    }
                }
                return false;
            });
        }
        return true;
    }

    public bool ShouldValidate(object p)
    {
        int propertyMatch = 0;
        foreach (Property prop in Conditions)
        {
            var pi = p.GetType().GetProperty(prop.PropertyName);
            if (pi != null)
            {
                var pValue = pi.GetValue(p) as string;
                if (pValue != null && pValue == prop.Value)
                {
                    propertyMatch++;
                }
            }
        }
        return propertyMatch == Conditions.Count;
    }
}

public class RuleFactory
{
    static IRule CreateRule(XmlNode xmlNode)
    {
        if (xmlNode != null)
        {
            //TODO: Create custom Rule to handle OR/AND type of rules
            //TODO: Create Custom Validators for supporting different operators
            return new Rule()
            {
                Conditions = xmlNode.SelectSingleNode("Condition").SelectNodes("Property").Cast<XmlNode>().Select(
                    property => new Property
                    {
                        PropertyName = property.Attributes["Name"].Value,
                        Value = property.Attributes["Value"].Value
                    }).ToList(),

                Validators = xmlNode.SelectSingleNode("Validation").SelectNodes("Property").Cast<XmlNode>().Select(
                    property => new Property
                    {
                        PropertyName = property.Attributes["Name"].Value,
                        Value = property.Attributes["Value"].Value,
                        Operator = property.Attributes["Operator"].Value
                    }).ToList()
            };
        }
        return null;
    }
}

Code above is the basic framework needed to get us going. Note that I'm only looking for hard-coded starts with operator here but we can extend this solution to support other operators as well. We'll of course need to refactor this class to add strongly type validator and support OR based conditions as well. This class only checks for object that satisfies all the conditions and that confirms to all validation rules (in summary only AND operation is supported). 

Now rest of the code to test this logic is posted below. Here we'll create some rules and test our objects based on the rules in the XML file.

C#
static void Main(string[] args)
{
    var document = new XmlDocument();
    document.Load(@"C:\Code\test.xml");
    var rules = document.SelectSingleNode("Rules").SelectNodes("Rule").Cast<XmlNode>().
        Select(xmlNode => RuleFactory.CreateRule(xmlNode));

    List<TestObject> testObjects = new List<TestObject>()
    {
        new TestObject(){Property1 = "Condition1", Property2="Condition2"},
        new TestObject(){Property1 = "Condition1", Property2="Condition3", ValidateProperty="Hello"},
        new TestObject(){Property1 = "Condition1", Property2="Condition2", ValidateProperty="TestProperty"},
        new TestObject(){Property1 = "Condition1", Property2="Condition2", ValidateProperty="Test123" },
        new TestObject(){Property1 = "Condition1", Property2="Condition1", ValidateProperty="TestProperty" },
        new TestObject(){Property1 = "Condition2", Property2="Condition1", ValidateProperty="test" },
        new TestObject(){Property1 = "Condition2", Property2="Condition2"}            
    };

    var success = testObjects.Where(pr =>
        {
            if (rules == null)
                return false;
            var ru = rules.FirstOrDefault(r => r.ShouldValidate(pr));
            return ru != null && ru.Validate(pr);
        });

    var result = success.Count();
}

Here in the main method we are creating rules based on the XML file and also creating a list of objects to validate. Based on the conditions specified in the configuration file only two objects satisfies the criteria and if you run the application you will see that the value of  result variable indeed is two.

Points of Interest

TBD: Good exercise with lambda and reflection.

History

First draft of the article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)