Во-первых, извините за большой пост (сначала я попытался провести некоторое исследование) и для сочетания технологий по одному и тому же вопросу (ASP.NET MVC 3, Ninject и MvcContrib).
Я разрабатываю проект с ASP.NET MVC 3 для обработки некоторых клиентских заказов.
Вкратце: У меня есть некоторые объекты, унаследованные от и абстрактного класса Order
, и мне нужно их проанализировать, когда к моему контроллеру будет отправлен запрос POST. Как я могу разрешить правильный тип? Нужно ли переопределять класс DefaultModelBinder
или есть какой-то другой способ сделать это? Может ли кто-нибудь предоставить мне код или другие ссылки о том, как это сделать? Любая помощь будет замечательной!
Если сообщение сбивает с толку, я могу сделать любое изменение, чтобы было ясно!
Итак, у меня есть следующее дерево наследования для заказов, которые мне нужно обрабатывать:
public abstract partial class Order {
public Int32 OrderTypeId {get; set; }
/* rest of the implementation ommited */
}
public class OrderBottling : Order { /* implementation ommited */ }
public class OrderFinishing : Order { /* implementation ommited */ }
Все классы генерируются Entity Framework, поэтому я не буду изменять их, потому что мне нужно будет обновить модель (я знаю, что могу их расширить). Кроме того, будет больше заказов, но все они получены из Order
.
У меня есть общий вид (Create.aspx
), чтобы создать заказ, и это представление вызывает строго типизированное частичное представление для каждого из унаследованных ордеров (в данном случае OrderBottling
и OrderFinishing
). Я определил метод Create()
для запроса GET и другого для запроса POST в классе OrderController
. Второй вариант выглядит следующим образом:
public class OrderController : Controller
{
/* rest of the implementation ommited */
[HttpPost]
public ActionResult Create(Order order) { /* implementation ommited */ }
}
Теперь проблема: когда я получаю запрос POST с данными из формы, связующее по умолчанию MVC пытается создать экземпляр объекта Order
, который является ОК, так как тип метода таков. Но поскольку Order
является абстрактным, он не может быть создан, что и должно делать.
Вопрос:, как я могу узнать, какой конкретный тип Order
отправляется представлением?
Я уже искал здесь в Qaru и много рассказывал об этом (я работаю над этой проблемой уже около 3 дней!) и нашел некоторые способы решения некоторых подобных проблем, но я ничего не мог найти как моя настоящая проблема. Два варианта решения этого вопроса:
- переопределить ASP.NET MVC
DefaultModelBinder
и использовать Direct Injection, чтобы узнать, какой тип являетсяOrder
; - создайте метод для каждого порядка (не красивый и будет проблематичным для поддержания).
Я не пробовал второй вариант, потому что я не думаю, что это правильный способ решить проблему. Для первого варианта я попробовал Ninject, чтобы разрешить тип заказа и создать его экземпляр. Мой модуль Ninject выглядит следующим образом:
private class OrdersService : NinjectModule
{
public override void Load()
{
Bind<Order>().To<OrderBottling>();
Bind<Order>().To<OrderFinishing>();
}
}
Я попытался получить один из типов с помощью метода Ninject Get<>()
, но он говорит мне, что это более чем один из способов разрешения типа. Итак, я понимаю, что модуль не очень хорошо реализован. Я также попытался реализовать подобное для обоих типов: Bind<Order>().To<OrderBottling>().WithPropertyInject("OrderTypeId", 2);
, но у него такая же проблема... Каким будет правильный способ реализовать этот модуль?
Я также попытался использовать MvcContrib Model Binder. Я сделал это:
[DerivedTypeBinderAware(typeof(OrderBottling))]
[DerivedTypeBinderAware(typeof(OrderFinishing))]
public abstract partial class Order { }
и на Global.asax.cs
Я сделал это:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ModelBinders.Binders.Add(typeof(Order), new DerivedTypeModelBinder());
}
Но это порождает исключение: System.MissingMethodException: Невозможно создать абстрактный класс. Поэтому я предполагаю, что связующее вещество не может или не может решить правильный тип.
Большое спасибо заранее!
Изменить: прежде всего, спасибо Мартину и Джейсону за ваши ответы и извините за задержку! Я пробовал оба подхода, и оба работали! Я отметил, что Мартин ответил правильно, потому что он более гибкий и отвечает некоторым потребностям моего проекта. В частности, идентификаторы для каждого запроса хранятся в базе данных, а их размещение в классе может сломать программное обеспечение, если я изменю идентификатор только в одном месте (база данных или класс). Мартин очень гибкий в этой точке.
@Martin: в моем коде я изменил строку
var concreteType = Assembly.GetExecutingAssembly().GetType(concreteTypeValue.AttemptedValue);
к
var concreteType = Assembly.GetAssembly(typeof(Order)).GetType(concreteTypeValue.AttemptedValue);
потому что мои классы, где на другом проекте (и так, на другой сборке). Я разделяю это, потому что это кажется более гибким, чем получение только исполняющей сборки, которая не может разрешать типы на внешних сборках. В моем случае все классы заказов находятся на одной и той же сборке. Это не лучше и не волшебная формула, но мне интересно поделиться этим;)