【问题标题】:Resolving several instances of the same class with different dependency implementations each time每次用不同的依赖实现解析同一个类的多个实例
【发布时间】:2011-12-10 17:50:07
【问题描述】:

我有一个服务,它将在运行时以 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));

欢迎提出改进建议。

【问题讨论】:

  • 请更新您的问题并仔细解释您的“在运行时从 XML 动态接收配置”的场景。特别是,1)您是指 app.config 还是作为参数传递的任意字符串? 2) 当您需要重新配置与MyService 无关的组件时(他们应该注意到这一点),应该怎么办? 3) 为什么需要 XML 而不是即时编译的 c#?
  • 1) 任意参数。 2)旧配置的所有实例都被丢弃(GCed)。 3) config 由用户在服务器端组成(不是直接,而是使用某种 UI 来维护 XML 完整性)。

标签: c# .net dependency-injection autofac


【解决方案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 的机器上)并用它交换旧的。当然,这种方法只适用于“启动时”配置。

【讨论】:

  • 我不能使用这种方法,因为配置是在运行时提供的,所以无论是服务类还是其他任何人都不知道它们在编译时可能的组合和构造函数参数。
  • @hydrechan “运行时”是什么意思?它是“启动时间”(即在应用程序启动时检查一次)还是“运行时间”(即配置可能在应用程序生命周期内发生变化?如果是第一个,我的方法很合适。如果是第二个,DI 不会帮助你,你最好考虑使用MEF。
  • @hydrechan 我添加了一个可能对您有所帮助的选项。如果您确实需要编写一些代码来配置容器,这是最佳选择。请查看更新。
  • MEF 是关于发现组件的。我需要配置现有组件。是的,配置可能会在程序生命周期内多次更改。
  • @hydrechan 这是您尝试使用 DI 解决任务的主要原因。这根本不是 DI 框架的问题。您最好编写自己的类来解决您的特定要求。我想我可以相当有把握地声称没有一个 DI 框架允许监听 XML 配置更改和动态重建容器。你找错地方了。
【解决方案2】:

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

【讨论】:

  • 我在学习 Autofac 基础知识时使用了这个例子作为参考,但是它并没有解释我的要求。
猜你喜欢
  • 1970-01-01
  • 2015-05-11
  • 1970-01-01
  • 1970-01-01
  • 2021-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-26
相关资源
最近更新 更多