An Asp.Net Validation Framework

I've been working on different ways to implement a validation framework that would really cut down on the code. It  had to be reusable and testable. I also didn't want to swap writing one bunch of repetitive code for another.

I'd looked at CSLA from Rockford Lhotka. I liked the way he implemented broken rules but CSLA had a lot of stuff for multiple undo's that was not relevant to my apps. I then used some of ScottGu's stuff for LINQ to SQL and begin using validation methods that threw exceptions. Reusable and custom exceptions made this very testable but I was still writing a bunch of repetitive code.

Microsoft's Enterprise Library has a Validation Block that was interesting but really seemed to be overkill.  I did like the declarative nature though and then found this from Imar Spaanjaars. That was more what I was looking for and I could see where I could make some changes.

First change was in the naming. NotNullOrEmptyAttribute became RequiredAttribute and ValidSsnAttribute became SsnAttribute. This seemed to work better for me.

Next was removing the localization. I've never had to write an app for anything but English. I know, next week I'll get one. That's ok, I left some places to hook in if I need to.

The largest change was to how I handle messages. I created a number of attributes to handle common elements like proper names, SSN, phone numbers, etc  (more on these in a later post) and I wanted to be able to do something like below.

[Required(DisplayName="SSN")]
[Ssn]
public string Ssn { get; set; }

Notice on the Ssn attribute there is no message property set. I have used pretty much the same error message for bad SSN's since I can remember. Why do I need to type a message on SSN element? I don't!

Everything starts with the ValidationAttribute class and all custom validation attributes will inherit from this class. Let's look at it.

/// <summary>
/// Base class for validation attributes.
/// </summary>
public abstract class ValidationAttribute
    : System.Attribute
{
    /// <summary>
    /// Initializes a new instance of the ValidationAttribute class.
    /// </summary>
    public ValidationAttribute()
    {
        this.DisplayName = string.Empty;
    }

    /// <summary>
    /// Gets or sets the BrokenRule with the error message.
    /// </summary>
    public BrokenRule Rule { get; set; }

    /// <summary>
    /// Gets or sets the name to be displayed for the element.
    /// </summary>
    public string DisplayName { get; set; }

    /// <summary>
    /// Gets or sets the error message.
    /// </summary>
    public string Message { get; set; }

    /// <summary>
    /// Indicates if the property is valid according to the attribute.
    /// </summary>
    /// <param name="item">The value of the property.</param>
    /// <param name="propName">The name of the property.</param>
    /// <returns>True if the property passes the validation rule, otherwise false.</returns>
    public abstract bool IsValid(object item, string propName);
}

There are three properties and one virtual method. Rule which is of type BrokenRule (more on this in a moment. DisplayName which holds the friendly name of the element. Notice how DisplayName is set to string.Empty in the constructor. Finally, we use Message to customize messages when needed.

The IsValid method checks to see if the element is valid (based on validation code in the subclasses). It accepts the value and name of the property as arguments. The value comes through as an object so that we can apply the attribute to different types. The name is passed because there is no link between the attribute and it's property.

Now we need a class that will actually validate something. The most common validation is required elements so we'll start with the RequiredAttribute.

/// <summary>
/// Attribute for validating that a value is not empty or null.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class RequiredAttribute
    : ValidationAttribute
{
    /// <summary>
    /// Initializes a new instance of the RequiredAttribute class.
    /// </summary>
    public RequiredAttribute()
    {
        DisplayName = "value";
    }
    
    /// <summary>
    /// Validates that the string is not empty or null.
    /// </summary>
    /// <param name="item">The string to be validated.</param>
    /// <param name="propName">The name of the invalid property.</param>
    /// <returns>True if the string has a value and is not empty, otherwise false.</returns>
    public override bool IsValid(object item, string propName)
    {
        Rule = new Rules.RequiredRule(propName, DisplayName);
        if (!Message.IsEmpty())
        {
            Rule.Message = Message;
        }

        if (item is string)
        {
            return !string.IsNullOrEmpty(item as string);
        }

        if (item is DateTime)
        {
            if (item == null)
            {
                return false;
            }

            if (Convert.ToDateTime(item) == DateTime.MinValue)
            {
                return false;
            }

            return true;
        }

        if (item is int)
        {
            if (Convert.ToInt32(item) == 0)
            {
                return false;
            }

            return true;
        }

        return item != null;
    }
}

The DisplayName is set to "value" by default but will get overridden if set. Otherwise, the only code is to check if the property actually has a value. Strings, DateTime and ints are all checked. In case you're wondering, IsEmpty is an extension method I wrote that checks if a string is null or empty.

What the Rule property? Well, we create an instance of the RequiredRule and pass the property and display names. RequiredRule is a subclass of BrokenRule with a default message. Here's the code for both.

/// <summary>
/// Simple representation of a broken business rule.
/// </summary>
public class BrokenRule
{
    /// <summary>
    /// Initializes a new instance of the BrokenRule class.
    /// </summary>
    public BrokenRule()
    {
    }

    /// <summary>
    /// Initializes a new instance of the BrokenRule class.
    /// </summary>
    /// <param name="propName">The name of the property that was invalid.</param>
    public BrokenRule(string propName)
    {
        this.PropertyName = propName;
    }

    /// <summary>
    /// Initializes a new instance of the BrokenRule class.
    /// </summary>
    /// <param name="propName">The name of the property that was invalid.</param>
    /// <param name="msg">The error message.</param>
    public BrokenRule(string propName, string msg)
    {
        this.PropertyName = propName;
        this.Message = msg;
    }

    /// <summary>
    /// Gets or sets the name of the property that was invalid.
    /// </summary>
    public string PropertyName { get; set; }

    /// <summary>
    /// Gets or sets the error message.
    /// </summary>
    public string Message { get; set; }
}

/// <summary>
/// Class to represent a broken required field rule.
/// </summary>
public class RequiredRule
    : BrokenRule
{
    /// <summary>
    /// Initializes a new instance of the RequiredRule class.
    /// </summary>
    /// <param name="propName">The name of the property that was invalid.</param>
    /// <param name="dispName">The name to be displayed for the element.</param>
    public RequiredRule(string propName, string dispName)
        : base(propName, string.Format(RuleMessages.RequiredMessage,
        dispName == string.Empty ? propName : dispName))
    {
    }
}

BrokenRule has the property name and the message. RequiredRule simply formats the constant RequiredMessage  with the DisplayName  (or property name if there isn't a DisplayName) and passes it to the base constructor.

You might be asking why not simply use a factory method to get the default message instead of a custom type? The answer is that this allows me to test for specific attributes without having to check all the different messages using Assert.IsInstanceOf.

Assert.IsInstanceOfType(testMock.BrokenRules[0], typeof(RequiredRule));

Assert.IsInstanceOfType(testMock.BrokenRules[0], typeof(SsnRule));
Now my unit tests can check if the Required and Ssn attributes were applied. Testability was just as important as getting rid of the repetitive code.

Next, we'll look at how to get your objects to use these attributes.

Comments

Popular posts from this blog

Migrating Legacy Apps to the New SimpleMembership Provider

Windows 8 Keyboard Shortcuts

VS Removes Usings and Can't Find Types