Заблокированные фабрики делегатов с параметрами конструктора времени выполнения?

Предположим, что у меня есть следующая служба и компоненты:

public interface IService
{
    void DoWork();
}

public class ServiceA : IService
{
    private readonly string _name;

    public ServiceA(string name)
    {
        _name = name;
    }

    public void DoWork()
    {
        //ServiceA DoWork implementation
    }
}

public class ServiceB : IService
{
    private readonly string _name;

    public ServiceB(string name)
    {
        _name = name;
    }

    public void DoWork()
    {
        //ServiceB DoWork implementation
    }
}

Обратите внимание, что каждый компонент принимает параметр конструктора name. Давайте также скажем, что name определяется во время выполнения.

Я просматриваю документацию AutoFac, чтобы попытаться найти безопасный для типа способ решения таких компонентов, не связавшись напрямую с контейнером. Если бы у меня была только одна реализация IService, тогда я мог бы просто использовать Delegate Factory, чтобы передать параметр runtime в конструктор. Тем не менее, у меня есть две реализации, и тот, который должен использоваться, также должен быть определен во время выполнения. Если у меня не было параметра конструктора name, то я мог бы зарегистрировать два компонента по ключу и разрешить с помощью IIndex

Я не могу понять, как это сделать. Я могу каким-то образом объединить использование делегированных фабрик и IIndex разрешения компонентов? Или есть другой способ зарегистрировать и разрешить оба компонента без прямой ссылки на контейнер?

Ответы

Ответ 1

Как вы говорите, ваши два индивидуальных требования поддерживаются AutoFac.

Однако, похоже, нет прямой поддержки для использования этих двух конструкций вместе. То есть следующий не работает:

public enum ServiceType
{
    ServiceA,
    ServiceB
}

public class MyComponent
{
    public MyComponent(Func<string, IIndex<ServiceType, IService> factory)
    {
        var service = factory("some_string")[ServiceType.ServiceA];
    }
}

Моя работа для этого всегда заключалась в переместить разрешение службы на factory для каждой реализации службы. Это работает следующим образом:

  • Компоненты, зависящие от конкретной реализации службы, принимают делегат AutoFac factory, который разрешает factory, относящийся к требуемой реализации службы
  • В свою очередь, сервисные заводы зависят от делегата AutoFac factory, который знает, как создать конкретную реализацию службы из параметров конструктора (runtime) службы
  • Этот подход использует собственные конструкторы AutoFac и не имеет каких-либо зависимостей от AutoFac за пределами проводки контейнера.

Вот пример грубого и готового. Обратите внимание, что несколько фабрик могут быть сведены к одному родовому factory, но я оставил его как есть для ясности:

Реализации служб

public enum ServiceType
{
    NotSet,
    ServiceA,
    ServiceB
}

public interface IService
{
    string DoWork();
}

public class ServiceA : IService
{
    private readonly string _name;

    public ServiceA(string name)
    {          
        _name = name;
    }

    public string DoWork()
    {
        throw new NotImplementedException();
    }
}

public class ServiceB : IService
{
    private readonly string _name;

    public ServiceB(string name)
    {           
        _name = name;
    }

    public string DoWork()
    {
        throw new NotImplementedException();
    }
}

Сервисные заводы

public interface IServiceFactory
{
    IService Create(string name);
}

public class ServiceAFactory : IServiceFactory
{
    private readonly Func<string, ServiceA> _factory;

    public ServiceAFactory(Func<string, ServiceA> factory)
    {            
        _factory = factory;
    }

    public IService Create(string name)
    {
        return _factory(name);
    }
}

public class ServiceBFactory : IServiceFactory
{
    private readonly Func<string, ServiceB> _factory;

    public ServiceBFactory(Func<string, ServiceB> factory)
    {            
        _factory = factory;
    }

    public IService Create(string name)
    {
        return _factory(name);
    }
}

Регистрация услуг

builder.RegisterType<ServiceA>().As<ServiceA>();
builder.RegisterType<ServiceB>().As<ServiceB>();
builder.RegisterType<ServiceAFactory>().Keyed<IServiceFactory>(ServiceType.ServiceA);
builder.RegisterType<ServiceBFactory>().Keyed<IServiceFactory>(ServiceType.ServiceB);
builder.RegisterType<ComponentWithServiceDependency>().As<ComponentWithServiceDependency>(); 

Использование примера

public class ComponentWithServiceDependency
{
    private readonly IService _service;

    public ComponentWithServiceDependency(IIndex<ServiceType, IServiceFactory> serviceFactories)
    {            
        // Resolve the ServiceB service implementation,
        // using the string "test" as its constructor dependency
        _service = serviceFactories[ServiceType.ServiceB].Create("test");
    }

    public string Test()
    {
        return _service.DoWork();
    }
}

Ответ 2

Вы можете использовать сегрегацию интерфейса.

public interface IService
{
    void DoWork();
}

public interface IServiceA : IService
{
}

public interface IServiceB : IService
{
}

public class ServiceA : IServiceA
{
    private readonly string _name;

    public ServiceA(string name)
    {
        _name = name;
    }

    public void DoWork()
    {
        //ServiceA DoWork implementation
    }
}

public class ServiceB : IServiceB
{
    private readonly string _name;

    public ServiceB(string name)
    {
        _name = name;
    }

    public void DoWork()
    {
        //ServiceB DoWork implementation
    }
}

Затем вы можете добавить такие фабрики делегатов:

public class ClientA
{
    public ClientA(Func<string, IServiceA> serviceAFactory, Func<string, IServiceB> serviceBFactory)
    {
        this.serviceAFactory = serviceAFactory;
        this.serviceBFactory = serviceBFactory;
    }

    public CreateServices()
    {
        var runTimeName = "runTimeName";
        var serviceA = this.serviceAFactory(runTimeName);
        var serviceB = this.ServiceBFactory(runTimeName);
    }
}

Autofac будет генерировать делегат factory для каждого зарегистрированного интерфейса:

public class MyModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<ServiceA>()
            .As<IService>()
            .As<IServiceA>();

        builder.RegisterType<ServiceB>()
            .As<IService>()
            .As<IServiceB>();
    }
}