Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

An Object Level Validation Framework

4.94/5 (28 votes)
10 Nov 20065 min read 2   492  
Provides a basic/advanced object level framework for validation.

Introduction

There is a basic need for validation of objects at all levels. ASP.NET provides this service at the presentation layer, but there is really no formal way to achieve this at the business layer.

Problem

I've been playing around a lot with O/R mappers and have come to realize that it sucks with hand-coding validation all the time. It is quite repetitive and boiler plate code that simply gets boring. Castle's Active Record(which is quite good) has a validation system built in. It is based on Ruby(on Rails)'s ActiveRecord pattern which has an even more robust validation system. The problem I have found is that the validation framework is tightly coupled with the underlying entity model. There is no way to leverage the framework elsewhere.

Initial Shoutouts

I've been working for a little while on this and recently found another Validation Framework and read through the source code. It had some good ideas which I selectively incorporated into this framework (the CompareValidator is the main one). He mentions an article he read which, by circumstance, I also read and got some ideas.

ValidationManager

There is really just one main class, the ValidationManager. This class represents the central repository for validators. It has 2 static methods: one to add a Validator to a type and one to validate an instance. For example:

C#
Validator val = RequiredValidator.CreateValidator<TestClass>("TestProperty");
ValidationManager.AddGlobalValidator<TestClass>(val);
TestClass tc = new TestClass();
if(ValidationManager.IsValid(tc))
   MessageBox.Show("WOO HOO");

This code has now registered a validator with the type TestClass for all new instances. In addition, any instances of TestClass already created are notified of the addition and they refresh their validator cache. (This method of validation is not preferable because of the overhead involved in instantiating a ValidationManager instance.)

Hmmm... what is the validator cache? The validator cache is an instance reference to the global validator cache for the instance's type. This means that we can also add instance validators as opposed to global validators. For example:

C#
TestClass tc = new TestClass();
ValidationManager vm = new ValidationManager(tc);

Validator val = RequiredValidator.CreateValidator<TestClass>("TestProperty");
vm.AddValidator(val);

This code has now registered a validator with the tc instance. No other instance of TestClass has this required validator on it. You'll also notice that ValidationManager takes an object as a constructor parameter. This instance of ValidationManager is tied to the instance of the object passed in. For example:

C#
TestClass tc = new TestClass();
ValidationManager vm = new ValidationManager(tc);
if(vm.IsValid())
   MessageBox.Show("WOO HOO");

Attribute Based Validation

The framework, while able to be manipulated programatically, is much easier to use through attributes. For example:

C#
public class TestClass
{
    private string testProperty;

    [RequiredValidator]
    [LengthValidator(1,10)]
    public string TestProperty
    {
        get { return this.testProperty; }
        set { this.testProperty = value; }
    }
}
...


TestClass tc = new TestClass();
ValidationManager vm = new ValidationManager(tc);

if(vm.IsValid())
   MessageBox.Show("WOO HOO");

When a ValidationManager is instantiated, it scans TestClass via reflection for ValidatorAttributes and adds them to the global validator cache. It only does this once per type so the overhead happens only once.

Each validator attribute contains the common properties ErrorMessage and Order. These can be specified as named parameters but are not required. ErrorMessage has a default and Order indicates the order in which to process the validator if there are more than one. (SIDENOTE: order only applies to attributes and not to instance validators).

Validators

Currently there are 7 validators. Each validator has a corresponding attribute to use. In addition, each attribute has static methods that facilitate the creation of itself programatically.

  1. Compare Validator

    Applicable to types that implement IComparable.

    Tests by comparing the instance value with a constant value using a specified comparison operator.

    C#
    ...
    [CompareValidator(ComparisonOperator.GreaterThan,0)]
    public int TestProperty
    {
        get { return this.testProperty; }
        set { this.testProperty = value; }
    }
    ...
  2. Custom Validator

    Applicable to any type.

    Takes a method name as a parameter that exists in the type. The method must conform to the Predicate<T> delegate signature.

    C#
    ...
    [CustomValidator("TestProperty cannot contain a period.","NoPeriodTest")]
    public string TestProperty
    {
        get { return this.testProperty; }
        set { this.testProperty = value; }
    }
    
    private bool NoPeriodTest(string value)
    {
        return !value.Contains(".");
    }
    ...
  3. Length Validator

    Applicable to System.String and types that implement ICollection.

    Tests by ensuring that the string's length or collection's count falls within the length restriction.

    C#
    ...
    [LengthValidator(1,10)]
    public string TestProperty
    {
        get { return this.testProperty; }
        set { this.testProperty = value; }
    }
    ...
  4. NotEmpty Validator

    Applicable to types that implement IEnumerable.

    Tests by ensuring that there is at least 1 element to enumerate.

    C#
    ...
    [NotEmptyValidator]
    public string TestProperty
    {
        get { return this.testProperty; }
        set { this.testProperty = value; }
    }
    ...
  5. Range Validator

    Applicable to types that implement IComparable.

    Tests by checking whether the value falls within the minvalue and the maxvalue. [inclusively].

    C#
    ...
            [RangeValidator(1,10)]
            public int TestProperty
            {
                get { return this.testProperty; }
                set { this.testProperty = value; }
            }
    ...
  6. Regular Expression Validator

    Applicable to strings.

    Tests by checking whether the value matches the regular expression.

    C#
    ...
    [RegexValidator(@"\d+")]
    public string TestProperty
    {
        get { return this.testProperty; }
        set { this.testProperty = value; }
    }
    ...
  7. Required Validator

    Applicable to any type. However, value types such as int will always pass validation. Nullable types work as expected.

    Tests for null. Therefore, while it is applicable to value types, it will always return true. Nullable types behave normally.

    C#
    ...
            [RequiredValidator]
            public string TestProperty
            {
                get { return this.testProperty; }
                set { this.testProperty = value; }
            }
    ...

Stuff Left to Do

I feel the library is fairly complete. There are 25 unit tests utilizing the NUnit framework. (I am by no means a unit test writer, so don't laugh at them.) The one thing I do wish to do is incorporate another way of registering validators with the framework. I am pursuing doing that through the app/web config files. This would completely decouple business logic validation from the domain model.

In addition, I'm not sure if the library is thread-safe. I think it is, because the underlying dictionary objects are thread-safe, but I'm not an expert in this area and will defer to another's judgement.

Finally, I'm still debating whether or not to allow the removal of validators at run-time from types and/or instances. I have not required this functionality, but perhaps others may find it useful.

Interesting Properties

Something I realized later, after having worked on the framework for some time, is that there is an inversion of control property associated with the framework. For instance, we can pass a ValidationManager and instance of any object, register some validators, and assure ourselves of its validity. For example:

C#
ArrayList list = new ArrayList();
ValidationManager vm = new ValidationManager(list);
vm.AddValidator(NotEmptyValidator.CreateValidator(typeof(ArrayList),"Count"));
Assert.IsFalse(vm.IsValid());
list.Add(2);
Assert.IsTrue(vm.IsValid());

Obviously, this is a trivial example, but the illustration is made.

Conclusion

We have created a formal validation framework that is extensible and easy to use. I hope you enjoyed the article, but hope that you enjoy using the library even more. Please keep me in the loop regarding code changes/enhancements/bug fixes that you make.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here