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
而已。