Asp – xVal How to validate child properties of complex types

asp.net-mvcdata-annotationsvalidationxval

I'm using xVal in my ASP.NET MVC application, which is great in general. Following Steve Sanderson's blog post, I created a DataAnnotationsValidationRunner to do server-side validation of attributed objects. This works great for a simple class. e.g. Person:

public static class DataAnnotationsValidationRunner
{
    public static IEnumerable<ErrorInfo> GetErrors(object o)
    {
        return from prop in TypeDescriptor.GetProperties(o).Cast<PropertyDescriptor>()
               from attribute in prop.Attributes.OfType<ValidationAttribute>()
               where !attribute.IsValid(prop.GetValue(o))
               select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), o);
    }
}

public class Person
{
    [Required(ErrorMessage="Please enter your first name")]
    public string FirstName { get; set; }

    [Required(ErrorMessage = "Please enter your last name")]
    public string LastName { get; set; }
}

However, if I add an Address property to this person, and mark the Address class with DataAnnotation attributes, they will not be validated. e.g.

public class Person
{
    [Required(ErrorMessage="Please enter your first name")]
    public string FirstName { get; set; }

    [Required(ErrorMessage = "Please enter your last name")]
    public string LastName { get; set; }

    public Address Address { get; set; }
}

public class Address 
{
    [Required(ErrorMessage="Please enter a street address")]
    public string Street { get; set; }

    public string StreetLine2 { get; set; }

    [Required(ErrorMessage = "Please enter your city")]
    public string City { get; set; }

    [Required(ErrorMessage = "Please enter your state")]
    public string State { get; set; }

    [Required(ErrorMessage = "Please enter your zip code")]
    public string Zip { get; set; }

    public string Country { get; set; }
}

One problem is that the DataAnnotationValidationRunner doesn't walk down the complex child properties. Also, if those errors are added to an errors collection, they still need to get prefixed correctly when added to the model state. For example. The Person errors are added like this:

    catch (RulesException ex)
    {
        ex.AddModelStateErrors(ModelState, "person");
    }

I think the Address rules exceptions would need to be prefixed with "person.address". Is there a supported way of handling child object validation with xVal, or would creating a flattened data transfer object be the only solution?

Best Answer

First of all, you need to differ between the Steve Sanderson's DataAnnotationsModelBinder and

Regarding your first question ("DataAnnotationValidationRunner doesn't walk down the complex child properties"):

Are you perhaps referring to Brad Wilson's DataAnnotationModelBinder? If so, it really should validate complex ViewModels down to the last properties. If not, try using that instead of the DataAnnoationsModelRunner you are using. This blog article blog article on Client-Side Validation with xVal shows how.

The first version of the DataAnnotationModelBinder had a bug that would make it crash when used with complex viewmodels. Perhaps there is a new version out which fixes the crash but ignores complex models?

In any case, I'd suggest giving the version of the DataAnnotationModelBinder used in the demo project of the blog article linked above a try. I am using it in my own real-world project and it does work on complex viewmodels.

Regarding your second question "Is there a supported way of handling child object validation with xVal":

You haven't posted any code residing on ASPX forms, but you might also be referring to the fact that a <%= Html.ClientSideValidation()%> only adds client-side validation to immediate properties of that Model Type, but not properties of child objects. You can simply circumvent the problem by using multiple ClientSideValidation statements, for example:

<%= Html.ClientSideValidation<ModelType>()%>
<%= Html.ClientSideValidation<ChildModelType>("ChildModelPropertyName")%>
Related Topic