
本文详解如何在 ASP.NET Core 6 中通过 JavaScript 动态增删表格行,并自动重索引 input 的 name 属性,确保提交时模型绑定不因删除中间行而失效。
本文详解如何在 asp.net core 6 中通过 javascript 动态增删表格行,并自动重索引 input 的 `name` 属性,确保提交时模型绑定不因删除中间行而失效。
在使用 ASP.NET Core MVC 模型绑定处理集合(如 List<POMaterial>)时,必须保证 HTML 表单中 <input> 元素的 name 属性严格按连续整数索引命名(例如 POMaterials[0].Amount、POMaterials[1].Amount),否则控制器接收的模型将丢失部分或全部数据。原始代码中仅依赖全局 counter 变量递增添加行,但删除非末尾行后索引出现断层(如剩下 [0]、[2]、[3]),导致模型绑定失败。
✅ 正确方案:删除后立即重索引所有行
推荐使用 jQuery + 语义化事件委托 方式实现健壮的动态表格管理(需引入 jQuery):
<!-- 确保页面已引用 jQuery --> <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
// 全局计数器(仅用于新增行初始索引)
let counter = 0;
function addRow() {
const tableBody = $('#grid2 tbody');
const newRow = `
<tr>
<td><input type="text" name="POMaterials[${counter}].ItemNumber"
id="POMaterials_${counter}_ItemNumber" required /></td>
<td><input type="text" name="POMaterials[${counter}].Description"
id="POMaterials_${counter}_Description" required /></td>
<td><input type="text" name="POMaterials[${counter}].Amount"
class="andi" required /></td>
<td><input type="text" name="POMaterials[${counter}].UnitCost"
class="andi" required /></td>
<td>
<button type="button" class="btn btn-sm btn-danger remove-row"
title="Remove Entry">
<span class="k-icon k-i-minus"></span>
</button>
</td>
</tr>`;
tableBody.append(newRow);
counter++;
}
// 使用事件委托绑定删除按钮(避免重复绑定)
$(document).on('click', '.remove-row', function () {
$(this).closest('tr').remove();
reindexRows(); // 关键:删除后立即重索引
});
function reindexRows() {
$('#grid2 tbody tr').each(function (rowIndex) {
const $row = $(this);
$row.find('input').each(function (inputIndex) {
const $input = $(this);
let newId, newName;
switch (inputIndex) {
case 0:
newName = `POMaterials[${rowIndex}].ItemNumber`;
newId = `POMaterials_${rowIndex}_ItemNumber`;
break;
case 1:
newName = `POMaterials[${rowIndex}].Description`;
newId = `POMaterials_${rowIndex}_Description`;
break;
case 2:
newName = `POMaterials[${rowIndex}].Amount`;
newId = `POMaterials_${rowIndex}_Amount`;
break;
case 3:
newName = `POMaterials[${rowIndex}].UnitCost`;
newId = `POMaterials_${rowIndex}_UnitCost`;
break;
default:
return;
}
$input.attr('name', newName);
$input.attr('id', newId);
});
});
}? 关键设计说明:
- 使用 $(document).on('click', '.remove-row', ...) 实现事件委托,避免多次调用 addRow() 导致重复绑定;
- reindexRows() 遍历当前所有 <tr>,按实际 DOM 顺序(rowIndex 从 0 开始)重新设置每个 <input> 的 name 和 id,彻底消除索引空洞;
- id 同步更新为符合 ASP.NET Core 的默认客户端验证 ID 规范(如 POMaterials_0_ItemNumber),保障前端验证正常工作。
⚠️ 注意事项与最佳实践
- 不要手动维护 counter 用于索引逻辑:counter 仅用于新增时生成初始唯一值;真实绑定索引应始终由 reindexRows() 动态计算。
- 禁用原生 onclick 内联脚本:原始代码中 onClick="removeCurrentRow()" 会因 DOM 变化导致事件绑定失效或重复执行,务必改用事件委托。
- 服务端验证不可省略:即使前端重索引正确,仍需在 Controller Action 中检查 ModelState.IsValid,并处理空/无效数据。
- 考虑使用 Tag Helpers 或 View Components:对于复杂表格场景,可封装为可复用组件,进一步提升可维护性。
✅ 验证效果
提交表单后,在控制器中接收模型:
[HttpPost]
public IActionResult Create(PurchaseOrderViewModel model)
{
if (ModelState.IsValid)
{
// model.POMaterials 将准确包含所有已填写的行(索引连续)
_context.PurchaseOrders.Add(model);
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}只要前端 name 属性连续且格式正确,ASP.NET Core 模型绑定引擎即可自动映射为 List<POMaterial>,无需额外配置。
通过上述方案,无论用户删除第 1 行、中间行还是最后一行,提交数据均能完整、准确地抵达服务端,彻底解决动态表格模型绑定断裂问题。

















