【问题标题】:Injecting IServiceProvider into Factory Class with Autofac使用 Autofac 将 IServiceProvider 注入工厂类
【发布时间】:2020-05-13 16:28:14
【问题描述】:

我在 Net Core 3 控制台应用程序中有一个工厂类,它需要能够在运行时针对 DI 容器进行解析:

public class OptionFactory : IOptionFactory
{
    private readonly IServiceProvider _svcProvider;

    public OptionFactory( IServiceProvider svcProvider )
    {
        _svcProvider = svcProvider;
    }

    public IOption<T>? CreateOption<T>( params string[] keys )
    {
        // code eliminated for brevity
        try
        {
            return retVal = _svcProvider.GetRequiredService<Option<T>>();
        }
        catch( Exception e )
        {
            return null;
        }
    }
}

我正在使用 Autofac 定义 DI 容器,然后在提供程序类中通过 new AutofacServiceProvider( builder.Build() ) 将其“分配”给 IServiceProvider

public class TestServiceProvider 
{
    public static IServiceProvider Instance { get; private set; }

    static TestServiceProvider()
    {
        var builder = new ContainerBuilder();

        builder.RegisterType<OptionFactory>()
            .As<IOptionFactory>()
            .SingleInstance();

        // code omitted for brevity
        Instance = new AutofacServiceProvider( builder.Build() );
    }
}

我不清楚如何将IServiceProvider 本身注册到 DI 容器中,以便可以将其注入构造函数中。这甚至可能吗?这似乎有点自我参照,这可能是有问题的。

我在网上看到的所有示例都要求引用 Autofac IContainer 本身(或在我的示例中为 TestServiceProvider.Instance)。我可以这样做,但它最终会将我的库与具体的服务提供者类联系起来。如果可以的话,我想我想避免。

我意识到注入 IServiceProvider 被一些/许多人认为是一种反模式,尽管其他人认为它在工厂类中是可以接受的,因为工厂“只是”扩展了 DI 容器。我对不依赖工厂类的其他方法持开放态度,只要它们允许我在运行时创建开放泛型类型的具体实例。

【问题讨论】:

  • 如果您还没有这样做,并且在您的工厂有要注入的 IEnumerable。您可以收集实例并返回与您的方法匹配的实例。
  • 关于碰撞的好点,我会解决的。 IEnumerable 方法会起作用——我经常使用它——但它需要(我认为)在初始化 IContainer 时知道可能的派生泛型类型。我宁愿不必提前指定那些(这是我正在尝试做的即时性质的另一个方面)。感谢您的反馈!
  • 这就是为什么我提出了基本 IOption 接口的想法,因此您可以在不指定实际派生类型的情况下解决所有这些问题。这是一个标记界面,只是为了帮助您将它们全部收集起来
  • 有这个想法的一篇很棒的博文在这里:stevejgordon.co.uk/…(顺便说一句,史蒂夫·戈登的内容很棒)

标签: c# dependency-injection autofac


【解决方案1】:

你有几个选择(没有双关语?)。

最简单:使用空集合调用 builder.Populate()

Autofac.Extensions.DependencyInjection 包(你正在使用它,因为你有AutofacServiceProvider)有一个扩展方法ContainerBuilder.Populate()which handles registering stuff from an IServiceCollection and auto-registering the AutofacServiceProvider。您可以使用空的服务集合调用该方法,它会起作用。

builder.Populate(Enumerable.Empty<ServiceDescriptor>());

这将为您提供您正在寻找的东西。但是,还有一个可供考虑的替代方案......

替代方案:使用ILifetimeScope

如果您的 OptionFactory 是否绑定到 Autofac 无关紧要,您可以注入 ILifetimeScope。 Autofac 具有自动注册的当前生命周期范围,因此这将起作用:

public OptionFactory(ILifetimeScope scope)
{
  // scope is whatever lifetime scope the
  // factory itself came from - if that's the
  // root container, then the scope is the
  // container
}

这里的好处是您将获得 Autofac 提供的更丰富的解析选项,而无需任何额外的工作。缺点是您在此级别上与 Autofac 相关联,这可能会或可能无关紧要。

小心!

这可能只是您的示例,但如果您按照示例显示的方式直接从根容器解析,则需要了解一些重要的信息:

您很容易导致大量内存泄漏。

Autofac 保留它解析的所有IDisposable 实例,因此在释放生命周期范围时可以安全地释放它们。如果您从容器中解析,这意味着任何IDisposable 都将被保留,直到容器本身被处置,对于大多数情况来说,这就是应用程序的生命周期。这意味着 - 假设 - 每个解决方案都可能只添加一点点内存,直到容器被处理后才会被处理。内存泄漏。

出于这个原因,我们推荐always resolving from a nested lifetime scope 而不是来自容器。在 Web 应用程序中,请求级生命周期范围是完美的,因为它在请求后消失。在这样的示例中,由您和您的应用代码来确定集成生命周期范围的最佳方式。

当然,如果您绝对,100% 保证永远不会解决任何问题IDisposable,不用担心。

【讨论】:

  • 感谢@Travis,提供答案和详细说明。我一直在使用 Autofac 一段时间(并且喜欢它),但我的绝大多数用例都没有涉及实现 IDisposable 的类(至少我不是故意的:))所以我对生命范围的理解一直很模糊.但现在已经改进了!
猜你喜欢
  • 2016-12-12
  • 1970-01-01
  • 1970-01-01
  • 2012-03-21
  • 1970-01-01
  • 1970-01-01
  • 2017-12-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多