针对特定场景的自定义模型绑定的MVC最佳实践

时间:2012-01-06 06:13:18

标签: asp.net asp.net-mvc model-binding

我正在处理一个复制像表这样的电子表格的应用程序。我一直在使用ASP.NET模型绑定以及ajax和表单POST的组合。我遇到了一些问题,我认为我可能会更好地创建自定义模型绑定器。

这是我的申请:

model binding sample

我们在那里有一个项目的阶段,然后是一堆与每个月对应的预测现金流量的列。

该图像右侧有一个按钮,可以添加额外的月份列,您可以使用添加阶段按钮添加舞台。这两个是用AJAX完成的,然后当你提交表单时,我希望它通过POST一次性保存所有更改。

我设法使用HTML帮助程序,查看模型和默认模型绑定来操作,以使其能够添加列:

在我的主视图中,我有这个来生成TBODY中的行/列

        <tbody>
            @for (int stageIndex = 0; stageIndex < Model.Stages.Count; stageIndex++)
            {
                @Html.EditorFor(x => x.Stages[stageIndex])
            }
            </tbody>
        <tfoot>

我有CashflowStageViewModel的部分视图,目前看起来像:

@model BWInvoicing.Ui.Models.CashflowStageViewModel
<tr class="stageRow">
    @Html.HiddenFor(x => x.Stage)
    <td class="stage">
        <div class="stageWrapper">
            <div>
                @Model.Stage
            </div>
            <div class="stageOptions">
                <img class="deleteRowButton"  src="../../../Content/Images/delete.png" id=@Model.Stage />
            </div>
        </div>
    </td>
    <td>
       @Html.TextBoxFor(x => x.Amount, new { @Class = "totalFee emTotal", CashflowStage = Model.Stage })
    </td>
    <td>
        @Html.TextBox("summation", 0, new { @Class = "emTotal summation", CashflowStage = Model.Stage, @readonly = "true" })
    </td>
    @for (int i = 0; i < Model.Months.Count; i++)
    {
        <td>
            @Html.HiddenFor(x => Model.Months[i].Date)
            @Html.TextBoxFor(x => Model.Months[i].Amount, new { @Class = "prediction", CashflowStage = Model.Stage })
        </td>
    }
    <td class="stageHeadingEnd">
        @Model.Stage
    </td>
</tr>

模型绑定的工作原理是创建ID和名称,如下所示:

id="Stages_5__Months_4__Amount" name="Stages[5].Months[4].Amount"

这会导致一些问题,因为我可以在生成这些行和将它们发送回服务器之间添加/删除行。我总是试图继续使用默认绑定,但现在它阻止了我,所以我认为是时候我只是创建了一个自定义模型绑定器,而不是尝试使用脏黑客来保持索引正确。

我的问题是这些:

  • 您是否同意定制模型活页夹的方法?或者是否有一个我缺少的简单技巧
  • 我建议我在自定义模型活页夹中使用我的字段和单元格的名称和ID,以便在http帖子中读取它。有关如何尝试命名的指导吗?或者我应该只进行字符串拆分?

我的意思是,对于每个单元格,我应该隐藏名称为STAGENAME_DATE以及它们在文本框中输入的值吗?然后我可以让一个模型绑定器拆分这些字符串以分割阶段名称和日期,解析它们,并在该特定日期为舞台指定一个值?或者有更合适的方法吗?

2 个答案:

答案 0 :(得分:3)

我建议你看看Steven Sanderson的excellent article就此问题。他使用自定义Html.BeginCollectionItem帮助程序,负责生成输入字段的正确名称,以便您不必担心绑定。默认的模型绑定器将开箱即用。

答案 1 :(得分:0)

我最后读到了如何在这里使用自己的arbritrary索引: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx

  

非顺序指数嗯,这一切都很棒,但是会发生什么   当你不能保证提交的值会保持一个   顺序索引?例如,假设您要允许删除行   在通过JavaScript提交书籍列表之前。好消息是   通过引入额外的隐藏输入,您可以允许任意   指数。在下面的示例中,我们提供了一个隐藏的输入   我们需要绑定到列表的每个项目的.Index后缀。的名字   这些隐藏的输入中的每一个都是相同的,如前所述,   这将为模型绑定器提供一个很好的索引集合   用于绑定到列表时。

结果我将viewmodel输出更新为:

@model BWInvoicing.Ui.Models.CashflowStageViewModel
<tr class="stageRow">

    @* Setup custom indexer to allow for add/remove rows *@
    @Html.Hidden("Index", Model.Stage)

    @* Store stage to rebuild view model, use bracket notation to allow default model binder to work correctly *@
    @Html.Hidden("[" + Model.Stage + "].Stage",
                   Model.Stage)
    <td class="stage">
        <div class="stageWrapper">
            <div>
                @Model.Stage
            </div>
            <div class="stageOptions">
                <img class="deleteRowButton"  src="../../../Content/Images/delete.png" id=@Model.Stage />
            </div>
        </div>
    </td>
    <td>
        @Html.TextBox("[" + Model.Stage + "].Amount", Model.Amount,
                    new { @Class = "totalFee emTotal", CashflowStage = Model.Stage })
    </td>
    <td>
        @* Readonly javascript display of summation *@ 
        @Html.TextBox("summation", 0,
                    new
                        {
                            @Class = "emTotal summation",
                            CashflowStage = Model.Stage,
                            @readonly = "true"
                        })
    </td>
     @* Output the month inputs, setup the correct indexes *@ 
    @for (int i = 0; i < Model.Months.Count; i++)
    {
        <td>
            @Html.Hidden("[" + Model.Stage + "].Months[" + i + "].Date",
                   Model.Months[i].Date)
            @Html.TextBox("[" + Model.Stage + "].Months[" + i + "].Amount",
                    Model.Months[i].Amount, new { @Class = "prediction", CashflowStage = Model.Stage })
        </td>
    }
    <td class="stageHeadingEnd">
        @Model.Stage
    </td>
</tr>

我对连接的混乱程度不是很满意,但它的效果非常好。