我有一个服务,它将在运行时以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));
欢迎改进建议。
答案 0 :(得分:1)
我担心Autofac不那么灵活。它支持XML configuration,但我希望它只支持原始类型作为构造函数参数。
另一方面,您的示例似乎是依赖注入的错误用法。当注入组件既不关心谁使用它,也不是组件消费者关心它接收哪种服务实现时,基本上应该使用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
不包含与配置相关的逻辑。
<强>更新强>
如果你真的需要这种复杂的逻辑用于依赖配置,最好用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模块,它提供一些额外的控制,例如,您可以只在记录器和存储之间创建一个字典,并从中进行配置