使用TryUpdateModel绑定时,MVC ValidationSummary忽略模型级验证错误

时间:2012-07-04 14:45:55

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

这与已在此处发布的问题非常相似: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,&#34; true&#34;作为参数,意味着它只会显示模型错误。

如果我按照以下方式实施我的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&gt;进行交易时EndDate,我发现TryUpdateModel返回false并且用户被定向回他们的交易。这看似合乎逻辑。不幸的是,ValidationSummary没有显示任何错误消息。

如果我在Create方法中放置断点并调查ModelState,我可以看到字典中有错误消息。这是针对&#34; TradeModel&#34;的密钥,而不是针对任何属性。再次,这似乎是合乎逻辑的。

关于为什么会这样做的一个理论是ValidationSummary假定针对非String.Empty的密钥的任何验证错误必须是属性验证错误,它忽略了我们的验证错误,因为我们有一个视图模型包含对模型的引用,因此导致Key为&#34; TradeModel&#34;。

这个理论脱离水的原因是:如果我重写控制器的创建功能如下......

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

     return this.View(trade);
  }

...因此依赖于MVC自动执行绑定&#34;并重新运行相同的测试场景,向用户显示预期的错误消息!

如果我添加一个断点并查看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。

将此值更改为&#34; TradeModel&#34;因此看似合乎逻辑,但它会导致每个HTML id或名称都带有前缀,因此绑定失败!

如果视图模型包含对业务模型的引用,则IValidatableObject添加到ModelState的任何错误都会添加一个键,该键包含一个前缀,该前缀等于视图模型中的业务模型属性名称(在我们的示例中为&#34; TradeModel&#34;),产生诸如&#34; TradeModel.CounterpartyId&#34;等的密钥。模型级错误添加了一个等于视图模型的业务模型属性名称的键(&#34; TradeModel& #34;。)

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

让我感到困惑的是,为什么这确实在我们的&#34;真实&#34;在编写控制器的Create函数时进行项目,以便将Trade视图模型对象作为参数。我一定错过了昨天,但今天看看它,当MVC绑定并且触发验证时,它似乎在字典末尾添加了一个额外的键,其值为String.Empty。这包含使用TradeModel的键添加的错误的副本。正如您所期望的那样,ValidationSummary然后选择它们!

那么为什么MVC会在我们的实时项目中执行此操作,而不是在简单的测试应用程序中执行此操作?

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

UPDATE ... AGAIN

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

我现在明白了进入的内容以及为什么ValidationSummary的行为如此。

1 个答案:

答案 0 :(得分:5)

确定。我现在正处于可以自己回答的地步。

如果将ValidationSummary与ExcludePropertyErrors参数设置为True一起使用,它将使用等于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 =&gt; m.TradeModel)

  4. Subclass ValidationSummary。这将涉及更改它,以便它知道ModelState中的哪些键引用视图模型的业务模型属性。