Search code examples
c#asp.net-core.net-coredependency-injectionasp.net-core-7.0

Does .NET Core IServiceScopeFactory child scope dispose transient services?


Whenever I need transient or scoped services inside a singleton or background service, I create them in a child scope:

using var serviceScope = _serviceScopeFactory.CreateScope();
var foo = serviceScope.ServiceProvider.GetRequiredService<Foo>();

However I just noticed the API docs for CreateScope state:

Returns: An IServiceScope controlling the lifetime of the scope. Once this is disposed, any scoped services that have been resolved from the IServiceScope.ServiceProvider will also be disposed.

Notice the wording "any scoped services".

Does that mean the scoped container does not dispose transient services (only scoped services)? I always thought a child container disposes everything it creates, and it's only the root container that holds on to transient references.

(I'm used to Autofac and some nuances between it and .NET DI are a little confusing.)


Solution

  • The wording in this part of the docs is not fully correct, at least for the default DI container - when scope is disposed it will dispose all dependencies which are owned/controlled/constructed by the scope itself. It is pretty easy to check:

    var services = new ServiceCollection();
    services.AddTransient<MyDisposable>();
    MyDisposable disposable;
    
    using(var scope = services.BuildServiceProvider().CreateScope())
    {
        disposable = scope.ServiceProvider.GetRequiredService<MyDisposable>();
    }
    
    Console.WriteLine(disposable.Disposed); // true
    
    class MyDisposable : IDisposable
    {
        public bool Disposed { get; private set; }
        public void Dispose()
        {
            Disposed = true;
        }
    }
    

    And to quote the docs:

    When you register Transient services that implement IDisposable, by default the DI container will hold onto these references, and not Dispose() of them until the container is disposed when application stops if they were resolved from the container, or until the scope is disposed if they were resolved from a scope