2015-10-22 3 views
11

Я пытаюсь создать с помощью Autofac «сгенерированную» фабрику, которая будет разрешать зависимости в режиме реального времени на основе параметра enum.Autofac - Как создать сгенерированный завод с параметрами

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

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 для создания какой-то завод, который имеет метод, который принимает один параметр: ConnectionType и возвращает правильное соединение объект.

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

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

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

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

  1. Названный
  2. шпоночным
  3. Метаданные

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

ответ

11

Обновить класс Engine, чтобы принять Func<ConnectionType, IConnection> вместо делегата. Autofac supports creating delegate factories on the fly via 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' заключается в том, чтобы избежать ручной записи этих больших корпусов коммутаторов, чтобы эта логика могла обрабатываться Autofac. В конечном счете, если у этих типов есть несколько аргументов конструктора, весь этот код может быть довольно запутанным. – nemesv

+0

Если OP хочет сохранить делегат 'ConnectionFactory' вместо' Func ', то следующая работа должна работать: 'builder.Register ((c, p) => { var type = p.Named ("ConnectionType"); переключатель (тип) { случай ConnectionType.Ssh: .... }} ) .Как (); 'точка является именованный параметр name '" connectionType "' соответствует параметру в объявлении делегата. – nemesv

+0

Я, конечно, не против 'IIndex', но опять же я всегда очень люблю держать Autofac в качестве контейнера! : D –

5

Если вам необходимо выбрать тип реализации на основе параметра Я вам нужно использовать IIndex<T,B> implicit relation type:

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 может оставаться неизменной.

+0

Я пробовал первое решение, и он работает нормально. Моя проблема с этим, что теперь мой код приложения имеет ссылку на DLL Autofac, так что интерфейс IIndex распознается. – Elie

+0

Что касается второго варианта, я еще не уверен, как заставить его работать.параметр 't' не распознается и, следовательно, не компилируется. Я что-то пропустил? – Elie

+0

Если вы не хотите регистрировать Autofac в своей DLL, вам нужно использовать второе решение, я не знаю, почему он не компилируется для вас. Что такое ошибка messge? Вы также можете попробовать: 'builder.Register (c => { var context = c.Resolve (); ConnectionFactory result = (ConnectionType t) => context.Resolve >() [t]; return result; }); ' – nemesv

Смежные вопросы