假设我有一个Product模型,Product模型具有ProductSubType(abstract)的属性,我们有两个具体的实现Shirt和Pants。
以下是来源:
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; }
}
在我的用户界面中,用户有一个下拉列表,他们可以选择产品类型,并根据正确的产品类型显示输入元素。我已完成所有这些(使用ajax get on dropdown更改,返回部分/编辑器模板并相应地重新设置jquery验证)。
接下来,我为ProductTypeBase创建了一个自定义模型绑定器。
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;
}
}
这会正确绑定值并在大多数情况下工作,除了我丢失了服务器端验证。因此,我预感到我做错了,我做了一些搜索,并找到了Darin Dimitrov的答案:
ASP.NET MVC 2 - Binding To Abstract Model
所以我将模型绑定器切换为仅覆盖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,它没有任何属性。
有人能发现我做错了吗?这似乎不应该是这么困难。我确信我遗漏了一些简单的东西...我有另一种选择,而不是在产品模型中有一个SubProduct属性,只有衬衫和裤子的单独属性。这些只是视图/表单模型,所以我认为这样可行,但是如果有什么需要了解正在发生的事情,那么希望当前的方法有效...
感谢您的帮助!
我没说清楚,但我添加的自定义模型绑定器继承自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);
}
答案 0 :(得分:57)
这可以通过覆盖CreateModel(...)来实现。我将通过一个例子证明这一点。
<强> 1。让我们创建一个模型和一些基础和子类。
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。现在创建一个模型绑定器并覆盖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。现在在控制器中创建get和post动作。
[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。现在将MyModelBinder设置为global.asax中的默认ModelBinder 这样做是为所有操作设置默认模型绑定器,对于单个操作,我们可以在操作参数中使用ModelBinder属性)
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
ModelBinders.Binders.DefaultBinder = new MyModelBinder();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
<强> 5。现在我们可以创建MyModel类型的视图和MyDerievedClass类型的部分视图
<强> 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”类型的Object。 验证将按预期进行。
答案 1 :(得分:4)
我遇到了同样的问题,最后我使用MvcContrib作为su here。
documentation已过时,但如果您查看样本,则非常简单。
您必须在Global.asax中注册您的类型:
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>
}
答案 2 :(得分:1)
好 我遇到了同样的问题,我想以更一般的方式解决了这个问题。 在我的情况下,我通过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))] 公共类ConfigurationItemDescription
就是这样。