Reputation: 1889
Not a duplicate, see appended clarification
I would like to bind a model setup like the following
public class Shop{
public string Name {get;set;}
public ICollection<Product> Products {get;set;} //Product is abstract
}
public abstract class Product{
public string Name {get;set;}
}
public class ProductA : Product{
public string foo {get;set;}
}
public class ProductB :Product{
public string bar {get;set;}
}
And a controller like so
public ActionResult(){
Shop model = ShopFactory.GetShop();
return View(model);
}
[HttpPost]
public ActionResult(Shop model){
//....
}
I'm using BeginCollectionItem to bind the collection, however a problem arrises when POSTing the form as it cannot create an abstract class - namely objects inside Shop.Products
I've looked at subclassing DefaultModelBinder
to override CreateModel
however CreateModel is never called with the argument modeltype = Product
, only modeltype = Shop
How do I create a ModelBinder that will bind an object that has an abstract collection as a property?
Clarification
This question is not a duplicate because we are not dealing with an abstract model, we are dealing with a Model that has a collection of abstract objects, this undergoes a separate process in the model binding system.
Upvotes: 3
Views: 3409
Reputation: 367
Just to clarify, for others like me who stumbled on this page looking for a solution:
MVC 3 Model Binding a Sub Type (Abstract Class or Interface) is exactly what you are looking for. You do not need to do anything special because of BeginCollectionItem.
Simply write a model binder for whatever abstract class you have a collection of, and decorate the abstract class with:
[ModelBinder(typeof(MyModelBinder))]
public abstract class MyClass { ...
Then in your ModelBinder, use something like this:
string typeName = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".type").AttemptedValue;
Type instantiationType = Type.GetType(typeName);
to get the type from your EditorTemplate's hidden field:
@Html.HiddenFor(m => type)
Finally, if you still can't get the properties of your concrete types, double check that they are in fact properties, and not fields!
Upvotes: 3
Reputation: 1889
Answered,
The issue I had was that I was creating a ModelBinder for Shop
, instead of Products
.
Simple.
Update
Since this got downvoted I thought I'd clarify.
I was attempting to modal bind the Shop
class because that was the class I was sending to the view. In my head that made sense because that was the Model I was binding to. The issue was that a method in DefaultModelBinder called CreateModel when it came across any complex object in an IEnumerable
. So the solution was to subclass DefaultModelBinder
public class ProductModelBinder : DefaultModelBinder{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
if (modelType.Equals(typeof(Product)))
{
//For now only support Product1's
// Todo: Add support for different types
Type instantiationType = typeof(Product1);
var obj = Activator.CreateInstance(instantiationType);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, instantiationType);
bindingContext.ModelMetadata.Model = obj;
return obj;
}
return base.CreateModel(controllerContext, bindingContext, modelType);
}
and register said subclass to binders:
ModelBinders.Binders.Add(new KeyValuePair<Type, IModelBinder>(typeof(FormItem), new Forms.Mvc.FormItemModelBinder()));
Upvotes: 3
Reputation:
I think you should write custom model binder.please see How to Bind a collection of Abstract Class or Interface to a Model – ASP.NET MVC and MVC 3 Model Binding a Sub Type (Abstract Class or Interface).
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);
}
}
Upvotes: 0