12

在单个视图上使用多个表单时遇到一些麻烦。

假设我有以下视图模型:

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 将被应用多次。因此,当对一个表单执行更新时,下一页加载所有表单都将具有发布的值。

有什么方法可以轻松防止这种情况发生吗?

我试过在没有部分视图的情况下这样做,但这有点搞砸了命名(它们是独一无二的,但服务器端模型绑定不会识别它们)。

感谢您的任何回答。

4

1 回答 1

11

这有点棘手。这是如何解决的。首先将您的_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();
}
于 2013-08-07T13:42:06.893 回答