【问题标题】:MVC3 Master-Details Validation not DisplayingMVC3 主从验证不显示
【发布时间】:2012-10-09 00:38:25
【问题描述】:

我有一个 MVC3 页面,其中包含一个对象(标题),其中包含我想在单个页面上更新的数据和对象列表(详细信息)。在详细信息对象上,我有自定义验证(IValidatableObject)也需要运行。

这似乎通常按预期工作,验证正在运行并返回 ValidationResults,如果我输入了 @Html.ValidationSummary(false);在页面上显示这些验证。但是,我不希望在顶部列出验证列表,而是在正在验证的项目旁边,即页面上的 Html.ValidationMessageFor ,但不显示相关消息。有什么我想念的吗?这适用于其他页面(没有这种 Master-Details 情况),所以我认为这与我将如何设置要更新的项目列表或项目的编辑器模板有关?

Edit.cshtml(Header-Details 编辑视图)

@foreach (var d in Model.Details.OrderBy(d => d.DetailId))
{
   @Html.EditorFor(item => d, "Detail")
}

Detail.ascx(详细信息编辑器模板)

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Detail>" %>

<tr>            
    <td>
        <%= Model.Name %>
        <%= Html.HiddenFor(model => model.DetailId) %>
    </td>
    <td class="colDescription">
        <%= Html.EditorFor(model => model.Description) %>
        <%= Html.ValidationMessageFor(model => model.Description) %>
    </td>
    <td class="colAmount">
        <%= Html.EditorFor(model => model.Amount) %>
        <%= Html.ValidationMessageFor(model => model.Amount) %>
    </td>
</tr>

Model 是实体框架,Header 包含 Name 和 HeaderId,Detail 包含 DetailId、HeaderId、Description 和 Amount

控制器代码:

public ActionResult Edit(Header header, FormCollection formCollection)
{
   if (formCollection["saveButton"] != null)
   {
      header = this.ProcessFormCollectionHeader(header, formCollection);
      if (ModelState.IsValid)
      {
         return new RedirectResult("~/saveNotification");
      }
      else
      {
         return View("Edit", header);
      }
   }
   else
   {
      return View("Edit", header);
   }
}

[我知道控制器代码可以稍微清理一下,只是在这种状态下尝试确定这里发生了什么]

IValidatableObject 实现:

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
   if (this.Name.Length < 5) && (this.Amount > 10))
   {
      yield return new ValidationResult("Item must have sensible name to have Amount larger than 10.", new[] { "Amount" });
   }
}

【问题讨论】:

  • 你说它适用于 ValidationSummary?但不是与个人消息?如果你查看源代码,你能看到消息吗?
  • 我可以在验证摘要中看到消息
    • 项目的金额无效。
    • ...但不适用于单个项目
  • 如果禁用客户端验证会发生什么?这只是一个测试,看看问题是否出在客户端。在 Web.Config 中将 ClientValidationEnabled 设置为 false。
  • 相同的结果:总结,而不是单个项目
  • 您能否编辑您的问题以包含模型定义并显示您的控制器代码?

标签: asp.net-mvc-3 validation mvc-editor-templates


【解决方案1】:

我建议您使用真正的编辑器模板。您的代码的问题是您正在视图中编写一个 foreach 循环来呈现模板,该模板会为相应的输入字段生成错误的名称。我想这就是为什么您在控制器操作中使用一些变通方法来填充模型 (header = this.ProcessFormCollectionHeader(header, formCollection);) 而不是简单地使用模型绑定器来完成这项工作。

那么让我向您展示实现这一目标的正确方法。

型号:

public class Header
{
    public IEnumerable<Detail> Details { get; set; }
}

public class Detail : IValidatableObject
{
    public int DetailId { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public int Amount { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if ((this.Name ?? string.Empty).Length < 5 && this.Amount > 10)
        {
            yield return new ValidationResult(
                "Item must have sensible name to have Amount larger than 10.", 
                new[] { "Amount" }
            );
        }
    }
}

控制器:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new Header
        {
            Details = Enumerable.Range(1, 5).Select(x => new Detail
            {
                DetailId = x,
                Name = "n" + x,
                Amount = 50
            }).OrderBy(d => d.DetailId)
        };
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(Header model)
    {
        if (ModelState.IsValid)
        {
            return Redirect("~/saveNotification");
        }
        return View(model);
    }
}

查看(~/Views/Home/Index.cshtml):

@model Header

@using (Html.BeginForm())
{
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Description</th>
                <th>Amount</th>
            </tr>
        </thead>
        <tbody>
            @Html.EditorFor(x => x.Details)
        </tbody>
    </table>
    <button type="submit">OK</button>
}

Detail 类型的编辑器模板(~/Views/Shared/EditorTemplates/Detail.ascx~/Views/Shared/EditorTemplates/Detail.cshtml 用于 Razor):

<%@ Control 
    Language="C#" 
    Inherits="System.Web.Mvc.ViewUserControl<MvcApplication1.Controllers.Detail>" 
%>

<tr>            
    <td>
        <%= Html.DisplayFor(model => model.Name) %>
        <%= Html.HiddenFor(model => model.DetailId) %>
        <%= Html.HiddenFor(model => model.Name) %>
    </td>
    <td class="colDescription">
        <%= Html.EditorFor(model => model.Description) %>
        <%= Html.ValidationMessageFor(model => model.Description) %>
    </td>
    <td class="colAmount">
        <%= Html.EditorFor(model => model.Amount) %>
        <%= Html.ValidationMessageFor(model => model.Amount) %>
    </td>
</tr>

以下是我为改进您的代码所做的几件事:

  • 我在控制器级别按 DetailId 对 Details 集合进行了排序。控制器负责准备视图模型以供显示。视图不应执行此排序。视图应该做的就是显示数据
  • 感谢之前的改进,我摆脱了视图中用于呈现编辑器模板的 foreach 循环,并将其替换为单个 @Html.EditorFor(x =&gt; x.Details) 调用。其工作方式是 ASP.NET MVC 检测到 Details 是一个集合属性(IEnumerable&lt;Detail&gt; 类型),它会自动在 ~/Views/SomeController/EditorTemplates~/Views/Shared/EditorTemplates 文件夹中查找自定义编辑器模板,称为 Detail.ascxDetail.cshtml(与集合类型同名)。然后它将为集合的每个元素呈现此模板,因此您无需担心
  • 感谢之前的改进,在[HttpPost] 操作中,您不再需要任何ProcessFormCollectionHeader hack。 header action 参数将被模型绑定器从请求数据中正确绑定
  • Detail.ascx 模板中,我已将 &lt;%= Model.Name %&gt; 替换为 &lt;%= Html.DisplayFor(model =&gt; model.Name) %&gt;,以便正确地对输出进行 HTML 编码并填补您网站上打开的 XSS 漏洞。
  • Validate 方法中,我确保Name 属性在测试其长度之前不为空。顺便说一句,在您的示例中,模板内的 Description 字段只有一个输入字段,而 Name 属性没有相应的输入字段,因此提交表单时,此属性将始终为空。因此,我为其添加了相应的隐藏输入字段。

【讨论】:

  • 应注意您已删除 Description 属性并将其替换为 Name。这解决了他会在验证器中获得的空引用异常,但不是他正在寻找的输出。
  • 哦,是的,我认为这很明显。我已更新我的答案以包含此信息。
  • 在我们解决了他的代码没有抛出异常的原因之后,我实际上打算解决前缀问题。这似乎表明他的验证器实际上并没有执行。
  • 看起来不错,@Html.EditorFor(x => x.Details) 现在似乎已经解决了大部分(所有)问题。绝对消除了对 ProcessFormCollectionHeader 的需求,以前(使用 foreach 循环)标题对象没有自动填充详细信息,这解决了这个问题。
  • 我又遇到了一个问题,得到了一个 InvalidOperationException “EntityCollection 已经被初始化。InitializeRelatedCollection 方法只能在对象图的反序列化过程中被调用来初始化一个新的 EntityCollection。” ...看起来它发生在控制器点击保存按钮之前发生
猜你喜欢
相关资源
最近更新 更多
热门标签