【问题标题】:How to force MVC to Validate IValidatableObject如何强制 MVC 验证 IValidatableObject
【发布时间】:2011-06-21 20:19:57
【问题描述】:

似乎当 MVC 验证模型时,它首先通过 DataAnnotation 属性(如必需或范围)运行,如果其中任何一个失败,它会跳过在我的 IValidatableObject 模型上运行 Validate 方法。

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

【问题讨论】:

  • 老实说,我开始喜欢这种默认行为。如果您在 Validate 方法中进行业务级验证,其中涉及数据库连接等昂贵的东西,那么除非模型有效,否则最好不要调用它们。

标签: validation asp.net-mvc-3


【解决方案1】:

您可以通过传入 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)错误的情况下,验证将运行两次。为避免这种情况,您可以向模型添加一个属性,例如一个布尔值 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;
}

【讨论】:

  • 感谢它的工作。但我有一个问题。因为 Ivalidate Object 正在触发,是否有任何原因。有时它可能不会?
【解决方案2】:

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

您需要将默认模型绑定器替换为您自己的模型绑定器:

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

您还需要一个 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;
}

【讨论】:

  • 谢谢,这对我帮助很大。
  • 我认为这个解决方案只适用于 DefaultModelBinder
猜你喜欢
  • 2019-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-18
  • 2012-01-29
  • 2012-08-26
  • 1970-01-01
相关资源
最近更新 更多