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
相关产品推荐
相关产品推荐

