How to Bind a collection of Abstract Class or Interface to a Model – ASP.NET MVC
Default Model binder is a very neat and slick piece of functionality, that automatically parses the Request.Form fields and maps the Form Fields to our Action parameters, mostly in case of POST.
In most of the scenario’s it will work pretty well, but it some of the cases it will not (Obviously !!!). So when it works out well we don’t have a problem, but if not then we are in trouble. Remember we are in trouble, but not a dead end. One thing about ASP.NET MVC is that it’s heavily customizable. But then we also need to know what to customize. In this case when the default model binder doesn’t work, we have to write our own ModelBinder.
Problem Statement: Let’s assume that we an Abstract Base class “Animal” and there are three concrete classes, namely Cat, Dog and Tiger which inherits from Animal class.
The Animal Class is defined as
1: public abstract class Animal
2: {
3: public Animal()
4: {
5: }
6:
7: public string Name { get; set; }
8: public string Color { get; set; }
9: public int Legs { get; set; }
10: public abstract string Shout();
11: public abstract string AnimalType { get;}
12: }
And the three derived classes are defined as
1: public abstract class Animal
2: {
3: public Animal()
4: {
5: }
6:
7: public string Name { get; set; }
8: public string Color { get; set; }
9: public int Legs { get; set; }
10: public abstract string Shout();
11: public abstract string AnimalType { get;}
12: }
13:
14: public class Cat : Animal
15: {
16: private string m_animalType = "Cat";
17:
18: public Cat()
19: {
20: this.Color = "Bluish Gray";
21: this.Name = "Tom";
22: this.Legs = 4;
23: }
24:
25: public override string Shout()
26: {
27: return (".");
28: }
29:
30: public override string AnimalType
31: {
32: get { return (m_animalType); }
33: }
34: }
35:
36: public class Dog : Animal
37: {
38: private string m_animalType = "Dog";
39:
40: public Dog()
41: {
42: this.Color = "Gray";
43: this.Name = "Spike";
44: this.Legs = 4;
45: }
46: public override string Shout()
47: {
48: return("..");
49: }
50:
51: public override string AnimalType
52: {
53: get
54: {
55: return (m_animalType);
56: }
57: }
58: }
59:
60:
61: public class Tiger : Animal
62: {
63: private string m_animalType = "Tiger";
64:
65: public Tiger()
66: {
67: this.Color = "White and Black Stripes";
68: this.Name = "Hobbes";
69: this.Legs = 4;
70: }
71: public override string Shout()
72: {
73: return ("....");
74: }
75:
76: public override string AnimalType
77: {
78: get { return (m_animalType); }
79: }
80: }
So now we will Bind the view with a List<Animal>. So the Action will look like following:
1: public ActionResult Index()
2: {
3: List<Animal> _lst = AnimalFactory.GetMyPets();
4: return View(_lst);
5: }
Now let’s say our View is a Bulk Edit screen, which looks like…
Now if we modify any value and try to Save it, it will call the “Save” controller.
1: [AcceptVerbs(HttpVerbs.Post)]
2: public ActionResult Save(IList<Animal> myPets)
But here if we try to run it it will throw an Exception,
Which is expected, because after all you can’t instantiate an abstract class. So here we require some mechanism, so that we can create instances of the concrete classes, and recreate the List. A custom Model binder will help us to do that.
First step to create a Model Binder is to create a class which Implements IModelBinder interface.
So my Model Binder will look like…
1: public class AnimalModelBinder : IModelBinder
2: {
3: #region IModelBinder Members
4:
5: object IModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
6: {
7: string typeKey = bindingContext.ModelName + ".Type";
8: string animalTypeName = GetValue<string>(bindingContext, typeKey);
9: Animal tempAnimal = Activator.CreateInstance(Assembly.GetAssembly(typeof(Animal)).GetName().FullName,animalTypeName).Unwrap() as Animal;
10:
11: typeKey = bindingContext.ModelName + ".Name";
12: tempAnimal.Name = GetValue<string>(bindingContext, typeKey);
13:
14: typeKey = bindingContext.ModelName + ".Color";
15: tempAnimal.Color = GetValue<string>(bindingContext, typeKey);
16:
17: typeKey = bindingContext.ModelName + ".Legs";
18: tempAnimal.Legs = GetValue<int>(bindingContext, typeKey);
19: return (tempAnimal);
20: }
21:
22: private T GetValue<T>(ModelBindingContext bindingContext, string key)
23: {
24: ValueProviderResult valueResult;
25: bindingContext.ValueProvider.TryGetValue(key, out valueResult);
26: bindingContext.ModelState.SetModelValue(key, valueResult);
27: return (T)valueResult.ConvertTo(typeof(T));
28: }
29:
30: #endregion
31: }
GetValue method I have copied from this Post.
And to get the concrete type of each of the item of the List<Animal>, I have included and hidden field in the View.
<%= Html.TextBox("myPets[" + i + "].Type",item.GetType(),new {style="display:none"})%>
So I am getting the Type from “myPets[i].Type” field. Once I have the Type, I am using Reflection to create object of that class, and assigning the properties.
That’s all !!!