ASP.NET MVC 2 - привязка к абстрактной модели
Если у меня есть следующее строго типизированное представление:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<XXX.DomainModel.Core.Locations.Location>" %>
Где Location - абстрактный класс.
И у меня есть следующий контроллер, который принимает строго типизированную модель через POST:
[HttpPost]
public ActionResult Index(Location model)
Я получаю сообщение об ошибке "Не удается создать абстрактный класс"
Что, конечно, имеет смысл. Однако - я не уверен, что лучшее решение здесь.
У меня есть много конкретных типов (около 8), и это представление, где вы можете редактировать только свойства абстрактного класса.
То, что я пытался сделать, это создать перегрузки для всех различных конкретных типов и выполнить мою логику в общем методе.
[HttpPost]
public ActionResult Index(City model)
{
UpdateLocationModel(model);
return View(model);
}
[HttpPost]
public ActionResult Index(State model)
{
UpdateLocationModel(model);
return View(model);
}
и т. д.
А потом:
[NonAction]
private void UpdateLocationModel (Location model)
{
// ..snip - update model
}
Но это также не работает, MVC жалуется, что методы действий неоднозначны (также имеет смысл).
Что мы делаем? Можем ли мы просто не привязываться к абстрактной модели?
3 ответа
Как насчет написания пользовательского связывателя модели для этого абстрактного класса:
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();
}
}
Это имеет смысл, только если у вас есть форма, в которой элементы ввода вставляются динамически в зависимости от действий пользователя. В этом случае вам нужно передать некоторый дополнительный параметр, чтобы указать, какой конкретный класс вам нужен. В противном случае я бы придерживался конкретных моделей представления в качестве параметров действий.
Вы также можете создать общий ModelBinder, который работает для всех ваших абстрактных моделей. Мое решение требует, чтобы вы добавили скрытое поле в ваше представление с именем "ModelTypeName" со значением, установленным для имени конкретного типа, который вы хотите. Однако должна быть возможность сделать это умнее и выбрать конкретный тип, сопоставив свойства типа с полями в представлении.
В вашем файле Global.asax.cs в 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);
}
}
Просто, чтобы бросить это там - меня очень интересует, что могут ответить другие, но это то, что я в итоге сделал в случае, когда у меня была похожая ситуация;
По сути, я не использовал класс модели в качестве параметра в методе Action, вместо этого передав FormCollection
и тестирование пары известных дискриминаторов, чтобы выяснить, какой тип создавать / редактировать, а затем использовать TryUpdateModel
оттуда.
Казалось, что может быть лучше, но я никогда не думал об этом больше.