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

B Z*_*B Z 50 asp.net-mvc model-binding asp.net-mvc-3

假设我有一个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; }
}
Run Code Online (Sandbox Code Playgroud)

在我的用户界面中,用户有一个下拉列表,他们可以选择产品类型,并根据正确的产品类型显示输入元素.我已经弄明白了(使用ajax get on dropdown change,返回部分/编辑器模板并相应地重新设置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;

    }
}
Run Code Online (Sandbox Code Playgroud)

这会正确地绑定值并且大部分都可以工作,除了我丢失了服务器端验证.所以,在预感到我这样做不正确的时候,我做了一些搜索,并且遇到了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;
    }
Run Code Online (Sandbox Code Playgroud)

通过MVC 3 src,似乎在BindProperties中,GetFilteredModelProperties返回一个空结果,我认为是因为bindingcontext模型设置为ProductTypeBase,它没有任何属性.

谁能发现我做错了什么?这似乎不应该是这么困难.我相信,我失去了一些东西简单...我有另一种选择考虑的,而不是在产品型号,只是有上衣和裤子分开的性质有一个子产品属性.这些只是视图/表单模型,所以我认为这样可行,但是如果有什么需要了解正在发生的事情,我希望当前的方法有效...

谢谢你的帮助!

更新:

我没说清楚,但我添加的自定义模型绑定器继承自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);

        }
Run Code Online (Sandbox Code Playgroud)

Man*_*nas 58

这可以通过重写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";
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

2.现在创建一个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);
    }

}
Run Code Online (Sandbox Code Playgroud)

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);
    }
Run Code Online (Sandbox Code Playgroud)

4.现在将MyModelBinder设置为global.asax中的默认模型绑定器这样做是为了为所有操作设置默认模型绑定器,对于单个操作,我们可以在操作参数中使用ModelBinder属性)

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

        ModelBinders.Binders.DefaultBinder = new MyModelBinder();

        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);
    }
Run Code Online (Sandbox Code Playgroud)

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>
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

现在它将按预期工作,Controller将收到"MyDerievedClass"类型的对象.验证将按预期进行.

在此输入图像描述

  • 完美,在创建中设置ModelMetaData和Model是缺失的部分,谢谢! (2认同)