1

我有一个 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>

模型是实体框架,标题具有名称和 HeaderId,详细信息具有 DetailId、HeaderId、描述和金额

控制器代码:

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" });
   }
}
4

1 回答 1

3

我建议您使用真正的编辑器模板。您的代码的问题是您正在视图中编写一个 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.cshtmlRazor)的编辑器模板:

<%@ 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 => x.Details)调用。其工作方式是 ASP.NET MVC 检测到这Details是一个集合属性(类型为),它会自动在名为or的文件夹IEnumerable<Detail>中查找模板化的自定义编辑器(与集合的类型同名)。然后它将为集合的每个元素呈现此模板,这样您就不必担心它~/Views/SomeController/EditorTemplates~/Views/Shared/EditorTemplatesDetail.ascxDetail.cshtml
  • 由于之前的改进,在[HttpPost]动作中你不再需要任何ProcessFormCollectionHeader黑客。headeraction 参数将由模型绑定器从请求数据中正确绑定
  • Detail.ascx我替换的模板<%= Model.Name %>内部<%= Html.DisplayFor(model => model.Name) %>,为了正确地对输出进行 HTML 编码并填充您网站上打开的 XSS 漏洞。
  • Validate方法内部,我确保Name在测试其长度之前该属性不为空。顺便说一句,在您的示例中,模板内只有一个 Description 字段的输入字段,并且没有相应的Name属性输入字段,因此在提交表单时,此属性将始终为空。因此,我为其添加了相应的隐藏输入字段。
于 2012-10-09T07:29:00.200 回答