Home > CSharp > DataAnnotations in Interfaces with Extension Methods

DataAnnotations in Interfaces with Extension Methods

Today’s blog post is about encapsulating validation rules in interfaces. If you are to develop a loosely coupled, unit-testable application you are for sure going to use some Dependency Injection framework (take a look at this post for Spring.NET by Kaloyan Bochevski). In order to accomplish this you start by designing some interfaces for your different logical layers. There you end up with interface contract definitions which must be implemented by your classes. You could even go further than just declaring some methods and properties and put validation rules there. This way you would encapsulate almost everything in the interface contract so it wouldn’t be spread across different class files or assemblies.

Data Annotations

DataAnnotations provide a great way of performing validation over data models in ASP.NET MVC and ASP.NET WebForms. Just by decorating your entity classes’ properties with the necessary attributes you can get out of the box validation with no code at all. These special attribute classes can be found in System.ComponentModel assembly which needs to be referenced from your project in order to get you ready to do your validations. In ASP.NET MVC this goes even further because you could enable even client side validation of your forms that are bound to data annotated models – the framework injects JavaScript code that handles all of this as long as you include some JavaScript files and enable client side validation. You could decorate your properties and make them required, fall in a specific range of values, have maximum number of characters or abide more complex rules with regular expressions. Here is what a sample entity interface definition might look like:

public interface IPerson : IValidationEntity
{
    [Required]
    string FirstName { get; set; }

    [Required]
    string LastName { get; set; }

    [Range(5, 100)]
    int Age { get; set; }

    [Required]
    [StringLength(20)]
    string City { get; set; }

    [Required]
    [StringLength(20)]
    string Country { get; set; }
}

If you are not developing against ASP.NET MVC or ASP.NET, let’s say for example you are doing a RESTful WCF service that needs to validate its input or you have some intermediary entities that are used to exchange information between different parts of a multi-layered system you could still take advantage of this validation model. Looking at the information for the System.ComponentModel.DataAnnotations Namespace at MSDN you can immediately see that all of the attributes derive from ValidationAttribute which has an IsValid() method. Basically we can get all validation attributes and check if the respective value is valid – this is how it works under the hood after all. Since we would have several classes that need to be validated then we need to create some common validation logic that we can reuse. This brings me to my next point.

Extension Methods

Extension methods provide a great deal of flexibility that I like to take advantage of whenever I can. In our scenario they will enable us to define some extensions over our interface and we will be able to validate all classes that implement this interface. First I’ll show you the definition of the interface:

public interface IValidationEntity
{
    bool Validate();
    IEnumerable<string> GetErrors();
}

There are only two methods that we need to develop:

public static class ValidationExtensions
{
    public static bool IsValid<T>(this T entity)
        where T : IValidationEntity
    {
        Type t = entity.GetType();
        Type[] tInterfaces = t.GetInterfaces();

        bool isValid = true;
        foreach (var ifc in tInterfaces)
        {
            foreach (var prop in ifc.GetProperties())
            {
                var propVal = prop.GetValue(entity, null);
                foreach (ValidationAttribute attr in prop.GetCustomAttributes(typeof(ValidationAttribute), true))
                {
                    if (attr.IsValid(propVal))
                        continue;

                    isValid = false;
                    break;
                }

                if (!isValid)
                    break;
            }

            if (!isValid)
                break;
        }

        return isValid;
    }

    public static IEnumerable<string> GetValidationErrors<T>(this T entity)
        where T : IValidationEntity
    {
        Type t = entity.GetType();
        Type[] tInterfaces = t.GetInterfaces();

        foreach (var ifc in tInterfaces)
        {
            foreach (var prop in ifc.GetProperties())
            {
                var propVal = prop.GetValue(entity, null);
                foreach (ValidationAttribute attr in prop.GetCustomAttributes(typeof(ValidationAttribute), true))
                {
                    if (attr.IsValid(propVal))
                        continue;

                    yield return attr.FormatErrorMessage(prop.Name);
                }
            }
        }
    }
}

IsValid checks if there are errors at all and GetValidationErrors returns a generic collection with descriptive error messages if we ever need to return some meaningful information (in case of a RESTful service). Both methods use reflection to get to the validation metadata that we have defined. The only interesting thing in the code above is that we need to iterate over the attributes of the class’s interfaces otherwise they won’t be available to us.

You could download a sample console application that utilizes the idea of defining validation rules on the interface.

Advertisements
Categories: CSharp
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: