Asp.net MVC RouteBase и IoC
Я создаю собственный маршрут путем создания подкласса RouteBase. У меня есть зависимость, которую я хотел бы подключить к IoC. Метод GetRouteData просто принимает HttpContext, но я также хочу добавить в свою единицу работы.... каким-то образом.
Я использую StructureMap, но информация о том, как вы будете это делать с любым IoC-фреймворком, будет полезна.
2 ответа
Ну, вот наше решение. Многие мелкие детали могут быть опущены, но общая идея здесь. Этот ответ может быть своего рода оффтопом к оригинальному вопросу, но он описывает общее решение проблемы.
Я попытаюсь объяснить ту часть, которая отвечает за простые пользовательские HTML-страницы, которые создаются пользователями во время выполнения и поэтому не могут иметь своего собственного контроллера / действия. Таким образом, маршруты должны быть либо каким-то образом построены во время выполнения, либо быть "универсальными" с пользовательским IRouteConstraint.
Прежде всего, давайте изложим некоторые факты и требования.
- У нас есть некоторые данные и некоторые метаданные о наших страницах, хранящиеся в БД;
- Мы не хотим заранее генерировать (гипотетически) целый миллион маршрутов для всех существующих страниц (т. Е. При запуске приложения), потому что что-то может измениться во время приложения, и мы не хотим заниматься внесением изменений в глобальную коллекцию RouteCollection;
Итак, мы делаем это так:
1. PageController
Да, специальный контроллер, который отвечает за все наши контентные страницы. И есть единственное действие, которое Display(int id)
(на самом деле у нас есть специальный ViewModel в качестве параметра, но я использовал int id
для простоты.
Страница со всеми данными разрешается по идентификатору внутри Display()
метод. Сам метод возвращает либо ViewResult
(строго напечатано после PageViewModel
) или же NotFoundResult
в случае, если страница не найдена.
2. Пользовательские IRouteConstraint
Мы должны где-то определить, действительно ли URL-адрес, запрошенный пользователем, относится к одной из наших пользовательских страниц. Для этого у нас есть специальный IsPageConstraint
который реализует IRouteConstraint
интерфейс. в Match()
Метод нашего ограничения мы просто называем наш PageRepository
чтобы проверить, есть ли страница, которая соответствует нашему запрошенному URL. У нас есть наш PageRepository, добавленный StructureMap. Если мы находим страницу, то добавляем этот параметр "id" (со значением) в словарь RouteData, и он автоматически связывается с PageController.Display(int id)
от DefaultModelBinder
,
Но нам нужен параметр RouteData для проверки. Где мы это получили? Сейчас начнется...
3. Отображение маршрута с параметром "поймать все"
Важное примечание: этот маршрут определен в самом конце списка отображений маршрутов, потому что он очень общий, а не конкретный. Сначала мы проверяем все наши явно определенные маршруты, а затем проверяем Page
(это легко изменить при необходимости).
Мы просто отображаем наш маршрут так:
routes.MapRoute("ContentPages",
"{*pagePath}",
new { controller = "Page", action = "Display" }
new { pagePath = new DependencyRouteConstraint<IsPageConstraint>() });
Стоп! Что это такое DependencyRouteConstraint
что появилось в картографии? Ну, вот что делает трюк.
4. Класс DependencyRouteConstraint
Это просто еще одна общая реализация IRouteConstraint
который принимает "настоящий" IRouteConstraint
(IsPageConstraint) и разрешает его (данный TConstraint
) только когда Match()
метод называется. Он использует внедрение зависимостей, поэтому наш IsPageConstraint
Экземпляр содержит все фактические зависимости
наш DependencyRouteConstraint
затем просто вызывает dependentConstraint.Match()
предоставляя все параметры, таким образом, просто делегируя фактическое "соответствие" "реальной" IRouteConstraint.
Примечание: этот класс фактически зависит от ServiceLocator.
Резюме
Таким образом, мы имеем:
- наш
Route
ясно и чисто; - Единственный класс, который зависит от Service Locator,
DependencyRouteConstraint
; - Любой обычай
IRouteConstraint
при необходимости использует внедрение зависимостей; - ???
- PROFIT!
Надеюсь это поможет.
Итак, проблема в том, что:
- Маршрут должен быть определен заранее, во время запуска приложения
- В обязанности Route входит сопоставление входящего шаблона URL с нужным контроллером / действием для выполнения некоторой задачи по запросу. И наоборот - генерировать ссылки с использованием этих картографических данных. Период. Все остальное является нарушением принципа единой ответственности, которое фактически привело к вашей проблеме.
- Но зависимости UoW (например, NHibernate ISession или EF ObjectContext) должны быть разрешены во время выполнения.
И именно поэтому я не считаю детей класса RouteBase хорошим местом для некоторой зависимости от работы с БД. Это делает все тесно связанным и не масштабируемым. На самом деле невозможно выполнить инъекцию зависимостей.
С этого момента (я думаю, что есть какая-то уже работающая система), у вас есть только одна более или менее жизнеспособная опция:
- Чтобы использовать шаблон Service Locator: разрешите свой экземпляр UoW прямо внутри метода GetRouteData (используйте CommonServiceLocator, поддерживаемый StructureMap IContainer). Это простая, но не очень хорошая вещь, потому что таким образом вы получаете зависимость от статического Service Locator в вашем
Route
,
С CSL нужно просто позвонить внутрь GetRouteData
:
var uow = ServiceLocator.Current.GetService<IUnitOfWork>();
или только с StructureMap (без фасада CSL):
var uow = ObjectFactory.GetInstance<IUnitOfWork>();
и вы сделали. Быстро и грязно. И ключевое слово "грязный" на самом деле:)
Конечно, есть гораздо более гибкое решение, но оно требует нескольких архитектурных изменений. Если вы предоставите более подробную информацию о том, какие именно данные вы получите в своих маршрутах, я могу попытаться объяснить, как мы решили Pages
проблема маршрутизации (используя DI и пользовательские IRouteConstraint
).