views:

679

answers:

2

In a recent question posed here: http://stackoverflow.com/questions/1558457/asp-net-mvc-is-data-annotation-validation-enough

...it was concluded that relying on data annotation validation (triggered by the model binder) was not enough to ensure that validation was always executed. We still need to add the same validation logic in the services layer (or somewhere else after ModelBinding happens). The unfortunately thing about this is that we will be duplicating our validation code (once with Data Annotations and again in the services layer). Is there an easy way for the services layer to trigger validation based on what's been defined in Data Annotations? If this can be possible, then we will get the best of both worlds...we won't need to repeat the validation code, but we'll still ensure that the validation always gets executed.

A: 

Haven't you checked my answer in your previous question?
I submitted some code that does automatic validation based on DataAnnotation attributes of DTOs. As long as your DTOs are used in your Controller Action's parameters, they will get picked up by this attribute and validated no matter what.

The only question is: how do you generate your DTOs?

  1. Do you write them yourself?
  2. Do you use EF or something similar?
  3. Do you auto generate them using some other technique (like T4)?

If you can control your DTO class generation, then you may as well add additiona interface to them. The code that I posted uses T4 over EF, xVal and DataAnnotation and custom interface that declares Validate() method that is implemented in each entity class.

Robert Koritnik
Thanks for your answer, Robert, but your solution still has a dependency on the controller action doing the model binding and then calling ModelState.IsValid. It looks to be a really nice solution and one that I may implement, but I'm also looking for an absolutely certain way for the validation to execute prior to persisting to the DB, and that has to be done in the Service (or Domain Model) layer. So I'm still hoping that there is an easy way to plug Data Annotation validation into my services layer.
JohnnyO
+2  A: 

With the help of this blog: http://goneale.com/2009/03/04/using-metadatatype-attribute-with-aspnet-mvc-xval-validation-framework/ I was able to create a method that will test my object based on validations defined by data annotations. It will execute any validation attribute that derives from ValidateAttribute. I can now pass my object to this method from my service layer (or DomainModel) and my service layer is no longer dependent on the controller. This will ensure that validation will always be executed prior to persisting data into the database. I couldn't use the code on the blog as is, as I don't seem to have access to some of the extension methods that Graham was using, so here's my version of it:

    public static IList<KeyValuePair<string, string>> GetErrors(object obj)
    {
        // get the name of the buddy class for obj
        MetadataTypeAttribute metadataAttrib = obj.GetType().GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault() as MetadataTypeAttribute;

        // if metadataAttrib is null, then obj doesn't have a buddy class, and in such a case, we'll work with the model class
        Type buddyClassOrModelClass = metadataAttrib != null ? metadataAttrib.MetadataClassType : obj.GetType();

        var buddyClassProperties = TypeDescriptor.GetProperties(buddyClassOrModelClass).Cast<PropertyDescriptor>();
        var modelClassProperties = TypeDescriptor.GetProperties(obj.GetType()).Cast<PropertyDescriptor>();

        var errors = from buddyProp in buddyClassProperties
                           join modelProp in modelClassProperties on buddyProp.Name equals modelProp.Name // as this is an inner join, it will return only the properties that are in both the buddy and model classes
                           from attribute in buddyProp.Attributes.OfType<ValidationAttribute>() // get only the attributes of type ValidationAttribute
                           where !attribute.IsValid(modelProp.GetValue(obj))
                           select new KeyValuePair<string, string>(buddyProp.Name, attribute.FormatErrorMessage(string.Empty));

        return errors.ToList();
    }

This code works with both classes that do and don't have buddy classes, although if you don't use buddy classes, this code can be simplified a bit. I hope you find this useful.

JohnnyO
Nice stuff. I also saw the .Net 4.0 has Validator Class in System.ComponentModel.DataAnnotations. That may also be useful in a service layer in the future.
Jon Kragh