MVC3主 - 详细信息验证未显示

时间:2012-10-09 00:38:25

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

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

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

Edit.cshtml(标题 - 详细信息编辑视图)

@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>

模型是带有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" });
   }
}

1 个答案:

答案 0 :(得分: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>
}

详细信息类型的编辑器模板(Razor为~/Views/Shared/EditorTemplates/Detail.ascx~/Views/Shared/EditorTemplates/Detail.cshtml):

<%@ 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是一个集合属性(类型为IEnumerable<Detail>),它会自动查找~/Views/SomeController/EditorTemplates或{{中模板化的自定义编辑器1}}名为~/Views/Shared/EditorTemplatesDetail.ascx的文件夹(与集合的类型相同)。然后,它将为集合的每个元素呈现此模板,以便您不必担心它
  • 感谢之前的改进,在Detail.cshtml操作中,您不再需要任何[HttpPost]黑客攻击。 ProcessFormCollectionHeader操作参数将通过模型绑定器
  • 与请求数据正确绑定
  • header模板中,我已将Detail.ascx替换为<%= Model.Name %>,以便对输出进行正确的HTML编码并填充您网站上打开的XSS漏洞。
  • <%= Html.DisplayFor(model => model.Name) %>方法中,我确保Validate属性在测试其长度之前不为null。顺便提一下,在您的示例中,您只有模板内的Description字段的输入字段,并且没有Name属性的相应输入字段,因此在提交表单时,此属性将始终为null。因此,我为它添加了相应的隐藏输入字段。