Register | Login

Stacking Code

public interface IBlog { string Dump(Stream consciousness); }

Validation with NHibernate and Data Annotations

Friday, 10 December, 2010 @ 6:44 PM < Adam Boddington
Tags: Building Neno, DataAnnotations, NHibernate, Validation

This is post #17 in the Building Neno series. Please click here for a description of the Building Neno project and instructions on how to access the source code for this post.

Wow, it's Friday again and I still don't have screens for CRUDing posts or comments. If the client was anyone but myself, I may have been fired. Before I get into a lot of CRUD screens however, I want to make sure I have the basics in my system. And that includes validation.

Wake up, dummy, you already have validation. I've seen it! - The Astute Reader

That's true, I do have validation, but it's UI validation, not application validation. It's MVC taking the data annotations on my domain classes and running it through its default model validation. There are a couple of problems with relying only on UI validation...

  • UI validation doesn't cover other applications hitting the service layer.
  • UI validation only cares about the object it is working with and not so much about the rest of the domain model.
  • Some validation rules are difficult to implement in the UI.

Where Does Validation Belong?

Preferrably in both the UI and in the service layer; but, if it's only going to be one of them, then validation should sit inside the service layer, the real boundary of this application. Whatever extra UIs, third party applications or data integration tools are hooked up to this application, they're all going to talk to it through the service layer. Validation in just one UI isn't going to cover everything.

That doesn't mean MVC validation isn't useful -- it is. UI validation can save service layer calls, it can even save postbacks with client-side JavaScript. It provides a richer, faster validation experience for the user. However it's still just a UI feature, there to enhance the UI experience. UI validation is "window dressing" by definition. ;)

Where Does Application Validation Belong?

Application validation belongs wherever objects are being saved. In this application that's happening in the repositories. Implementing it there can be problematic, however, especially when passing object graphs into repository methods. Is just the top object checked? Should the object graph be walked to check every object? How far should that go? It can be far more effective to let the ORM manage the validation instead, which is what I will do in this case.

NHibernate's inner workings can be hooked into through listeners and interceptors. In my last post I built an interceptor to check for object staleness on update and delete. Today I'm going to build another one to check for validity on create and update. The great thing about doing validation via an NHibernate interceptor is that it will automatically cover every object that requires work, not just the tip of an object graph. For example, if I give NHibernate a new post, with three new tags attached to it and two unchanged ones, NHibernate will save four objects, and the interceptor OnSave method will be fired four times. It doesn't get easier than that.

Here's my quick and dirty interceptor...

namespace StackingCode.Moja.Repositories.NHibernate.Interceptors
{
    public class InvalidInterceptor : Interceptor
    {
        public override bool OnCreate(object entity, object id, IEnumerable<EntityProperty> properties)
        {
            entity.Validate();

            // No properties have changed, return false.
            return false;
        }

        public override bool OnUpdate(object entity, object id, IEnumerable<EntityPropertyForUpdate> properties)
        {
            entity.Validate();

            // No properties have changed, return false.
            return false;
        }
    }
}

Note, I'm using a different interceptor architecture than NHibernate's out-of-the-box one interceptor per session. See this post for details.

Wow, magic. But where's that Validate method coming from? In most validation frameworks there is typically a method that will check all the validation rules for a given object and throw an exception on failure. That's all I need to call here. But this is data annotations... does it have a method like that?

Data Annotations

The System.ComponentModel.DataAnnotations namespace is handy. It's inbuilt into .NET, it has some basic validation attributes, and it can be easily extended for custom validation attributes on properties and classes. But unfortunately it doesn't have a method for checking all the attributes on a given object! To be honest, it's not much use without it. And it's such an obvious oversight that I think it must be in the framework somewhere and I just can't find it. If you know where it is, please tell me.

EDIT: It turns out the Validator class that was in Silverlight 3 has been moved into the System.ComponentModel.DataAnnotations namespace in .NET 4.0. I can't find much documentation on it though, which is a little disconcerting. This blog post on the Silverlight version indicates validation may not check everything at once.

When validating an object, the following process is applied in Validator.ValidateObject:

  1. Validate property-level attributes
  2. If any validators are invalid, abort validation returning the failure(s)
  3. Validate the object-level attributes
  4. If any validators are invalid, abort validation returning the failure(s)
  5. If on the desktop framework and the object implements IValidatableObject, then call its Validate method and return any failure(s)

Hmm, partial validation. The user loves hitting their submit button multiple times. I would prefer all validation errors to be returned at once, so here's an alternative.

namespace StackingCode.Moja.ComponentModel.DataAnnotations
{
    public static class ObjectExtensions
    {
        public static bool IsValid(this object entity)
        {
            if (entity == null)
                throw new ArgumentNullException("entity");

            bool isValid = true;
            WorkTheAttributes(entity, (validationAttribute, value, displayName) => isValid &= validationAttribute.IsValid(value));

            return isValid;
        }

        public static void Validate(this object entity)
        {
            if (entity == null)
                throw new ArgumentNullException("entity");

            IList<ValidationException> validationExceptions = new List<ValidationException>();

            WorkTheAttributes(entity,
                (validationAttribute, value, displayName) =>
                {
                    try
                    {
                        validationAttribute.Validate(value, displayName);
                    }
                    catch (ValidationException validationException)
                    {
                        validationExceptions.Add(validationException);
                    }
                });

            if (validationExceptions.Count == 1)
                throw validationExceptions.First();

            if (validationExceptions.Count > 1)
                throw new AggregateException(validationExceptions);
        }

        // ...
    }
}

Here I've rolled my own IsValid and Validate methods. The first returns a boolean -- it's farming out to another method WorkTheAttributes, telling it to logically AND each attribute's IsValid method. The second throws an exception if something isn't valid -- it's farming out to the same method, telling it to collect all the exceptions from each attribute's Validate method, then bundling them up for it's own throw.

The WorkTheAttributes method looks like this...

namespace StackingCode.Moja.ComponentModel.DataAnnotations
{
    public static class ObjectExtensions
    {
        // ...

        private static void WorkTheAttributes(object entity, Action<ValidationAttribute, object, string> work)
        {
            Type entityType = entity.GetType();

            // Validation attributes can be defined in a metadata type.

            Type metadataType = entityType;
            MetadataTypeAttribute metadataTypeAttribute = entityType.GetCustomAttributes(false).OfType<MetadataTypeAttribute>().FirstOrDefault();

            if (metadataTypeAttribute != null)
                metadataType = metadataTypeAttribute.MetadataClassType;

            // Go to work on the class validation attributes.
            foreach (ValidationAttribute validationAttribute in metadataType.GetCustomAttributes(true).OfType<ValidationAttribute>())
                work(validationAttribute, entity, GetDisplayName(metadataType));

            // Go to work on the property validation attributes.
            foreach (PropertyInfo metadataProperty in metadataType.GetProperties())
                foreach (ValidationAttribute validationAttribute in metadataProperty.GetCustomAttributes(true).OfType<ValidationAttribute>())
                {
                    PropertyInfo entityProperty = entityType.GetProperty(metadataProperty.Name);
                    work(validationAttribute, entityProperty.GetValue(entity, null), GetDisplayName(metadataProperty));
                }
        }

        private static string GetDisplayName(MemberInfo metadataMemberInfo)
        {
            DisplayNameAttribute displayNameAttribute = metadataMemberInfo.GetCustomAttributes(true).OfType<DisplayNameAttribute>().FirstOrDefault();

            if (displayNameAttribute != null)
                return displayNameAttribute.DisplayName;

            return metadataMemberInfo.Name;
        }
    }
}

It takes into account that data annotations can be on a metadata class. It then looks at the class attributes, and then the attributes on all the properties. It also passes the display name, if it's defined, into the work method.

Don't Forget Spring

Including the new validation interceptor is simply a matter of adding it to Spring.

<object name="Session" factory-object="SessionFactory" factory-method="OpenSession" scope="request">
    <constructor-arg>
        <list>
            <ref object="StaleInterceptor" />
            <ref object="InvalidInterceptor" />
        </list>
    </constructor-arg>
</object>
<object name="StaleInterceptor" type="StackingCode.Moja.Repositories.NHibernate.Interceptors.StaleInterceptor, StackingCode.Moja.Repositories.NHibernate" scope="request" />
<object name="InvalidInterceptor" type="StackingCode.Moja.Repositories.NHibernate.Interceptors.InvalidInterceptor, StackingCode.Moja.Repositories.NHibernate" scope="request" />
<object name="SessionFactory" type="StackingCode.Moja.Repositories.NHibernate.SessionFactoryWrapper, StackingCode.Moja.Repositories.NHibernate" />

Validate Me

I'll test this out with a small modification to my profile action.

//TryUpdateModel(user, new[] { "Version", "DisplayName", "Email", "Website" });
user.Version = int.Parse(collection["Version"]);
user.DisplayName = collection["DisplayName"];
user.Email = collection["Email"];
user.Website = collection["Website"];

Putting in my crappy data, I get the following back from my service layer call.

Invalid

Great, now I can sleep at night knowing my application validation will always make a complete check of my validation rules whenever anything is saved. Hopefully this also has been a decent demonstration of how easy it is to hook any validation library up to NHibernate provided it has methods to do the validation grunt work for you.

There are 0 comments.


Comments

Leave a Comment

Please register or login to leave a comment.


Older
Optimistic Concurrency and NHibernate

Newer
Data Migration and Archive Screens

Older
Optimistic Concurrency and NHibernate

Newer
Data Migration and Archive Screens

browse with Pivot


About


Projects

Building Neno


RSS
Recent Posts

Codility Nitrogenium Challenge
OS X Lock
HACT '13
Codility Challenges
Priority Queue


Tags

Architecture (13)
ASP.NET (2)
ASP.NET MVC (13)
Brisbane Flood (1)
Building Neno (38)
C# (4)
Challenges (3)
Collections (1)
Communicator (1)
Concurrency Control (2)
Configuration (1)
CSS (5)
DataAnnotations (2)
Database (1)
DotNetOpenAuth (2)
Entity Framework (1)
FluentNHibernate (2)
Inversion of Control (5)
JavaScript (1)
jQuery (4)
Kata (2)
Linq (7)
Markdown (4)
Mercurial (5)
NHibernate (20)
Ninject (2)
OpenID (3)
OS X (1)
Pivot (6)
PowerShell (8)
Prettify (2)
RSS (1)
Spring (3)
SQL Server (5)
T-SQL (2)
Validation (2)
Vim (1)
Visual Studio (2)
Windows Forms (3)
Windows Service (1)


Archives


Powered by Neno, ASP.NET MVC, NHibernate, and small furry mammals. Copyright 2010 - 2011 Adam Boddington.
Version 1.0 Alpha (d9e7e4b68c07), Build Date Sunday, 30 January, 2011 @ 11:37 AM