Связывание полиморфной модели
этот вопрос был просил в более ранних версиях MVC. Есть также эта запись в блоге о способе обойти проблему. Мне интересно, если MVC3 ввел что-нибудь, что может помочь, или если есть какие-либо другие варианты.
в двух словах. Вот такая ситуация. У меня есть абстрактная базовая модель и 2 конкретных подкласса. У меня есть строго типизированное представление, которое отображает модели с EditorForModel()
. Тогда у меня есть пользовательские шаблоны для визуализируйте каждый конкретный тип.
проблема приходит в почтовое время. Если я заставлю метод действия post принять базовый класс в качестве параметра, то MVC не сможет создать его абстрактную версию (чего я бы не хотел в любом случае, я бы хотел, чтобы он создал фактический конкретный тип). Если я создаю несколько методов действия post, которые различаются только сигнатурой параметра, то MVC жалуется, что это неоднозначно.
насколько я могу судить, у меня есть несколько вариантов, как решить эту проблему. Я не нравится ни один из них по разным причинам, но я перечислю их здесь:
- создайте пользовательскую модель связующего, как предлагает Дарин в первом сообщении, с которым я связан.
- создайте атрибут дискриминатора в качестве второго сообщения, которое я связал с предложениями.
- сообщение в различные методы действий на основе типа
- ???
мне не нравится 1, потому что это в основном конфигурации, которая скрыта. Некоторые другие разработчики, работающие над кодом, могут не знаю об этом и тратить много времени, пытаясь выяснить, почему вещи ломаются, когда меняет вещи.
мне не нравится 2, потому что как-то суховато. Но, я склоняюсь к этому подходу.
я не люблю 3, потому что это означает нарушение сухой.
какие-то другие предложения?
Edit:
Я решил пойти с методом Дарина, но сделал небольшое изменение. Я добавил Это к моей абстрактной модели:
[HiddenInput(DisplayValue = false)]
public string ConcreteModelType { get { return this.GetType().ToString(); }}
затем скрытый автоматически генерируется в my DisplayForModel()
. Единственное, что вы должны помнить, что если вы не используете DisplayForModel()
, вам придется добавить его самостоятельно.
4 ответа:
поскольку я, очевидно, выбираю вариант 1 (: -)) позвольте мне попытаться разработать его немного больше, чтобы он был меньше бьющиеся и избегайте жесткого кодирования конкретных экземпляров в модель связующего. Идея в том, чтобы передать конкретный тип в скрытое поле и использовать отражение для создания экземпляра конкретного типа.
предположим, что у вас есть следующие модели вида:
public abstract class BaseViewModel { public int Id { get; set; } } public class FooViewModel : BaseViewModel { public string Foo { get; set; } }
следующий контроллер:
public class HomeController : Controller { public ActionResult Index() { var model = new FooViewModel { Id = 1, Foo = "foo" }; return View(model); } [HttpPost] public ActionResult Index(BaseViewModel model) { return View(model); } }
соответствующего
Index
вид:@model BaseViewModel @using (Html.BeginForm()) { @Html.Hidden("ModelType", Model.GetType()) @Html.EditorForModel() <input type="submit" value="OK" /> }
и
~/Views/Home/EditorTemplates/FooViewModel.cshtml
редактор шаблонов:мы могли бы иметь следующую пользовательскую модель связующего:@model FooViewModel @Html.EditorFor(x => x.Id) @Html.EditorFor(x => x.Foo)
public class BaseViewModelBinder : DefaultModelBinder { protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { var typeValue = bindingContext.ValueProvider.GetValue("ModelType"); var type = Type.GetType( (string)typeValue.ConvertTo(typeof(string)), true ); if (!typeof(BaseViewModel).IsAssignableFrom(type)) { throw new InvalidOperationException("Bad Type"); } var model = Activator.CreateInstance(type); bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type); return model; } }
фактический тип выводится из значения
ModelType
скрытое поле. Он не жестко закодирован, что означает, что вы можете добавить другие дочерние типы позже, не касаясь этой модели binder.эта же техника может быть легко наносится к коллекциям базового вида модели.
Я только что подумал об интересном решении этой проблемы. Вместо того, чтобы использовать параметр bsed модель привязки, как это:
[HttpPost] public ActionResult Index(MyModel model) {...}
вместо этого я могу использовать TryUpdateModel (), чтобы позволить мне определить, к какой модели привязываться в коде. Например я делаю что-то вроде этого:
[HttpPost] public ActionResult Index() {...} { MyModel model; if (ViewData.SomeData == Something) { model = new MyDerivedModel(); } else { model = new MyOtherDerivedModel(); } TryUpdateModel(model); if (Model.IsValid) {...} return View(model); }
это на самом деле работает намного лучше в любом случае, потому что если я делаю какую-либо обработку, то мне придется бросить модель на то, что она на самом деле так или иначе, или использовать
is
к выясните правильную карту для вызова с помощью AutoMapper.Я думаю, те из нас, кто не использует MVC с первого дня, забывают о
UpdateModel
иTryUpdateModel
, но он все еще имеет свои применения.
в моем случае у меня есть абстрактный базовый тип для ряда различных типов моделей представления. Итак, в основной модели представления у меня есть свойство абстрактного базового типа:
class View { public AbstractBaseItemView ItemView { get; set; } }
у меня есть несколько подтипов AbstractBaseItemView, многие из которых определите свои собственные исключительные свойства.
моя проблема в том, что модель связующего не смотрит на тип объекта прилагается для просмотра.ItemView, но вместо этого смотрит только на объявленный тип свойства, который является AbstractBaseItemView-и решает привязать только свойства, определенные в абстрактном типе, игнорируя свойства, характерные для конкретного типа AbstractBaseItemView, который используется.
обходной путь для этого не является довольно:
using System.ComponentModel; using System.ComponentModel.DataAnnotations; // ... public class ModelBinder : DefaultModelBinder { // ... override protected ICustomTypeDescriptor GetTypeDescriptor(ControllerContext controllerContext, ModelBindingContext bindingContext) { if (bindingContext.ModelType.IsAbstract && bindingContext.Model != null) { var concreteType = bindingContext.Model.GetType(); if (Nullable.GetUnderlyingType(concreteType) == null) { return new AssociatedMetadataTypeTypeDescriptionProvider(concreteType).GetTypeDescriptor(concreteType); } } return base.GetTypeDescriptor(controllerContext, bindingContext); } // ... }
хотя это изменение кажется хакерским и очень "системным", оно, похоже, работает - и, насколько я могу судить, не представляет значительного риска для безопасности, поскольку оно делает не свяжите в CreateModel () и таким образом делает не позвольте вам опубликовать все, что угодно, и обмануть модель-связующее в создании любого объекта.
Он также работает только тогда, когда заявленные свойства-тип аннотация тип, например, абстрактный класс или взаимодействие.
в соответствующей заметке мне приходит в голову, что другие реализации, которые я видел здесь, которые переопределяют CreateModel (), вероятно, будут только работайте, когда вы публикуете совершенно новые объекты-и будете страдать от той же проблемы, с которой я столкнулся, когда объявленный тип свойства имеет абстрактный тип. Так что вы скорее всего не сможете edit специфические свойства конкретных типов по существующей объекты модели, но только создавать новые те.
другими словами, вам, вероятно, потребуется интегрировать эту работу в вашу привязку, чтобы также иметь возможность правильно редактировать объекты, которые были добавлены в модель представления до привязки... Лично я считаю, что это более безопасный подход, поскольку я контролирую, какой конкретный тип добавляется, поэтому контроллер/действие может косвенно указать конкретный тип, который может быть связан, просто заполнив свойство пустым экземпляром.
Я надеюсь, что это поможет другие...
используя метод Дарина, чтобы различать ваши типы моделей через скрытое поле в вашем представлении, я бы рекомендовал вам использовать пользовательский
RouteHandler
чтобы различать типы моделей и направлять каждый из них на уникально именованное действие на вашем контроллере. Например, если у вас есть две конкретные модели, foo и Bar, дляCreate
действие в контроллере, сделатьCreateFoo(Foo model)
действие иCreateBar(Bar model)
действие. Затем сделайте пользовательский RouteHandler следующим образом:public class MyRouteHandler : IRouteHandler { public IHttpHandler GetHttpHandler(RequestContext requestContext) { var httpContext = requestContext.HttpContext; var modelType = httpContext.Request.Form["ModelType"]; var routeData = requestContext.RouteData; if (!String.IsNullOrEmpty(modelType)) { var action = routeData.Values["action"]; routeData.Values["action"] = action + modelType; } var handler = new MvcHandler(requestContext); return handler; } }
затем, в глобальном масштабе.асакс.цезий, изменить
RegisterRoutes()
следующим образом:public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); AreaRegistration.RegisterAllAreas(); routes.Add("Default", new Route("{controller}/{action}/{id}", new RouteValueDictionary( new { controller = "Home", action = "Index", id = UrlParameter.Optional }), new MyRouteHandler())); }
затем, когда приходит запрос на создание, если тип модели определен в возвращаемой форме, RouteHandler добавит Тип модели к имени действия, позволяя определить уникальное действие для каждой конкретной модели.