Click here to Skip to main content
Click here to Skip to main content

An Object Level Validation Framework

By , 10 Nov 2006
Rate this:
Please Sign up or sign in to vote.

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:

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:

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:

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:

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.

    ...
    [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.

    ...
    [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.

    ...
    [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.

    ...
    [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].

    ...
            [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.

    ...
    [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.

    ...
            [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:

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

About the Author

Craig G. Wilson
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 PinmemberZecic2-Sep-09 4:36 
GeneralRe: ValidationManager causes MemoryLeak PinmvpSacha Barber25-Mar-10 9:29 
GeneralReally great work Pinmemberseesharper15-Mar-07 10:26 
GeneralRe: Really great work PinmemberCraig G. Wilson15-Mar-07 10:36 
GeneralRe: Really great work Pinmemberseesharper24-Apr-07 9:34 
GeneralRe: Really great work PinmemberCraig G. Wilson24-Apr-07 10:49 
GeneralRe: Really great work Pinmemberseesharper24-Apr-07 23:27 
GeneralRe: Really great work PinmemberCraig G. Wilson25-Apr-07 2:19 
GeneralRe: Really great work Pinmemberseesharper25-Apr-07 2:39 
GeneralRe: Really great work PinmemberHenry Nguyen30-Jul-07 4:07 
GeneralGreat! Note on Error Messages PinmemberStevieGen21-Feb-07 8:39 
GeneralRe: Great! Note on Error Messages PinmemberCraig G. Wilson21-Feb-07 8:54 
QuestionCustomValidator with Strongly Typed Dataset Pinmemberrgu1237-Feb-07 10:35 
AnswerRe: CustomValidator with Strongly Typed Dataset PinmemberCraig G. Wilson7-Feb-07 10:47 
GeneralRe: CustomValidator with Strongly Typed Dataset Pinmemberrgu1237-Feb-07 11:14 
GeneralGood work! & localization question Pinmemberbxb23-Nov-06 1:57 
GeneralRe: Good work! & localization question PinmemberCraig G. Wilson23-Nov-06 12:52 
Thanks. Yeah, you basically use resources for all your messages. I used resources for my default messages, but you would simply make some location specific ones and "change" the culture info. I've never really done it, but there are plenty of articles here on Code Project to help you out.
 
Bltoolkit has some good stuff from what I've seen in the past.
 

GeneralRe: Good work! & localization question Pinmemberbxb27-Nov-06 23:20 
GeneralGood Work PinmemberKeith Barrett17-Nov-06 11:24 
GeneralRe: Good Work PinmemberCraig G. Wilson17-Nov-06 11:41 
GeneralRe: Good Work PinmemberD Aguilar14-May-07 22:21 
GeneralRe: Good Work PinmemberCraig G. Wilson15-May-07 3:11 
GeneralRe: Good Work PinmemberJosh Smith17-Nov-06 11:54 
QuestionLicense? Pinmembermarkeric15-Nov-06 9:04 
AnswerRe: License? PinmemberCraig G. Wilson15-Nov-06 10:17 
GeneralRe: License? Pinmembermarkeric15-Nov-06 11:01 
GeneralLittle bug PinmemberMenthor14-Nov-06 23:03 
GeneralRe: Little bug PinmemberCraig G. Wilson15-Nov-06 2:36 
Generalthanks Pinmembercqwydz12-Nov-06 7:16 
GeneralRe: thanks PinmemberCraig G. Wilson12-Nov-06 7:44 
GeneralExcellent PinmemberJosh Smith11-Nov-06 14:29 
GeneralRe: Excellent PinmemberCraig G. Wilson11-Nov-06 14:35 
GeneralRe: Excellent PinmemberAl Ortega11-Nov-06 14:55 
GeneralRe: Excellent PinmemberCraig G. Wilson11-Nov-06 15:08 
GeneralThanks! PinprotectorMarc Clifton11-Nov-06 11:44 
GeneralRe: Thanks! PinmemberCraig G. Wilson11-Nov-06 14:24 

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

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

| Advertise | Privacy | Mobile
Web01 | 2.8.140415.2 | Last Updated 10 Nov 2006
Article Copyright 2006 by Craig G. Wilson
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid