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

ASP.NET MVC提交表单时如何将嵌套复杂模型完整回传至控制器

问题背景

模型定义

ConflictModel

public class ConflictModel
{
    public ConflictModel()
    {
        ...
    }
    public GeneralViewModel BaseTable { get; set; }
    public GeneralViewModel Table1{ get; set; }
    public GeneralViewModel Table2 { get; set; }
    public List<dynamic> DistinctRows { get; set; }
    public List<string> Values { get; set; }
}

GeneralViewModel

public class GeneralViewModel
{
    public List<RowTypeModel> RowType {get; set;}
    public List<RowTypeChangesModel> RowTypeChanges { get; set; }
}

当前可正常工作的代码(Values字段回传)

@using (Html.BeginForm("ConflictResolved", "Home", FormMethod.Post))
{
    ...
    @Html.DropDownListFor(m => m.Values, new SelectList(selectList))
}

尝试过但无效的反射+JS代码

JS处理部分

<script>
    $(document).ready(function() {
        $("input[type='hidden']").each(function() {
            var actualPropertyName = $(this).attr("id").substr(0, ($(this).attr("id").length - 2));
            var value = $("#" + actualPropertyName).val();
            if (value != null) {
                $(this).remove();
            } else {
                $(this).attr("name", actualPropertyName);
            }
        });
    });
</script>

反射生成隐藏字段部分

@model Models.GeneralViewModel

@{
    var gvmProps = @Model.GetType().GetProperties();
    
}

@foreach (var gvmProp in gvmProps)
{
    var item = (System.Collections.IList)gvmProp.GetValue(@Model);
    if (item != null && item.Count > 0)
    {
        //This is the List<model> which has been populated so find the properties and put in the hidden tag
        var props = item[0].GetType().GetProperties();
        for (int i = 0; i < item.Count; i++)
        {
            foreach (var prop in props)
            {
                if (prop.GetValue(item[i]) != null)
                {
                    <input type="hidden" name="@(String.Format("{0}_2", prop.Name))" id="@(String.Format("{0}_2", prop.Name))" value="@prop.GetValue(item[i], null)" />
                }                    
            }
        }
    }
}

核心问题分析

之前的方案失败原因是生成的隐藏字段name属性不符合ASP.NET MVC模型绑定规则:MVC要求嵌套属性/集合的字段命名必须是父属性.子属性[索引].字段名(如Table1.RowType[0].Id),而你生成的是PropertyName_2这种无层级的命名,模型绑定器无法识别这些字段属于哪个嵌套属性,导致Table1/Table2回传后为null。


可行解决方案

以下两种方案均支持GeneralViewModel未来扩展属性,无需手动修改视图代码。

方案1:通用强类型隐藏字段生成扩展

通过Razor扩展方法递归遍历模型属性,自动生成符合绑定规则的隐藏字段。

步骤1:创建Razor扩展类

using System;
using System.Collections;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Mvc.Html;

public static class HtmlHelperExtensions
{
    public static MvcHtmlString GenerateHiddenFieldsForNestedModel<TModel>(this HtmlHelper<TModel> html, string parentPrefix, object model)
    {
        if (model == null) return MvcHtmlString.Empty;
        
        var sb = new System.Text.StringBuilder();
        var modelType = model.GetType();
        
        foreach (var prop in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            var propValue = prop.GetValue(model);
            if (propValue == null) continue;
            
            // 处理集合类型(List、IEnumerable等)
            if (typeof(IEnumerable).IsAssignableFrom(prop.PropertyType) && prop.PropertyType != typeof(string))
            {
                var collection = (IEnumerable)propValue;
                int index = 0;
                foreach (var item in collection)
                {
                    if (item == null) continue;
                    var itemPrefix = $"{parentPrefix}.{prop.Name}[{index}]";
                    sb.Append(GenerateHiddenFieldsForNestedModel(html, itemPrefix, item));
                    index++;
                }
            }
            // 处理值类型或非集合引用类型
            else
            {
                var fullName = $"{parentPrefix}.{prop.Name}";
                var hiddenField = html.Hidden(fullName, propValue).ToHtmlString();
                sb.Append(hiddenField);
            }
        }
        
        return MvcHtmlString.Create(sb.ToString());
    }
}

步骤2:在主视图中调用扩展方法

主视图模型为ConflictModel,直接在表单内生成Table1/Table2的隐藏字段:

@using (Html.BeginForm("ConflictResolved", "Home", FormMethod.Post))
{
    // 原有Values下拉框
    @Html.DropDownListFor(m => m.Values, new SelectList(selectList))
    
    // 生成Table1的所有嵌套属性隐藏字段
    @Html.GenerateHiddenFieldsForNestedModel("Table1", Model.Table1)
    
    // 生成Table2的所有嵌套属性隐藏字段
    @Html.GenerateHiddenFieldsForNestedModel("Table2", Model.Table2)
    
    <input type="submit" value="Submit" />
}

该方法会自动生成Table1.RowType[0].XXX、Table2.RowTypeChanges[1].XXX格式的隐藏字段,模型绑定器可直接识别并完整绑定嵌套模型。


方案2:JSON序列化+单个隐藏字段

将嵌套模型序列化为JSON字符串,提交后在控制器反序列化,代码更简洁。

步骤1:视图中添加JSON隐藏字段

@using (Html.BeginForm("ConflictResolved", "Home", FormMethod.Post))
{
    @Html.DropDownListFor(m => m.Values, new SelectList(selectList))
    
    // 序列化Table1为JSON
    @Html.Hidden("Table1Json", Newtonsoft.Json.JsonConvert.SerializeObject(Model.Table1))
    
    // 序列化Table2为JSON
    @Html.Hidden("Table2Json", Newtonsoft.Json.JsonConvert.SerializeObject(Model.Table2))
    
    <input type="submit" value="Submit" />
}

步骤2:控制器中反序列化

修改Action方法,接收JSON字符串并转换为模型:

[HttpPost]
public ActionResult ConflictResolved(ConflictModel model, string Table1Json, string Table2Json)
{
    if (!string.IsNullOrEmpty(Table1Json))
    {
        model.Table1 = Newtonsoft.Json.JsonConvert.DeserializeObject<GeneralViewModel>(Table1Json);
    }
    if (!string.IsNullOrEmpty(Table2Json))
    {
        model.Table2 = Newtonsoft.Json.JsonConvert.DeserializeObject<GeneralViewModel>(Table2Json);
    }
    
    // 后续业务逻辑处理
    return View();
}

注意:需确保项目引用Newtonsoft.Json(Json.NET),若使用.NET Core可替换为System.Text.Json。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 15:45:00