ASP.NET Core 3.0 MVC嵌套对象CRUD:动态添加文档表单问题求助
解决方案:ASP.NET Core动态添加表单行并正确绑定列表模型
你的问题核心在于动态生成的表单元素没有符合ASP.NET Core模型绑定要求的name属性格式,导致提交后Documents列表无法被正确绑定。下面是针对ASP.NET Core的完整解决方案,不需要依赖第三方包,完全用原生方式实现:
1. 调整主视图(AddFolder.cshtml)
我们需要修改视图,确保动态添加的文档行拥有正确的name属性格式(Documents[唯一索引].Name、Documents[唯一索引].Path)。ASP.NET Core的模型绑定支持非连续的索引(甚至字符串索引),所以可以用GUID生成唯一标识:
@model Folder <div> <h3>Add Folder</h3> <input id="add-document" type="button" value="Add Document" class="btn btn-primary" /> </div> <form asp-controller="Folder" asp-action="AddFolder" method="post" role="form"> <div class="form-group form-row"> <label asp-for="Name" class="col-sm-2 col-form-label"></label> <div class="col-sm-10"> <input asp-for="Name" type="text" class="form-control" /> <span asp-validation-for="Name" class="text-danger"></span> </div> </div> <div class="form-group form-row"> <table class="table table-hover"> <thead> <tr> <th>DOCUMENT NAME</th> <th>DOCUMENT PATH</th> <th></th> </tr> </thead> <tbody id="documentRows"> <!-- 初始添加一行空文档行 --> @await Html.PartialAsync("_DocumentRow", new Document(), new ViewDataDictionary(ViewData) { { "Index", Guid.NewGuid().ToString() } }) </tbody> </table> </div> <div class="form-group form-row"> <div class="col-sm-10 offset-sm-2"> <button type="submit" class="btn btn-success">Save Folder</button> </div> </div> </form> <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script> <script type="text/javascript"> $(document).ready(function () { $("#add-document").click(function () { // 生成唯一索引 const index = generateGuid(); // 请求新的文档行局部视图 $.ajax({ url: '@Url.Action("GetNewDocumentRow")', data: { index: index }, type: "GET", success: function (html) { $("#documentRows").append(html); } }); }); // 新增删除行功能 $(document).on("click", ".delete-row", function () { $(this).closest("tr").remove(); }); }); // 辅助生成GUID的函数 function generateGuid() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } </script>
2. 创建文档行局部视图(_DocumentRow.cshtml)
这个局部视图负责生成单个文档的表单行,使用传入的索引构造符合模型绑定要求的name属性:
@model Document @{ var index = ViewData["Index"] as string; } <tr> <td> <input type="text" name="Documents[@index].Name" value="@Model.Name" class="form-control" placeholder="Enter document name" /> <span class="text-danger" data-valmsg-for="Documents[@index].Name" data-valmsg-replace="true"></span> </td> <td> <input type="text" name="Documents[@index].Path" value="@Model.Path" class="form-control" placeholder="Enter document path" /> <span class="text-danger" data-valmsg-for="Documents[@index].Path" data-valmsg-replace="true"></span> </td> <td> <button type="button" class="btn btn-sm btn-danger delete-row">Delete</button> </td> </tr>
3. 调整控制器方法
添加返回文档行局部视图的方法,以及处理表单提交的方法:
using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; public class FolderController : Controller { // 返回新的文档行局部视图 public IActionResult GetNewDocumentRow(string index) { return PartialView("_DocumentRow", new Document(), new ViewDataDictionary(ViewData) { { "Index", index } }); } // 处理表单提交 [HttpPost] public IActionResult AddFolder(Folder folder) { if (ModelState.IsValid) { // 执行你的保存逻辑,比如存入数据库 // _dbContext.Folders.Add(folder); // await _dbContext.SaveChangesAsync(); return RedirectToAction("Index"); } // 验证失败时返回原视图 return View(folder); } } // 你的原有模型类 public class Folder { public Folder() { Documents = new List<Document>(); } public string Name { get; set; } public List<Document> Documents { get; set; } } public class Document { public string Name { get; set; } public string Path { get; set; } }
关键说明
- 模型绑定格式:ASP.NET Core要求列表项的表单元素
name属性必须为[列表属性名][索引].[属性名](比如Documents[abc123].Name),这样模型绑定器才能识别并将其添加到Folder.Documents列表中。 - 唯一索引:使用GUID作为索引可以避免重复,而且模型绑定不要求索引是连续数字,只要唯一即可。
- 验证支持:如果需要客户端验证,确保页面引入了
jquery.validate.js和jquery.validate.unobtrusive.js脚本,视图中的验证消息元素会自动显示错误提示。
为什么你的第二种方案失败?
你使用的Html.BeginCollectionItem是ASP.NET MVC时代的第三方扩展,在ASP.NET Core中没有内置支持,且它生成的name属性格式不符合ASP.NET Core的模型绑定规则,导致提交后列表为空。上面的方案完全基于ASP.NET Core原生功能,不需要额外依赖。
内容的提问来源于stack exchange,提问作者Vasileios Kagklis
相关产品推荐
相关产品推荐

