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

发布于 2024-12-24 22:40:46 字数 2579 浏览 2 评论 0 原文

我目前正在开发一个应用程序,它可以复制类似表格的电子表格。我一直使用 ASP.NET 模型绑定以及 ajax 和表单 POST 的组合来完成这一切。我遇到了一些问题,我认为创建自定义模型绑定器可能会更好。

这是我的应用程序:

我们拥有项目的各个阶段,然后是与每个阶段相对应的一堆列预测现金流的月份。

该图像右侧有一个按钮,可让您添加额外的月份列,并且您可以使用添加阶段按钮添加阶段。这两个都是通过 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" [email protected] />
            </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 的隐藏单元格以及它们在文本框中输入的任何值吗?然后我可以让模型绑定器拆分这些字符串以拆分阶段名称和日期,解析它们,并在特定日期为阶段分配一个值?或者有更合适的方法吗?

I'm working on an application at the moment that replicates a spreadsheet like table. I've been doing it all with ASP.NET model binding and a combination of ajax and a form POST. I've run into some issues where I think I might be better off creating a custom model binder.

Here's my application:

model binding sample

What we have there are stages of a project, and then a bunch of columns corresponding to each month with the predicted cashflows.

There is a button to the right of that image that lets you add an extra month column, and you can add a stage using the add stage button. Those two are done with AJAX, and then when you submit the form I want it to save all changes in one go via a POST.

I've managed to manoeuvre my way around using HTML helpers, view models and default model binding to get it working with adding columns:

In my main view, I have this to generate the rows/columns in the TBODY

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

I have a partial view for CashflowStageViewModel which currently looks like:

@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" [email protected] />
            </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>

The model binding works by creating IDs and name's that look like this:

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

This causes some issues as I can add/delete rows between the time that these are generated and the time they are sent back to the server. I always try to keep using the default binding, but now it's holding me back so I think it's time I just created a custom model binder, rather than try dirty hacks to keep the indexes right.

My questions are these:

  • Do you agree a custom model binder is the way to go? or is there an easy trick I'm missing
  • What do you recommend I do in my custom model binder in terms of the names and ID's of my fields and cells in order to read it back in the http post. Is there some sort of guidance on how you should try and name them? Or should I just do string splitting?

By this I mean, for each cell should I have a hidden with name of STAGENAME_DATE and value of whatever they enter in the text box? and then I can have a model binder split those strings up to split the stage name and the date, parse them, and assign a value to the stage on that certain date? Or is there a more appropriate way of doing it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

鹤仙姿 2024-12-31 22:40:46

我建议您查看 Steven Sanderson 的 关于此事的优秀文章。他使用自定义的 Html.BeginCollectionItem 帮助器来负责生成输入字段的正确名称,这样您就不必担心绑定。默认模型绑定器开箱即用。

I would recommend you taking a look at the Steven Sanderson's excellent article on this matter. He uses a custom Html.BeginCollectionItem helper which takes care of generating proper names of the input fields so that you don't have to worry about the binding. The default model binder will work out of the box.

水晶透心 2024-12-31 22:40:46

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

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

结果,我将视图模型输出更新为以下内容:

@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" [email protected] />
            </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>

我对连接的混乱程度不太满意,但它工作得很好。

I ended up reading about how you can use your own arbritrary indexes here:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx

Non-Sequential Indices Well that’s all great and all, but what happens
when you can’t guarantee that the submitted values will maintain a
sequential index? For example, suppose you want to allow deleting rows
before submitting a list of books via JavaScript. The good news is
that by introducing an extra hidden input, you can allow for arbitrary
indices. In the example below, we provide a hidden input with the
.Index suffix for each item we need to bind to the list. The name of
each of these hidden inputs are the same, so as described earlier,
this will give the model binder a nice collection of indices to look
for when binding to the list.

As a result I updated my viewmodel output to be following:

@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" [email protected] />
            </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>

I'm not very happy with how messy the concatenation is, but it works pretty well.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文