If i have the following strongly-typed view:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<XXX.DomainModel.Core.Locations.Location>" %>
Where Location is an abstract class.
And i have the following Controller, which accepts a strongly-typed Model via a POST:
[HttpPost]
public ActionResult Index(Location model)
I get a runtime error stating "Cannot Create Abstract Class
Which of course makes sense. However - i'm not sure what the best solution is here.
I have many concrete types (around 8), and this is a view where you can only edit properties of the abstract class.
What i've tried to do is create overloads for all the different concrete types, and perform my logic in a common method.
[HttpPost]
public ActionResult Index(City model)
{
UpdateLocationModel(model);
return View(model);
}
[HttpPost]
public ActionResult Index(State model)
{
UpdateLocationModel(model);
return View(model);
}
etc etc
And then:
[NonAction]
private void UpdateLocationModel (Location model)
{
// ..snip - update model
}
But this doesn't work either , MVC complains the action methods are ambiguous (also makes sense).
What do we do? Can we simply not bind to an abstract model?
How about writing a custom model binder for this abstract class:
public class CustomBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
// TODO: based on some request parameter choose the proper child type
// to instantiate here
return new Child();
}
}
This make sense only if you have a form where input elements are inserted dynamically based on some user action. In this case you need to pass some additional parameter to indicate which concrete class you need. Otherwise I would stick to concrete view models as action parameters.
You can also build a generic ModelBinder that works for all of your abstract models. My solution requires you to add a hidden field to your view called ' ModelTypeName ' with the value set to the name of the concrete type that you want. However, it should be possible to make this thing smarter and pick a concrete type by matching type properties to fields in the view.
In your Global.asax.cs file in Application_Start() :
ModelBinders.Binders.DefaultBinder = new CustomModelBinder();
CustomModelBinder:
public class CustomModelBinder2 : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var modelType = bindingContext.ModelType;
if (modelType.IsAbstract)
{
var modelTypeValue = controllerContext.Controller.ValueProvider.GetValue("ModelTypeName");
if (modelTypeValue == null)
throw new Exception("View does not contain ModelTypeName");
var modelTypeName = modelTypeValue.AttemptedValue;
var type = modelType.Assembly.GetTypes().SingleOrDefault(x => x.IsSubclassOf(modelType) && x.Name == modelTypeName);
if (type != null)
{
var instance= bindingContext.Model ?? base.CreateModel(controllerContext, bindingContext, type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => instance, type);
}
}
return base.BindModel(controllerContext, bindingContext);
}
}
Just to throw it out there - I'm very much interested in what others might answer, but this is what I ended up doing in the case where I had a similar situation;
Basically, I did not use the model class as a parameter in the Action method, instead passing in FormCollection
and testing a couple known discriminators to figure out which type to create/edit, then used TryUpdateModel
from there.
It seemed there might be a better way, but I'd never gotten around to thinking about it more.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.