在CSHTML中实现点击按钮动态添加表格字段的问题
问题分析与解决方案
你的核心问题是混淆了服务器端Razor代码和客户端JavaScript代码的执行时机:
- Razor代码(比如
@Model.Books.Add(...))在页面渲染阶段就已经在服务器执行完毕,点击按钮时这段代码不会重新运行,所以点击Add按钮不会新增Model里的对象。 - 要实现客户端动态添加表格行,需要通过JavaScript操作DOM来新增表单元素,同时保证元素的
name属性符合ASP.NET MVC的列表绑定规则,这样提交表单时后端才能正确接收新增的数据。
正确实现步骤
1. 调整视图结构,添加行模板
先在表格里添加一个隐藏的模板行,用于克隆生成新行:
<button type="button" onclick="addBtn()">Add</button> <table id="booksTable"> <tr> <th>Page</th> <th>Price</th> </tr> <!-- 现有数据行 --> @for (int i = 0; i < Model.Books.Count; i++) { <tr class="book-row"> <td> <label asp-for="@Model.Books[i].Page" class="control-label">Page</label> <input type="number" asp-for="@Model.Books[i].Page" class="form-control" /> <span asp-validation-for="@Model.Books[i].Page" class="text-danger"></span> </td> <td> <label asp-for="@Model.Books[i].Price" class="control-label">Price</label> <input type="number" asp-for="@Model.Books[i].Price" class="form-control" /> <span asp-validation-for="@Model.Books[i].Price" class="text-danger"></span> </td> </tr> } <!-- 隐藏的模板行,用于克隆 --> <tr id="bookRowTemplate" style="display: none;"> <td> <label class="control-label">Page</label> <input type="number" name="Books[__INDEX__].Page" class="form-control" /> <span class="text-danger field-validation-valid" data-valmsg-for="Books[__INDEX__].Page" data-valmsg-replace="true"></span> </td> <td> <label class="control-label">Price</label> <input type="number" name="Books[__INDEX__].Price" class="form-control" /> <span class="text-danger field-validation-valid" data-valmsg-for="Books[__INDEX__].Price" data-valmsg-replace="true"></span> </td> </tr> </table>
2. 编写JavaScript动态添加行的函数
在Scripts区块实现addBtn函数,克隆模板行并替换索引,同时重新初始化表单验证:
@section Scripts { <script> function addBtn() { // 获取表格与模板行 const table = document.getElementById('booksTable'); const templateRow = document.getElementById('bookRowTemplate'); // 计算当前索引:现有数据行的数量 const currentIndex = document.querySelectorAll('.book-row').length; // 克隆模板行并移除隐藏属性 const newRow = templateRow.cloneNode(true); newRow.removeAttribute('id'); newRow.classList.add('book-row'); newRow.style.display = ''; // 替换模板中的占位索引为实际值 newRow.innerHTML = newRow.innerHTML.replace(/__INDEX__/g, currentIndex); // 将新行添加到表格末尾 table.appendChild(newRow); // 重新初始化表单验证,确保新增字段能触发验证 $('form').removeData('validator').removeData('unobtrusiveValidation'); $.validator.unobtrusive.parse('form'); } </script> @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } }
3. 后端接收逻辑(保持原有结构即可)
你的ViewModel需包含List<BooksDto> Books,Action直接接收ViewModel就能获取所有数据:
[HttpPost] public IActionResult YourAction(YourViewModel model) { // model.Books会包含原有和新增的所有数据 if (ModelState.IsValid) { // 业务逻辑处理 return RedirectToAction("Index"); } return View(model); }
关键说明
- ASP.NET MVC绑定列表时,要求表单元素的
name属性格式为ListName[Index].PropertyName(比如Books[0].Page),这样后端才能正确将表单数据映射到列表。 - 新增行后必须重新初始化验证,否则新增字段不会触发客户端验证。
- 服务器端Razor代码与客户端JS代码运行环境完全分离,不能在JS函数中直接执行Razor代码,前者在页面渲染时执行,后者在浏览器中运行。
内容的提问来源于stack exchange,提问作者veyekem647
相关产品推荐
相关产品推荐

