MVC视图中的多个表单:ModelState应用于所有表单

Kip*_*pie 12 asp.net-mvc modelstate

在单个视图上遇到多个表单的问题.

假设我有以下viewmodel:

public class ChangeBankAccountViewModel  
{  
     public IEnumerable<BankInfo> BankInfos { get; set; }  
}

public class BankInfo  
{  
    [Required]  
    public string BankAccount { get; set; }  
    public long Id { get; set; }  
}
Run Code Online (Sandbox Code Playgroud)

在我的viewmodel中,我希望所有BankInfos都显示在彼此之下,每个都在单独的表单中.

为实现这一点,我正在使用局部视图_EditBankInfo:

@model BankInfo

@using (Html.BeginForm())
{
   @Html.HiddenFor(m => m.InvoiceStructureId)
   @Html.TextBoxFor(m => m.IBANAccount)

   <button type="submit">Update this stuff</button>
}
Run Code Online (Sandbox Code Playgroud)

以及我的实际观点BankInfo:

foreach(var info in Model.BankInfos)
{
    Html.RenderPartial("_EditBankInfo", info);
}
Run Code Online (Sandbox Code Playgroud)

最后,这是我的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();
}
Run Code Online (Sandbox Code Playgroud)

所有这一切都是工作hunky dory:验证工作顺利,发布模型得到正确识别和验证...但是,当页面重新加载时问题出现.因为我多次使用相同的表单,我的ModelState将被多次应用.因此,当在一个表单上执行更新时,下一页加载所有表单将具有已发布的值.

有没有办法轻易防止这种情况发生?

我尝试过没有部分视图的做法,但这有点搞砸了命名(它们是唯一的,但是服务器端模型绑定将无法识别它们).

谢谢你的回答.

Dar*_*rov 10

这有点棘手.这是如何解决的.首先将您的_EditBankInfo.cshtmlpartial 移动到一个~/Views/Shared/EditorTemplates/BankInfo.cshtml看起来像这样的编辑器模板(注意模板的名称和位置很重要.它应放在内部~/Views/Shared/EditorTemplates并命名为您的IEnumerable<T>collection属性中使用的typed的名称,在您的情况下是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>
Run Code Online (Sandbox Code Playgroud)

然后在你的主视图中摆脱foreach循环并用一个简单的调用EditorFor帮助器替换它:

@model ChangeBankAccountViewModel

@Html.EditorFor(x => x.BankInfos)
Run Code Online (Sandbox Code Playgroud)

现在,对于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>

...
Run Code Online (Sandbox Code Playgroud)

现在,由于每个字段都有一个特定的名称,因此在发布表单时不再存在任何冲突.注意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);
    }
}
Run Code Online (Sandbox Code Playgroud)

将在您的注册Application_Start:

ModelBinders.Binders.Add(typeof(BankInfo), new BankInfoModelBinder());
Run Code Online (Sandbox Code Playgroud)

好的,现在我们很高兴.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();
}
Run Code Online (Sandbox Code Playgroud)