Мультитенантное приложение с MVC 3 и DI
У меня есть приложение MVC, разделенное на несколько областей:
+ Root
+ -- Areas
--+ US
+ -- USCodeController : XXXCodeBaseController
--+ UK
-- UKCodeController : XXXCodeBaseController
--+ (Other countries pending)
+ -- Controllers
--+ XXXCodeBaseController
Итак, я определил некоторые базовые функции в Controllers
папка, а затем унаследовал и расширил функциональность в папке областей. Таким образом, пользовательские интерфейсы могут быть настроены вокруг компонентов пользовательского интерфейса, специфичных для каждой страны, рабочего процесса и т. Д.
Когда дело доходит до использования Autofac, я должен передать IComponentContext
до параметра конструктора, что не очень хорошо. Проблемы включают в себя:
- При использовании истинного IoC мне необходимо внедрить IXXXServices, поэтому вместо Autofac я получаю класс для конкретной области
IComponentContext
- Каждая область связана с другой базой данных, но с общей схемой - поэтому используется одна и та же библиотека EF, только с другой строкой соединения.
Дизайн должен определенно быть более элегантным непосредственно в сервисах инъекций - и это то, что мне нужно. Но распознаватель, по-видимому, недостаточно гибок, чтобы разрешать контроллеры без масштабного взлома, который становится довольно большой связью для каждого используемого контроллера. Вот пример, если вы подключаете контроллер вручную:
builder.Register(s => new OrderService(r.ResolveNamed<IOrderRepository>("US"))
.Named("US")
.InstancePerHttpRequest();
// Lets say we add a DeclinedOrderController
public class DeclinedOrderControllerBase
{
public DeclinedOrderControllerBase ( IDeclinedOrderService service )
{ }
}
Чтобы подключиться, вам нужно добавить регистрацию в Autofac, так как нам нужна строка подключения для США / Великобритании в DeclinedOrderService
builder.Register(s => new DeclinedOrderController(r.ResolvedName<IDeclinedOrderService>("US")
.Named("US")
.InstancePerHttpRequest();
Мой вопрос (наконец):
- Существует ли способ динамического выбора набора сопоставленных регистраций Autofac по области, который автоматически разрешается для новых контроллеров?
2 ответа
Я хотел порекомендовать кастом LifetimeScope
на площадь, но кажется, что существует существующее решение.
Посмотрите на это: https://code.google.com/p/autofac/wiki/MultitenantIntegration.
В MVC4 вы можете создать собственную фабрику контроллеров для захвата экземпляров контроллеров при их создании. Вы можете использовать это как одну композиционную точку вместо того, чтобы делать это в каждом контроллере.
Создайте собственную фабрику контроллеров, унаследовав от System.Web.Mvc.DefaultControllerFactory
, Переопределить GetControllerInstance
метод и позвольте базовой функции создать контроллер для вас. Теперь вы можете сделать так, чтобы ваш инжектор зависимостей составлял контроллер.
public class AreaControllerFactory : DefaultControllerFactory
{
// We use a controller factory to hook into the MVC pipeline in order to get a reference to controllers
// as they are being created. We inherit from the default controller factory because we do not want to
// have to locate the appropriate controllers ourself (we only want a reference to it). Once we have a
// reference, we simply hand the controller off for composition.
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
IController controller = base.GetControllerInstance(requestContext, controllerType); // create the controller
if (controller != null)
{
var container = ...; //construct your composition container
container.ComposeParts(controller); //compose your controller
}
return controller;
}
}
Зарегистрируйте свой контроллер фабрики в Application_Start
метод в Global.asax.cs
,
var controllerFactory = new AreaControllerFactory();
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
Чтобы отфильтровать составные объекты по области, я создаю собственный словарь компонентов глобальной области (например, словарь имени области, список применимых типов) для каждой области.
// NOTE:ComposablePartCatalog is MEF specific, change as needed
internal static class AreaComponents
{
/// <summary>
/// A list of Area name, catalog pairs.
/// Each Area can provide a custom list of components to import from.
/// </summary>
internal static readonly Dictionary<string, ComposablePartCatalog> AreaCompositionCatalogs =
new Dictionary<string, ComposablePartCatalog>();
}
Заполните словарь внутри RegisterArea
метод каждой области AreaRegistration
реализация.
public class XAreaRegistration : AreaRegistration
{
public override void RegisterArea(AreaRegistrationContext context)
{
/* ... Standard route logic ... */
// Set up an MEF catalog with Area components
var xCatalog = new AssemblyCatalog(
typeof(MyPluginsNamespace.ArbitraryTypeToLookupAssembly).Assembly);
AreaComponents.AreaCompositionCatalogs["X"] = xCatalog;
}
}
Используйте этот словарь для выбора подходящего подмножества составных объектов в моей фабрике пользовательских контроллеров при создании составного контейнера для данного контроллера.
// Capture current area name
System.Web.HttpContextBase contextBase = new System.Web.HttpContextWrapper(System.Web.HttpContext.Current);
System.Web.Routing.RouteData routeData = System.Web.Routing.RouteTable.Routes.GetRouteData(contextBase);
object areaObject = routeData.DataTokens["area"];
string areaName = areaObject as string ?? string.Empty;
// Create a composition container specific to this area
ComposablePartCatalog areaCatalog =
AreaMefComponents.AreaCompositionCatalogs.ContainsKey(areaName) ?
AreaMefComponents.AreaCompositionCatalogs[areaName] : null;
var container = new CompositionContainer(areaCatalog);