How to Implement Custom Dependency Injection Scopes in .NET Without Using Third-Party Libraries etd_admin, November 25, 2024November 25, 2024 Dependency Injection (DI) is a cornerstone of modern .NET applications, helping to decouple components and manage the lifecycle of services. While .NET Core’s built-in DI container provides support for common lifetimes like Singleton, Scoped, and Transient, there are scenarios where you might need to implement custom dependency injection scopes in .NET without relying on third-party libraries. In this article, we’ll explore how to create custom scopes for dependency injection in .NET and ensure proper disposal of scoped services at the end of the scope. Understanding the Default Scopes in .NET DI Before diving into custom scopes, let’s briefly revisit the default lifetimes: Singleton – A single instance is created and shared across the application’s lifetime. Scoped – A new instance is created per request or logical operation. Transient – A new instance is created every time the service is requested. Custom scopes go beyond these default lifetimes to manage services dynamically for specific logical operations or workflows. Why Create Custom Dependency Injection Scopes? Custom scopes are useful in scenarios like: Background jobs or batch processing where each job needs its own service scope. Unit of work patterns where each transaction requires isolated service instances. Advanced resource management where services should be disposed of after specific workflows. Steps to Implement Custom Dependency Injection Scopes in .NET 1. Use IServiceProvider to Create Child Scopes .NET’s built-in DI container supports scope creation using IServiceProvider. You can create a new scope by calling IServiceProvider.CreateScope(). Here’s how it works: using Microsoft.Extensions.DependencyInjection; using System; public class CustomScopeExample { public void ExecuteScopedOperation(IServiceProvider serviceProvider) { using (var scope = serviceProvider.CreateScope()) { var scopedService = scope.ServiceProvider.GetRequiredService<IMyScopedService>(); scopedService.PerformOperation(); } // Scope is disposed here, ensuring proper cleanup } } public interface IMyScopedService { void PerformOperation(); } public class MyScopedService : IMyScopedService, IDisposable { public void PerformOperation() { Console.WriteLine("Operation performed in custom scope."); } public void Dispose() { Console.WriteLine("Scoped service disposed."); } } Explanation: CreateScope() creates a child scope that isolates services from the root provider. GetRequiredService retrieves the service instance specific to the new scope. The using statement ensures that all scoped services are disposed of when the scope ends. 2. Register Services in the DI Container You must register your services with appropriate lifetimes in the IServiceCollection. Scoped services are the most relevant for custom scopes. var serviceCollection = new ServiceCollection(); serviceCollection.AddScoped<IMyScopedService, MyScopedService>(); var serviceProvider = serviceCollection.BuildServiceProvider(); 3. Custom Scopes for Background Operations If your application involves background tasks or logical workflows, you can create and manage custom dependency injection scopes dynamically. Here’s an example for processing tasks in a custom scope: public class TaskProcessor { private readonly IServiceProvider _serviceProvider; public TaskProcessor(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public void ProcessTask() { using (var scope = _serviceProvider.CreateScope()) { var service = scope.ServiceProvider.GetRequiredService<IMyScopedService>(); service.PerformOperation(); } } } 4. Ensuring Proper Disposal of Scoped Services To ensure services are properly disposed of: Always wrap the custom scope in a using block. Implement IDisposable for services that need cleanup. public class MyScopedService : IMyScopedService, IDisposable { public void PerformOperation() { Console.WriteLine("Performing operation..."); } public void Dispose() { Console.WriteLine("Cleaning up resources..."); } } When the Dispose method is called, it ensures that any unmanaged resources or connections are released. Advanced Example: Custom Scope Wrapper You can create a helper class to encapsulate the creation and disposal of custom scopes: public class CustomScopeManager : IDisposable { private readonly IServiceScope _scope; public CustomScopeManager(IServiceProvider serviceProvider) { _scope = serviceProvider.CreateScope(); } public T GetService<T>() where T : notnull { return _scope.ServiceProvider.GetRequiredService<T>(); } public void Dispose() { _scope.Dispose(); } } // Usage: public void ExecuteCustomScope(IServiceProvider serviceProvider) { using (var scopeManager = new CustomScopeManager(serviceProvider)) { var service = scopeManager.GetService<IMyScopedService>(); service.PerformOperation(); } } This approach reduces repetitive code and makes managing custom dependency injection scopes in .NET more convenient. Implementing custom dependency injection scopes in .NET without third-party libraries is straightforward with the built-in DI container. By using IServiceProvider.CreateScope(), you can dynamically manage service lifetimes and ensure proper resource cleanup. This approach is highly useful for background tasks, logical workflows, and complex operations requiring scoped lifetimes. Key Takeaways: Use IServiceProvider.CreateScope() to create child scopes. Always dispose of scopes properly to avoid resource leaks. Register services with appropriate lifetimes in the DI container. Custom scopes provide flexibility while maintaining the core principles of dependency injection, enabling you to design robust, scalable .NET applications. .NET .NETDependency Injection