Мультитенантное приложение с MVC 3 и DI

У меня есть приложение MVC, разделенное на несколько областей:

+ Root
+ -- Areas
   --+ US
     + -- USCodeController : XXXCodeBaseController
   --+ UK
       -- UKCodeController : XXXCodeBaseController
   --+ (Other countries pending)
+ -- Controllers
   --+ XXXCodeBaseController

Итак, я определил некоторые базовые функции в Controllers папка, а затем унаследовал и расширил функциональность в папке областей. Таким образом, пользовательские интерфейсы могут быть настроены вокруг компонентов пользовательского интерфейса, специфичных для каждой страны, рабочего процесса и т. Д.

Когда дело доходит до использования Autofac, я должен передать IComponentContext до параметра конструктора, что не очень хорошо. Проблемы включают в себя:

  1. При использовании истинного IoC мне необходимо внедрить IXXXServices, поэтому вместо Autofac я получаю класс для конкретной области IComponentContext
  2. Каждая область связана с другой базой данных, но с общей схемой - поэтому используется одна и та же библиотека 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);
Другие вопросы по тегам