如何强制MVCvalidationIValidatableObject

看来,当MVCvalidation一个模型,它首先通过DataAnnotation属性(如需要,或范围)运行,如果其中任何一个失败,它跳过我的IValidatableObject模型上运行validation方法。

有没有办法让MVC继续运行该方法,即使其他validation失败?

您可以通过传递ValidationContext的新实例手动调用Validate(),如下所示:

[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); } } 

这种方法的一个警告是,在没有属性级(DataAnnotation)错误的情况下,validation将运行两次。 为了避免这种情况,你可以添加一个属性到你的模型中,比如说一个布尔型Validated,在你的Validate()方法中设置为true,然后在手动调用控制器中的方法之前检查。

所以在你的控制器里

 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); } 

在你的模型中:

 public bool Validated { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { // perform validation Validated = true; } 

有一种方法可以做到这一点,而不需要在每个控制器动作的顶部添加样板代码。

你需要用你自己的默认模型绑定器replace:

 protected void Application_Start() { // ... ModelBinderProviders.BinderProviders.Clear(); ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider()); // ... } 

您的模型联编程序提供程序如下所示

 public class CustomModelBinderProvider : IModelBinderProvider { public IModelBinder GetBinder(Type modelType) { return new CustomModelBinder(); } } 

现在创build一个实际强制validation的自定义模型联编程序。 这是完成繁重工作的地方:

 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); } } } } 

你也需要一个IndexOf扩展方法。 这是一个便宜的实施,但它会工作:

 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; }