MVC 3模型绑定子types(抽象类或接口)

说我有一个产品模型,产品模型有一个ProductSubType属性(抽象),我们有两个具体的实现衬衫和裤子。

这里是来源:

public class Product { public int Id { get; set; } [Required] public string Name { get; set; } [Required] public decimal? Price { get; set; } [Required] public int? ProductType { get; set; } public ProductTypeBase SubProduct { get; set; } } public abstract class ProductTypeBase { } public class Shirt : ProductTypeBase { [Required] public string Color { get; set; } public bool HasSleeves { get; set; } } public class Pants : ProductTypeBase { [Required] public string Color { get; set; } [Required] public string Size { get; set; } } 

在我的用户界面中,用户有一个下拉菜单,他们可以select产品types,并根据正确的产品types显示input元素。 我有这一切想通了(使用ajax get下拉更改,返回一个部分/编辑器模板,并相应地重新设置jqueryvalidation)。

接下来,我为ProductTypeBase创build了一个自定义模型绑定器。

  public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { ProductTypeBase subType = null; var productType = (int)bindingContext.ValueProvider.GetValue("ProductType").ConvertTo(typeof(int)); if (productType == 1) { var shirt = new Shirt(); shirt.Color = (string)bindingContext.ValueProvider.GetValue("SubProduct.Color").ConvertTo(typeof(string)); shirt.HasSleeves = (bool)bindingContext.ValueProvider.GetValue("SubProduct.HasSleeves").ConvertTo(typeof(bool)); subType = shirt; } else if (productType == 2) { var pants = new Pants(); pants.Size = (string)bindingContext.ValueProvider.GetValue("SubProduct.Size").ConvertTo(typeof(string)); pants.Color = (string)bindingContext.ValueProvider.GetValue("SubProduct.Color").ConvertTo(typeof(string)); subType = pants; } return subType; } } 

这正确地绑定了这些值,并且大部分工作,除了我失去了服务器端validation。 所以在一个预感,我做了这个不正确的,我做了一些更多的search,并通过Darin Dimitrov这个答案:

ASP.NET MVC 2 – 绑定到抽象模型

所以我切换模型联编程序只覆盖CreateModel,但现在它不绑定值。

 protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { ProductTypeBase subType = null; var productType = (int)bindingContext.ValueProvider.GetValue("ProductType").ConvertTo(typeof(int)); if (productType == 1) { subType = new Shirt(); } else if (productType == 2) { subType = new Pants(); } return subType; } 

虽然MVC 3 src,似乎在BindProperties,GetFilteredModelProperties返回一个空的结果,我认为是因为bindingcontext模型设置为ProductTypeBase没有任何属性。

任何人都可以发现我做错了什么? 这似乎不应该是这样的困难。 我相信我缺less一些简单的东西…我有另一种select,而不是在产品模型中有一个SubProduct属性只有衬衫和裤子的单独的属性。 这些只是视图/表单模型,所以我认为这将工作,但想要得到当前的方法工作,如果有什么要了解正在发生的事情…

谢谢你的帮助!

更新:

我没有说清楚,但是我添加的自定义模型绑定器inheritance自DefaultModelBinder

回答

设置ModelMetadata和Model是缺失的部分。 感谢玛纳斯!

 protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { if (modelType.Equals(typeof(ProductTypeBase))) { Type instantiationType = null; var productType = (int)bindingContext.ValueProvider.GetValue("ProductType").ConvertTo(typeof(int)); if (productType == 1) { instantiationType = typeof(Shirt); } else if (productType == 2) { instantiationType = typeof(Pants); } var obj = Activator.CreateInstance(instantiationType); bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, instantiationType); bindingContext.ModelMetadata.Model = obj; return obj; } return base.CreateModel(controllerContext, bindingContext, modelType); } 

这可以通过重写CreateModel(…)来实现。 我将以一个例子来certificate这一点。

1.让我们创build一个模型和一些基类和子类

 public class MyModel { public MyBaseClass BaseClass { get; set; } } public abstract class MyBaseClass { public virtual string MyName { get { return "MyBaseClass"; } } } public class MyDerievedClass : MyBaseClass { public int MyProperty { get; set; } public override string MyName { get { return "MyDerievedClass"; } } } 

2.现在创build一个modelbinder并覆盖CreateModel

 public class MyModelBinder : DefaultModelBinder { protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { /// MyBaseClass and MyDerievedClass are hardcoded. /// We can use reflection to read the assembly and get concrete types of any base type if (modelType.Equals(typeof(MyBaseClass))) { Type instantiationType = typeof(MyDerievedClass); var obj=Activator.CreateInstance(instantiationType); bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, instantiationType); bindingContext.ModelMetadata.Model = obj; return obj; } return base.CreateModel(controllerContext, bindingContext, modelType); } } 

3.现在在控制器中创build获取和发布操作。

 [HttpGet] public ActionResult Index() { ViewBag.Message = "Welcome to ASP.NET MVC!"; MyModel model = new MyModel(); model.BaseClass = new MyDerievedClass(); return View(model); } [HttpPost] public ActionResult Index(MyModel model) { return View(model); } 

4.现在在global.asax中将MyModelBinder设置为默认的ModelBinder这是为了设置所有动作的默认模型联编程序,对于一个动作,我们可以在动作参数中使用ModelBinder属性)

 protected void Application_Start() { AreaRegistration.RegisterAllAreas(); ModelBinders.Binders.DefaultBinder = new MyModelBinder(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); } 

5.现在我们可以创buildMyModeltypes的视图和MyDerievedClasstypes的局部视图

Index.cshtml

 @model MvcApplication2.Models.MyModel @{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>Index</h2> @using (Html.BeginForm()) { @Html.ValidationSummary(true) <fieldset> <legend>MyModel</legend> @Html.EditorFor(m=>m.BaseClass,"DerievedView") <p> <input type="submit" value="Create" /> </p> </fieldset> } 

DerievedView.cshtml

 @model MvcApplication2.Models.MyDerievedClass @Html.ValidationSummary(true) <fieldset> <legend>MyDerievedClass</legend> <div class="editor-label"> @Html.LabelFor(model => model.MyProperty) </div> <div class="editor-field"> @Html.EditorFor(model => model.MyProperty) @Html.ValidationMessageFor(model => model.MyProperty) </div> </fieldset> 

现在它将按预期工作,Controller将收到一个“MyDerievedClass”types的对象。 validation将按预期进行。

在这里输入图像说明

我有同样的问题,我结束了使用MvcContrib作为这里 sugested 。

文档已经过时了,但是如果你看样本,这很容易。

您必须在Global.asax中注册您的types:

 protected void Application_Start(object sender, EventArgs e) { // (...) DerivedTypeModelBinderCache.RegisterDerivedTypes(typeof(ProductTypeBase), new[] { typeof(Shirt), typeof(Pants) }); } 

将两行添加到您的部分视图中:

 @model MvcApplication.Models.Shirt @using MvcContrib.UI.DerivedTypeModelBinder @Html.TypeStamp() <div> @Html.LabelFor(m => m.Color) </div> <div> @Html.EditorFor(m => m.Color) @Html.ValidationMessageFor(m => m.Color) </div> 

最后,在主视图(使用EditorTemplates )中:

 @model MvcApplication.Models.Product @{ ViewBag.Title = "Products"; } <h2> @ViewBag.Title</h2> @using (Html.BeginForm()) { <div> @Html.LabelFor(m => m.Name) </div> <div> @Html.EditorFor(m => m.Name) @Html.ValidationMessageFor(m => m.Name) </div> <div> @Html.EditorFor(m => m.SubProduct) </div> <p> <input type="submit" value="create" /> </p> } 

以及我有这个相同的问题,我已经以更通用的方式解决了我的想法。 在我的情况下,我通过JSON从后端发送对象到客户端,从客户端发送到后端:

首先在抽象类中,我有我在构造函数中设置的字段:

 ClassDescriptor = this.GetType().AssemblyQualifiedName; 

所以在JSON中,我有ClassDescriptor字段

接下来是编写自定义绑定程序:

 public class SmartClassBinder : DefaultModelBinder { protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { string field = String.Join(".", new String[]{bindingContext.ModelName , "ClassDescriptor"} ); var values = (ValueProviderCollection) bindingContext.ValueProvider; var classDescription = (string) values.GetValue(field).ConvertTo(typeof (string)); modelType = Type.GetType(classDescription); return base.CreateModel(controllerContext, bindingContext, modelType); } } 

而现在我要做的就是用属性来装饰类。 例如:

[ModelBinder(typeof(SmartClassBinder))] public class ConfigurationItemDescription

而已。