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

在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 00:30:45