MVC 3模型绑定一个子类型(抽象类或接口) [英] MVC 3 Model Binding a Sub Type (Abstract Class or Interface)

查看:102
本文介绍了MVC 3模型绑定一个子类型(抽象类或接口)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

下面是源:

 公共类产品
 {
    公众诠释标识{搞定;组; }    [需要]
    公共字符串名称{;组; }    [需要]
    公共小数?价格{搞定;组; }    [需要]
    公众诠释? ProductType {搞定;组; }    公共ProductTypeBase子产品{搞定;组; }
}公共抽象类ProductTypeBase {}公共类衬衫:ProductTypeBase
{
    [需要]
    公共串色{搞定;组; }
    公共BOOL HasSleeves {搞定;组; }
}公共类裤子:ProductTypeBase
{
    [需要]
    公共串色{搞定;组; }
    [需要]
    公共字符串大小{搞定;组; }
}

在我的UI,用户有一个下拉菜单,可以选择的产品类型和输入元素是按照正确的产品类型显示。我已经这一切想通了(使用Ajax获得下拉变化,因此返回部分/编辑模板并重新安装了jQuery验证)。

接下来我创建了一个自定义模型粘结剂ProductTypeBase。

 公众覆盖对象BindModel(ControllerContext controllerContext,ModelBindingContext的BindingContext)
 {        ProductTypeBase子类型= NULL;        变种productType =(int)的bindingContext.ValueProvider.GetValue(ProductType)的ConvertTo(typeof运算(INT))。        如果(productType == 1)
        {
            VAR衬衫=新衬衫();            shirt.Color =(字符串)bindingContext.ValueProvider.GetValue(SubProduct.Color)的ConvertTo(typeof运算(字符串))。
            shirt.HasSleeves =(布尔)bindingContext.ValueProvider.GetValue(SubProduct.HasSleeves)的ConvertTo(typeof运算(布尔))。            子类型=衬衫;
        }
        否则如果(productType == 2)
        {
            VAR裤子=新裤子();            pants.Size =(字符串)bindingContext.ValueProvider.GetValue(SubProduct.Size)的ConvertTo(typeof运算(字符串))。
            pants.Color =(字符串)bindingContext.ValueProvider.GetValue(SubProduct.Color)的ConvertTo(typeof运算(字符串))。            子类型=裤;
        }        返回子类型;    }
}

本绑定正确的价值观和工作在大多数情况下,除非我失去了服务器端验证。因此,对一个预感,我做这个错误我做了一些更多的搜索和达林季米特洛夫跨越这个答案来了:

ASP.NET MVC 2 - 结合抽象模型

于是我打开模型绑定只覆盖CreateModel,但现在它不绑定的值。

 保护覆盖对象CreateModel(ControllerContext controllerContext,ModelBindingContext的BindingContext,类型modelType)
    {
        ProductTypeBase子类型= NULL;        变种productType =(int)的bindingContext.ValueProvider.GetValue(ProductType)的ConvertTo(typeof运算(INT))。        如果(productType == 1)
        {
            子类型=新衬衫();
        }
        否则如果(productType == 2)
        {
            子类型=新裤子();
        }        返回子类型;
    }

步进虽然MVC 3 SRC,好像在BindProperties的GetFilteredModelProperties返回一个空的结果,我想是因为的BindingContext模型设置为ProductTypeBase不具有任何属性。

任何人能发现什么,我做错了什么?这似乎并不像它应该是这个困难。我相信,我失去了一些东西简单...我有另一种选择考虑的,而不是在产品型号,只是有上衣和裤子分开的性质有一个子产品属性。这些都只是查看/表款,所以我认为这工作,但想获得当前的方法工作,如果什么了解正在发生的事情...

感谢您的帮助!

更新:

我没有说清楚,但自定义模型绑定我说,从DefaultModelBinder

继承

设置ModelMetadata和型号是缺少的部分。玛纳斯谢谢!

 保护覆盖对象CreateModel(ControllerContext controllerContext,ModelBindingContext的BindingContext,类型modelType)
        {
            如果(modelType.Equals(typeof运算(ProductTypeBase))){
                键入instantiationType = NULL;                变种productType =(int)的bindingContext.ValueProvider.GetValue(ProductType)的ConvertTo(typeof运算(INT))。                如果(productType == 1){
                    instantiationType = typeof运算(衬衫);
                }
                否则如果(productType == 2){
                    instantiationType = typeof运算(裤);
                }                VAR OBJ = Activator.CreateInstance(instantiationType);
                bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(NULL,instantiationType);
                bindingContext.ModelMetadata.Model = OBJ;
                返回OBJ;
            }            返回base.CreateModel(controllerContext,BindingContext中,modelType);        }


解决方案

这可以通过覆盖CreateModel(...)来实现的。我将演示与一个例子。

1。让我们创建一个模型以及一些基础类和子类

 公共类为MyModel
{
    公共MyBaseClass的BaseClass {搞定;组; }
}公共抽象类MyBaseClass
{
    公共虚拟字符串MYNAME
    {
        得到
        {
            返回MyBaseClass
        }
    }
}公共类MyDerievedClass:MyBaseClass
{    公众诠释myProperty的{搞定;组; }
    公众覆盖字符串MYNAME
    {
        得到
        {
            返回MyDerievedClass
        }
    }
}

2。现在创建一个模型绑定器,并覆盖CreateModel

 公共类MyModelBinder:DefaultModelBinder
{
    保护覆盖对象CreateModel(ControllerContext controllerContext,ModelBindingContext的BindingContext,类型modelType)
    {
        /// MyBaseClass和MyDerievedClass很难codeD。
        ///我们可以使用反射来阅读组装,并得到具体类型的基本类型的
        如果(modelType.Equals(typeof运算(MyBaseClass)))
        {
            键入instantiationType = typeof运算(MyDerievedClass);
            VAR OBJ = Activator.CreateInstance(instantiationType);
            bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(NULL,instantiationType);
            bindingContext.ModelMetadata.Model = OBJ;
            返回OBJ;
        }
        返回base.CreateModel(controllerContext,BindingContext中,modelType);
    }}

3。现在,在控制器创建get和post行动。

  [HTTPGET]
公众的ActionResult指数()
    {
        ViewBag.Message =欢迎使用ASP.NET MVC!;        为MyModel模型=新为MyModel();
        model.BaseClass =新MyDerievedClass();        返回查看(模型);
    }    [HttpPost]
    公众的ActionResult指数(为MyModel模型)
    {        返回查看(模型);
    }

4。立即设置MyModelBinder作为默认ModelBinder的Global.asax中这样做是为了设置默认模型绑定的所有动作,一个动作,我们可以使用动作参数ModelBinder的属性)

 保护无效的Application_Start()
    {
        AreaRegistration.RegisterAllAreas();        ModelBinders.Binders.DefaultBinder =新MyModelBinder();        RegisterGlobalFilters(GlobalFilters.Filters);
        的RegisterRoutes(RouteTable.Routes);
    }

5。现在,我们可以创建视图类型为MyModel和类型MyDerievedClass的局部视图的

Index.cshtml

  @model MvcApplication2.Models.MyModel@ {
ViewBag.Title =指数;
布局=〜/查看/共享/ _Layout.cshtml
}< H2>指数< / H>@using(Html.BeginForm()){
@ Html.ValidationSummary(真)
<&字段集GT;
    <传奇>及为MyModel LT; /传说>
    @ Html.EditorFor(M => m.BaseClassDerievedView)
    &所述p为H.;
        <输入类型=提交值=创建/>
    &所述; / P>
< /字段集>
}

DerievedView.cshtml

  @model MvcApplication2.Models.MyDerievedClass@ Html.ValidationSummary(真)
<&字段集GT;
    <传奇> MyDerievedClass< /传说>    < D​​IV CLASS =编辑标记>
        @ Html.LabelFor(型号=> model.MyProperty)
    < / DIV>
    < D​​IV CLASS =主编场>
        @ Html.EditorFor(型号=> model.MyProperty)
        @ Html.ValidationMessageFor(型号=> model.MyProperty)
    < / DIV>< /字段集>

现在它将会按预期工作,控制器将获得类型的对象MyDerievedClass。
如预期的验证会发生。

Say I have a Product model, the Product model has a property of ProductSubType (abstract) and we have two concrete implementations Shirt and Pants.

Here is the source:

 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; }
}

In my UI, user has a dropdown, they can select the product type and the input elements are displayed according to the right product type. I have all of this figured out (using an ajax get on dropdown change, return a partial/editor template and re-setup the jquery validation accordingly).

Next I created a custom model binder for 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;

    }
}

This binds the values correctly and works for the most part, except I lose the server side validation. So on a hunch that I am doing this incorrectly I did some more searching and came across this answer by Darin Dimitrov:

ASP.NET MVC 2 - Binding To Abstract Model

So I switched the model binder to only override CreateModel, but now it doesn't bind the values.

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;
    }

Stepping though the MVC 3 src, it seems like in BindProperties, the GetFilteredModelProperties returns an empty result, and I think is because bindingcontext model is set to ProductTypeBase which doesn't have any properties.

Can anyone spot what I am doing wrong? This doesn't seem like it should be this difficult. I am sure I am missing something simple...I have another alternative in mind of instead of having a SubProduct property in the Product model to just have separate properties for Shirt and Pants. These are just View/Form models so I think that would work, but would like to get the current approach working if anything to understand what is going on...

Thanks for any help!

Update:

I didn't make it clear, but the custom model binder I added, inherits from the DefaultModelBinder

Answer

Setting ModelMetadata and Model was the missing piece. Thanks Manas!

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);

        }

解决方案

This can be achieved through overriding CreateModel(...). I will demonstrate that with an example.

1. Lets create a model and some base and child classes.

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. Now create a modelbinder and override 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. Now in the controller create get and post action.

[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. Now Set MyModelBinder as Default ModelBinder in global.asax This is done to set a default model binder for all actions, for a single action we can use ModelBinder attribute in action parameters)

protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        ModelBinders.Binders.DefaultBinder = new MyModelBinder();

        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);
    }

5. Now we can create view of type MyModel and a partial view of type 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>

Now it will work as expected, Controller will receive an Object of type "MyDerievedClass". Validations will happen as expected.

这篇关于MVC 3模型绑定一个子类型(抽象类或接口)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆