Disappointed
Disappointed

Reputation: 1120

How to implement validation rules which are known only during runtime


I have the following situation.
I have a bunch of simple classes, for example this one

public class Student
{
    public int Id { get; set; }
    public int Age { get; set; }
    public decimal AverageMark { get; set; }
    public string Name { get; set; }
    public string University { get; set; }
}

There is web page for every of them where user can create, edit and delete. When we create Student of update it, we need to validate it. The problem is that we do not know validation rules during compilation !!!
We have separate web page for administrator where he set up validation criterias, for example, that Student Age cannot be less then 15 or University have to be equal "SomeUniversity".
As result i have some list of criterias stored in my database

public class Criteria
{
    public string PropertyName { get; set; }
    public string OperationName { get; set; }
    public string OperationValue { get; set; }
}

I have created simple console application for investigation purposes. Here is code

namespace DynamicValidation
{
class Program
{
    static void Main(string[] args)
    {
        //set up students
        var student1 = new Student() { Age = 20, AverageMark = 4, Name = "Ihor", University = "Lviv National University" };
        var student2 = new Student() { Age = 20, AverageMark = 4, Name = "Taras", University = "Lviv National University" };
        var student3 = new Student() { Age = 20, AverageMark = 5, Name = "Marko", University = "" };
        var student4 = new Student() { Age = 20, AverageMark = 3, Name = "Tanya", University = "" };
        var student5 = new Student() { Age = 22, AverageMark = 4, Name = "Ira", University = "" };


        var students = new List<Student>() { student1, student2, student3, student4, student5 };

        //set up validation rules
        var criteria1 = new Criteria("Age", "Equal", "20");
        var criteria2 = new Criteria("AverageMark", "NotLessThan", "4");
        var criteria3 = new Criteria("University", "Contains", "Lviv");

        var criterias = new List<Criteria>() { criteria1, criteria2, criteria3 };

        var result = new List<Student>();
        foreach (var currentStudent in students)
        {
            foreach (var currentCriteria in criterias)
            {
                object currentPropertyValue = typeof(Student).GetProperty(currentCriteria.PropertyName).GetValue(currentStudent);

                //what is next ???!!!
            }
        }
    }
}

public class Student
{
    public int Id { get; set; }
    public int Age { get; set; }
    public decimal AverageMark { get; set; }
    public string Name { get; set; }
    public string University { get; set; }
}

public class Criteria
{
    public string PropertyName { get; set; }
    public string OperationName { get; set; }
    public string OperationValue { get; set; }
}

}

How can i implement this piece of code ? (expression trees, dynamic ?)
I do not want that you do work for me but maybe there are some articles about this ? (i tried to found but without success)
Maybe some advices about approach ? Maybe there is some similar open code ?
Or maybe it is already implemented in some libraries ?

Will be thankful for any help :)

Upvotes: 1

Views: 336

Answers (2)

Disappointed
Disappointed

Reputation: 1120

IMHO, I have found a little better solution then proposed
Now we do not need to change validation logic inside Student class if new property will be added. Also this code can be applied to any other class (not only for Student class as before)

Interface for validation

public interface IValidator
{
    bool Validate(object value, object validateWith);
}

Set of implementations

public class ContainsValidator : IValidator
{
    public bool Validate(object value, object validateWith)
    {
        string valueString = Convert.ToString(value);
        string validateWithString = Convert.ToString(validateWith);

        return valueString.Contains(validateWithString);
    }
}

public class StartWithValidator : IValidator
{
    public bool Validate(object value, object validateWith)
    {
        string valueString = Convert.ToString(value);
        string validateWithString = Convert.ToString(validateWith);

        return valueString.StartsWith(validateWithString);
    }
}

public class LengthValidator : IValidator
{
    public bool Validate(object value, object validateWith)
    {
        string valueString = Convert.ToString(value);
        int valueLength = Convert.ToInt32(validateWith);

        return (valueString.Length == valueLength);
    }
}

public class LessThanValidator : IValidator
{
    public bool Validate(object value, object validateWith)
    {
        decimal valueDecimal = Convert.ToDecimal(value);
        decimal validateWithDecimal = Convert.ToDecimal(validateWith);

        return (valueDecimal < validateWithDecimal);
    }
}

public class MoreThanValidator : IValidator
{
    public bool Validate(object value, object validateWith)
    {
        decimal valueDecimal = Convert.ToDecimal(value);
        decimal validateWithDecimal = Convert.ToDecimal(validateWith);

        return (valueDecimal > validateWithDecimal);
    }
}

public class EqualValidator : IValidator
{
    public bool Validate(object value, object validateWith)
    {
        string valueString = Convert.ToString(value);
        string validateWithString = Convert.ToString(validateWith);

        return (valueString == validateWithString);
    }
}

And usages

class Program
{
    static void Main(string[] args)
    {
        //set up students
        var student1 = new Student() { Age = 20, AverageMark = 5, Name = "Ihor", University = "Lviv National University" };
        var student2 = new Student() { Age = 20, AverageMark = 5, Name = "SomeLongName", University = "Lviv National University" };
        var student3 = new Student() { Age = 20, AverageMark = 5, Name = "Taras", University = "Kyiv National University" };
        var student4 = new Student() { Age = 20, AverageMark = 5, Name = "Marko", University = "Some University" };
        var student5 = new Student() { Age = 20, AverageMark = 4, Name = "Tanya", University = "Lviv National University" };
        var student6 = new Student() { Age = 22, AverageMark = 4, Name = "Ira", University = "" };

        var students = new List<Student>() { student1, student2, student3, student4, student5, student6 };

        //set up validation rules
        var criteria1 = new Criteria("Age", "Equal", "20");
        var criteria2 = new Criteria("AverageMark", "MoreThen", "4");
        var criteria3 = new Criteria("University", "Contains", "National");
        var criteria4 = new Criteria("University", "StartWith", "Lviv");
        var criteria5 = new Criteria("Name", "Length", "4");

        var criterias = new List<Criteria>() { criteria1, criteria2, criteria3, criteria4, criteria5 };

        var result = new List<Student>();
        foreach (var currentStudent in students)
        {
            var isValid = true;
            foreach (var currentCriteria in criterias)
            {
                object currentPropertyValue = typeof(Student).GetProperty(currentCriteria.PropertyName).GetValue(currentStudent);
                IValidator currentValidator = ValidatorFactory.GetValidator(currentCriteria.OperationName);

                bool validationResult = currentValidator.Validate(currentPropertyValue, currentCriteria.OperationValue);
                if (!validationResult)
                {
                    isValid = false;
                    break;
                }
            }

            if (isValid)
                result.Add(currentStudent);
        }
    }
}

In the end the the code of ValidatorFactory

public class ValidatorFactory
{
    public static IValidator GetValidator(string validatorName)
    {
        validatorName = validatorName.ToUpper();
        switch (validatorName)
        {
            case "CONTAINS": return new ContainsValidator();
            case "STARTWITH": return new StartWithValidator();
            case "EQUAL": return new EqualValidator();
            case "MORETHEN": return new MoreThanValidator();
            case "LENGTH": return new LengthValidator();
            default: throw new Exception("There are not appropriate validator.");
        }
    }
}

Maybe this will help someone in the future :)

Upvotes: 0

DGibbs
DGibbs

Reputation: 14618

You could write a student validator function, see IsValidStudent(Criteria criteria):

public class Student
{
    public int Id { get; set; }
    public int Age { get; set; }
    public decimal AverageMark { get; set; }
    public string Name { get; set; }
    public string University { get; set; }

    public bool IsValidStudent(Criteria criteria)
    {
        return IsValidByAge(criteria) 
            && IsValidByMarks(criteria) 
            && IsValidByUniversity(criteria);
    }

    private bool IsValidByAge(Criteria criteria)
    {
        switch (criteria.OperationType)
        {
            case Criteria.Operation.GreaterThan:
                return Convert.ToInt32(criteria.OperationValue) > this.Age;
            case Criteria.Operation.LessThan:
                return Convert.ToInt32(criteria.OperationValue) < this.Age;
            case Criteria.Operation.EqualTo:
                return Convert.ToInt32(criteria.OperationValue) == this.Age;
            default:
                return false;
        }
    }

    private bool IsValidByMarks(Criteria criteria)
    {
        // etc...
    }

    private bool IsValidByUniversity(Criteria criteria)
    {
        // etc...
    }
}

Usage:

var result = new List<Student>();
foreach (var currentStudent in students)
{
     foreach (var currentCriteria in criterias)
     {
           if (currentStudent.IsValidStudent(currentCriteria))
           {
               result.Add(currentStudent);
           }
     }
}

I also extended your Criteria class:

public class Criteria
{
    public string PropertyName { get; set; }
    public Operation OperationType { get; set; }
    public string OperationValue { get; set; }

    public enum Operation
    {
        EqualTo,
        GreaterThan,
        LessThan,
        Contains
    }

    public Criteria(string propertyName, Operation operationType, string operationValue)
    {
        this.PropertyName = propertyName;
        this.OperationType = operationType;
        this.OperationValue = operationValue;
    }
} 

Upvotes: 1

Related Questions