这是一个与此处已发布的问题非常相似的问题:ASP.NET MVC:在 TryUpdateModel 中设置的验证消息未显示 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 控制器方法来创建一个新的 Trade 如下......
[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 函数被编写为它以 Trade 视图模型对象作为参数时,这在我们的“真实”项目中确实有效。我昨天一定错过了这个,但是今天看它,当 MVC 绑定并触发验证时,它似乎在字典的末尾添加了一个额外的键,其值为 String.Empty。这包含使用 TradeModel 键添加的错误的副本。如您所料,ValidationSummary 会选择它们!
那么为什么 MVC 在我们的 live 项目中这样做,而不是在简单的测试应用程序中呢?
当编写控制器函数以将视图模型作为参数时,我已经看到两次触发验证。也许这每次都在做一些微妙的不同?
更新...再次
它在我们的实际项目中起作用的原因是在我们的基本控制器中隐藏了一些代码(所有其他控制器都从中继承),它将模型状态中发现的所有错误复制到具有 String.Empty 键的新条目中。此代码仅在两种情况之一中被调用。因此,真实应用程序和测试应用程序之间没有实际区别。
我现在明白发生了什么以及为什么 ValidationSummary 会这样。