如何在Simple Injector中为工厂创建的对象解析装饰器

Jas*_*oyd 3 c# decorator simple-injector

所以这将涉及到我展示了很多我的管道,但我会尽量将它保持在最低限度,以保持这个问题的简单.

我的一个API端点依赖外部提供程序来完成调用.当用户向该端点发送查询时,他们可以指定在处理查询时他们希望我们使用哪个提供商,假设提供商是BingGoogle.

所以我有一个IProvider接口和两个具体的实现BingProviderGoogleProvider(在我真正的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我通过这样的方式在容器中注册我的工厂:

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);
        }
    }));
Run Code Online (Sandbox Code Playgroud)

我测试一下 有用.太棒了.

现在我需要为我创建和注册多个装饰器IProvider.每个具体实现IProvider必须在容器解析它们时应用这些装饰器.为了这个例子,我可以说我有Decorator1Decorator2哪个实现IProvider.我用这样的容器注册它们:

container.RegisterDecorator(typeof(IProvider), typeof(Decorator1), Lifestyle.Singleton);
container.RegisterDecorator(typeof(IProvider), typeof(Decorator2), Lifestyle.Singleton);
Run Code Online (Sandbox Code Playgroud)

这就是问题所在.当我的工厂解析一个实例BingProvider或者GoogleProvider我正好得到的实例时.我想要得到的是一个实例,Decorator2它装饰一个实例,Decorator1然后依次装饰IProvider我要求的具体实现.我假设这是因为我没有具体要求容器解析一个实例,IProvider而是我要求它解决一个具体的实现IProvider.

我似乎已经把自己纠结在这里,我不确定解决它的最佳方法是什么.

编辑

好的,在考虑了这个之后,我理解为什么装饰器永远不会被解决.以Decorator1BingProvider为实例.双方Decorator1BingProvider实施IProvider,但Decorator1并没有实现BingProvider,所以当我问简单的注射器来解决一个实例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);
        }
    }));
Run Code Online (Sandbox Code Playgroud)

我的新问题是,当我运行我的单元测试以验证容器时,测试失败并显示如下错误(我不得不将此错误信息告诉我,以使其与我的示例匹配,希望这不会导致任何问题丢失在翻译中):

配置无效.报告了以下诊断警告:
- [Torn Lifestyle] IProvider的注册映射到与IProvider注册相同的实施和生活方式.它们都映射到Decorator1(Singleton).这将导致每个注册解析到另一个实例:每个注册都有自己的实例.
- [Torn Lifestyle] IProvider的注册映射到与IProvider注册相同的实现和生活方式.它们都映射到Decorator2(Singleton).这将导致每个注册解析到另一个实例:每个注册都有自己的实例.
有关警告的详细信息,请参阅Error属性.请参阅https://simpleinjector.org/diagnostics如何解决问题以及如何抑制个别警告.

Ste*_*ven 5

Simple Injector不允许注册同一接口的多个具体实现

这个说法不正确.实际上有多种方法可以做到这一点.我认为最常见的三种方法是:

  1. 使用条件注册
  2. 注册一系列类型
  3. 手动创建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();
    }
}));
Run Code Online (Sandbox Code Playgroud)

这里我们创建两个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();
    }
}
Run Code Online (Sandbox Code Playgroud)

注册看起来非常类似于前一个:

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));
Run Code Online (Sandbox Code Playgroud)

而不是将Func<T>代表注入工厂,根据您的需要,您可以IProvider直接注入.这意味着您的构造函数将如下所示:

public ProviderFactory(IProvider bing, IProvider google) { ... }
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用条件注册IProvider来消除构造函数参数的歧义:

container.RegisterSingleton<ProviderFactory>();
container.RegisterConditional<IProvider, BingProvider>(
    c => c.Consumer.Target.Name == "bing");
container.RegisterConditional<IProvider, GoogleProvider>(
    c => c.Consumer.Target.Name == "google");
Run Code Online (Sandbox Code Playgroud)

这样做的好处是你不会延迟构建对象图; 当工厂的消费者得到解决时,所有提供者都会直接注入.

或者,您可能还想尝试使用调度程序抽象替换工厂的设计.工厂抽象通常不是消费者最简单的解决方案,因为他们现在必须处理工厂类型和返回的服务抽象.另一方面,调度程序或处理器为消费者提供单一抽象.这使得消费者(及其单元测试)通常更简单.

这样的调度程序看起来很像IProvider接口本身,但是string provider为其实例方法添加了一个参数.例如:

interface IProviderDispatcher {
    void DoSomething(string provider, ProviderData data);
}
Run Code Online (Sandbox Code Playgroud)

调度员的实施可能如下所示:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

调度员的解决方案可以与工厂相同,但现在我们隐藏了消费者的额外步骤.

如果我们可以IProviderDispatcher完全删除抽象,那就更好了,但这只有在string provider运行时数据是请求期间可用的上下文数据时才有可能.在这种情况下,我们可以执行以下操作:

interface IProviderContext {
    string CurrentProvider { get; }
}
Run Code Online (Sandbox Code Playgroud)

而不是单独的提供者抽象,我们可以有一个代理实现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"];
}
Run Code Online (Sandbox Code Playgroud)

同样,在内部它仍然像以前一样,但现在,因为提供者价值是我们可以从可用的环境上下文(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));
Run Code Online (Sandbox Code Playgroud)

现在您可以简单地IProvider向您的消费者注入一个,并在后台自动发送调度.