Как использовать Unity DI для загрузки сборки с зависимостями, а затем зарегистрировать внутренние компоненты

У меня есть веб-приложение asp.net mvc 3, в котором есть сборки плагинов, реализующие зависящие от системы контексты и службы EF.

Например, у меня есть сборка, которая в целом выглядит следующим образом:

System1Controller : IController // from System.Web.Mvc

ISystem1Service {
    IList<System1Type> GetOperation(string something);
}

System1Service : ISystem1Service 
{
    private ISystem1Entities context;
    public System1Service(ISystem1Entities context)
    {
        this.context = context;
    }
}

ISystem1Entities
{
    IDbSet<System1Type> Operations { get; set; }
}

System1Entities : DbContext, ISystem1Entities

Использование Unity Bootstrapper и вызов Bootstrapper.Initialize() работает со следующей реализацией BuildUnityContainer()

private static IUnityContainer BuildUnityContainer()
{
    var theContainer = new UnityContainer();
    var connectionString = ConfigurationManager.ConnectionStrings["WebAppConnectionString"];
    if (connectionString == null)
    {
        throw new ApplicationException("The ConnectionString is not defined.");
    }

    // register all your components with the container here
    // it is NOT necessary to register your controllers
    theContainer.RegisterType<ISystem1Entities, System1Entities>(
        new HierarchicalLifetimeManager(), 
        new InjectionConstructor(connectionString.ConnectionString));

    theContainer.RegisterType<ISystem1Service, System1Service>();

    var factory = new UnityControllerFactory(theContainer);
    ControllerBuilder.Current.SetControllerFactory(factory);

    return theContainer;
}

Я хочу реорганизовать загрузчик, чтобы я мог загружать «неизвестные» сборки во время компиляции, регистрировать содержащиеся в них типы и позволять веб-приложению ссылаться на контроллер, службы и контексты по мере необходимости.

Я просмотрел проект Unity AutoRegistraion, и, похоже, он приближает меня к реализации, которую я хочу, но не знаю, как реализовать следующую идею:

BootStrapper.BuildUnityConfiguration
    Initialize Container
    RegisterMyAssemblies()

Я хочу, чтобы процесс «RegisterMyAssemblies» зарегистрировал список подстановочных знаков сборок, а затем приступил к регистрации типов в каждой отдельной сборке. Какие-либо предложения?


person scott-pascoe    schedule 08.10.2012    source источник


Ответы (2)


Вы не должны пытаться использовать основной загрузчик приложения (корень композиции) для настройки этих сборок плагинов, так как это будет довольно сложно сделать правильно. Это трудно сделать правильно, потому что основная программа не знает, какие типы регистрировать и к какому сроку жизни эти типы должны быть зарегистрированы.

Вместо этого пусть каждая сборка подключаемого модуля имеет собственный метод начальной загрузки и передает экземпляр контейнера приложения в этот загрузчик подключаемого модуля.

Например, вы можете определить интерфейс IBootstrapper в основной сборке и позволить каждой сборке подключаемого модуля реализовать этот интерфейс. Например:

public class System1Bootstrapper : IBootstrapper
{
    void IBootstrapper.Bootstrap(IUnityContainer container)
    {
        var conString = ConfigurationManager
            .ConnectionStrings["WebAppConnectionString"];

        if (conString == null)
        {
            throw new ApplicationException(
                    "The ConnectionString is not defined.");
        }

        // register all your components with the container here
        // it is NOT necessary to register your controllers
        container.RegisterType<ISystem1Entities, System1Entities>(
            new HierarchicalLifetimeManager(), 
            new InjectionConstructor(conString.ConnectionString));

        container.RegisterType<ISystem1Service, System1Service>();
    }
}

В корне композиции вашего приложения теперь вы можете просто добавить этот код для регистрации всех типов плагинов:

var pluginBootstrappers =
    from Assembly assembly in BuildManager.GetReferencedAssemblies()
    from type in assembly.GetExportedTypes()
    where typeof(IBootstrapper).IsAssignableFrom(type)
    select (IBootstrapper)Activator.CreateInstance(type);

pluginBootstrappers.ToList().ForEach(b => b.Bootstrap(container));
person Steven    schedule 08.10.2012
comment
Спасибо за понимание. Я смог очень аккуратно вписать ваше решение в мою архитектуру. - person scott-pascoe; 08.10.2012
comment
Другой вариант — позволить каждому плагину управлять своим экземпляром контейнера. Это удерживает подсистемы изолированными друг от друга, но это возможно только тогда, когда взаимодействие между подсистемами незначительно или отсутствует. - person Steven; 09.10.2012

Это, по общему признанию, плагин, но взгляните на мой Unity Automapper на NuGet. Он позволяет вам делать именно такие вещи — вы просто предоставляете ему набор типов или имен сборок; он автоматически соединит зависимости на основе сопоставлений интерфейса и бетона. Он также работает с внутренними типами, поэтому вы можете сделать «правильный» DIP, сделав ваши интерфейсы общедоступными, а поставщиков этих интерфейсов — внутренними.

person Isaac Abraham    schedule 09.10.2012