ASP.NET MVC 2 - привязка к абстрактной модели
Если у меня есть следующее строго типизированное представление:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<XXX.DomainModel.Core.Locations.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 жалуется, что методы действия неоднозначны (также имеет смысл).
Что нам делать? Можем ли мы просто не привязываться к абстрактной модели?
Ответы
Ответ 1
Как насчет написания настраиваемого связующего объекта для этого абстрактного класса:
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();
}
}
Это имеет смысл только в том случае, если у вас есть форма, в которой элементы ввода вставляются динамически на основе некоторых действий пользователя. В этом случае вам нужно передать дополнительный параметр, чтобы указать, какой конкретный класс вам нужен. В противном случае я буду придерживаться конкретных моделей представления в качестве параметров действия.
Ответ 2
Вы также можете создать общий 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);
}
}
Ответ 3
Просто, чтобы выбросить его там - меня очень интересует то, что другие могут ответить, но это то, что я сделал в случае, когда у меня была аналогичная ситуация;
В принципе, я не использовал класс модели в качестве параметра в методе Action, а вместо этого передавал FormCollection
и тестировал пару известных дискриминаторов, чтобы выяснить, какой тип для создания/редактирования, а затем использовал TryUpdateModel
.
Казалось, что может быть лучший способ, но я никогда не думал об этом больше.