每次使用不同的依赖项实现解析同一个类的多个实例

时间:2011-12-10 17:50:08

标签: c# .net dependency-injection autofac

我有一个服务,它将在运行时以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实例的集合,类似于上面的手工示例?

更新

我自己为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 个答案:

答案 0 :(得分:1)

我担心Autofac不那么灵活。它支持XML configuration,但我希望它只支持原始类型作为构造函数参数。

另一方面,您的示例似乎是依赖注入的错误用法。当注入组件既不关心谁使用它,也不是组件消费者关心它接收哪种服务实现时,基本上应该使用DI。我会向ILoggerIStorage添加一个标识属性,使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不包含与配置相关的逻辑。

<强>更新

如果你真的需要这种复杂的逻辑用于依赖配置,最好用c#代码表示,你最好的选择是使用Modules。只需将用于配置所需内容的代码解压缩到单独的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的计算机上)并与之交换旧模块。当然,这种方法仅适用于“启动时”配置。

答案 1 :(得分:0)

是的,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模块,它提供一些额外的控制,例如,您可以只在记录器和存储之间创建一个字典,并从中进行配置