使用 HTML 帮助程序时不正确的列表模型绑定索引 [英] Incorrect List Model Binding indices when using HTML Helpers

查看:20
本文介绍了使用 HTML 帮助程序时不正确的列表模型绑定索引的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这很难解释,所以我会尝试使用项目符号.

问题:

  1. 用户可在查看(添加/删除)时使用动态行(集合)
  2. 用户删除行并保存(POST)
  3. 集合通过非顺序索引传回控制器
  4. 逐步执行代码,一切看起来都很好,包括集合项、索引等.
  5. 页面呈现后,项目无法正确显示 - 它们全部为 1,因此在新的 0 位置复制顶部项目.

我发现了什么:

只有在 Razor 代码中使用 HTML 帮助程序时才会发生这种情况.如果我使用传统的 元素(不理想),它工作正常.

问题:

有没有人遇到过这个问题?或者有谁知道为什么会这样,或者我做错了什么?

请查看下面我的代码,感谢您查看我的问题!

控制器:

 [HttpGet]公共 ActionResult 索引(){列表<汽车>汽车 = 新列表<汽车>{新车 { ID = 1, Make = "BMW 1", Model = "325" },新车 { ID = 2, Make = "Land Rover 2", Model = "Range Rover" },新车 { ID = 3, Make = "Audi 3", Model = "A3" },新车 { ID = 4, Make = "Honda 4", Model = "Civic" }};CarModel 模型 = new CarModel();模型.汽车 = 汽车;返回视图(模型);}[HttpPost]公共 ActionResult 索引(CarModel 模型){//这仅用于调试目的列表<汽车>保存的汽车 = 模型.汽车;返回视图(模型);}

Index.cshtml:

如您所见,我有制作"和实际制作"输入.一个是 HTML Helper,另一个是传统的 HTML Input.

 @using (Html.BeginForm()){<div class="col-md-4">@for (int i = 0; i < Model.Cars.Count; i++){<div id="car-row-@i" class="form-group row"><br/><小时/><label class="control-label">Make (@i)</label>@Html.TextBoxFor(m => m.Cars[i].Make, new { @id = "car-make-" + i, @class = "form-control" })<label class="control-label">实际制作</label><input class="form-control" id="car-make-@i" name="Cars[@i].Make" type="text" value="@Model.Cars[i].Make"/><div><input type="hidden" name="Cars.Index" value="@i"/>

<br/><button id="delete-btn-@i" type="button" class="btn btn-sm btn-danger" onclick="DeleteCarRow(@i)">删除条目</button>

}<div class="form-group"><input type="submit" class="btn btn-sm btn-success" value="Submit"/>

}

Javascript 删除功能

function DeleteCarRow(id) {$("#car-row-" + id).remove();}

用户界面中发生的事情:

第 1 步(删除行)第 2 步(提交表格)第 3 步(结果)

解决方案

这种行为的原因是 HtmlHelper 方法使用来自 ModelState 的值(如果存在的话)) 设置 value 属性而不是实际的模型值.这种行为的原因在 TextBox 用于显示初始值,而不是从代码更新的值.

在您的情况下,当您提交时,以下值将添加到 ModelState

Cars[1].Make: Land Rover 2汽车[2].制造:奥迪 3汽车[3].制造:本田 4

请注意,Cars[0].Make 没有值,因为您删除了视图中的第一项.

当您返回视图时,集合现在包含

Cars[0].Make: Land Rover 2汽车[1].制造:奥迪 3汽车[2].制造:本田 4

所以在循环的第一次迭代中,TextBoxFor() 方法检查 ModelState 是否匹配,没有找到,并生成 value="Land Rover 2"(即模型值)和您的手动输入也会读取模型值并设置 value="Land Rover 2"

在第二次迭代中,TextBoxFor() 确实在 ModelState 中找到了 Cars[1]Make 的匹配项,因此它设置了 value="Land Rover 2" 和手动输入读取模型值并设置value="Audi 3".

我假设这个问题只是为了解释行为(实际上,您会保存数据,然后重定向到 GET 方法以显示新列表),但是您可以在返回视图时生成正确的输出通过调用 ModelState.Clear() 将清除所有 ModelState 值,以便 TextBoxFor() 生成 value基于模型值的属性.

旁注:您的视图包含许多不良做法,包括用行为污染您的标记(使用 UnobtrusiveJavaScript),创建不作为标签的标签元素(点击它们不会将焦点设置到关联的控件上),不必要地使用 <br/> 元素(使用 css用边距等设置元素的样式)和不必要地使用 new { @id = "car-make-" + i }.循环中的代码可以是

@for (int i = 0; i  m.Cars[i].Make, "Make (@i)")@Html.TextBoxFor(m => m.Cars[i].Make, new { @class = "form-control" })....<input type="hidden" name="Cars.Index" value="@i"/><button type="button" class="btn btn-sm btn-danger delete">删除条目</button>

}$('.delete').click(function() {$(this).closest('.form-group').remove();}

this is a tricky one to explain, so I'll try bullet pointing.

Issue:

  1. Dynamic rows (collection) available to user on View (add/delete)
  2. User deletes row and saves (POST)
  3. Collection passed back to controller with non-sequential indices
  4. Stepping through code, everything looks fine, collection items, indices etc.
  5. Once the page is rendered, items are not displaying correctly - They are all out by 1 and therefore duplicating the top item at the new 0 location.

What I've found:

This happens ONLY when using the HTML Helpers in Razor code. If I use the traditional <input> elements (not ideal), it works fine.

Question:

Has anyone ever run into this issue before? Or does anyone know why this is happening, or what I'm doing wrong?

Please check out my code below and thanks for checking my question!

Controller:

    [HttpGet]
    public ActionResult Index()
    {
        List<Car> cars = new List<Car>
        {
            new Car { ID = 1, Make = "BMW 1", Model = "325" },
            new Car { ID = 2, Make = "Land Rover 2", Model = "Range Rover" },
            new Car { ID = 3, Make = "Audi 3", Model = "A3" },
            new Car { ID = 4, Make = "Honda 4", Model = "Civic" }

        };

        CarModel model = new CarModel();
        model.Cars = cars;

        return View(model);
    }

    [HttpPost]
    public ActionResult Index(CarModel model)
    {
        // This is for debugging purposes only
        List<Car> savedCars = model.Cars;

        return View(model);
    }

Index.cshtml:

As you can see, I have "Make" and "Actual Make" inputs. One being a HTML Helper and the other a traditional HTML Input, respectively.

    @using (Html.BeginForm())
{
    <div class="col-md-4">

        @for (int i = 0; i < Model.Cars.Count; i++)
        {
            <div id="car-row-@i" class="form-group row">
                <br />
                <hr />

                <label class="control-label">Make (@i)</label>
                @Html.TextBoxFor(m => m.Cars[i].Make, new { @id = "car-make-" + i, @class = "form-control" })

                <label class="control-label">Actual Make</label>
                <input class="form-control" id="car-make-@i" name="Cars[@i].Make" type="text" value="@Model.Cars[i].Make" />

                <div>
                    <input type="hidden" name="Cars.Index" value="@i" />
                </div>

                <br />

                <button id="delete-btn-@i" type="button" class="btn btn-sm btn-danger" onclick="DeleteCarRow(@i)">Delete Entry</button>

            </div>
        }

        <div class="form-group">
            <input type="submit" class="btn btn-sm btn-success" value="Submit" />
        </div>

    </div>
}

Javascript Delete Function

function DeleteCarRow(id) {

    $("#car-row-" + id).remove();

}

What's happening in the UI:

Step 1 (delete row) Step 2 (Submit form) Step 3 (results)

解决方案

The reason for this behavior is that the HtmlHelper methods use the value from ModelState (if one exists) to set the value attribute rather that the actual model value. The reason for this behavior is explained in the answer to TextBoxFor displaying initial value, not the value updated from code.

In your case, when you submit, the following values are added to ModelState

Cars[1].Make: Land Rover 2
Cars[2].Make: Audi 3
Cars[3].Make: Honda 4

Note that there is no value for Cars[0].Make because you deleted the first item in the view.

When you return the view, the collection now contains

Cars[0].Make: Land Rover 2
Cars[1].Make: Audi 3
Cars[2].Make: Honda 4

So in the first iteration of the loop, the TextBoxFor() method checks ModelState for a match, does not find one, and generates value="Land Rover 2" (i.e. the model value) and your manual input also reads the model value and sets value="Land Rover 2"

In the second iteration, the TextBoxFor() does find a match for Cars[1]Make in ModelState so it sets value="Land Rover 2" and manual inputs reads the model value and sets value="Audi 3".

I'm assuming this question is just to explain the behavior (in reality, you would save the data and then redirect to the GET method to display the new list), but you can generate the correct output when you return the view by calling ModelState.Clear() which will clear all ModelState values so that the TextBoxFor() generates the value attribute based on the model value.

Side note:You view contains a lot of bad practice, including polluting your markup with behavior (use Unobtrusive JavaScript), creating label element that do not behave as labels (clicking on them will not set focus to the associated control), unnecessary use of <br/> elements (use css to style your elements with margins etc) and unnecessary use of new { @id = "car-make-" + i }. The code in your loop can be

@for (int i = 0; i < Model.Cars.Count; i++)
{
    <div class="form-group row">
        <hr />
        @Html.LabelFor(m => m.Cars[i].Make, "Make (@i)")
        @Html.TextBoxFor(m => m.Cars[i].Make, new { @class = "form-control" })
        ....
        <input type="hidden" name="Cars.Index" value="@i" />
        <button type="button" class="btn btn-sm btn-danger delete">Delete Entry</button>
    </div>
}

$('.delete').click(function() {
    $(this).closest('.form-group').remove();
}

这篇关于使用 HTML 帮助程序时不正确的列表模型绑定索引的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
C#/.NET最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆