Получите экземпляр контейнера для Simple Injector

Я использую Simple Injector с проектом ASP.NET MVC. Я добавил пакет SimpleInjector.Integration.Web.Mvc nuget. Это добавляет класс SimpleInjectorInitializer в папку App_Start и инициализирует DI. Код выглядит примерно так

public static void Initialize()
{
    // Did you know the container can diagnose your configuration? 
    // Go to: https://simpleinjector.org/diagnostics
    var container = new Container();

    //Container configuration code
    DependencyResolver.SetResolver(
        new SimpleInjectorDependencyResolver(container));
}

Это правильно настраивает DI для контроллера MVC.

Мой вопрос: если я хочу получить экземпляр контейнера в любом из контроллеров \ классов, чтобы вручную разрешить некоторую зависимость, как я могу это сделать.

Раньше я работал с AutoFac, и у него есть интерфейс зависимостей IComponentContext, который может быть введен в любой класс, которому необходимо выполнить любое разрешение вручную.

Обновление:

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

Я понимаю, что это в некоторой степени антипаттерн для DI, но он требуется в некоторых местах, и, следовательно, инъекция контейнера DI - следующая лучшая вещь. В примерах Simple Injector следует использовать статическую переменную для совместного использования контейнера, которого я хочу избежать, а также это невозможно, кстати, SimpleInjectorInitializer работает.


person Chandermani    schedule 28.07.2013    source источник
comment
Вы можете ввести Контейнер, но не должны. В этом случае вы злоупотребляете контейнером как указателем службы. Зачем вам нужен контейнер в этом контроллере?   -  person Steven    schedule 28.07.2013
comment
Добавлен сценарий, который нужно поддерживать   -  person Chandermani    schedule 28.07.2013


Ответы (1)


За исключением любого кода, который является частью пути запуска приложения, никакой код не должен напрямую зависеть от контейнера (или абстракции контейнера, фасада контейнера и т. Д.). Этот шаблон называется Service Locator и Марк Земанн имеет хорошее объяснение почему это плохая идея.

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

Мой контроллер использует службу, инициализация которой зависит от входного параметра, переданного в методе контроллера, и, следовательно, зависимость не может быть создана во время строительства.

Для этой проблемы существует общий шаблон: шаблон проектирования абстрактной фабрики. Шаблон фабрики позволяет отложить создание типов и позволяет передавать дополнительные параметры времени выполнения для создания определенного типа. Когда вы это делаете, ваш контроллер не должен зависеть от контейнера и избавляет вас от необходимости передавать сконструированный контейнер в ваших модульных тестах (структуры DI, как правило, не должны использоваться в ваших проектах модульного тестирования).

Обратите внимание, однако, что разрешение вашим компонентам требовать данные среды выполнения во время создания - это код. запах. Не допускайте этого.

Вы можете подумать, что, делая это, мы просто переносим проблему на фабричную реализацию. Хотя мы переносим зависимость от контейнера в реализацию фабрики, мы фактически решаем проблему, поскольку реализация фабрики будет частью Composition Root, который позволяет самому коду приложения игнорировать любую структуру DI.

Вот как я советую вам структурировать свой код:

// Definition of the factory in the UI or BL layer
public interface ISomeServiceFactory
{
    ISomeService Create(int inputParameter);
}

// Controller depending on that factory:
public class MyController : Controller
{
    private readonly ISomeServiceFactory factory;

    public MyController(ISomeServiceFactory factory)
    {
        this.factory = factory;
    }

    public ActionResult Index(int value)
    {
        // here we use that factory
        var service = this.factory.Create(value);
    }
}

В корне вашей композиции (путь запуска) мы определяем реализацию фабрики и регистрацию для нее:

private class SomeServiceFactory : ISomeServiceFactory
{
    private readonly Container container;

    // Here we depend on Container, which is fine, since
    // we're inside the composition root. The rest of the
    // application knows nothing about a DI framework.
    public SomeServiceFactory(Container container)
    {
        this.container = container;
    }

    public ISomeService Create(int inputParameter)
    {
        // Do what ever we need to do here. For instance:
        if (inputParameter == 0)
            return this.container.GetInstance<Service1>();
        else
            return this.container.GetInstance<Service2>();
    }
}

public static void Initialize()
{
    var container = new Container();

    container.RegisterSingle<ISomeServiceFactory, SomeServiceFactory>();
}

После создания Container регистрируется (с помощью вызова RegisterSingle<Container>(this)), поэтому вы всегда можете вставить контейнер в любой компонент. Это похоже на введение IComponentContext при работе с Autofac. Но то же самое относится к Autofac, Simple Injector и любому другому контейнеру: вы не хотите внедрять свой контейнер в компоненты, расположенные за пределами корня композиции (и для этого вряд ли когда-либо есть причина).

person Steven    schedule 28.07.2013
comment
Спасибо, Стив, да, я понимаю эту часть и применяю тот же шаблон, который вы объяснили здесь. Но поскольку сам Factory требует контейнер, я использую AutoFac IComponentContext. Меня смутило, как сделать то же самое в SimpleInjector. - person Chandermani; 28.07.2013
comment
В дополнение к отличному ответу Стивена, мы можем легко использовать дженерики для возврата конкретной конкретной реализации ISomeService (вместо жесткого кодирования с помощью if / else), используя следующее: public T Create<T>() where T : ISomeService { return (T)this.container.GetInstance(typeof(T)); }. Затем использовать: var service = this.factory.Create<Service1>();. Вам также потребуется обновить ISomeServiceFactory интерфейс следующим образом: T Create<T>() where T : ISomeService;. - person GFoley83; 01.12.2015
comment
Я понимаю, что теоретически создание фабричных интерфейсов для всех типов, создаваемых вашим кодом, является хорошей идеей. Однако на самом деле может быть код, в котором вам нужно создать много разных типов объектов. Тогда вы могли бы использовать интерфейс, который действительно может создавать для вас много разных типов. Некоторые фундаменталисты возненавидят меня за это, но, IContainer. - person Johan Franzén; 18.11.2019