Validation 如何强制MVC验证IValidatableObject
看起来,当MVC验证一个模型时,它首先通过DataAnnotation属性(如required或range)运行,如果其中任何属性失败,它将跳过在我的IValidatableObject模型上运行Validate方法Validation 如何强制MVC验证IValidatableObject,validation,asp.net-mvc-3,Validation,Asp.net Mvc 3,看起来,当MVC验证一个模型时,它首先通过DataAnnotation属性(如required或range)运行,如果其中任何属性失败,它将跳过在我的IValidatableObject模型上运行Validate方法 有没有办法让MVC继续运行该方法,即使其他验证失败?您可以通过传入ValidationContext的新实例手动调用Validate(),如下所示: [HttpPost] public ActionResult Create(Model model) { if (!Mode
有没有办法让MVC继续运行该方法,即使其他验证失败?您可以通过传入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)错误的实例中,验证将运行两次。为了避免这种情况,您可以向模型中添加一个属性,比如一个boolean 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;
}
public bool验证{get;set;}
公共IEnumerable验证(ValidationContext ValidationContext){
//执行验证
已验证=正确;
}
有一种方法可以做到这一点,而无需在每个控制器操作的顶部添加样板代码
您需要将默认模型活页夹替换为您自己的:
protected void Application_Start()
{
// ...
ModelBinderProviders.BinderProviders.Clear();
ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider());
// ...
}
您的模型绑定器提供程序如下所示:
public class CustomModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(Type modelType)
{
return new CustomModelBinder();
}
}
现在,创建一个自定义模型绑定器,该绑定器实际上强制进行验证。这是完成重物搬运的地方:
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);
}
}
}
}
公共类CustomModelBinder:DefaultModelBinder
{
模型更新时受保护的覆盖无效(ControllerContext ControllerContext、ModelBindingContext bindingContext)
{
base.OnModelUpdated(controllerContext、bindingContext);
ForceModelValidation(bindingContext);
}
私有静态void ForceModelValidation(ModelBindingContext bindingContext)
{
var model=bindingContext.model作为IValidatableObject;
if(model==null)返回;
var modelState=bindingContext.modelState;
var errors=model.Validate(新的ValidationContext(model,null,null));
foreach(错误中的var错误)
{
foreach(变量memberName出错。memberName)
{
//仅添加尚未添加的错误。
//(如果多次调用模型的Validate(…)方法,则可能会发生这种情况,这将在
//没有属性级验证失败。)
var memberNameClone=memberName;
var idx=modelState.Keys.IndexOf(k=>k==memberNameClone);
如果(idx<0)继续;
如果(modelState.Values.ToArray()[idx].Errors.Any())继续;
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;
}
public static int IndexOf(此IEnumerable源,Func谓词)
{
如果(source==null)抛出新的ArgumentNullException(“source”);
如果(谓词==null)抛出新的ArgumentNullException(“谓词”);
var i=0;
foreach(源中的var项)
{
if(谓语(项))返回i;
i++;
}
返回-1;
}
老实说,我开始喜欢这种默认行为了。如果您在Validate方法中执行业务级验证,该方法涉及昂贵的东西,如数据库连接,那么最好不要调用它们,除非模型有效。谢谢,这对我帮助很大。我认为此解决方案仅适用于DefaultModelBinder感谢它的工作。但我有一个问题,这有什么原因吗,因为Ivalidate物体在开火。有时可能不是吗?