Note that there are some explanatory texts on larger screens.

plurals
  1. POMVC 3 Model Binding a Sub Type (Abstract Class or Interface)
    text
    copied!<p>Say I have a Product model, the Product model has a property of ProductSubType (abstract) and we have two concrete implementations Shirt and Pants.</p> <p>Here is the source:</p> <pre><code> 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; } } </code></pre> <p>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).</p> <p>Next I created a custom model binder for ProductTypeBase. </p> <pre><code> 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; } } </code></pre> <p>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:</p> <p><a href="https://stackoverflow.com/questions/4012217/asp-net-mvc-2-binding-to-abstract-model">ASP.NET MVC 2 - Binding To Abstract Model</a></p> <p>So I switched the model binder to only override CreateModel, but now it doesn't bind the values.</p> <pre><code>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; } </code></pre> <p>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. </p> <p>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...</p> <p>Thanks for any help!</p> <h1>Update:</h1> <p>I didn't make it clear, but the custom model binder I added, inherits from the DefaultModelBinder</p> <h1>Answer</h1> <p>Setting ModelMetadata and Model was the missing piece. Thanks Manas!</p> <pre><code>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); } </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload