MVC 視圖中的多個表單:ModelState 應用於所有表單
在單個視圖上使用多個表單時遇到一些麻煩。
假設我有以下視圖模型:
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(); }所有這一切都很有效:驗證工作順利,發布的模型得到正確辨識和驗證……但是,當頁面重新載入時,問題就出現了。因為我多次使用同一個表單,所以我的 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@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類型: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當您不再需要它時,去掉in 您的控制器操作:[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(); }