Saturday, April 30, 2011

MVC3 Validation Oddities

I had created a post to demonstrate an example for how to do validation in MVC 3. I worked off of an assumption that is now proving to be false.

1) All validation attributes are run, even if one of them fails.

My initial assumption was that if one of the validation attributes were to fail (say Required) that no other attributes would run. In my post, I didn't add a null check to my custom validator based on this assumption. In fact, according to the debugger, all validation attributes are run.

I decided to play around with this a bit to see what the logic was behind this. My colleague mentioned that when you validate, you would probably want to get all the errors back at once, as opposed to only one. This made sense at the time, but was still a little big confusing. Obviously, if you enter a null value for something like username, you probably only want the one error message (User name is required). I can understand where my colleague is coming from , however. Lets say that the username is not null. You would probably want an error message if the length is too small, or doesn't contain the correct characters.

The first thing I tried was to add two custom validators, and see what would happen. It turns out that although both attributes are run, only the error message from one is actually displayed.

[Address]
        [Address2]
        public string Address { get; set; }

It seems (at least for me) that Address2 was the error message that always came up. It did not matter on ordering. I found that one of the methods you can override is IsDefaultAttribute. I found that even setting that didn't change which message gets sent back to the client.


public class AddressAttribute : ValidationAttribute
    {
        public AddressAttribute()
        {
            ErrorMessage = "Address attribute failed";
        }

        public override bool IsDefaultAttribute()
        {
            return true;
        }

        public override bool IsValid(object value)
        {
           
            var address = value as string;

            return false;
        }
    }

I even tried this using the built in validators that come with MVC2. Same result, only one error message made it back to the form.

I know for a fact that when client side validation is turned on, you seem to get all the error messages displayed back. With that being said, it is interesting that you don't get all the error messages displayed if you are just doing simple, no js dependent, error checking. What is also interesting, is that you have to do things like a null check in all of your custom validators, otherwise they will blow up.

I decided as one last final step, to go take a look at some of the source code to see if I could make sense of all of this. Without resharper (or a a full VS at home) it was pretty hard to piece things together. What I did find was the RegularExpressionValidator in Sys.Mvc.

2) Included Validators seem to be dependent on each other.

I'm not sure where this is used, but the implementation of this seems to depend on the the RequiredValidator. A quick test of this yields that a null will pass through the regular expression attribute no problem!

namespace Sys.Mvc {
    using System;

    public sealed class RegularExpressionValidator {

        private readonly string _pattern;

        public RegularExpressionValidator(string pattern) {
            _pattern = pattern;
        }

        public static Validator Create(JsonValidationRule rule) {
            string pattern = (string)rule.ValidationParameters["pattern"];
            return new RegularExpressionValidator(pattern).Validate;
        }

        public object Validate(string value, ValidationContext context) {
            if (ValidationUtil.StringIsNullOrEmpty(value)) {
                return true; // let the RequiredValidator handle this case
            }

            RegularExpression regExp = new RegularExpression(_pattern);
            string[] matches = regExp.Exec(value);
            return (!ValidationUtil.ArrayIsNullOrEmpty(matches) && matches[0].Length == value.Length);
        }

    }
}

I'm sorry this post was a little bit of a ramble, I guess I am just confused on the implementation of validation in MVC 3.