Category Archives: Data Annotations

Validation in WebForms with Data Annotations

Some very old projects get enough development time so that certain parts of them move forward, but too often this time isn’t enough for a full re-write to modern day technologies.

In most scenarios the persistence layer will move forward before the Web UI. This is because legacy WebForms projects relied on patterns like the Supervising Controller that don’t translate directly to modern implementations like ASP.NET’s MVC. Migrating projects from old legacy patterns to ASP.NET MVC usually starts with the persistence layer because presenters wrap and transform models into what the view needs instead of providing a way to directly bind these; changing the persistence layer into something that can be bound directly, refactoring the presenter first and paving the way to replacing presenters with controllers is usually the way to go.

In a scenario where a legacy persistence layer is left intact and a change from WebForms to ASP.NET MVC is done instead, it usually takes longer to execute due to the fact that Model wrappers need to be written that wrap around the old persistence layer and bind directly to the views, along with the usual view re-writing and refactoring presenters into controllers. These wrappers also add obscurity to the overall solution, so anyone maintaining the solution between the changes will have a hard time with it.

Doing ASP.NET WebForms validation with Data Annotations

ASP.NET WebForms does validation through a series of ASP.NET Validation Server Controls, what they have in common is that they all inherit from BaseValidator. The strategy is to start from this inheritance, expose two additional properties for the name of the property we want to validate and another for the type of the class where this property exists.

/// <summary>
/// Exposes the Property Name that we want to validate against.
/// </summary>
public string PropertyName { get; set; }
        
/// <summary>
/// Exposes the SourceType for Data Annotation lookup that we want to validate against.
/// </summary>
public string SourceType { get; set; }

The BaseValidator class has an abstract method EnsureIsValid that is the main override point for creating our own Validator. By getting the PropertyName and the SourceType you can use reflection and get the Data Annotations, then use these to check for Validation and to properly create and format the Error Message.

/// <summary>
/// Performs the real Validation process, sets the isValid flag on the
/// BaseValidator class.
/// </summary>
/// <returns>If the property if Valid or Not.</returns>
protected override bool EvaluateIsValid()
{
    var objectType = Type.GetType(SourceType, true, true);
    var property = objectType.GetProperty(PropertyName);

    var control = base.FindControl(ControlToValidate) as TextBox;

    if(control == null)
        throw new InvalidOperationException("This implementation can only be used to validate Textbox controls, attempting to validate something else will fail!");

    foreach (var attr in property.GetCustomAttributes(typeof (ValidationAttribute), true)
                                    .OfType<ValidationAttribute>()
                                    .Where(attr => !attr.IsValid(control.Text)))
    {
        // This implementation will break on the first attribute fail and will only return the first error found.
        // I kept the foreach and the where clause to allow for easier transition into an implementation that
        // tracks and displays all the errors found and not just the first one!
        var displayNameAttr = property.GetCustomAttributes(typeof (DisplayNameAttribute), true)
                                        .OfType<DisplayNameAttribute>()
                                        .FirstOrDefault();

        var displayName = displayNameAttr == null ? property.Name : displayNameAttr.DisplayName;
        ErrorMessage = attr.FormatErrorMessage(displayName);
        return false; 
    }

    return true;
}

This is a very naive implementation, it will only work with the TextBox control, explicitly throwing otherwise:

var control = base.FindControl(ControlToValidate) as TextBox;

if(control == null)
    throw new InvalidOperationException("This implementation can only be used to validate Textbox controls, attempting to validate something else will fail!");

And it doesn’t do any proper logging and trapping of the reflection bits in the code, if there’s any problem setting the SourceType and PropertyName, like for example a typo, it just blows up without any exception handling:

var objectType = Type.GetType(SourceType, true, true);
var property = objectType.GetProperty(PropertyName);

Usage examples

To use the DataAnnotationValidator simply add it where you want the validation text to appear like for example:

<asp:Label ID="CardFirstNameTextLabel" runat="server" CssClass="FormLabel" AssociatedControlID="CardFirstNameText">First Name</asp:Label>
<asp:TextBox ID="CardFirstNameText" runat="server" AutoCompleteType="firstname" />

<val:DataAnnotationValidator ID="FirstNameValidator" runat="server"
    ControlToValidate="CardFirstNameText" Text="**" PropertyName="FirstName" SourceType="InnerWorkings.Model.CardDetails, InnerWorkings.Model" />

<span class="Notes">(as it appears on the card)</span>

You can also use the built in ASP.NET ValidationSummary control to display the validation errors summary:

<asp:ValidationSummary runat="server" ID="vSumAll" DisplayMode="BulletList" CssClass="validation-errors" HeaderText="<span>Oops! Please fix the following errors:</span>" />

The full source Code for the DataAnnotationValidator

namespace ValidationWithDataAnnotations
{
    using System;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.Web.UI.WebControls;

    /// <summary>
    /// Reasonable wrapper for performing Validation using Data Annotations.
    /// With the inclusion of EntityFramework in the solution, all model elements are properly
    /// Data Annotated, so the logical path is to perform UI validation using the same set
    /// of annotations used by EF.
    /// The Validator still requires the setting of PropertyName and SourceType, this is
    /// where this class could be improved, as both these things can be looked up instead of
    /// just set.
    /// </summary>
    public class DataAnnotationValidator : BaseValidator
    {
        /// <summary>
        /// Exposes the Property Name that we want to validate against.
        /// </summary>
        public string PropertyName { get; set; }
        
        /// <summary>
        /// Exposes the SourceType for Data Annotation lookup that we want to validate against.
        /// </summary>
        public string SourceType { get; set; }

        /// <summary>
        /// Performs the real Validation process, sets the isValid flag on the
        /// BaseValidator class.
        /// </summary>
        /// <returns>If the property if Valid or Not.</returns>
        protected override bool EvaluateIsValid()
        {
            var objectType = Type.GetType(SourceType, true, true);
            var property = objectType.GetProperty(PropertyName);

            var control = base.FindControl(ControlToValidate) as TextBox;

            if(control == null)
                throw new InvalidOperationException("This implementation can only be used to validate Textbox controls, attempting to validate something else will fail!");

            foreach (var attr in property.GetCustomAttributes(typeof (ValidationAttribute), true)
                                         .OfType<ValidationAttribute>()
                                         .Where(attr => !attr.IsValid(control.Text)))
            {
                // This implementation will break on the first attribute fail and will only return the first error found.
                // I kept the foreach and the where clause to allow for easier transition into an implementation that
                // tracks and displays all the errors found and not just the first one!
                var displayNameAttr = property.GetCustomAttributes(typeof (DisplayNameAttribute), true)
                                              .OfType<DisplayNameAttribute>()
                                              .FirstOrDefault();

                var displayName = displayNameAttr == null ? property.Name : displayNameAttr.DisplayName;
                ErrorMessage = attr.FormatErrorMessage(displayName);
                return false; 
            }

            return true;
        }
    }
}