You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
}

问题原因分析

  1. 编辑器模板模型错误:原模板绑定的是PurchaseOrderCreateViewModel,但@Html.EditorFor(model => model.PurchaseOrderLineItems)会遍历列表中的每个PurchaseOrderLineItemDto,需要针对单个Dto创建编辑器模板,而非整个ViewModel。
  2. 模板绑定目标错误:原模板试图绑定整个PurchaseOrderLineItems列表,导致Razor直接输出Dto的ToString()结果,而非表单控件。
  3. 模板命名与路径错误:编辑器模板需放在Views/Shared/EditorTemplates或对应控制器的Views/[ControllerName]/EditorTemplates目录下,文件名必须与Dto类名一致(PurchaseOrderLineItemDto.cshtml),否则Razor无法自动匹配。
  4. 表头循环逻辑错误:原表头遍历列表生成重复表头,正确做法是直接引用列表元素的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 08:30:07