Click here to Skip to main content
15,867,488 members
Articles / Web Development / ASP.NET
Article

An Object Level Validation Framework

Rate me:
Please Sign up or sign in to vote.
4.94/5 (28 votes)
10 Nov 20065 min read 110.2K   492   85   36
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


Written By
Web Developer
United States United States
I work at a financial services firm using mostly .NET. I enjoy my job immensely and love teaching, training, and mentoring younger developers.

Comments and Discussions

 
GeneralValidationManager causes MemoryLeak Pin
Dominik Hasiwar2-Sep-09 4:36
Dominik Hasiwar2-Sep-09 4:36 
GeneralRe: ValidationManager causes MemoryLeak Pin
Sacha Barber25-Mar-10 9:29
Sacha Barber25-Mar-10 9:29 
GeneralReally great work Pin
seesharper15-Mar-07 10:26
seesharper15-Mar-07 10:26 
GeneralRe: Really great work Pin
Craig G. Wilson15-Mar-07 10:36
Craig G. Wilson15-Mar-07 10:36 
Thanks for the comments. To address your first concern, the object does not have to inherit from ValidattionBase, it just can. This is there for short circuiting some common scenarios. I don't use data binding that often, so I didn't place the attribute on the object, but you're right, it should be there.

As I've mentioned before, I have rewritten this entire library and integrated it into a much larger framework we use in my department. It is configurable via XML and most of all, it caches all the reflection and uses DynamicMethods to speed up the "reflective" calls. I assume when you say you use emits, you are referring to DynamicMethod creation at runtime.

Anyways, thanks for your comments and let me know if I can aid you in your framework building.
GeneralRe: Really great work Pin
seesharper24-Apr-07 9:34
seesharper24-Apr-07 9:34 
GeneralRe: Really great work Pin
Craig G. Wilson24-Apr-07 10:49
Craig G. Wilson24-Apr-07 10:49 
GeneralRe: Really great work Pin
seesharper24-Apr-07 23:27
seesharper24-Apr-07 23:27 
GeneralRe: Really great work Pin
Craig G. Wilson25-Apr-07 2:19
Craig G. Wilson25-Apr-07 2:19 
GeneralRe: Really great work Pin
seesharper25-Apr-07 2:39
seesharper25-Apr-07 2:39 
GeneralRe: Really great work Pin
Henry Nguyen30-Jul-07 4:07
professionalHenry Nguyen30-Jul-07 4:07 
GeneralGreat! Note on Error Messages Pin
StevieGen21-Feb-07 8:39
StevieGen21-Feb-07 8:39 
GeneralRe: Great! Note on Error Messages Pin
Craig G. Wilson21-Feb-07 8:54
Craig G. Wilson21-Feb-07 8:54 
QuestionCustomValidator with Strongly Typed Dataset Pin
rgu1237-Feb-07 10:35
rgu1237-Feb-07 10:35 
AnswerRe: CustomValidator with Strongly Typed Dataset Pin
Craig G. Wilson7-Feb-07 10:47
Craig G. Wilson7-Feb-07 10:47 
GeneralRe: CustomValidator with Strongly Typed Dataset Pin
rgu1237-Feb-07 11:14
rgu1237-Feb-07 11:14 
GeneralGood work! & localization question Pin
bxb23-Nov-06 1:57
bxb23-Nov-06 1:57 
GeneralRe: Good work! & localization question Pin
Craig G. Wilson23-Nov-06 12:52
Craig G. Wilson23-Nov-06 12:52 
GeneralRe: Good work! & localization question Pin
bxb27-Nov-06 23:20
bxb27-Nov-06 23:20 
GeneralGood Work Pin
Keith Barrett17-Nov-06 11:24
Keith Barrett17-Nov-06 11:24 
GeneralRe: Good Work Pin
Craig G. Wilson17-Nov-06 11:41
Craig G. Wilson17-Nov-06 11:41 
GeneralRe: Good Work Pin
D Aguilar14-May-07 22:21
D Aguilar14-May-07 22:21 
GeneralRe: Good Work Pin
Craig G. Wilson15-May-07 3:11
Craig G. Wilson15-May-07 3:11 
GeneralRe: Good Work Pin
Josh Smith17-Nov-06 11:54
Josh Smith17-Nov-06 11:54 
QuestionLicense? Pin
Mark Ericksen15-Nov-06 9:04
Mark Ericksen15-Nov-06 9:04 
AnswerRe: License? Pin
Craig G. Wilson15-Nov-06 10:17
Craig G. Wilson15-Nov-06 10:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.