2012-02-23 67 views
44

說我有一個產品型號,產品型號有ProductSubType(抽象)的性質,我們有兩個具體的實現上衣和褲子。MVC 3模型綁定一個子類型(抽象類或接口)

這裏是源:

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

以我UI,用戶有一個下拉菜單中,他們可以選擇產品類型和輸入元件根據合適的產品類型顯示。我已經想通了所有這些(使用ajax get 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; 

    } 
} 

這正確地綁定了這些值,並且大部分工作,除了我失去了服務器端驗證。因此,在一種預感,我做這個錯誤我做了一些更多的搜索和達林季米特洛夫碰到這個答案出來:

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和型號是缺少的部分繼承。感謝瑪納斯!

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

     } 

回答

53

這可以通過重寫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的屬性)

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> 

現在預期它會工作,控制器將獲得類型的對象 「MyDerievedClass」。 驗證將按預期進行。

enter image description here

+2

完美的是,將ModelMetaData和Model設置爲創建是缺失的部分,謝謝! – 2012-02-24 14:50:27

+0

我有一個非常類似的問題與繼承和派生類型和上述模型聯編程序代碼做了伎倆。乾杯! – 2012-03-27 04:54:47

+0

謝謝!我花了差不多三天的時間嘗試不同的方式來最終使用這個解決方案,儘管我的問題有所不同。 – 2013-02-05 11:19:37

4

我有同樣的問題,我結束了使用MvcContrib作爲sugested 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> 
} 
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

就是這樣。