使用 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),创建不作为标签的标签元素(点击它们不会将焦点设置到关联的控件) ,不必要地使用<br/>
元素(使用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 助手时不正确的列表模型绑定索引的主要内容,如果未能解决你的问题,请参考以下文章
如何将 Html.DevExtreme().LookupFor 列表绑定到模型