Autofac - Как создать сгенерированный factory с параметрами

Я пытаюсь создать с Autofac 'сгенерированный' factory, который будет разрешать зависимости в режиме реального времени на основе параметра enum.

Учитывая следующие интерфейсы/классы:

public delegate IConnection ConnectionFactory(ConnectionType connectionType);

public enum ConnectionType
{
    Telnet,
    Ssh
}

public interface IConnection
{
    bool Open();
}

public class SshConnection : ConnectionBase, IConnection
{
    public bool Open()
    {
        return false;
    }
}

public class TelnetConnection : ConnectionBase, IConnection
{
    public bool Open()
    {
        return true;
    }
}

public interface IEngine
{
    string Process(ConnectionType connectionType);
}

public class Engine : IEngine
{
    private ConnectionFactory _connectionFactory;

    public Engine(ConnectionFactory connectionFactory)
    {
        _connectionFactory = connectionFactory;
    }

    public string Process(ConnectionType connectionType)
    {
        var connection = _connectionFactory(connectionType);

        return connection.Open().ToString();
    }
}

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

Я начал со следующих регистраций:

builder.RegisterType<AutoFacConcepts.Engine.Engine>()
    .As<IEngine>()
    .InstancePerDependency();

builder.RegisterType<SshConnection>()
    .As<IConnection>();
builder.RegisterType<TelnetConnection>()
    .As<IConnection>();

Затем я продолжал играть с регистрацией TelnetConnection/SshConnection с различными параметрами:

  • Названа
  • Keyed
  • Метаданные

Я не смог найти правильную комбинацию регистраций, которая позволит мне определить сгенерированный делегат factory, который вернет правильный объект соединения (SshConnection для ConnectionType.Ssh и TelnetConnection для ConnectionType.Telnet).

Ответ 1

Обновите класс Engine, чтобы вместо <делегата принять Func<ConnectionType, IConnection>. Autofac поддерживает создание делегатных фабрик на лету через Func<T>.

public class Engine : IEngine
{
    private Func<ConnectionType, IConnection> _connectionFactory;

    public Engine(Func<ConnectionType, IConnection> connectionFactory)
    {
        _connectionFactory = connectionFactory;
    }

    public string Process(ConnectionType connectionType)
    {
        var connection = _connectionFactory(connectionType);

        return connection.Open().ToString();
    }
}

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

builder.Register<IConnection>((c, p) =>
{
    var type = p.TypedAs<ConnectionType>();
    switch (type)
    {
        case ConnectionType.Ssh:
            return new SshConnection();
        case ConnectionType.Telnet:
            return new TelnetConnection();
        default:
            throw new ArgumentException("Invalid connection type");
    }
})
.As<IConnection>();

Если для самого соединения требуется зависимость, вы можете вызвать Resolve в параметре c, чтобы разрешить его из текущего контекста вызова. Например, new SshConnection(c.Resolve<IDependency>()).

Ответ 2

Если вам нужно выбрать тип реализации на основе параметра, вам нужно использовать IIndex<T,B> тип неявного отношения:

public class Engine : IEngine
{
    private IIndex<ConnectionType, IConnection> _connectionFactory;

    public Engine(IIndex<ConnectionType, IConnection> connectionFactory)
    {
        _connectionFactory = connectionFactory;
    }

    public string Process(ConnectionType connectionType)
    {
        var connection = _connectionFactory[connectionType];

        return connection.Open().ToString();
    }
}

И зарегистрируйте свои реализации IConnection с помощью ключей перечисления:

builder.RegisterType<Engine>()
.           As<IEngine>()
    .InstancePerDependency();

builder.RegisterType<SshConnection>()
    .Keyed<IConnection>(ConnectionType.Ssh);
builder.RegisterType<TelnetConnection>()
    .Keyed<IConnection>(ConnectionType.Telnet);

Если вы хотите сохранить свой ConnectionFactory, вы можете вручную зарегистрировать его, чтобы использовать IIndex<T,B> внутренне с:

builder.Register<ConnectionFactory>(c =>
{
    var context = c.Resolve<IComponentContext>();
    return t => context.Resolve<IIndex<ConnectionType, IConnection>>()[t];
});

В этом случае вам все равно нужно зарегистрировать типы IConnection в качестве ключа, но ваша реализация Engine может оставаться неизменной.