在单一视图中遇到多种形式的麻烦.
假设我有以下viewmodel:
public class ChangeBankAccountviewmodel { public IEnumerable<BankInfo> BankInfos { get; set; } } public class BankInfo { [required] public string BankAccount { get; set; } public long Id { get; set; } }
在我的视图模型中,我希望所有的BankInfos都可以在彼此之间显示,每个表单分开.
要实现这一点,我使用的是部分视图_EditBankInfo:
@model BankInfo @using (Html.BeginForm()) { @Html.HiddenFor(m => m.InvoiceStructureId) @Html.TextBoxFor(m => m.IBANAccount) <button type="submit">Update this stuff</button> }
以及我的实际看法BankInfo:
foreach(var info in Model.BankInfos) { Html.RenderPartial("_EditBankInfo",info); }
最后,这是我的2动作方法:
[HttpGet] public ActionResult BankInfo() { return View(new ChangeBankAccountviewmodel{BankInfos = new [] {new BankInfo...}); } [HttpPost] public ActionResult BankInfo(BankInfo model) { if(ModelState.IsValid) ModelState.Clear(); return BankInfo(); }
所有这些都是工作的hunky dory:验证工作顺利,发布模型得到认可和验证正确…
但是,当页面重新加载时出现问题.
因为我使用相同的表单多次,我的ModelState将被多次应用.所以当在一个表单上执行更新时,下一页加载它们将具有已发布的值.
有什么办法容易防止这种情况发生吗?
我试过这样做,没有部分的意见,但螺丝的命名有点(他们是独一无二的,但服务器模型绑定将不会识别他们).
感谢任何答案.
解决方法
这有点棘手.这是如何解决的.首先将_EditBankInfo.cshtml部分移动到一个编辑器模板〜/ Views / Shared / EditorTemplates / BankInfo.cshtml,看起来像这样(注意模板的名称和位置很重要,应该放在〜/ Views / Shared / EditorTemplates,并命名为您的IEnumerable&T>集合属性中使用的类型名称,在您的情况下为BankInfo.cshtml):
@model BankInfo <div> @using (Html.BeginForm()) { <input type="hidden" name="model.prefix" value="@ViewData.TemplateInfo.HtmlFieldPrefix" /> @Html.HiddenFor(m => m.Id) @Html.TextBoxFor(m => m.BankAccount) <button type="submit">Update this stuff</button> } </div>
然后在你的主视图中摆脱foreach循环,并用一个简单的调用来替代EditorFor Helper:
@model ChangeBankAccountviewmodel @Html.EditorFor(x => x.BankInfos)
现在,对于BankInfos集合的每个元素,自定义编辑器模板将被渲染.与部分相反,编辑器模板尊重导航上下文,并将生成以下标记:
<div> <form action="/" method="post"> <input type="hidden" name="model.prefix" value="BankInfos[0]" /> <input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="BankInfos_0__Id" name="BankInfos[0].Id" type="hidden" value="1" /> <input data-val="true" data-val-required="The BankAccount field is required." id="BankInfos_0__BankAccount" name="BankInfos[0].BankAccount" type="text" value="account 1" /> <button type="submit">Update this stuff</button> </form> </div> <div> <form action="/" method="post"> <input type="hidden" name="model.prefix" value="BankInfos[1]" /> <input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="BankInfos_1__Id" name="BankInfos[1].Id" type="hidden" value="2" /> <input data-val="true" data-val-required="The BankAccount field is required." id="BankInfos_1__BankAccount" name="BankInfos[1].BankAccount" type="text" value="account 2" /> <button type="submit">Update this stuff</button> </form> </div> ...
现在,由于每个字段都有一个特定的名称,因此在发布表单时不会再有冲突.注意我明确放置在每个窗体中的名为model.prefix的隐藏字段.这将由BankInfo类型的自定义模型binder使用:
public class BankInfoModelBinder: DefaultModelBinder { public override object BindModel(ControllerContext controllerContext,ModelBindingContext bindingContext) { bindingContext.ModelName = controllerContext.HttpContext.Request.Form["model.prefix"]; return base.BindModel(controllerContext,bindingContext); } }
这将在您的Application_Start中注册:
ModelBinders.Binders.Add(typeof(BankInfo),new BankInfoModelBinder());
好的,现在我们很好去在您不再需要它的控制器动作中摆脱ModelState.Clear:
[HttpGet] public ActionResult BankInfo() { var model = new ChangeBankAccountviewmodel { // This is probably populated from some data store BankInfos = new [] { new BankInfo... },} return View(model); } [HttpPost] public ActionResult BankInfo(BankInfo model) { if(ModelState.IsValid) { // TODO: the model is valid => update its value into your data store // DO NOT CALL ModelState.Clear anymore. } return BankInfo(); }