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

如何用JavaScript新增表单表格行并提交至ASP.NET Core控制器

实现动态新增子表单行并提交到ASP.NET Core控制器

我之前处理过类似的场景,核心是要让ASP.NET Core的模型绑定能正确识别你新增的子项,关键在于遵循集合类型的命名规则。下面一步步给你讲具体实现:

1. 调整ViewModel和初始视图结构

首先确保你的IssueViewModel里的子项集合用List<T>(模型绑定对List支持更好),比如:

public class IssueViewModel
{
    // 父项属性
    public string Title { get; set; }
    // 子项集合,初始化避免空引用
    public List<IssueItemViewModel> ChildItems { get; set; } = new List<IssueItemViewModel>();
}

public class IssueItemViewModel
{
    public string Description { get; set; }
    public int Quantity { get; set; }
}

然后修改视图,用for循环生成初始子行(不要用foreach,因为foreach不会生成带索引的name属性):

@model IssueViewModel
@section css {
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" />
}
<h2>New Issue</h2>

<form asp-action="Create" method="post">
    <!-- 父项表单字段 -->
    <div class="form-group">
        <label asp-for="Title"></label>
        <input asp-for="Title" class="form-control" />
        <span asp-validation-for="Title" class="text-danger"></span>
    </div>

    <!-- 子项表格 -->
    <h4>Issue Items</h4>
    <table id="childItemsTable" class="table">
        <thead>
            <tr>
                <th>Description</th>
                <th>Quantity</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            @for (int i = 0; i < Model.ChildItems.Count; i++)
            {
                <tr class="child-item-row">
                    <td>
                        <input asp-for="ChildItems[i].Description" class="form-control" />
                        <span asp-validation-for="ChildItems[i].Description" class="text-danger"></span>
                    </td>
                    <td>
                        <input asp-for="ChildItems[i].Quantity" type="number" class="form-control" />
                        <span asp-validation-for="ChildItems[i].Quantity" class="text-danger"></span>
                    </td>
                    <td>
                        @if (i > 0)
                        {
                            <button type="button" class="btn btn-danger btn-remove-row">Remove</button>
                        }
                    </td>
                </tr>
            }
        </tbody>
    </table>

    <button type="button" id="btnAddRow" class="btn btn-primary">Add New Item</button>
    <button type="submit" class="btn btn-success">Save</button>
</form>

2. 编写JavaScript实现动态增删行

用jQuery来处理行的克隆、索引更新和移除逻辑,关键是要更新所有表单元素的name和id属性里的索引值,确保符合ChildItems[index].PropertyName的格式:

@section scripts {
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>
        $(document).ready(function() {
            // 添加新行
            $('#btnAddRow').click(function() {
                const tableBody = $('#childItemsTable tbody');
                const rowCount = tableBody.find('.child-item-row').length;
                const lastRow = tableBody.find('.child-item-row:last');
                
                // 克隆最后一行并清空输入值
                const newRow = lastRow.clone();
                newRow.find(':input').each(function() {
                    // 替换name和id里的索引
                    const newName = $(this).attr('name').replace(/\[(\d+)\]/, `[${rowCount}]`);
                    const newId = $(this).attr('id').replace(/\_(\d+)\_/, `_${rowCount}_`);
                    $(this).attr('name', newName).attr('id', newId).val('');
                    // 清除旧的验证提示
                    $(this).next('.text-danger').text('');
                });

                // 给新行添加移除按钮
                newRow.find('.btn-remove-row').remove();
                newRow.find('td:last').html('<button type="button" class="btn btn-danger btn-remove-row">Remove</button>');
                
                tableBody.append(newRow);
            });

            // 移除行(用事件委托处理动态生成的按钮)
            $('#childItemsTable').on('click', '.btn-remove-row', function() {
                $(this).closest('.child-item-row').remove();
                
                // 移除后重新更新所有行的索引,避免模型绑定跳过项
                $('#childItemsTable tbody .child-item-row').each(function(index) {
                    $(this).find(':input').each(function() {
                        const updatedName = $(this).attr('name').replace(/\[(\d+)\]/, `[${index}]`);
                        const updatedId = $(this).attr('id').replace(/\_(\d+)\_/, `_${index}_`);
                        $(this).attr('name', updatedName).attr('id', updatedId);
                    });
                });
            });
        });
    </script>
}

3. 控制器动作处理提交

确保你的Create动作接收IssueViewModel,并处理模型验证和数据库保存:

[HttpPost]
public IActionResult Create(IssueViewModel model)
{
    if (ModelState.IsValid)
    {
        // 将ViewModel转换为实体并保存到数据库
        var issue = new Issue
        {
            Title = model.Title,
            IssueItems = model.ChildItems.Select(item => new IssueItem
            {
                Description = item.Description,
                Quantity = item.Quantity
            }).ToList()
        };

        _context.Issues.Add(issue);
        _context.SaveChanges();

        return RedirectToAction(nameof(Index));
    }

    // 验证失败时,确保子项集合不为空,返回视图时显示至少一行
    if (model.ChildItems == null || model.ChildItems.Count == 0)
    {
        model.ChildItems = new List<IssueItemViewModel> { new IssueItemViewModel() };
    }
    return View(model);
}

关键注意点

  • 模型绑定命名规则:必须保证子项表单元素的name属性是ChildItems[index].PropertyName格式,ASP.NET Core才能将其识别为集合的一部分。
  • 索引连续:移除行后必须重新排序索引,否则中间有空缺的索引会导致模型绑定跳过后面的子项。
  • 验证处理:克隆行时要清除旧的验证提示,避免错误信息残留。

内容的提问来源于stack exchange,提问作者ifinlay

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:24:00