ASP.NET Core使用jQuery Repeater时模型绑定失败问题咨询
问题原因
jQuery Repeater默认生成的表单字段name格式为前缀[索引][字段名],不符合ASP.NET Core模型绑定要求的前缀[索引].字段名规则。手动修改初始行的name属性后,插件内置的新增行命名生成逻辑未同步调整,因此新行name会生成错误的前缀[索引].0格式,导致绑定失败。
方案1:调整jQuery Repeater配置适配绑定规则
无需手动修改静态HTML的name属性,直接在插件初始化阶段重写命名生成逻辑即可:
- 初始化时将repeater的prefix设置为C#模型中对应的集合属性名
ProductDetails - 重写字段名生成逻辑,将插件默认生成的方括号嵌套字段名替换为点分隔格式
- 新增/删除行后自动重排索引,保证索引从0开始连续,避免ASP.NET Core因索引断层跳过绑定
$('.repeater-wrapper').repeater({ isFirstItemUndeletable: false, defaultValues: { Quantity: 1, Rate: 0 }, show: function () { $(this).slideDown(); // 修正新增行的name格式 updateInputNames(this); }, hide: function (deleteEl) { if (confirm('确认删除当前商品行?')) { $(this).slideUp(deleteEl); // 删除后重排所有行索引 reindexAllRows(); } }, ready: function () { // 初始化时修正首行name格式 $('.repeater-wrapper .product-row').each(function () { updateInputNames(this); }) } }); // 修正单行内所有输入框的name格式 function updateInputNames(rowEl) { $(rowEl).find(':input').each(function () { const name = $(this).attr('name'); // 匹配 ProductDetails[x][field] 格式 const matchRes = name.match(/^ProductDetails\[(\d+)\]\[(\w+)\]$/); if (matchRes) { const idx = matchRes[1]; const field = matchRes[2]; $(this).attr('name', `ProductDetails[${idx}].${field}`); } }); } // 重排所有行索引,保证连续 function reindexAllRows() { $('.repeater-wrapper .product-row').each(function (idx) { $(this).find(':input').each(function () { const newName = $(this).attr('name').replace(/ProductDetails\[\d+\]/, `ProductDetails[${idx}]`); $(this).attr('name', newName); }); }); }
配置完成后,所有初始行、新增行的name都会自动生成为ProductDetails[0].ProductId这类符合绑定规则的格式,无需手动修改静态代码。
方案2:原生JS实现(推荐,无兼容问题)
jQuery Repeater长期无更新,适配框架规则成本高,可直接用几十行原生JS实现相同功能,完全适配ASP.NET Core绑定规则,无第三方依赖问题:
- 编写动态行容器与首行模板,首行name严格匹配模型绑定格式:
<div class="repeater-wrapper"> <div id="product-container"> <!-- 首行模板 --> <div class="product-row mb-2"> <input class="form-control d-inline-block w-25" name="ProductDetails[0].ProductId" placeholder="商品ID" /> <input type="number" class="form-control d-inline-block w-25" name="ProductDetails[0].Quantity" placeholder="数量" /> <input type="number" class="form-control d-inline-block w-25" name="ProductDetails[0].Rate" placeholder="单价" /> <button type="button" class="btn btn-sm btn-danger delete-row">删除</button> </div> </div> <button type="button" id="add-product" class="btn btn-sm btn-primary mt-2">新增商品行</button> </div>
- 编写新增/删除/重排索引的JS逻辑:
// 新增行 $('#add-product').click(function () { const container = $('#product-container'); const newIdx = container.find('.product-row').length; // 克隆首行,清空值,替换索引 const newRow = container.find('.product-row').first().clone(); newRow.find('input').val(''); newRow.find(':input').each(function () { const name = $(this).attr('name').replace(/\[\d+\]/, `[${newIdx}]`); $(this).attr('name', name); }); // 绑定删除事件 newRow.find('.delete-row').click(deleteRowHandler); container.append(newRow); // 如果用了客户端不侵入验证,新增行后重新解析验证规则 $.validator.unobtrusive.parse(newRow); }); // 删除行逻辑 function deleteRowHandler() { $(this).closest('.product-row').remove(); reindexRows(); } // 重排索引保证连续 function reindexRows() { $('#product-container .product-row').each(function (idx) { $(this).find(':input').each(function () { const newName = $(this).attr('name').replace(/\[\d+\]/, `[${idx}]`); $(this).attr('name', newName); }); }); } // 初始绑定首行删除事件 $('.delete-row').click(deleteRowHandler);
注意事项
- ASP.NET Core对集合类型的模型绑定要求索引从0开始且连续,删除行后必须重排索引,否则会出现断层后字段绑定丢失的问题
- 如果使用客户端验证,动态新增的DOM节点需要手动调用
$.validator.unobtrusive.parse()触发验证规则解析,否则新增字段不会触发客户端校验 - 后端Action代码无需任何调整,前端name格式符合规则时,
List<ProductDetails>参数会自动完成绑定
内容的提问来源于stack exchange,提问作者Rupesh Plant
相关产品推荐
相关产品推荐

