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