.NET Framework MVC中如何HTTP POST动态长度的派生模型集合?
问题场景
现有一组继承自基类Shape的派生类模型:
public class Shape { public string Color { get; set; } } public class Circle : Shape { public int Radius { get; set; } } public class Triangle : Shape { public string Type { get; set; } // 例如"isosceles"、"equilateral"等 }
控制器通过MyShapes Action返回包含多种形状的集合视图:
[HttpGet] public ActionResult MyShapes() { List<Shape> listOfShapes = GetShapesFromDatabase(); return View(listOfShapes); }
当前视图通过判断类型渲染分部视图,但回发时只能通过AJAX序列化数据,不符合MVC原生模式。需要实现原生表单提交,让控制器正确接收修改后的不同类型形状集合。
解决方案:基于EditorTemplates与自定义ModelBinder的实现
1. 创建派生类的EditorTemplates
在Views/Shared/EditorTemplates目录下,为每个派生类创建对应的模板文件,模板中需包含类型标识隐藏字段,用于后续ModelBinder识别具体类型:
Circle.cshtml
@model Circle <div class="shape-item"> @Html.Hidden("[$index].ShapeType", "Circle") @Html.LabelFor(m => m.Color) @Html.EditorFor(m => m.Color) @Html.LabelFor(m => m.Radius) @Html.EditorFor(m => m.Radius) </div>
Triangle.cshtml
@model Triangle <div class="shape-item"> @Html.Hidden("[$index].ShapeType", "Triangle") @Html.LabelFor(m => m.Color) @Html.EditorFor(m => m.Color) @Html.LabelFor(m => m.Type) @Html.EditorFor(m => m.Type) </div>
2. 修改主视图,自动匹配EditorTemplate
使用Html.EditorFor(m => m)替代原有的foreach判断,MVC会根据集合中每个元素的类型自动加载对应的模板:
@model List<Shape> @using (Html.BeginForm("SaveShapes", "Shape", FormMethod.Post)) { <p>修改形状。</p> @Html.EditorFor(m => m) <button type="button" id="add-shape">添加新形状</button> <button type="submit">保存更改</button> } <!-- 隐藏的新形状模板容器,用于动态添加 --> <div id="shape-templates" style="display:none;"> <div id="circle-template"> @Html.EditorFor(m => new Circle()) </div> <div id="triangle-template"> @Html.EditorFor(m => new Triangle()) </div> </div>
3. 实现自定义ModelBinder
创建ShapeModelBinder,通过隐藏字段的ShapeType值实例化对应的派生类,确保MVC能正确绑定集合中的不同类型:
public class ShapeModelBinder : DefaultModelBinder { protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { var shapeTypeValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".ShapeType"); if (shapeTypeValue != null) { string shapeType = shapeTypeValue.AttemptedValue; // 替换为你的命名空间和程序集名称 Type concreteType = Type.GetType($"YourNamespace.{shapeType}, YourAssembly"); if (concreteType != null && typeof(Shape).IsAssignableFrom(concreteType)) { var model = Activator.CreateInstance(concreteType); bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, concreteType); return model; } } return base.CreateModel(controllerContext, bindingContext, modelType); } }
在全局注册该ModelBinder(Global.asax或Startup.cs):
// Global.asax Application_Start方法内 ModelBinders.Binders.Add(typeof(Shape), new ShapeModelBinder());
4. 控制器接收Action
现在可以直接接收List<Shape>参数,集合中会自动包含正确类型的实例:
[HttpPost] public ActionResult SaveShapes(List<Shape> shapes) { // 处理数据库更新逻辑 UpdateShapesInDatabase(shapes); return RedirectToAction("MyShapes"); }
5. 动态添加新形状的前端处理
添加JS代码实现动态克隆模板并替换索引,确保新添加的形状能正确回发:
document.getElementById('add-shape').addEventListener('click', function() { var shapeType = prompt("选择要添加的形状类型:Circle/Triangle"); if (!shapeType) return; var templateId = shapeType.toLowerCase() + "-template"; var template = document.getElementById(templateId).innerHTML; var shapesContainer = document.querySelector('.shape-item').parentNode; var index = document.querySelectorAll('.shape-item').length; // 替换模板中的[$index]为当前集合长度(新元素的索引) var newShapeHtml = template.replace(/\[\$index\]/g, `[${index}]`); shapesContainer.insertAdjacentHTML('beforeend', newShapeHtml); });
内容的提问来源于stack exchange,提问作者RotundChinchilla
相关产品推荐
相关产品推荐

