如何解析工厂在简单注入器中创建的对象的装饰器

How can I resolve decorators for objects created by factory in Simple Injector

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

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

所以我有一个 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);
    }
}

我通过执行以下操作在容器中注册我的工厂:

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 的每个具体实现都必须在容器解析它们时应用这些装饰器。为了这个例子,假设我有 Decorator1Decorator2,它们实现了 IProvider。我像这样在容器中注册它们:

container.RegisterDecorator(typeof(IProvider), typeof(Decorator1), Lifestyle.Singleton);
container.RegisterDecorator(typeof(IProvider), typeof(Decorator2), Lifestyle.Singleton);

这就是问题所在。当我的工厂解析 BingProviderGoogleProvider 的实例时,这正是我得到的。我想要得到的是 Decorator2 的实例,它装饰了 Decorator1 的实例,后者又装饰了我请求的 IProvider 的任何具体实现。我认为这是因为我没有特别要求容器解析 IProvider 的实例,而是要求它解析 IProvider.

的具体实现

我好像把自己搞得一头雾水,不知道最好的解决办法是什么。

编辑

好吧,再想一想我明白了为什么装饰器永远不会被解决。以 Decorator1BingProvider 为例。 Decorator1BingProvider 都实现了 IProviderDecorator1 没有实现 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);
        }
    }));

我的新问题是,当我 运行 我的单元测试来验证容器时,测试失败并出现如下所示的错误(我不得不修改此错误消息以使其与我在此处的示例匹配, 希望这不会导致翻译中丢失任何内容):

The configuration is invalid. The following diagnostic warnings were reported:
-[Torn Lifestyle] The registration for IProvider maps to the same implementation and lifestyle as the registration for IProvider does. They both map to Decorator1 (Singleton). This will cause each registration to resolve to a different instance: each registration will have its own instance.
-[Torn Lifestyle] The registration for IProvider maps to the same implementation and lifestyle as the registration for IProvider does. They both map to Decorator2 (Singleton). This will cause each registration to resolve to a different instance: each registration will have its own instance.
See the Error property for detailed information about the warnings. Please see https://simpleinjector.org/diagnostics how to fix problems and how to suppress individual warnings.

Simple Injector does not allow registering multiple concrete implementations of the same interface

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

  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();
    }
}));

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

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

如果我们可以完全删除 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 注入到您的消费者中,系统就会在后台自动为您进行调度。