Autofac - разрешение параметров времени выполнения без необходимости прохождения контейнера

У меня есть более простой класс "ServiceHelper", который принимает два параметра в конструкторе:

public ServiceHelper(ILogger<ServiceHelper> log, string serviceName)

(Общую оболочку ILogger для NLog, которую Autofac предоставляет просто отлично, а имя службы - это имя службы Windows для управления, которую мне нужно предоставить во время выполнения.)

У меня возникают проблемы с тем, как создавать новые экземпляры этого класса во время выполнения, используя разные имена служб, используя Autofac. Что-то вроде этого не работает, конечно, так как мне нужно указать разные имена служб во время выполнения:

builder.RegisterType<ServiceHelper>().As<IServiceHelper>().WithParameter(new NamedParameter("serviceName", null)).InstancePerDependency();

Из того, что я прочитал, его плохая привычка пропускать контейнер и вызвать "Разрешить" вручную ( "Анти-шаблон" "Об авторе", о котором предупреждает AutoFac), или это так? Если бы я это сделал, я мог бы сделать

container.Resolve<ServiceHelper>(new NamedParameter("serviceName", "some service name"));

Но даже дойти до этого я не совсем уверен, как заставить Autofac вводить контейнер в классы, нужно просто зарегистрировать себя, как именно так? А затем, чтобы мои классы нуждались в IContainer в своих конструкторах? (Это в службе С# с использованием вставки конструктора)

builder.RegisterType<Container>().As<IContainer>().InstancePerDependency();

Я тоже читал о фабриках делегатов, но это не похоже на то, чтобы пропускать контейнер.

На самом деле большинство моих классов, которые потребляют ServiceHelper, просто нуждаются в 1 или 2 ServiceHelpers для определенных имен служб, поэтому его не так, как будто я делаю тысячи с неожиданными параметрами serviceName, это просто заставляет мою голову немного болеть.

Ответы

Ответ 1

Да, прохождение контейнера повсюду - это анти-шаблон.

Вы можете избежать этого, используя factory следующим образом:

(примечание: весь код в этом ответе непроверен, я пишу это в текстовом редакторе на машине без 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 в Autofac, как и все остальное:

builder.RegisterType<ServiceHelperFactory>().As<IServiceHelperFactory>();

Затем, когда вам нужно ServiceHelper где-то еще, вы можете получить factory через инсталляцию конструктора:

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");
    }
}

Создав сам factory с помощью встраивания конструктора с помощью Autofac, вы убедитесь, что factory знает о контейнере, без необходимости самостоятельно передавать контейнер.

Я признаю, на первый взгляд это решение выглядит не совсем иначе, чем прямое обращение контейнера. Но преимущество в том, что ваше приложение все еще отключено от контейнера - единственное место, где известно контейнер (кроме запуска), находится внутри factory.


EDIT:

ОК, я забыл. Как я уже сказал выше, я пишу это на машине без 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>();
    }
}

Ответ 2

Я пошел по пути вышеприведенного подхода, и он отлично работает, однако мне не удалось unit test из-за того, что метод "Resolve < > " в IContainer является методом расширения. Он также никогда не чувствовал себя "правильно" со всеми разговорами о том, чтобы не пропустить ваш контейнер.

Я вернулся к чертежной доске и нашел "правильный" способ создания объектов с использованием Autofac Delegate Factories http://docs.autofac.org/en/latest/advanced/delegate-factories.html

Ответ 3

Резолюция должна выполняться только для объектов корневой композиции. Разрешение вызова близко к тому же, что и "новинг" объекта, который является испытанием на запах. Бывают случаи, когда разрешение динамическое и может определяться только "на лету", но большинство зависимостей детерминированы и могут регистрироваться спереди. Как это сделать с Autofac - это проблема. Награжденный ответ от @Christian Specht - хороший ответ, но он предполагает, что во время выполнения все определяется.

Чтобы определить цепочку зависимостей во время разработки, см. раздел SO Регистрация цепочек зависимостей Autofac...