MVC 3 Model Validation Issue - Oversight or By Des

2020-06-01 05:53发布

问题:

I ran into a scenario where I needed to know which property was currently being validated in a custom ValidationAttribute. I assumed this would be easy in MVC 3 since the ValidationContext is being passed into the IsValid method.

Without going into detail, here is the basic idea:

protected override ValidationResult IsValid(Object value, ValidationContext validationContext) {

   if (ShouldICareAboutYou(validationContext.MemberName))
   {
       //Do some stuff
   }

   //Return the results
}

This seemed like the perfect solution, and indeed, when unit testing my custom ValidationAttribute using Validator.TryValidateObject everything worked beautifully!

HOWEVER...

When calling TryUpdateModel, or TryValidateModel within my controller, the validation runs, but ValidationContext.MemberName is null.

Whaa Huh?!?

I did a little investigation and sure enough, right there inside of DataAnnotationsModelValidator is the code... or lack thereof.

public override IEnumerable<ModelValidationResult> Validate(object container) {
    // Per the WCF RIA Services team, instance can never be null (if you have
    // no parent, you pass yourself for the "instance" parameter).
    ValidationContext context = new ValidationContext(container ?? Metadata.Model, null, null); 
    context.DisplayName = Metadata.GetDisplayName();

    // Setting the MemberName here would be trivial!
    // However, philh told me not to. Something about
    // a guy named Josh who pushed him down on the playground
    // in middle school.

    //context.MemberName = Metadata.PropertyName; (Suck It, Josh!!!)

    ValidationResult result = Attribute.GetValidationResult(Metadata.Model, context); 
    if (result != ValidationResult.Success) {
        yield return new ModelValidationResult { 
            Message = result.ErrorMessage
        };
    }
} 

I realize that DisplayName could be the property name if no DisplayAttribute has been applied to the property. Unfortunately I can't really deal in hypotheticals. I need to know exactly what the property name is.

So what is the deal? Is this by design or an honest oversight. If it is an oversight, then it would be awesome to get this fixed in MVC 4 :)

Disclaimer:

The added comments in the code sample above are meant to be funny. I do not know, nor have I ever met Phil Haack. From what I can tell he seems like a really nice guy. And pushing him down in middle school would have made me a royal douche!

回答1:

I had this same problem and decided to pass in the property name as a parameter in the attribute constructor and then store it in the attribute. For example:

[MyValidationAttribute("MyProperty")]
public string MyProperty { get; set; }

Then in MyValidationAttribute.cs:

public class MyValidationAttribute
{
    private string PropertyName;

    public MyValidationAttribute(string propertyName)
    {
        this.PropertyName = propertyName;
    }
}

It is a little annoying that now I have to type the name of my property twice but it solves the problem.



回答2:

Josh,

Frustrating, yes.

However, for your purposes, you can create your own class inheriting from DataAnnotationsModelValidator, override the Validate() method, and uncomment that line of code that is taunting you. Then, in Global.asax.cs, clear ModelValidatorProviders.Providers and add your class.

Not an optimal solution, but one that will get you to where you need to go.



回答3:

You need to call DataAnnotationsModelValidatorProvider.RegisterAdapter or DataAnnotationsModelValidatorProvider.RegisterAdapterFactory method for your attribute type and providing your custom ModelValidator.



回答4:

Had the same issue and this question set me on the right tracks. I fixed it by changing my custom validator to populate MemberName when ValidationResult is created, like so (note the second parameter in ValidationResult constructor):

protected override ValidationResult IsValid(Object value, ValidationContext     validationContext) {

   if (ShouldICareAboutYou(validationContext.MemberName))
   {
       //Do some stuff
   }

   return new ValidationResult(FormatErrorMessage(validationContext.DisplayName), new string[] { validationContext.MemberName });
}