Is it ok to keep building ServiceProviders over and over?

Viewed 622

In a .NET Core Console application, I want to use the built-in dependency injection, including the automatic disposal of IDisposable objects in the chain. In ASP.NET Core, the objects are created and disposed with each request, which seems reasonable.

But in a Console app, I have to build the service provider, then start it by retrieving the first item in the chain from the provider and running:

class Program {
    static void Main() {
        using var serviceProvider = new ServiceCollection()
            .AddTransient<Program>()
            .AddTransient<Repository>()
            .AddTransient<MyContext>()
            .BuildServiceProvider();
        serviceProvider.GetService<Program>().Go();
    }

    private readonly Repository _repo;
    public Program(Repository repo) => _repo = repo;

    public void Go() => _repo.Go();
}

public class Repository : IDisposable {
    private readonly MyContext _context;
    public Repository(MyContext context) => _context = context;

    public void Go() => Console.WriteLine("Go!");
    public void Dispose() => Console.WriteLine("Repository Disposed!");
}

public class MyContext : DbContext {
    public override void Dispose() {
        base.Dispose();
        Console.WriteLine("MyContext Disposed!");
    }
}

When the serviceProvider is disposed, the rest get disposed as well:

Go!
Repository Disposed!
MyContext Disposed!

If I want to run the process in a loop or with a timer, nothing gets disposed until the service provider itself is disposed:

for (int i = 0; i < 3; ++i)
{
    serviceProvider.GetService<Program>().Go();
}

Go!
Go!
Go!
Repository Disposed!
MyContext Disposed!
Repository Disposed!
MyContext Disposed!
Repository Disposed!
MyContext Disposed!

If I dispose and recreate the service provider every time, I get the results I want:

for (int i = 0; i < 3; ++i)
{
    using var serviceProvider = new ServiceCollection()
        .AddTransient<Program>()
        .AddTransient<Repository>()
        .AddTransient<MyContext>()
        .BuildServiceProvider();

    serviceProvider.GetService<Program>().Go();
}

Go!
Repository Disposed!
MyContext Disposed!
Go!
Repository Disposed!
MyContext Disposed!
Go!
Repository Disposed!
MyContext Disposed!

My question: Is it ok to keep building and disposing the provider over and over again like this? I'm thinking of doing this on a timer every few seconds indefinitely. Is it an expensive process to do this, and is there a better way, without having to manually dispose the objects throughout the chain?

Or is this maybe what ASP.NET Core is doing behind the scenes anyway, recreating the provider with every request?

3 Answers

Create a scope, resolve all of the dependencies from that, then dispose of the scope. That looks something like this:

using var serviceProvider = new ServiceCollection()
    .AddTransient<Program>()
    .AddTransient<Repository>()
    .AddTransient<MyContext>()
    .BuildServiceProvider();

for (int i = 0; i < 3; ++i)
{
    using var scope = serviceProvider.CreateScope();
    scope.ServiceProvider.GetService<Program>().Go();
}

All scoped and transient services created by a service-scope are disposed when the service-scope itself is disposed. In this example, all involved services are registered as transient, which just means that new instances are created every time Program is requested. If you were to request Program multiple times from the same service-scope, you'd get multiple instances of all the required registered services. Each one of these instances is tracked by the service-scope and disposed along with the service-scope.

In short, no! I would definitely steer clear of recreating the service provider each time. This seems rather wasteful.

And no, ASP.Net DI does not work like this, the service provider is built only upon application startup.

If you change Program to implement IDisposable and change the loop to

for (int i = 0; i < 3; ++i)
{
    using(var program = serviceProvider.GetService<Program>()){
        program.Go();
    }
}

you should acheive the same result.

UNTESTED

Garbage collection will handle the disposal of the injected objects if and when applicable, so you don't have to worry about it. Create a static ServiceProvider you can utilise:

static void Main() {
    serviceProvider = new ServiceCollection()
        .AddTransient<Program>()
        .AddTransient<Repository>()
        .AddTransient<MyContext>()
        .BuildServiceProvider();
    serviceProvider.GetService<Program>().Go();
}

private static IServiceProvider serviceProvider;
Related