Autofac - разрешение параметров времени выполнения без необходимости передавать контейнер
У меня есть более простой класс "ServiceHelper", который принимает два параметра в конструкторе:
public ServiceHelper(ILogger<ServiceHelper> log, string serviceName)
(Универсальная оболочка ILogger для NLog, которую Autofac предоставляет просто отлично, а serviceName - это имя службы Windows, которую я должен предоставить во время выполнения.)
У меня возникают проблемы, когда я не могу понять, как создавать новые экземпляры этого класса во время выполнения, передавая разные имена служб, используя Autofac. Что-то вроде этого не работает, конечно, так как мне нужно указать разные имена служб во время выполнения:
builder.RegisterType<ServiceHelper>().As<IServiceHelper>().WithParameter(new NamedParameter("serviceName", null)).InstancePerDependency();
Из того, что я прочитал, это плохая привычка передавать контейнер и вызывать Resolve вручную правильно (сервисный локатор "анти-шаблон", о котором предупреждает AutoFac), или это так? Если бы я сделал это, то я мог бы сделать
container.Resolve<ServiceHelper>(new NamedParameter("serviceName", "some service name"));
Но даже для того, чтобы зайти так далеко, я не совсем уверен, как заставить Autofac внедрить контейнер в классы, ему просто нужно было бы зарегистрироваться, как именно, как это? И потом, мои классы требуют IContainer в своих конструкторах? (Это в C# Service с использованием инжектора конструктора)
builder.RegisterType<Container>().As<IContainer>().InstancePerDependency();
Я тоже читал о фабриках делегатов, но это, кажется, не избавляет от необходимости передавать контейнер.
На самом деле большинству моих классов, которые используют ServiceHelper, просто нужны 1 или 2 ServiceHelpers для конкретных имен сервисов, так что не то, чтобы я зарабатываю тысячи с неожиданными параметрами serviceName, это просто немного огорчает мою голову.
3 ответа
Да, пропускать контейнер повсюду - это анти-паттерн.
Вы можете избежать этого, используя такую фабрику:
(примечание: весь код в этом ответе не проверен, я пишу это в текстовом редакторе на машине без Visual Studio)
public interface IServiceHelperFactory
{
IServiceHelper CreateServiceHelper(string serviceName);
}
public class ServiceHelperFactory : IServiceHelperFactory
{
private IContainer container;
public ServiceHelperFactory(IContainer container)
{
this.container = container;
}
public IServiceHelper CreateServiceHelper(string serviceName)
{
return container.Resolve<ServiceHelper>(new NamedParameter("serviceName", serviceName));
}
}
При запуске вы регистрируете ServiceHelperFactory
в автофаке, как и все остальное:
builder.RegisterType<ServiceHelperFactory>().As<IServiceHelperFactory>();
Затем, когда вам нужно ServiceHelper
в другом месте вы можете получить фабрику с помощью инжектора конструктора:
public class SomeClass : ISomeClass
{
private IServiceHelperFactory factory;
public SomeClass(IServiceHelperFactory factory)
{
this.factory = factory;
}
public void ThisMethodCreatesTheServiceHelper()
{
var helper = this.factory.CreateServiceHelper("some service name");
}
}
Создавая саму фабрику с помощью инжектора конструктора с помощью Autofac, вы убедитесь, что фабрика знает о контейнере, без необходимости самим передавать контейнер.
Признаюсь, на первый взгляд это решение не выглядит совсем иначе, чем передача контейнера напрямую. Но преимущество в том, что ваше приложение по-прежнему отделено от контейнера - единственное место, где контейнер известен (кроме запуска), находится внутри фабрики.
РЕДАКТИРОВАТЬ:
Хорошо я забыл Как я уже говорил выше, я пишу это на компьютере без Visual Studio, поэтому я не могу протестировать мой пример кода.
Теперь, когда я прочитал ваш комментарий, я помню, что у меня была похожая проблема, когда я использовал Autofac и попытался зарегистрировать сам контейнер.
Моя проблема была в том, что мне нужно было зарегистрировать контейнер в сборщике.
Но чтобы зарегистрировать экземпляр контейнера, мне нужно было вызвать builder.Build()
... который создает контейнер, что означает, что я не могу зарегистрировать вещи в компоновщике впоследствии.
Я не помню полученное сообщение об ошибке, но, похоже, у вас сейчас такая же проблема.
Решение, которое я нашел, состояло в том, чтобы создать второго компоновщика, зарегистрировать контейнер там, а затем использовать второй компоновщик для обновления одного-единственного контейнера.
Вот мой рабочий код из одного из моих проектов с открытым исходным кодом:
При запуске я регистрирую контейнер:
var builder = new ContainerBuilder();
// register stuff here
var container = builder.Build();
// register the container
var builder2 = new ContainerBuilder();
builder2.RegisterInstance<IContainer>(container);
builder2.Update(container);
... который затем используется WindowService
создать новые окна WPF:
public class WindowService : IWindowService
{
private readonly IContainer container;
public WindowService(IContainer container)
{
this.container = container;
}
public T GetWindow<T>() where T : MetroWindow
{
return (T)this.container.Resolve<T>();
}
}
Я пошел по пути вышеупомянутого подхода, и он отлично работает, однако я счел невозможным модульное тестирование из-за того, что метод "Resolve<>" в IContainer является методом расширения. Это также никогда не казалось "правильным" со всеми разговорами о том, чтобы не передавать свой контейнер.
Я вернулся к чертежной доске и нашел "правильный" способ создания объектов с использованием Autofac Delegate Factories http://docs.autofac.org/en/latest/advanced/delegate-factories.html
Разрешение должно происходить только для корневых композиционных объектов. Вызов решимости - это почти то же самое, что и "создание нового" объекта, что является тестом на запах. Есть моменты, когда разрешение является динамическим и может быть определено только на лету, но большинство зависимостей являются детерминированными и могут быть зарегистрированы заранее. Как это сделать с помощью Autofac - это сложная задача. Наградами ответ @Christian Specht является хорошим ответом, но он предполагает, что все определяется во время выполнения.
Чтобы определить цепочку зависимостей во время разработки, см. Тему SO Регистрация цепочки зависимостей Autofac...