c# - 当使用TryUpdateModel绑定(bind)时,MVC ValidationSummary会忽略模型级别的验证错误

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

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

我不确定那个旧主题是否与MVC的早期版本有关,但是在MVC3中,我遇到了类似的奇怪现象。

我有一个称为“贸易”的模型课。这是从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;
  }

我们有一个 View 模型来帮助显示交易。它包含通过TradeModel属性对交易模型的引用。因此,基本上, View 模型是交易模型,再加上一些有关下拉列表(如交易对手)等附加信息。

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

如果我实现我的HttpPost Controller 方法来创建新的交易,如下所示...
  [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必须是属性验证错误,它会忽略我们的验证错误,因为我们有一个 View 模型,其中包含对模型的引用,因此,导致 key 为“TradeModel”。

令这一理论震惊的是:如果我按如下方式重写 Controller 的Create函数...
  [HttpPost]
  public ActionResult Create(Trade trade, FormCollection collection)
  {
     if (this.SaveChanges(this.ModelState, trade))
     {
        return this.RedirectToAction("Index");
     }

     return this.View(trade);
  }

...因此依赖于MVC“自动”执行绑定(bind),然后重新运行相同的测试方案,从而向用户显示预期的错误消息!

如果我添加一个断点并查看ModelState,我将看到与以前相同的针对相同键的错误消息,但是这次ValidationSummary会选择它们!

如果我按以下方式修改验证,则它可以与以任何一种方式编写的 Controller 的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;
  }

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

任何帮助,将不胜感激!有一些原因(我现在不再讨论)为什么我们需要手动创建 View 模型的实例并使用TryUpdateModel调用绑定(bind)。

提前致谢!

更新

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

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

如果 View 模型包含对业务模型的引用,则通过IValidatableObject添加到ModelState的任何错误都将添加一个键,该键包含等于 View 模型中的业务模型属性名称的前缀(在我们的示例中为“TradeModel”),从而导致键,例如“TradeModel.CounterpartyId”等。使用等于 View 模型的业务模型属性名称的键(“TradeModel”)添加模型级错误。

因此,如果以这种方式构造 View 模型,则企业似乎无法添加模型级验证错误。

令我感到困惑的是,为什么当编写 Controller 的Create函数,使其以Trade view模型对象作为参数时,这种方法在我们的“真实”项目中确实起作用。我昨天一定错过了,但是今天看一下,当MVC绑定(bind)并触发验证时,它似乎在字典的末尾添加了一个额外的键,其值为String.Empty。其中包含与TradeModel关键字一起添加的错误的副本。如您所料,ValidationSummary然后将它们接起来!

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

我已经看到在编写 Controller 函数以将 View 模型作为参数时触发了两次验证。也许这每次都在做一些细微的不同?

更新...再次

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

我现在了解发生了什么以及为什么ValidationSummary表现为这种方式。

最佳答案

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

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

如果您有一个可以公开您的业务模型的 View 模型,则类似于以下内容:

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中,并带有属性名称的前缀 View 模型的示例-在此示例中为“TradeModel”。

目前,我们正在考虑几种解决方法。
  • 不要在 View 模型之外公开业务模型。这实质上涉及到绑定(bind)到 View 模型,而不是绑定(bind)到 View 模型的业务模型。这可以通过两种方式完成:使 View 模型成为业务模型的子类;使 View 模型成为业务模型的子类。或将属性从业务模型复制到 View 模型。我更喜欢前者。
  • 编写代码以将模型级错误复制到String.Empty的新ModelState字典键中。不幸的是,这可能无法做到优雅,因为公开业务模型的 View 模型的属性名称是用作键的。每个 Controller / View 模型可能有所不同。
  • 在 View 中使用以下内容。这将显示针对业务模型的错误消息。从本质上讲,这假装业务模型的模型级错误实际上是属性错误。这些显示与ValidationSummary不同,但是也许可以使用CSS来解决:

    @ Html.ValidationMessageFor(m => m.TradeModel)
  • 子类ValidationSummary。这将涉及更改它,以便它知道ModelState中的哪些键引用 View 模型的业务模型属性。
  • 关于c# - 当使用TryUpdateModel绑定(bind)时,MVC ValidationSummary会忽略模型级别的验证错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11331303/

    相关文章:

    c# - 使用 REST 和 HTTPClient 在 SP2013 中创建文件夹

    c# - 在C#中将项目添加到字符串数组列表

    asp.net - Linq to SQL,仅返回特定角色的用户

    c# - 如果 users.Claims 包含等于参数的声明 (LINQ),则为 linq 语句

    c# - 从最后删除字符串

    c# - WordPress WooCommerce ASP.net API WebHookHandler : The WebHook request must contain an entity body formatted as HTML Form Data

    html - 如何将文本放在来自数据库的图片上?

    asp.net-mvc - ASP.NET MVC : Custom Html Helpers in Razor

    asp.net - 将 MVC 添加到 ASP.NET 网站

    asp.net-mvc-3 - 为什么 @Html.ActionLink ("Home", "Index", "home") 生成 <a href ="\">Home</a>