所以这将涉及到我展示了很多我的管道,但我会尽量将它保持在最低限度以保持这个问题的简单。
我的一个API端点依赖外部提供程序来完成调用。当用户向该端点发送查询时,他们可以指定在处理查询时他们希望我们使用哪个提供商,假设提供商是 Bing 和 Google 。
所以我有一个IProvider
接口和两个具体的实现BingProvider
和GoogleProvider
(在我的真实API中,提供者接口实际上是一个通用的接口,但是我要保留泛型以避免让它变得比它更加混乱)。我需要根据查询中的字段解析正确的提供程序。 Simple Injector不允许注册同一接口的多个具体实现,因此我必须使用工厂;我创建一个看起来像这样的东西:
public class ProviderFactory
{
private readonly Func<string, IProvider> _Selector;
public ProviderFactory(Func<string, IProvider> selector)
{
this._Selector = selector;
}
public IProvider Get(string provider)
{
return this._Selector(provider);
}
}
我通过这样的方式在容器中注册我的工厂:
container.RegisterSingleton<ProviderFactory>(new ProviderFactory(provider =>
{
switch (provider)
{
case "Bing":
return container.GetInstance<BingProvider>()
case "Google":
return container.GetInstance<GoogleProvider>()
default:
throw new ArgumentOutOfRangeException("Unknown provider: " + provider);
}
}));
我测试了一下。有用。太棒了。
现在我需要为IProvider
创建并注册多个装饰器。 IProvider
的每个具体实现必须在容器解析它们时应用这些装饰器。为了这个例子,我可以说Decorator1
和Decorator2
实现了IProvider
。我用这样的容器注册它们:
container.RegisterDecorator(typeof(IProvider), typeof(Decorator1), Lifestyle.Singleton);
container.RegisterDecorator(typeof(IProvider), typeof(Decorator2), Lifestyle.Singleton);
这就是问题所在。当我的工厂解析BingProvider
或GoogleProvider
的实例时,这正是我得到的。我想得到的是Decorator2
的一个实例,它装饰了Decorator1
的一个实例,它反过来装饰我请求的IProvider
的具体实现。我认为这是因为我并没有特别要求容器解析IProvider
的实例,而是要求它解决IProvider
的具体实现。
我似乎已经把自己搞得一团糟,我不知道解决问题的最佳方法是什么。
修改
好的,在考虑了这一点后,我理解为什么装饰器永远不会被解决。以Decorator1
和BingProvider
为例。 Decorator1
和BingProvider
都实现了IProvider
但Decorator1
没有实现BingProvider
所以当我要求Simple Injector解析BingProvider
的实例时,它是Decorator1
甚至不可能给它一个IProvider
的实例。我将不得不要求它以某种方式解析var bingProvider = Lifestyle.Singleton
.CreateProducer<IProvider, BingProvider>(container);
var googleProvider = Lifestyle.Singleton
.CreateProducer<IProvider, GoogleProvider>(container);
container.RegisterSingleton<ProviderFactory>(new ProviderFactory(provider =>
{
switch (provider)
{
case "Bing":
return bingProvider.GetInstance();
case "Google":
return googleProvider.GetInstance();
default:
throw new ArgumentOutOfRangeException("Unknown provider: " + provider);
}
}));
的实例,但在适当的位置给我正确的具体实现。
很好,我理解,但现在我不太确定如何继续。
更新
根据史蒂文的回答,我修改了我的工厂注册:
{{1}}
我的新问题是,当我运行单元测试以验证容器时,测试失败并显示如下错误(我不得不将此错误信息告诉我,以使其与我的示例匹配,希望这不会导致在翻译中丢失的任何东西):
配置无效。报告了以下诊断警告:
- [Torn Lifestyle] IProvider的注册映射到与IProvider注册相同的实现和生活方式。它们都映射到Decorator1(Singleton)。这将导致每个注册解析到另一个实例:每个注册都有自己的实例。
- [Torn Lifestyle] IProvider的注册映射到与IProvider注册相同的实现和生活方式。它们都映射到Decorator2(Singleton)。这将导致每个注册解析到另一个实例:每个注册都有自己的实例。
有关警告的详细信息,请参阅Error属性。请参阅https://simpleinjector.org/diagnostics如何解决问题以及如何压制个别警告。
答案 0 :(得分:5)
Simple Injector不允许注册同一接口的多个具体实现
此声明不正确。实际上有多种方法可以做到这一点。我认为最常见的三种方法是:
InstanceProducer
个实例。特别是选项1和3似乎最适合您的情况,所以让我们从选项3开始:创建InstanceProducers
:
// Create two providers for IProvider according to the required lifestyle.
var bing = Lifestyle.Singleton.CreateProducer<IProvider, BingProvider>(container);
var google = Lifestyle.Singleton.CreateProducer<IProvider, GoogleProvider>(container);
container.RegisterSingleton<ProviderFactory>(new ProviderFactory(provider => {
switch (provider) {
case "Bing": return bing.GetInstance();
case "Google": return google.GetInstance();
default: throw new ArgumentOutOfRangeException();
}
}));
在这里,我们创建了两个InstanceProducer
个实例,每个IProvider
一个。这里的重要部分是为IProvider
抽象创建一个生成器,因为这允许应用IProvider
的装饰器。
或者,您可以选择在switch
内移动case
- ProviderFactory
语句,并为其提供两个单独的代理人;每个提供商一个。例如:
public ProviderFactory(Func<IProvider> bingProvider, Func<IProvider> googleProvider) { .. }
public IProvider Get(string provider) {
switch (provider) {
case "Bing": return bingProvider();
case "Google": return googleProvider();
default: throw new ArgumentOutOfRangeException();
}
}
注册看起来非常类似于前一个:
var bing = Lifestyle.Singleton.CreateProducer<IProvider, BingProvider>(container);
var google = Lifestyle.Singleton.CreateProducer<IProvider, GoogleProvider>(container);
container.RegisterSingleton<ProviderFactory>(new ProviderFactory(
bingProvider: bing.GetInstance,
googleProvider: google.GetInstance));
而不是将Func<T>
个代表注入工厂,根据您的需要,您可以直接注入IProvider
。这意味着您的构造函数将如下所示:
public ProviderFactory(IProvider bing, IProvider google) { ... }
现在,您可以在IProvider
上使用条件注册来消除构造函数参数的歧义:
container.RegisterSingleton<ProviderFactory>();
container.RegisterConditional<IProvider, BingProvider>(
c => c.Consumer.Target.Name == "bing");
container.RegisterConditional<IProvider, GoogleProvider>(
c => c.Consumer.Target.Name == "google");
这样做的好处是你不会延迟构建对象图;当工厂的消费者得到解决时,所有提供者都会直接注入。
或者,您可能还想尝试使用调度程序抽象替换工厂的设计。工厂抽象通常不是消费者最简单的解决方案,因为他们现在必须处理工厂类型和返回的服务抽象。另一方面,调度程序或处理器为消费者提供单一抽象。这使得消费者(及其单元测试)通常更简单。
这样的调度程序看起来很像IProvider
接口本身,但在其实例方法中添加了string provider
参数。例如:
interface IProviderDispatcher {
void DoSomething(string provider, ProviderData data);
}
调度员的实施可能如下所示:
public ProviderDispatcher(IProvider bing, IProvider google) { .. }
public void DoSomething(string provider, ProviderData data) {
this.Get(provider).DoSomething(data);
}
private IProvider Get(string provider) {
switch (provider) {
case "Bing": return this.bing;
case "Google": return this.google;
default: throw new ArgumentOutOfRangeException();
}
}
调度员的解决方案可以与工厂相同,但现在我们隐藏了消费者的额外步骤。
如果我们可以完全删除IProviderDispatcher
抽象,那就更好了,但这只有在string provider
运行时数据是请求期间可用的上下文数据时才有可能。在这种情况下,我们可以执行以下操作:
interface IProviderContext {
string CurrentProvider { get; }
}
而不是单独的提供者抽象,我们可以在IProvider
上实现代理实现:
class ProviderDispatcherProxy : IProvider {
public ProviderDispatcherProxy(Func<IProvider> bingProvider,
Func<IProvider> googleProvider,
IProviderContext providerContext) { ... }
void IProvider.DoSomething(ProviderData data) {
// Dispatch to the correct provider
this.GetCurrentProvider.DoSomething(data);
}
private IProvider GetCurrentProvider() =>
switch (this.providerContext.CurrentProvider) {
case "Bing": return this.bingProvider();
case "Google": return this.googleProvider();
default: throw new ArgumentOutOfRangeException();
}
};
}
class AspNetProviderContext : IProviderContext {
public CurrentProvider => HttpContext.Current.Request.QueryString["provider"];
}
同样,在内部,它仍然像以前一样,但现在,因为提供者价值是我们可以从可用的环境上下文(HttpContext.Current
)解决的,我们将能够让消费者直接与IProvider
合作。您可以按如下方式注册:
container.RegisterSingleton<IProviderContext>(new AspNetProviderContext());
var bing = Lifestyle.Singleton.CreateProducer<IProvider, BingProvider>(container);
var google = Lifestyle.Singleton.CreateProducer<IProvider, GoogleProvider>(container);
container.RegisterSingleton<IProvider>(new ProviderDispatcherProxy(
bingProvider: bing.GetInstance,
googleProvider: google.GetInstance));
现在您可以简单地向您的消费者注入IProvider
,并在后台自动发送调度。