简体   繁体   中英

Injecting IServiceProvider into Factory Class with Autofac

I have a factory class in a Net Core 3 console app which needs to be able to resolve against a DI container at runtime:

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

I'm using Autofac to define the DI container and then "assign" it to IServiceProvider via new AutofacServiceProvider( builder.Build() ) in a provider class:

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

I'm unclear about how to register IServiceProvider itself with the DI container so that it can be injected into the constructor. Is that even possible? It seems a little self-referential, which could be problematic.

All the examples I've seen online call for referencing back to the Autofac IContainer itself, (or to TestServiceProvider.Instance in my example). I can do that, but it would end tie my library to a concrete service provider class. Which I think I'd like to avoid if I can.

I realize injecting IServiceProvider is considered an anti-pattern by some/many, although others deem it acceptable in a factory class because the factory is "simply" extending the DI container. I'm open to other approaches which don't rely on a factory class, provided they allow me to create concrete instances of open generic types at runtime.

You have a couple of options (no pun intended ).

Easiest: Call builder.Populate() with an empty collection

The Autofac.Extensions.DependencyInjection package (which you're using, since you have AutofacServiceProvider ) has an extension method ContainerBuilder.Populate() which handles registering stuff from an IServiceCollection and auto-registering the AutofacServiceProvider . You could call that method with an empty service collection and it'll work.

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

This will get you exactly the thing you're looking for. However, there's an alternative to consider...

Alternative: Use ILifetimeScope

If it doesn't matter whether your OptionFactory is tied to Autofac, you can inject ILifetimeScope . Autofac has the current lifetime scope auto-registered, so this will work:

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
}

The benefit here is you'll get the richer resolve options Autofac offers without any extra work. The drawback would be you're tied to Autofac at this level, which may or may not matter.

Beware!

It may just be your example, but there's something important to know if you're resolving directly from the root container the way the example shows:

You could easily end up with a big memory leak.

Autofac holds on to all IDisposable instances it resolves so they can be safely disposed when the lifetime scope is disposed. If you are resolving from the container, that means any IDisposable will be held onto until the container itself is disposed, which, for most, is the lifetime of the application. That means - hypothetically - every resolution could be adding just a tiny little bit of memory that won't be disposed until the container is disposed. Memory leak.

For this reason we recommend always resolving from a nested lifetime scope rather than from the container. In a web app, that request-level lifetime scope is perfect because it disappears after a request. In an example like this, it's up to you and your app code to determine the best way to integrate lifetime scopes.

And, of course, if you're definitely, 100% guaranteed never resolving anything IDisposable , no worries.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM