【问题标题】:MVC ValidationSummary ignores model level validation errors when bound using TryUpdateModel使用 TryUpdateModel 绑定时,MVC ValidationSummary 会忽略模型级别的验证错误
【发布时间】:2012-07-05 02:07:33
【问题描述】:

这是一个与已在此处发布的问题非常相似的问题:ASP.NET MVC: Validation messages set in TryUpdateModel not showning ValidationSummary

我不确定那个旧主题是否引用了早期版本的 MVC,但在 MVC3 中,我遇到了一些类似的奇怪行为。

我有一个名为 Trade 的模型类。这继承自 IValidatableObject,因此实现了一个 Validate 方法。在此我们对整个模型进行了一些验证(与强制验证属性的数据注释相反)。验证如下:

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
  {
     var validationResults = new List<ValidationResult>();

     if (this.EndDate < this.StartDate)
     {
        validationResults.Add(new ValidationResult("End date must be greater than start date"));
     }

     return validationResults;
  }

我们有一个视图模型来帮助显示交易。这包含通过 TradeModel 属性对贸易模型的引用。所以基本上视图模型是一个贸易模型,加上一些额外的信息,用于下拉列表的人口,如交易对手等。

我们的 CSHTML 类包含一个 ValidationSummary,以“true”作为参数,这意味着它只会显示模型错误。

如果我按如下方式实现用于创建新交易的 HttpPost 控制器方法...

  [HttpPost]
  public ActionResult Create(FormCollection collection)
  {
     var trade = new Trade();

     if (this.TryUpdateModel(trade))
     {
        if (this.SaveChanges(this.ModelState, trade))
        {
           return this.RedirectToAction("Index");
        }
     }

     return this.View(trade);
  }

...当我使用 StartDate > EndDate 输入交易时,我发现 TryUpdateModel 返回 false 并且用户被引导回他们的交易。这似乎合乎逻辑。不幸的是,ValidationSummary 没有显示任何错误消息。

如果我在 Create 方法中放置一个断点并调查 ModelState,我可以看到字典中有一条错误消息。它是针对“TradeModel”的键,而不是针对任何属性。同样,这似乎是合乎逻辑的。

关于为什么会这样的一个理论是 ValidationSummary 假定针对非 String.Empty 键的任何验证错误都必须是属性验证错误,它忽略了我们的验证错误,因为我们有一个包含对一个模型,因此导致 Key 为“TradeModel”。

让这个理论脱颖而出的是:如果我重写控制器的 Create 函数如下......

  [HttpPost]
  public ActionResult Create(Trade trade, FormCollection collection)
  {
     if (this.SaveChanges(this.ModelState, trade))
     {
        return this.RedirectToAction("Index");
     }

     return this.View(trade);
  }

...因此依赖MVC“自动”执行绑定,并重新运行相同的测试场景,用户会看到预期的错误消息!

如果我添加断点并查看 ModelState,我会看到针对相同键的相同错误消息,但这次 ValidationSummary 将它们拾取!

如果我按如下方式修改验证,那么它可以与以任何一种方式编写的控制器的 Create 函数一起使用:

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
  {
     var validationResults = new List<ValidationResult>();

     if (this.EndDate < this.StartDate)
     {
        validationResults.Add(new ValidationResult("End date must be greater than start date", new[] { "StartDate" }));
     }

     return validationResults;
  }

很明显,这只是模型级别验证错误的问题。

对此的任何帮助将不胜感激!我们需要手动创建视图模型的实例并使用 TryUpdateModel 调用绑定是有原因的(我现在不再赘述)。

提前致谢!

更新

看起来 ValidationSummary 的理论只在 String.Empty 的 ModelState 中显示错误时,被告知排除属性错误实际上是正确的。我查看了源代码,它实际上使用 ViewData.TemplateInfo.HtmlFieldPrefix 来查找模型级别的验证错误。默认情况下这是 String.Empty。

因此,将此值更改为“TradeModel”似乎是合乎逻辑的,但它会导致每个 HTML id 或名称都有前缀,因此绑定会失败!

如果视图模型包含对业务模型的引用,则由 IValidatableObject 添加到 ModelState 的任何错误都将添加一个键,该键包含与视图模型中的业务模型属性名称相同的前缀(在我们的示例中为“TradeModel”) ,从而产生诸如“TradeModel.CounterpartyId”等键。模型级错误被添加一个与视图模型的业务模型属性名称(“TradeModel”)相等的键。

因此,如果视图模型以这种方式构建,业务似乎无法添加模型级别的验证错误。

让我感到困惑的是,为什么当控制器的 Create 函数被编写为它将贸易视图模型对象作为参数时,这在我们的“真实”项目中确实有效。我昨天一定错过了这个,但是今天看它,当 MVC 绑定并触发验证时,它似乎在字典的末尾添加了一个额外的键,其值为 String.Empty。这包含使用 TradeModel 键添加的错误的副本。如您所料,ValidationSummary 会提取它们!

那么为什么 MVC 在我们的实时项目中这样做,而不是在简单的测试应用程序中呢?

我已经看到当控制器函数被编写为将视图模型作为参数时触发了两次验证。也许这每次都在做一些微妙的不同?

更新...再次

它在我们的实际项目中起作用的原因是在我们的基本控制器中隐藏了一些代码(所有其他控制器都从该控制器继承),它将模型状态中发现的所有错误复制到具有 String.Empty 键的新条目中。此代码仅在两种情况之一中被调用。所以实际应用和测试应用之间没有实际区别。

我现在明白发生了什么以及为什么 ValidationSummary 会这样。

【问题讨论】:

  • 我试图重现您的问题,但没有成功。对我来说,在一个新的 MVC3 项目中,您的 第二个操作 public ActionResult Create(Trade trade, FormCollection collection)不显示 预期的验证消息。您可以自己在一个空项目中创建一个复制品来验证行为,然后上传到某个地方吗?
  • 有趣。感谢您尝试复制它。我将按照您的建议在新项目中创建一个简单的示例。
  • 我创建了一个简单的项目,正如你所说,它不适用于控制器的 Create 函数的任何一个版本。

标签: c# asp.net asp.net-mvc-3 validationsummary


【解决方案1】:

好的。我现在可以自己回答这个问题了。

如果在 ExcludePropertyErrors 参数设置为 True 的情况下使用 ValidationSummary,它将使用等于 ViewData.TemplateInfo.HtmlFieldPrefix 的键来查找模型状态中的错误。默认情况下这是 String.Empty。

如果您有一个公开您的业务模型的视图模型,如下所示:

namespace ValidationSummary.Models
{
   using System;
   using System.Collections.Generic;
   using System.ComponentModel.DataAnnotations;

   public class TradeModel : IValidatableObject
   {
      public DateTime StartDate { get; set; }

      public DateTime EndDate { get; set; }

      public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
      {
         List<ValidationResult> validationResults = new List<ValidationResult>();

         if (EndDate < StartDate)
         {
            validationResults.Add(new ValidationResult("End date must not be before start date"));
         }

         return validationResults;
      }
   }
}

namespace ValidationSummary.ViewModels
{
   public class Trade
   {
      public Trade()
      {
         this.TradeModel = new Models.TradeModel();
      }

      public Models.TradeModel TradeModel { get; private set; }
   }
}

当验证发生时,在模型级别添加的错误 (ValidationResults)(其中没有进一步的参数到采用属性名称的 ValidationResult 构造函数,将添加到带有前缀的 ModelState视图模型的属性名称 - 在本例中为“TradeModel”。

我们目前正在考虑解决这个问题的几种方法。

  1. 不要将业务模型暴露在视图模型之外。这实质上涉及绑定到视图模型,而不是绑定到视图模型的业务模型。这可以通过两种方式完成:使视图模型成为业务模型的子类;或将业务模型中的属性复制到视图模型。我更喜欢前者。
  2. 编写代码将模型级错误复制到 String.Empty 的新 ModelState 字典键中。不幸的是,这可能无法优雅地完成,因为暴露业务模型的视图模型的属性名称被用作键。这可能因控制器/视图模型而异。
  3. 在视图中使用以下内容。这将显示针对业务模型的错误消息。本质上,这是假装业务模型的模型级错误实际上是属性错误。这些的显示与 ValidationSummary 不同,但也许这可以用 CSS 解决:

    @Html.ValidationMessageFor(m => m.TradeModel)

  4. 子类验证摘要。这将涉及对其进行更改,以便它知道 ModelState 中的哪些键引用视图模型的业务模型属性。

【讨论】:

    猜你喜欢
    • 2010-11-24
    • 1970-01-01
    • 1970-01-01
    • 2013-08-02
    • 2011-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-19
    相关资源
    最近更新 更多