views:

85

answers:

1

I'm using the module-level validator: 'PropertiesMustMatch' on my view-model, like so:

[PropertiesMustMatch("Password", "PasswordConfirm")]
public class HomeIndex
{
    [Required]
    public string Name { get; set; }

    public string Password { get; set; }

    public string PasswordConfirm { get; set; }
}

I'm noticing that if I submit the form without Name filled in, the ValidationSummary() helper returns only the following error:

  • The Name field is required.

However, if I fill in Name, then ValidationSummary() will return a PropertiesMustMatch error:

  • 'Password' and 'PasswordConfirm' do not match.

So it looks like the property-level validators are being evaluated first, then the model-level validators.

I would much prefer if they were all validated at once, and ValidationSummary would return:

  • The Name field is required.
  • 'Password' and 'PasswordConfirm' do not match.

Any ideas what I can do to fix this?

I'm studying the MVC 2 source-code to try to determine why this happens.

A: 

I found what's causing this, but my "solution" is probably going to break the normal processing of validators. Use with caution.

I found a conditional return statement in the OnModelUpdated function of the DefaultModelBinder:

protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
    IDataErrorInfo errorProvider = bindingContext.Model as IDataErrorInfo;
    if (errorProvider != null)
    {
        string errorText = errorProvider.Error;
        if (!String.IsNullOrEmpty(errorText))
        {
            bindingContext.ModelState.AddModelError(bindingContext.ModelName, errorText);
        }
    }

    // BEGIN CONDITION
    if (!IsModelValid(bindingContext))
    {
        return;
    }
    // END CONDITION

    foreach (ModelValidator validator in bindingContext.ModelMetadata.GetValidators(controllerContext))
    {
        foreach (ModelValidationResult validationResult in validator.Validate(null))
        {
            bindingContext.ModelState.AddModelError(CreateSubPropertyName(bindingContext.ModelName, validationResult.MemberName), validationResult.Message);
        }
    }
}

If I understand this code (which I might not) it seems that the MVC team intended model validators be skipped at this point.

I've made my own custom ModelBinder in which I re-run the code that would have been avoided by the condition:

public class CustomModelBinder : DefaultModelBinder
{
    protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        base.OnModelUpdated(controllerContext, bindingContext);

        foreach (ModelValidator validator in bindingContext.ModelMetadata.GetValidators(controllerContext))
        {
            foreach (ModelValidationResult validationResult in validator.Validate(null))
            {
                bindingContext.ModelState.AddModelError(CreateSubPropertyName(bindingContext.ModelName, validationResult.MemberName), validationResult.Message);
            }
        }
    }
}

This seems to fix the issue.

jonathanconway
Yup, as I suspected, inheriting this class creates issues: basically it skips every validator except [Required]. :(
jonathanconway
Fixed the above issue. For some weird reason I have to call 'base.OnModelUpdated', then it all works as usual.
jonathanconway