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.

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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); | |
| } |
- 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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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)); | |
| } | |
| //… | |
| } |
Similar to the BooksService, the BooksViewModel class defines services to be injected via the constructor:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); | |
| } | |
| } |
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 🙂
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







Leave a comment