Introduction
Normally when we bind the Derived type (or derived type collection) to the MVC View, it always return the actual base type on controller, because our Model (business or domain entity) always has the BaseType
. To persist the derived type (during postbacks), we have to create our own custom binder that actually maintain its derived type.
This tip contains a solution of persisting Derived Types binded to base type object in MVC view.
Background
I have faced a problem in my project. I have an array of base class objects, whose individual items refer to derived class objects. I need to bind this array in my MVC view, but in Model binding approach it loses the actual derived type on post back. So to resolve this problem, I have created a customized DerivedTypeModelBinder
, with the help of which I am able to persist derived types binded to base class objects.
Using the Code
I have base class WarrantyBase
as below:
[DataContract]
[Serializable]
[XmlInclude(typeof(AWarranty))]
[XmlInclude(typeof(BWarranty))]
[KnownType(typeof(AWarranty))]
[KnownType(typeof(BWarranty))]
public class WarrantyBase
{
}
And the other derived classes as below…
[DataContract]
[Serializable]
public class AWarranty : WarrantyBase
{
}
[DataContract]
[Serializable]
public class BWarranty: WarrantyBase
{
}
Then I have an array of Base class that I need to bind on my view as:
<% for (int i = 0; i < Model.Warranties.Count; i++)
{ %>
<tr>
<td>
<%= this.CheckBox(c => c.Warranties[i].IsSelected)%>
</td>
</tr>
When I am getting my model in Postback, I always get the base class objects and not able to convert those base objects to derived. So, I need a solution by which my view always return the actual derived type and not the base ones. So to achieve this, I have to write a DerivedTypeModelBinder
which is derived from DefaultModelBinder
, as shown in the below code snippet.
public class DerivedTypeModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext,
ModelBindingContext bindingContext, Type modelType)
{
return base.CreateModel(controllerContext, bindingContext,
GetModelType(controllerContext, bindingContext, modelType));
}
protected override ICustomTypeDescriptor GetTypeDescriptor
(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var modelType = GetModelType(controllerContext, bindingContext, bindingContext.ModelType);
return new AssociatedMetadataTypeTypeDescriptionProvider(modelType).GetTypeDescriptor(modelType);
}
private static Type GetModelType(ControllerContext controllerContext,
ModelBindingContext bindingContext, Type modelType)
{
if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName + ".BindingType"))
{
modelType = System.Type.GetType(((string[])bindingContext.ValueProvider.GetValue
(bindingContext.ModelName + ".BindingType").RawValue)[0]);
}
return modelType;
}
}
And then, you have to take a hidden control to bind the actual type in Views to persist the same during postbacks.
<%= Html.Hidden(string.Format("Warranties[{0}].BindingType", i),
Model.Warranties[i].GetType().AssemblyQualifiedName)%>
Then the last step is to register your customized binder in Global.asax file in RegisterArea
or RegisterRoute
method.
public override void RegisterArea(AreaRegistrationContext context)
{
RegisterAreaEmbeddedResources();
ModelBinders.Binders.Add(typeof(WarrantyBase), new Common.Helpers.DerivedTypeModelBinder());
}
Conclusion
This solution solves the problem of binding base class object in view and getting actual derived class objects in postback.