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

.NET 6动态生成表单字段如何绑定到ViewModel并传递给控制器

.NET 6动态表单行绑定ViewModel解决方案

问题根源

原代码存在两个核心问题:

  • 页面模型用了IEnumerable<AddToMealViewModel>,但ASP.NET模型绑定需要可索引的集合类型(比如List<T>)才能识别批量提交的字段
  • 克隆表单行时没有更新输入控件的name属性索引,导致所有行的字段名称重复,控制器无法区分不同条目

解决方案步骤

1. 确认ViewModel结构(补充示例)

首先确保你的AddToMealViewModel包含必要属性:

public class AddToMealViewModel
{
    public int SelectedMealId { get; set; }
    public int SelectedProductId { get; set; }
    public decimal Amount { get; set; }

    // 下拉列表数据源(建议从控制器传入ViewData,而非ViewModel本身)
    public List<Meal> Meals { get; set; }
    public List<Product> Products { get; set; }
}

// 辅助实体示例
public class Meal { public int Id { get; set; } public string Name { get; set; } }
public class Product { public int Id { get; set; } public string Name { get; set; } }

2. 调整页面模型与表单渲染

把页面模型改为List<AddToMealViewModel>,用for循环渲染初始行(确保每个控件的name带索引):

@model List<AddToMealViewModel>

<button class="btn btn-success" onclick="addMore()">Add more</button>
<form method="post">
    <div class="wrapper">
        @for (int i = 0; i < Model.Count; i++)
        {
            <div class="row-fluid">
                <div>
                    @Html.DropDownListFor(m => m[i].SelectedMealId,
                             new SelectList(Model[i].Meals, "Id", "Name"),
                             "Select")
                    @Html.DropDownListFor(m => m[i].SelectedProductId,
                             new SelectList(Model[i].Products, "Id", "Name"),
                             "Select")
                    <input asp-for="@Model[i].Amount" />
                </div>
            </div>
        }
    </div>
    <button type="submit" class="btn btn-primary">Add</button>
</form>

3. 修改动态添加行的脚本

克隆后要更新新行所有控件的name、id属性的索引,并清空输入值:

<script>
    function addMore() {
        const wrapper = document.querySelector('.wrapper');
        const lastRow = wrapper.querySelector('.row-fluid:last-child');
        const newRow = lastRow.cloneNode(true);
        
        // 获取当前最大索引,计算新索引
        const rows = wrapper.querySelectorAll('.row-fluid');
        const newIndex = rows.length;

        // 更新新行内所有表单控件的name和id
        newRow.querySelectorAll('select, input').forEach(elem => {
            // 替换旧索引为新索引
            elem.name = elem.name.replace(/\[\d+\]/, `[${newIndex}]`);
            elem.id = elem.id.replace(/\[\d+\]/, `[${newIndex}]`);
            // 清空输入值
            elem.value = '';
            // 重置下拉框到默认选项
            if (elem.tagName === 'SELECT') {
                elem.selectedIndex = 0;
            }
        });

        wrapper.appendChild(newRow);
    }
</script>

4. 控制器接收参数

控制器的Post方法直接接收List<AddToMealViewModel>类型参数:

[HttpPost]
public IActionResult AddMeals(List<AddToMealViewModel> model)
{
    if (ModelState.IsValid)
    {
        // 处理提交的数据
        return RedirectToAction("Index");
    }

    // 验证失败时,重新填充下拉数据源并返回页面
    foreach (var item in model)
    {
        item.Meals = GetMeals(); // 你的获取Meals逻辑
        item.Products = GetProducts(); // 你的获取Products逻辑
    }
    return View(model);
}

// 模拟数据源方法
private List<Meal> GetMeals() => new List<Meal> { new Meal { Id = 1, Name = "早餐" }, new Meal { Id = 2, Name = "午餐" } };
private List<Product> GetProducts() => new List<Product> { new Product { Id = 1, Name = "面包" }, new Product { Id = 2, Name = "牛奶" } };

额外优化建议

  • 下拉列表的数据源(Meals、Products)建议通过ViewData或ViewBag传入,避免每个ViewModel实例都重复携带相同数据
  • 可以给动态添加的行添加删除按钮,提升用户体验
  • 前端可以添加表单验证逻辑,确保每一行的字段都填写完整

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 04:06:27