Disposing Injected Services (or: Using Dependency Injection Scopes)

The DI container Microsoft.Extensions.DependencyInjection disposes registered services automatically. It’s just the question when the dispose takes place. Automatic dispose of transient and scoped services happen at the end of a scope. With ASP.NET Core applications, scopes are created with every HTTP request – after the request, services are disposed.
I’m using Microsoft.Extensions.DependencyInjection not only with ASP.NET Core and EF Core, but also with XAML-based applications (UWP, WPF, and Xamarin). Here it can be useful to create scopes with view-models that are associated with a XAML page. As soon as the page is not needed anymore, the scope is disposed and with it all the services from the current scope as well as all transient services. Singleton services are kept alive. Singletons are always associated with the root container – and only disposed when the root container is disposed.

This article shows creating and using DI scopes with XAML-based applications.

Recycling waste and garbage

In case you use your own factory to pass service instances to the container, you still need to dispose the services yourself.

Service Registrations with Microsoft.Extensions.DependencyInjection

The container coming with .NET Core, Microsoft.Extensions.DependencyInjection, is deeply used with ASP.NET Core and EF Core. However, because it is shipped in a .NET Standard library, you can use it with other application types as well. Since the availability of NET Core, I’m using this DI container not only with ASP.NET Core, but also with WPF, UWP, and Xamarin applications.

In the UWP sample application, the container is created in the AppServices class. Here, scoped, and transient services are registered.


public class AppServices
{
private AppServices()
{
var services = new ServiceCollection();
// view-models
services.AddTransient<BooksViewModel>();
// services
services.AddTransient<IItemsService<Book>, BooksService>();
services.AddTransient<IShowProgressInfo, ShowProgressInfo>();
// stateful services
services.AddScoped<IItemToViewModelMap<Book, BookItemViewModel>, BookToBookItemViewModelMap>();
services.AddScoped<ISharedItems<Book>, SharedItems<Book>>();
services.AddScoped<IRepository<Book, int>, BooksSampleRepository>();
// logging configuration
services.AddLogging(builder =>
{
#if DEBUG
builder.AddDebug().SetMinimumLevel(LogLevel.Trace);
#endif
});
ServiceProvider = services.BuildServiceProvider();
}
public IServiceProvider ServiceProvider { get; }
private static AppServices _instance;
private static readonly object _instanceLock = new object();
private static AppServices GetInstance()
{
lock (_instanceLock)
{
return _instance ?? (_instance = new AppServices());
}
}
public static AppServices Instance => _instance ?? GetInstance();
}

view raw

AppServices.cs

hosted with ❤ by GitHub

  • A transient service is instantiated every time an instance is requested.
  • A singleton service is instantiated only once. On requests of this type, always the same instance is returned.
  • With scoped registrations, an instance is created within every scope where the type is requested.

Transient service registration should be the preferred method to register services. Scoped and singleton services are useful to share state.

One more note to the lifetime of services:

A service injected should have the same or longer lifetime than the service where it is injected. For example, you can inject a singleton service within a transient service. The singleton service has a longer lifetime than the transient service. You cannot inject a transient service within a singleton. This would expand the lifetime of the transient service. Doing this with Microsoft.Extensions.DependencyInjection results in a exception.

What is a Scope?

With ASP.NET Core, DI scopes are created with every HTTP request. When a service is requested within a HTTP request (in middleware, controllers, services, or views), and the service is registered as scoped service, the same instance is used in case the same type is requested multiple times within a request. For example, if a scoped service is injected within a controller, a service, and within a view, the same instance is returned. With the flow of another HTTP request, a different instance is used. When the request is completed, the scope is disposed. Disposing the scope results in disposing of all transient and scoped services that are associated with that scope.

Singleton services are never associated with a scope – they are associated with the root container, and disposed when the root container is disposed.

Disposable Services and View-Models

Some services and view-models need to be disposed. For example, the MasterDetailViewModel base class implements the interface IDisposable to unsubscribe from events:


public abstract class MasterDetailViewModel<TItemViewModel, TItem> : ViewModelBase, IDisposable where TItemViewModel : IItemViewModel<TItem> where TItem : class
{
public MasterDetailViewModel(
IItemsService<TItem> itemsService,
IItemToViewModelMap<TItem, TItemViewModel> viewModelMap,
IShowProgressInfo showProgressInfo,
ILoggerFactory loggerFactory)
: base(showProgressInfo)
{
//…
_itemsService.SelectedItemChanged += ItemsService_SelectedItemChanged;
_itemsService.PropertyChanged += ItemsService_PropertyChanged;
//…
}
public virtual void Dispose()
{
_itemsService.SelectedItemChanged -= ItemsService_SelectedItemChanged;
_itemsService.PropertyChanged -= ItemsService_PropertyChanged;
}
}

Injection of services into services and view-models

The BooksService class needs an implementation of the IBooksRepository interface. This is injected via constructor injection and assigned to a readonly member field. The base class of BooksService, the abstract class ItemsService needs some more services that are passed to the constructor of the base class.


public class BooksService : ItemsService<Book>
{
private readonly IRepository<Book, int> _booksRepository;
public BooksService(
IRepository<Book, int> booksRepository,
ISharedItems<Book> booksItems,
ILoggerFactory loggerFactory)
: base(booksItems, loggerFactory)
{
_booksRepository = booksRepository ?? throw new ArgumentNullException(nameof(booksRepository));
}
//…
}

view raw

BooksService.cs

hosted with ❤ by GitHub

Similar to the BooksService, the BooksViewModel class defines services to be injected via the constructor:


public class BooksViewModel : MasterDetailViewModel<BookItemViewModel, Book>
{
public BooksViewModel(
IItemsService<Book> itemsService,
IItemToViewModelMap<Book, BookItemViewModel> viewModelMap,
IShowProgressInfo showProgressInfo, ILoggerFactory loggerFactory)
: base(itemsService, viewModelMap, showProgressInfo, loggerFactory)
{
}
}

Instantiating view-models in a scope

The view-model is associated to a page by assigning it to the ViewModel property of the page. Here, a DI scope is created by invoking the CreateScope method of the ServiceProvider. This scope is disposed when the page is unloaded which is handled in the Unloaded event handler method. Because the BooksViewModel class is registered as transient service in the container, the instance is associated with the newly created scope, and disposed when the scope is disposed. All the transient and scoped services that are created directly or indirectly when the BooksViewModel is instantiated, are disposed as well when the scope is disposed.


public sealed partial class BooksPage : Page
{
private IServiceScope _scope;
public BooksPage()
{
InitializeComponent();
_scope = AppServices.Instance.ServiceProvider.CreateScope();
Unloaded += (sender, e) => _scope.Dispose();
ViewModel = _scope.ServiceProvider.GetRequiredService<BooksViewModel>();
}
public BooksViewModel ViewModel { get; }
}

Disposing in Reverse Creation Order

In previous versions of the DI container, the DI container had a bug that is relevant if the disposing of services disposing services in the wrong order. This has been fixed with dispose services in reverse creation order.

See my ServicesLifetime sample of my book Professional C# 7 and .NET Core 2.0 creates multiple scopes with disposable services in a simple console application and uses the services.


private static void UsingScoped()
{
Console.WriteLine(nameof(UsingScoped));
ServiceProvider RegisterServices()
{
var services = new ServiceCollection();
services.AddSingleton<INumberService, NumberService>();
services.AddScoped<IServiceA, ServiceA>();
services.AddSingleton<IServiceB, ServiceB>();
services.AddTransient<IServiceC, ServiceC>();
return services.BuildServiceProvider();
}
using (ServiceProvider container = RegisterServices())
{
using (IServiceScope scope1 = container.CreateScope())
{
IServiceA a1 = scope1.ServiceProvider.GetService<IServiceA>();
a1.A();
IServiceA a2 = scope1.ServiceProvider.GetService<IServiceA>();
a2.A();
IServiceB b1 = scope1.ServiceProvider.GetService<IServiceB>();
b1.B();
IServiceC c1 = scope1.ServiceProvider.GetService<IServiceC>();
c1.C();
IServiceC c2 = scope1.ServiceProvider.GetService<IServiceC>();
c2.C();
}
Console.WriteLine("end of scope1");
using (IServiceScope scope2 = container.CreateScope())
{
IServiceA a3 = scope2.ServiceProvider.GetService<IServiceA>();
a3.A();
IServiceB b2 = scope2.ServiceProvider.GetService<IServiceB>();
b2.B();
IServiceC c3 = scope2.ServiceProvider.GetService<IServiceC>();
c3.C();
}
Console.WriteLine("end of scope2");
Console.WriteLine();
}
}

view raw

Program.cs

hosted with ❤ by GitHub

Running the application, you can see that the services C and A are disposed when the scope is disposed – in the reverse creation order. Service A is a scoped service, and service B a transient service. Service B is disposed with the disposal of the root container – this service is registered as singleton:

UsingScoped
ctor ServiceA, 1
A, 1
A, 1
ctor ServiceB, 2
B, 2
ctor ServiceC, 3
C, 3
ctor ServiceC, 4
C, 4
disposing ServiceC, 4
disposing ServiceC, 3
disposing ServiceA, 1
end of scope1
ctor ServiceA, 5
A, 5
B, 2
ctor ServiceC, 6
C, 6
disposing ServiceC, 6
disposing ServiceA, 5
end of scope2

disposing ServiceB, 2

Summary

Using dependency injection makes the code a lot more maintainable. You can easily see dependencies. Unit tests can be done easily. Using services that are injected, functionality typically is better separated. With this article you’ve seen that you do not need to invoke the Dispose method of services yourself – this is dealt with from the container. You just need to create scopes to early dispose transient and scoped services.

If this information helped you with the architecture of your application or fixed issues, consider buying me multiple coffees. This helps staying up longer and writing more articles šŸ™‚

Buy Me A Coffee

More Information

See the complete XAML app sample with UWP, WPF, and Xamarin using dependency injection BooksSample. This sample will be extended over time to show several features of the library Generic.ViewModels.

The complete services lifetime sample is in the Professional C# 7 and .NET Core 2.0 repository.

My new book Professional C# 7 and .NET Core 2.0 has a complete chapter dedicated to dependency injection: chapter 20, Dependency Injection.

More information on XAML and writing UWP applications is in my book Professional C# 7 and .NET Core 2.0, and in my Programming Windows Apps workshops.

Enjoy learning and programming!

Christian

13 thoughts on “Disposing Injected Services (or: Using Dependency Injection Scopes)

  1. Thank you for your article. It helped me a lot in understanding that even transient services are kept “alive” by the container that created them.
    To dispose the objects it created, the container keeps track of all of them. Even those objects which are not disposable.
    I had created a long running console application that from time to time created new transient objects without using scopes. After a few weeks of running, the (root) container held a lot of objects, since it was never disposed. That caused a huge memory consumption.
    Lessons learned: Container keeps track of all its created objects and prevents GC from freeing “out of scope” transient objects. –> Don’t let containers (children) grow out of bounds. Use scoped containers even for transient objects.

    Like

  2. Great article! Even though i’ve been using similar structure, I just noticed i’ve always ommited the lock, making my code vulnerable to thread locks.

    I have one question though. AppServices.Instance is left not being disposed. Services registered as singleton, will remain in memory after application is closed. How do you handle that part?

    Like

    1. As the root scope is disposed, also singleton services are disposed. Singleton services do not remain in memory after the application (the process) is closed. Anyway, when the service implements IDisposable this should be invoked as this might deal with other issues as well – which is easy possible disposing the root scope.
      With newer projects (.NET Core 3), I’m using the Host class in all my projects which makes setting up DI, configuration, and logging simpler. Disposing the Host instance disposes singleton services.

      Like

      1. Christian, when you are injecting a DbContext, I believe it defaults to being scoped. For your UWP apps, do you leave it scoped, or change it to Singleton or Transient?

        Like

      2. Thank you Christian.

        Indeed, when root scope is disposed, everything should be disposed. But, I have spot some really strange behavior. I will post very simple Winform example.

        [STAThread]
        static void Main()
        {
        var host = Host.CreateDefaultBuilder()
        .ConfigureServices((context, services) =>
        {
        // Map some services here
        })
        .Build();

        Debug.Print($”##### {host.GetHashCode().ToString()}”);
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        var form = host.Services.GetRequiredService();
        Application.Run(form);
        }

        If you run the application several times, you will notice that host always has the same HashCode. This is not directly related to Winforms only, it happens on Web applications as well. Everything has correct behavior within the application instance, but whenever you run for the second time, you will get the same hashes.

        I’m not quite sure if the VS is assigning the same hashes, or they’re really the same objects.

        Anyhow, thank you for your posts and answers. The issue bothering me for some time,so I just wanted to share it.

        Like

      3. I would not wonder about the same hash code. Typical implementations are based on the readonly state of the object. If it’s the same state, it can be the same hash code no matter how often you run the application. With the Host class, it looks like GetHashCode is not overridden, so it get’s the implementation of the Object class. If you create a custom class not overriding GetHashCode you’ll see the same experience with the same code no matter how often you run the application.
        A Happy New Year 2020,
        Christian

        Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.