It seems that when MVC validates a Model that it runs through the DataAnnotation attributes (开发者_StackOverflowlike required, or range) first and if any of those fail it skips running the Validate method on my IValidatableObject model.
Is there a way to have MVC go ahead and run that method even if the other validation fails?
You can manually call Validate() by passing in a new instance of ValidationContext, like so:
[HttpPost]
public ActionResult Create(Model model) {
    if (!ModelState.IsValid) {
        var errors = model.Validate(new ValidationContext(model, null, null));
        foreach (var error in errors)                                 
            foreach (var memberName in error.MemberNames)
                ModelState.AddModelError(memberName, error.ErrorMessage);
        return View(post);
    }
}
A caveat of this approach is that in instances where there are no property-level (DataAnnotation) errors, the validation will be run twice. To avoid that, you could add a property to your model, say a boolean Validated, which you set to true in your Validate() method once it runs and then check before manually calling the method in your controller.
So in your controller:
if (!ModelState.IsValid) {
    if (!model.Validated) {
        var validationResults = model.Validate(new ValidationContext(model, null, null));
        foreach (var error in validationResults)
            foreach (var memberName in error.MemberNames)
                ModelState.AddModelError(memberName, error.ErrorMessage);
    }
    return View(post);
}
And in your model:
public bool Validated { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
    // perform validation
    Validated = true;
}
There's a way to do it without requiring boilerplate code at the top of each controller action.
You'll need to replace the default model binder with one of your own:
protected void Application_Start()
{
    // ...
    ModelBinderProviders.BinderProviders.Clear();
    ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider());
    // ...
}
Your model binder provider looks like this:
public class CustomModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(Type modelType)
    {
        return new CustomModelBinder();
    }
}
Now create a custom model binder that actually forces the validation. This is where the heavy lifting's done:
public class CustomModelBinder : DefaultModelBinder
{
    protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        base.OnModelUpdated(controllerContext, bindingContext);
        ForceModelValidation(bindingContext);
    }
    private static void ForceModelValidation(ModelBindingContext bindingContext)
    {
        var model = bindingContext.Model as IValidatableObject;
        if (model == null) return;
        var modelState = bindingContext.ModelState;
        var errors = model.Validate(new ValidationContext(model, null, null));
        foreach (var error in errors)
        {
            foreach (var memberName in error.MemberNames)
            {
                // Only add errors that haven't already been added.
                // (This can happen if the model's Validate(...) method is called more than once, which will happen when
                // there are no property-level validation failures.)
                var memberNameClone = memberName;
                var idx = modelState.Keys.IndexOf(k => k == memberNameClone);
                if (idx < 0) continue;
                if (modelState.Values.ToArray()[idx].Errors.Any()) continue;
                modelState.AddModelError(memberName, error.ErrorMessage);
            }
        }
    }
}
You'll need an IndexOf extension method, too. This is a cheap implementation but it'll work:
public static int IndexOf<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
    if (source == null) throw new ArgumentNullException("source");
    if (predicate == null) throw new ArgumentNullException("predicate");
    var i = 0;
    foreach (var item in source)
    {
        if (predicate(item)) return i;
        i++;
    }
    return -1;
}
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
 加载中,请稍侯......
      
精彩评论