Sponsored By Aspose - File Format APIs for .NET

Aspose are the market leader of .NET APIs for file business formats – natively work with DOCX, XLSX, PPT, PDF, MSG, MPP, images formats and many more!

Validation – Part 1 – Getting Started

There are a lot of good and free frameworks to help you deal with validating user-submitted data. You can use attribute-based frameworks, such as Castle Validators or .NET 3.5 Data Annotations to decorate your objects with simple rules, and then leverage frameworks such as xVal or the MVC Validation Toolkit to enforce those attribute both on the client and server side. (For those who dislike using attributes in this manner, you could also use something like FluentValidation).

Despite these frameworks, there may be situations where you need to roll your own, which is what we’re going to look at in this series. We won’t be doing anything fancy, but we will look at an end-to-end custom solution.

Attributes

The first thing we need to do is decide how we’ll define our rules. We’ll pick a mix of attributes for simple validation cases, and an interface for more advanced/custom validation. Our goal is to end up with something like:

public interface IValidate
{
    ValidationError[] Validate(IRepository repository);
}

public class User : IValidate
{        
	private string _email;
	private string _password;

	[Required, StringLength(50), Pattern(".+@.+\\..+"), Tip("Please enter a valid email, a confirmation email will be sent.")]
	public string Email
	{
	    get { return _email; }
	    set { _email = value; }
	}
	[Required, StringLength(4, 30), Tip("Please enter your password. 4-30 characters")]
	public string Password
	{
	    get { return _password; }
	    set { _password = value; }
	}

	public virtual ValidationError[] Validate(IRepository repository)
	{
	    if (repository.Exists<User>(u => u.Email == Email))
	    {
	        return new[] {new ValidationError("Email", "This email is already registered")};
	    }
	    return null;
	}
}

We could always externalize the “Tips” for localization (or cleanliness) purposes, but this keeps everything rather simple. (If you’re put off by the attributes, check outFluentValidation for an idea on how else you migth define your rules).

The foundation for our attributes is a simple base class:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public abstract class BaseValidatorAttribute : Attribute
{
   public abstract IDictionary<string, string> ToJson();
   public abstract bool IsValid(object value);
}

And here’s the simplest implementation:

public class RequiredAttribute : BaseValidatorAttribute
{
    public override IDictionary<string, string> ToJson()
    {
        return new Dictionary<string, string> {{"required", "true"}};
    }  

    public override bool IsValid(object value)
    {
        if (value == null)
        {
            return false;
        }
        return !(value is string) || ((string)value).Length != 0;
    }
}

The IsValid method is used to validate the object server-side. The ToJson method is used to generate a javascript object for client-side use.

Let’s take a look at the StringLengthAttribute – which is slightly more complicated:

public class StringLengthAttribute : BaseValidatorAttribute
{
   private readonly int _minimumLength;
   private readonly int _maximumLength;

   public int MinimumLength
   {
      get { return _minimumLength; }
   }
   public int MaximumLength
   {
      get { return _maximumLength; }
   }

   public StringLengthAttribute(int maximumLength) : this(0, maximumLength){}
   public StringLengthAttribute(int minimumLength, int maximumLength)
   {
      _minimumLength = minimumLength;
      _maximumLength = maximumLength;
   }

   public override IDictionary<string, string> ToJson()
   {            
      return new Dictionary<string, string>
         {
            { "min", _minimumLength.ToString() },
            { "max", _maximumLength.ToString() },
         };
   }

   public override bool IsValid(object value)
   {            
      if (!(value is string))
      {
         return false;
      }            
      var length = ((string)value).Length;
      return length >= _minimumLength && length <= _maximumLength;
   }
}

There really is no limit to what you’re able to do with these attributes. You can build groups of validation rules (each group might have its own tip), compare validators, or even validators that hit the DB, such as a UniqueAttribute (although we’ve opted to implement that via the IValidate interface).

ValidatorConfiguration

There are two problems we’ll run into by using attributes – they are somewhat cumbersome to program against, and they can impact performance. Since validation is static, we can parse and cache more useful structures which we can then use when doing our actual client-side and server-side validation. We’ll use two classes to represent our attributes:

public class EntityValidationInfo
{
   public IEnumerable<PropertyValidationInfo> Properties { get; private set; }
   public EntityValidationInfo(IEnumerable<PropertyValidationInfo> properties)
   {        
      Properties = properties;
   }
}
public class PropertyValidationInfo
{
   public IEnumerable<BaseValidatorAttribute> Validators { get; private set; }
   public PropertyInfo Property { get; private set;}
   public PropertyValidationInfo(PropertyInfo property, IEnumerable<BaseValidatorAttribute> validators)
   {
      Property = property;
      Validators = validators;
   }
}

The purpose behind these is that given an object type, we can lookup its EntityValidationInfo which contains a collection of properties and validators. Next we’ll create a ValidatorConfiguration class with a couple static members:

public class ValidatorConfiguration
{
    private static IDictionary<Type, EntityValidationInfo> _rules;
    public static IDictionary<Type, EntityValidationInfo> Rules
    {
        get { return _rules; }
    }
    public static void Initialize(params string[] assemblyNames)
    {     
          //todo
    }
}

Our goal is to initialize ValidatorConfiguration once, on startup, and then access its Rules property as needed. The Initialize method takes in assembly names to scan for classes using validation attributes (you might have these spread out across multiple assemblies). In an ASP.NET application, we’d use the Application_Start event and use something like:

ValidatorConfiguration.Initialize("MyAssembly", "MyAssembly.Web");

Finally, the implementation of Initialize looks like:

public static void Initialize(params string[] assemblyNames)
{
   _rules = new Dictionary<Type, EntityValidationInfo>(10);
   foreach (var assemblyName in assemblyNames)
   {
       foreach (var kvp in LoadFromAssembly(Assembly.Load(assemblyName)))
       {
           _rules.Add(kvp.Key, kvp.Value);
       }                
   }
}
private static IDictionary<Type, EntityValidationInfo> LoadFromAssembly(Assembly assembly)
{
   var rules = new Dictionary<Type, EntityValidationInfo>(10);
   foreach (var type in assembly.GetExportedTypes())
   {
       var info = GetEntityValidationInfo(type);
       if (info != null) { rules.Add(type, info); }
   }
   return rules;
}
private static EntityValidationInfo GetEntityValidationInfo(Type type)
{
   var properties = new List<PropertyValidationInfo>();
   foreach (var property in type.GetProperties())
   {
       var attributes = (BaseValidatorAttribute[])property.GetCustomAttributes(typeof(BaseValidatorAttribute), true);
       if (attributes.Length == 0) { continue; }
       properties.Add(new PropertyValidationInfo(property, attributes));
   }
   return properties.Count == 0 ? null : new EntityValidationInfo(properties);
}

There isn’t much magic going on here. We loop through each property, of each public type of each assembly, looking for validation attributes. If we don’t find any, we simply move on to the next. If we do find them, we create new instances of our EntityValidationInfo and PropertyValidationInfo and associate that to a Type within the static _rules field.

Conclusion

We’ve managed to lay the foundation for actually being able to validate stuff. In Part 2 we’ll look at client-side validation, and in Part 3 we’ll cover server-side validation. Stay tuned.

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

8 Responses to Validation – Part 1 – Getting Started

  1. Mark Nijhof says:

    Karl,

    Actually atm the objects don’t know anything about the validation, this is all taken care outside the objects them selfs. Only reason for the interface implementation on the view model is to transfer the results not for the actual validation.

    The reason for including the rules in the objects is to be more DDD compliant.

    -Mark

  2. karl says:

    @Tom:
    That’s the purporse of part 2 :)

    @Mark:
    We have multiple systems using these entities (web services, web apps, mobile apps) so that’s why I didn’t consider tying this more closely to the view.

  3. Peter Morris says:

    Nice start, I look forward to seeing the others. I certainly prefer technical posts.

  4. Mark Nijhof says:

    Hi Karl,

    Interesting post! I am currently also working on a validation framework to be used for FubuMVC I took a little different approach where the object to be validated doesn’t know about the rules (a view model it needs to implement an interface only to transfer the validation results to the controller and or view). I wrote about it here: http://blog.fohjin.com/blog/2009/3/21/FubuMVC_Validation_Convention_based_validation

    But after talking to Jimmy Nilson I am working on a different approach where the actual objects will contain the validation rules, but I still want to assign them using the convention based parts already created.

    Anyway looking forward to reading the next parts.

    -Mark

  5. Tom de Koning says:

    Good one.

    Karl, would you know wheter it is possible to hook up the validation in webforms (using jQuery perhaps?)?

  6. karl says:

    Oded:
    I’m going to assume that the basic validation like UserName length, email pattern and so on is going to be the same regardless of where something is in a workflow.

    Everything else you should be able to handle in the Validate method. I passed in an IRepository, but you might also pass in a IWorkflowManager.

    Not sure if that helps.

  7. Please keep posting this kind of technical stuffs, avoid those negative posts.

  8. Oded says:

    Just wondering about how context sensitive validation can fit into this.

    How would you handle situations where the same object requires different validation depending on (for example) where in the workflow we are validating?