Разрешение нескольких экземпляров одного и того же класса с разными реализациями зависимостей каждый раз - PullRequest
3 голосов
/ 10 декабря 2011

У меня есть служба, которая будет динамически получать конфигурацию в XML во время выполнения. У меня есть класс обслуживания, и мне нужно создать несколько его экземпляров, обеспечивающих различные реализации зависимостей для каждого экземпляра класса обслуживания. Рассмотрим следующий пример:

interface ILogger { }
class FileLogger : ILogger { }
class DebugLogger : ILogger { }
class ConsoleLogger : ILogger { }

interface IStorage { }
class RegistrySrorage : IStorage { }
class FileStorage : IStorage { }
class DatabaseStorage : IStorage { }

class MyService
{
    ILogger _logger;
    IStorage _storage;
    public MyService(ILogger logger, IStorage storage)
    {
        _logger = logger;
        _storage = storage;
    }
}

Я могу сделать инъекцию зависимостей вручную:

IEnumerable<MyService> services = new List<MyService>()
{
    new MyService(new FileLogger(), new RegistrySrorage()),
    new MyService(new FileLogger(), new DatabaseStorage()),
    new MyService(new ConsoleLogger(), new FileStorage()),
    new MyService(new DebugLogger(), new FileStorage()),
    // same implementations as in previous instance are used but with different
    // constructor parameter: for example, different destination in FileStorage
    new MyService(new DebugLogger(), new FileStorage()),
};

Есть ли способ создать конфигурацию XML и иметь структуру DI для предоставления коллекции сконфигурированных экземпляров MyService, аналогично приведенному выше ручному примеру?

UPDATE

Я сам нашел решение для autofac, но я не думаю, что это лучший способ сделать это.

Создан список услуг:

<component service="System.Collections.IList, mscorlib" type="System.Collections.ArrayList, mscorlib" name="ServicesList">
    <parameters>
        <parameter name="c">
            <list>
                <item value="loggerUID,storageUID"/>
            </list>
        </parameter>
    </parameters>
</component>

Затем создал список всех необходимых компонентов для разрешения зависимостей и назвал их уникально:

<component service="Test.ILogger"
        type="Test.FileLogger"
        name="loggerUID">
    <parameters>
        <parameter name="logFile" value="C:\Temp\MyLogForSvc_1.log" />
    </parameters>
</component>

Затем в коде при первом проходе я получаю список всех сервисов (компонент с именем «ServicesList»). И на втором этапе, после загрузки компонентов из XML, я регистрирую все службы в коде, используя предоставленные имена компонентов в качестве ключей (здесь нет проверок работоспособности):

foreach (string cfg in servicesList)
{
    string[] impl = cfg.Split(',');

    builder.Register<MyService>(c => new MyService(
        c.ResolveKeyed<ILogger>(impl[0]),
        c.ResolveKeyed<IStorage>(impl[1])))
        .Named<MyService>(cfg);
}
IContainer container = builder.Build();

List<MyService> services = new List<MyService>();
foreach (string svcName in servicesList)
    services.Add(container.ResolveNamed<MyService>(svcName));

Предложения по улучшению приветствуются.

Ответы [ 2 ]

1 голос
/ 10 декабря 2011

Боюсь, Autofac не такой гибкий. Он поддерживает XML-конфигурацию , но я ожидаю, что он будет поддерживать только примитивные типы в качестве параметров конструктора.

С другой стороны, ваш пример, кажется, неверно использует внедрение зависимостей. DI в основном должен использоваться, когда ни компоненту инъекции не важно, кто его использует, ни потребителю компонента не важно, какую реализацию сервиса он получает. Я бы добавил свойство идентификации к ILogger и IStorage, чтобы MyService получал все доступные регистраторы и хранилища, а внутри него реализовывал логику, которая обрабатывает свою конкретную конфигурацию, чтобы определить, какие комбинации использовать. Примерно так:

public interface ILogger
{
  string Id { get; }
}
public class FileLogger : ILogger
{
  public string Id { get { return "Logger.File"; } }
}
// etc.

public interface IStorage
{
  string Id { get; }
}
public class RegistrySrorage : IStorage
{
  public string Id { get { return "Storage.Registry"; } }
}

public class MyService
{
  IList<Config> _EnabledConfigs;

  public MyService(IEnumerable<ILogger> loggers, IEnumerable<IStorage> storages)
  {
    _EnabledConfigs = ParseXmlConfigAndCreateRequiredCombinations(loggers, storages);
  }

  class Config
  {
    public ILogger Logger { get; set; }
    public IStorage Storage { get; set; }
  }
}

// container config:
public static void ConfigureContainer(IContainerBuilder builder)
{
  builder.RegisterType<FileLogger>.AsImplementedInterfaces();
  // other loggers next...

  builder.RegisterType<RegisterStorage>.AsImplementedInterfaces();
  // then other storages

  builder.RegisterType<MyService>();
}

И конфиг выглядит так:

<MyServiceConfig>
  <EnabledCombinations>
    <Combination Logger="Logger.File" Storage="Storage.Registry"/>
    <!-- Add other enabled combinations -->
  </EnabledCombinations>
</MyServiceConfig>

Подумай об этом. Бьюсь об заклад, это будет намного проще.

В качестве опции вы можете создать отдельный класс, отвечающий за настройку MyService, чтобы MyService не содержал логику, связанную с конфигурацией.

UPDATE

Если вам действительно нужна такая сложная логика для конфигураций зависимостей, которая лучше всего выражается в коде c #, лучше всего использовать Модули . Просто извлеките код, который настраивает то, что вам нужно, в отдельный модуль Autofac:

public class MyServiceConfigModule : Module
{
  protected override void Load(ContainerBuilder builder)
  {
    // register some compopnent that uses MyService and initialize it with
    // the required set of loggers and storages
    builder.Register(ctx => new MyServiceConsumer(
      new List<MyService>()
      {
        new MyService(new FileLogger(), new RegistrySrorage()),
        new MyService(new FileLogger(), new DatabaseStorage()),
        new MyService(new ConsoleLogger(), new FileStorage()),
        new MyService(new DebugLogger(), new FileStorage()),
        // same implementations as in previous instance are used but with different
        // constructor parameter: for example, different destination in FileStorage
        new MyService(new DebugLogger(), new FileStorage()),
      }));
  }
}

, поместите его в отдельную сборку «MyServiceConfig» и добавьте пару строк конфигурации в app.config:

<autofac>
  <modules>
    <module type="MyServiceConfigModule, MyServiceConfig" />
  </modules>
</autofac>

Когда вам нужно изменить его, вы можете написать новый исходный файл модуля, скомпилировать его на месте (csc.exe всегда присутствует на компьютере с .NET) и заменить его старым. Конечно, этот подход подходит только для конфигурации «время запуска».

0 голосов
/ 10 декабря 2011

Да, autofac допускает традиционную конфигурацию xml, в этом примере, например (взято из autofac docs )

<autofac defaultAssembly="Autofac.Example.Calculator.Api">
            <components>
                    <component
                            type="Autofac.Example.Calculator.Addition.Add, Autofac.Example.Calculator.Addition"
                            service="Autofac.Example.Calculator.Api.IOperation" />

                    <component
                            type="Autofac.Example.Calculator.Division.Divide, Autofac.Example.Calculator.Division"
                            service="Autofac.Example.Calculator.Api.IOperation" >
                            <parameters>
                                    <parameter name="places" value="4" />
                            </parameters>
                    </component>

Вы также можете использовать модуль autofac, который дает некоторые дополнительныенапример, вы можете создать словарь между регистраторами и хранилищами и настроить его из этого

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...