I have a view that lists tables being added to a floor plan. Tables derive from TableInputModel
to allow for RectangleTableInputModel
, CircleTableInputModel
, etc
The ViewModel has a list of TableInputModel
which are all one of the derived types.
I have a partial view for each of the derived types and given a List
of mixed derived types the framework knows how to render them.
However, on submitting the form the type information is lost. I have tried with a custom model binder but because the type info is lost when it's being submitted, it wont work...
Has anyone tried this before?
Assuming you have the following models:
public abstract class TableInputModel
{
}
public class RectangleTableInputModel : TableInputModel
{
public string Foo { get; set; }
}
public class CircleTableInputModel : TableInputModel
{
public string Bar { get; set; }
}
And the following controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new TableInputModel[]
{
new RectangleTableInputModel(),
new CircleTableInputModel()
};
return View(model);
}
[HttpPost]
public ActionResult Index(TableInputModel[] model)
{
return View(model);
}
}
Now you could write views.
Main view Index.cshtml
:
@model TableInputModel[]
@using (Html.BeginForm())
{
@Html.EditorForModel()
<input type="submit" value="OK" />
}
and the corresponding editor templates.
~/Views/Home/EditorTemplates/RectangleTableInputModel.cshtml
:
@model RectangleTableInputModel
<h3>Rectangle</h3>
@Html.Hidden("ModelType", Model.GetType())
@Html.EditorFor(x => x.Foo)
~/Views/Home/EditorTemplates/CircleTableInputModel.cshtml
:
@model CircleTableInputModel
<h3>Circle</h3>
@Html.Hidden("ModelType", Model.GetType())
@Html.EditorFor(x => x.Bar)
and final missing peace of the puzzle is the custom model binder for the TableInputModel
type which will use the posted hidden field value to fetch the type and instantiate the proper implementation:
public class TableInputModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var typeValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".ModelType");
var type = Type.GetType(
(string)typeValue.ConvertTo(typeof(string)),
true
);
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
return model;
}
}
which will be registered in Application_Start
:
ModelBinders.Binders.Add(typeof(TableInputModel), new TableInputModelBinder());
and that's pretty much all. Now inside the Index Post action the model array will be properly initialzed with correct types.