Получить экземпляр контейнера для простого инжектора
Я использую 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 - следующая лучшая вещь. Простые образцы Injector должны использовать статическую переменную, чтобы разделить контейнер, который я хочу избежать, а также это невозможно, кстати SimpleInjectorInitializer
работает.
1 ответ
За исключением любого кода, который является частью пути запуска приложения, код не должен напрямую зависеть от контейнера (или абстракции контейнера, фасада контейнера и т. Д.). Этот шаблон называется Service Locator, и у Марка Симанна есть хорошее объяснение, почему это плохая идея.
Таким образом, компоненты (такие как контроллеры) не должны напрямую зависеть от контейнера, поскольку это скрывает используемые зависимости и затрудняет тестирование классов. Более того, ваш код начинает зависеть от внешней структуры (что усложняет ее изменение) или от абстракции, о которой он не должен знать.
Мой контроллер использует сервис, инициализация которого зависит от входного параметра, переданного в методе контроллера, и, следовательно, зависимость не может быть создана во время создания
Для этой проблемы есть общий шаблон: абстрактный шаблон фабричного дизайна. Заводской шаблон позволяет задерживать создание типов и позволяет передавать дополнительные параметры времени выполнения для построения определенного типа. Когда вы делаете это, ваш контроллер не должен зависеть от Контейнера, и это препятствует тому, чтобы вам пришлось проходить в построенном контейнере в ваших модульных тестах (платформы DI вообще не должны использоваться в ваших проектах модульных тестов).
Однако обратите внимание, что разрешение компонентам требовать данных во время создания - это запах кода. Не делай этого.
Вы можете подумать, что, делая это, мы просто переносим проблему на заводскую реализацию. Хотя мы переносим зависимость от контейнера в реализацию фабрики, мы на самом деле решаем проблему, потому что реализация фабрики будет частью корня композиции приложения, что позволяет самому коду приложения не обращать внимания на любую инфраструктуру 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 и любому другому контейнеру: вы не хотите внедрять свой контейнер в компоненты, которые находятся за пределами корня композиции (и вряд ли когда-либо есть причина для этого).