使用 HTML 助手时不正确的列表模型绑定索引

Posted

技术标签:

【中文标题】使用 HTML 助手时不正确的列表模型绑定索引【英文标题】:Incorrect List Model Binding indices when using HTML Helpers 【发布时间】:2016-02-24 15:05:42 【问题描述】:

这是一个很难解释的问题,所以我会尝试使用项目符号。

问题:

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

我的发现:

仅当在 Razor 代码中使用 html 助手时才会发生这种情况。 如果我使用传统的<input> 元素(不理想),它可以正常工作。

问题:

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

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

控制器:

    [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:

如您所见,我有“Make”和“Actual Make”输入。一个是 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 />
                <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 删除功能

function DeleteCarRow(id) 

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


用户界面中发生了什么:

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

【问题讨论】:

在您的 POST 方法中,在 return View(model); 之前添加 ModelState.Clear();。如果这样可以解决问题,则表明您与删除项目相关的代码是错误的。 @StephenMuecke 非常感谢您的回复。添加ModelState.Clear(); 有效!我现在已将用于删除行的 JQuery 添加到我的 OP 中。你知道我错过了什么吗?删除行后,我是否需要在此函数中以某种方式更新模型? 【参考方案1】:

这种行为的原因是HtmlHelper 方法使用来自ModelState 的值(如果存在)来设置value 属性而不是实际的模型值。这种行为的原因在TextBoxFor displaying initial value, not the value updated from code的回答中进行了解释。

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

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

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

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

Cars[0].Make: Land Rover 2
Cars[1].Make: Audi 3
Cars[2].Make: Honda 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 属性。

旁注:你的视图包含很多不好的做法,包括用行为污染你的标记(使用Unobtrusive javascript),创建不作为标签的标签元素(点击它们不会将焦点设置到关联的控件) ,不必要地使用&lt;br/&gt; 元素(使用css 为元素设置边距等)以及不必要地使用new @id = "car-make-" + i 。循环中的代码可以是

@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 助手时不正确的列表模型绑定索引的主要内容,如果未能解决你的问题,请参考以下文章

在视图中循环列表并根据列表索引将值绑定到模型

将复杂模型绑定到 MVC3 中的列表

如何将 Html.DevExtreme().LookupFor 列表绑定到模型

来自Web服务的DropDownList绑定在asp.net中编辑时不传递数据源

Xamarin 从视图模型传递列表数据以进行视图绑定

ScaleTransform 绑定但在绑定属性更改时不更新