ASP.NET Core 7 MVC动态添加行返回DTO文本而非视图问题求助
问题:ASP.NET Core MVC动态添加行显示DTO文本而非控件
我之前在小型基础项目里成功实现了ASP.NET Core 7 MVC的动态添加行功能,现在把这套逻辑用到采用ViewModel和DTO的大型.NET 7项目时出现问题:Razor视图仅输出PurchaseOrderLineItemDto的文本形式,而非预期的表单控件。需求是尽量少用JavaScript,实现简易订单系统的动态行功能,网上现有方案多针对基础模型,求问题原因及解决办法。
当前视图输出
PurchaseOrderLineItemDto { PurchaseOrderLineItemId = 0, LineItemNumber = 0, LineItemDescription = , LineItemQuantity = 0, LineItemUnitPrice = 0, LineItemExtPrice = 0 }
现有代码
主视图代码
@model MvcApplication.ViewModels.PurchaseOrders.PurchaseOrderCreateViewModel @{ ViewData["Title"] = "Create new Order"; } <div class="row"> <div class="container pt-4 table-responsive text-nowrap" id="orderItemsContainer"> <table class="table table-borderless" id="tblPurchaseOrderLineItems"> <thead> @foreach (var orderLineItem in Model.PurchaseOrderLineItems) { <th> @Html.DisplayNameFor(model => orderLineItem.LineItemDescription) </th> <th> @Html.DisplayNameFor(model => orderLineItem.LineItemQuantity) </th> <th> @Html.DisplayNameFor(model => orderLineItem.LineItemUnitPrice) </th> <th> @Html.DisplayNameFor(model => orderLineItem.LineItemExtPrice) </th> } </thead> <tbody> @Html.EditorFor(model => model.PurchaseOrderLineItems) </tbody> </table> </div> </div>
视图模型类
namespace MvcApplication.ViewModels.PurchaseOrders { public record PurchaseOrderCreateViewModel { [DefaultValue(null)] public List<PurchaseOrderLineItemDto> PurchaseOrderLineItems { get; set; } public PurchaseOrderCreateViewModel() { PurchaseOrderLineItems = new List<PurchaseOrderLineItemDto>(); } } }
原编辑器模板
@model MvcApplication.ViewModels.PurchaseOrders.PurchaseOrderCreateViewModel <tr> <td class="col-8"> @Html.TextBoxFor(m => m.PurchaseOrderLineItems, new {style="width: 100%", @class="form-control rounded-0", @type="text"}) </td> <td> <td class="d-flex" style="width:20%;"> <button class="btnDeleteLineItemRow btn btn-danger rounded-0 flex-fill" id="deleteLineItemRow"><i class="fa fa-trash"></i></button> </td> </tr>
控制器添加行方法
[HttpPost] [Route("AddOrderItem")] public async Task<ActionResult> AddOrderItem([Bind("PurchaseOrderLineItems")] PurchaseOrderCreateViewModel purchaseOrderCreateViewModel) { purchaseOrderCreateViewModel.PurchaseOrderLineItems.Add(new PurchaseOrderLineItemDto()); return PartialView("PurchaseOrderLineItems", purchaseOrderCreateViewModel); }
问题原因分析
- 编辑器模板模型错误:原模板绑定的是
PurchaseOrderCreateViewModel,但@Html.EditorFor(model => model.PurchaseOrderLineItems)会遍历列表中的每个PurchaseOrderLineItemDto,需要针对单个Dto创建编辑器模板,而非整个ViewModel。 - 模板绑定目标错误:原模板试图绑定整个
PurchaseOrderLineItems列表,导致Razor直接输出Dto的ToString()结果,而非表单控件。 - 模板命名与路径错误:编辑器模板需放在
Views/Shared/EditorTemplates或对应控制器的Views/[ControllerName]/EditorTemplates目录下,文件名必须与Dto类名一致(PurchaseOrderLineItemDto.cshtml),否则Razor无法自动匹配。 - 表头循环逻辑错误:原表头遍历列表生成重复表头,正确做法是直接引用列表元素的DisplayName,避免重复渲染。
解决步骤
1. 创建正确的编辑器模板
在Views/Shared/EditorTemplates目录下创建PurchaseOrderLineItemDto.cshtml,绑定单个Dto的属性:
@model MvcApplication.ViewModels.PurchaseOrders.PurchaseOrderLineItemDto <tr> <td class="col-8"> @Html.TextBoxFor(m => m.LineItemDescription, new { style = "width: 100%", @class = "form-control rounded-0" }) @Html.HiddenFor(m => m.PurchaseOrderLineItemId) @Html.HiddenFor(m => m.LineItemNumber) </td> <td> @Html.TextBoxFor(m => m.LineItemQuantity, new { style = "width: 100%", @class = "form-control rounded-0", type = "number", min = "1" }) </td> <td> @Html.TextBoxFor(m => m.LineItemUnitPrice, new { style = "width: 100%", @class = "form-control rounded-0", type = "number", step = "0.01", min = "0" }) </td> <td> @Html.TextBoxFor(m => m.LineItemExtPrice, new { style = "width: 100%", @class = "form-control rounded-0", type = "number", step = "0.01", disabled = "disabled" }) </td> <td class="d-flex" style="width:15%;"> <button class="btnDeleteLineItemRow btn btn-danger rounded-0 flex-fill"><i class="fa fa-trash"></i></button> </td> </tr>
2. 修正主视图表头逻辑
删除原表头的循环,直接引用列表元素的DisplayName,同时确保ViewModel初始化时至少有一个空Dto(避免索引越界):
<thead> <th>@Html.DisplayNameFor(model => model.PurchaseOrderLineItems[0].LineItemDescription)</th> <th>@Html.DisplayNameFor(model => model.PurchaseOrderLineItems[0].LineItemQuantity)</th> <th>@Html.DisplayNameFor(model => model.PurchaseOrderLineItems[0].LineItemUnitPrice)</th> <th>@Html.DisplayNameFor(model => model.PurchaseOrderLineItems[0].LineItemExtPrice)</th> <th>操作</th> </thead>
同时修改ViewModel构造函数,初始化一个空行:
public PurchaseOrderCreateViewModel() { PurchaseOrderLineItems = new List<PurchaseOrderLineItemDto> { new PurchaseOrderLineItemDto() }; }
3. 修正PartialView(PurchaseOrderLineItems.cshtml)
确保PartialView正确调用编辑器模板:
@model MvcApplication.ViewModels.PurchaseOrders.PurchaseOrderCreateViewModel @Html.EditorFor(model => model.PurchaseOrderLineItems)
4. 优化控制器方法
移除不必要的Bind属性,确保模型绑定完整:
[HttpPost] [Route("AddOrderItem")] [ValidateAntiForgeryToken] public async Task<ActionResult> AddOrderItem(PurchaseOrderCreateViewModel purchaseOrderCreateViewModel) { purchaseOrderCreateViewModel.PurchaseOrderLineItems.Add(new PurchaseOrderLineItemDto()); return PartialView("PurchaseOrderLineItems", purchaseOrderCreateViewModel); }
5. 添加添加行触发逻辑(极简JS)
在主视图中添加按钮和少量JS,异步获取新行HTML:
<div class="container pt-2"> <button id="btnAddOrderItem" class="btn btn-primary">添加行</button> </div> <script> document.getElementById('btnAddOrderItem').addEventListener('click', async () => { const token = document.querySelector('input[name="__RequestVerificationToken"]').value; const formData = new FormData(); // 收集现有表单数据,保持模型状态 document.querySelectorAll('#tblPurchaseOrderLineItems input').forEach(input => { formData.append(input.name, input.value); }); formData.append('__RequestVerificationToken', token); const response = await fetch('/AddOrderItem', { method: 'POST', body: formData }); if (response.ok) { const html = await response.text(); document.querySelector('#tblPurchaseOrderLineItems tbody').innerHTML = html; } }); </script>
内容的提问来源于stack exchange,提问作者JLudt
相关产品推荐
相关产品推荐

