.NET 6动态生成表单字段如何绑定到ViewModel并传递给控制器
.NET 6动态表单行绑定ViewModel解决方案
问题根源
原代码存在两个核心问题:
- 页面模型用了
IEnumerable<AddToMealViewModel>,但ASP.NET模型绑定需要可索引的集合类型(比如List<T>)才能识别批量提交的字段 - 克隆表单行时没有更新输入控件的
name属性索引,导致所有行的字段名称重复,控制器无法区分不同条目
解决方案步骤
1. 确认ViewModel结构(补充示例)
首先确保你的AddToMealViewModel包含必要属性:
public class AddToMealViewModel { public int SelectedMealId { get; set; } public int SelectedProductId { get; set; } public decimal Amount { get; set; } // 下拉列表数据源(建议从控制器传入ViewData,而非ViewModel本身) public List<Meal> Meals { get; set; } public List<Product> Products { get; set; } } // 辅助实体示例 public class Meal { public int Id { get; set; } public string Name { get; set; } } public class Product { public int Id { get; set; } public string Name { get; set; } }
2. 调整页面模型与表单渲染
把页面模型改为List<AddToMealViewModel>,用for循环渲染初始行(确保每个控件的name带索引):
@model List<AddToMealViewModel> <button class="btn btn-success" onclick="addMore()">Add more</button> <form method="post"> <div class="wrapper"> @for (int i = 0; i < Model.Count; i++) { <div class="row-fluid"> <div> @Html.DropDownListFor(m => m[i].SelectedMealId, new SelectList(Model[i].Meals, "Id", "Name"), "Select") @Html.DropDownListFor(m => m[i].SelectedProductId, new SelectList(Model[i].Products, "Id", "Name"), "Select") <input asp-for="@Model[i].Amount" /> </div> </div> } </div> <button type="submit" class="btn btn-primary">Add</button> </form>
3. 修改动态添加行的脚本
克隆后要更新新行所有控件的name、id属性的索引,并清空输入值:
<script> function addMore() { const wrapper = document.querySelector('.wrapper'); const lastRow = wrapper.querySelector('.row-fluid:last-child'); const newRow = lastRow.cloneNode(true); // 获取当前最大索引,计算新索引 const rows = wrapper.querySelectorAll('.row-fluid'); const newIndex = rows.length; // 更新新行内所有表单控件的name和id newRow.querySelectorAll('select, input').forEach(elem => { // 替换旧索引为新索引 elem.name = elem.name.replace(/\[\d+\]/, `[${newIndex}]`); elem.id = elem.id.replace(/\[\d+\]/, `[${newIndex}]`); // 清空输入值 elem.value = ''; // 重置下拉框到默认选项 if (elem.tagName === 'SELECT') { elem.selectedIndex = 0; } }); wrapper.appendChild(newRow); } </script>
4. 控制器接收参数
控制器的Post方法直接接收List<AddToMealViewModel>类型参数:
[HttpPost] public IActionResult AddMeals(List<AddToMealViewModel> model) { if (ModelState.IsValid) { // 处理提交的数据 return RedirectToAction("Index"); } // 验证失败时,重新填充下拉数据源并返回页面 foreach (var item in model) { item.Meals = GetMeals(); // 你的获取Meals逻辑 item.Products = GetProducts(); // 你的获取Products逻辑 } return View(model); } // 模拟数据源方法 private List<Meal> GetMeals() => new List<Meal> { new Meal { Id = 1, Name = "早餐" }, new Meal { Id = 2, Name = "午餐" } }; private List<Product> GetProducts() => new List<Product> { new Product { Id = 1, Name = "面包" }, new Product { Id = 2, Name = "牛奶" } };
额外优化建议
- 下拉列表的数据源(Meals、Products)建议通过
ViewData或ViewBag传入,避免每个ViewModel实例都重复携带相同数据 - 可以给动态添加的行添加删除按钮,提升用户体验
- 前端可以添加表单验证逻辑,确保每一行的字段都填写完整
内容的提问来源于stack exchange,提问作者klaudiaRajs
相关产品推荐
相关产品推荐

