Dependency Injection Features In .NET 6

Introduction

.NET 6 which was released in November 2021. It includes a number of new features and improvements, including several new features related to dependency injection (DI).

Here is a list of the new DI features in .NET 6, along with some examples of how to use them,

Step 1

Scoped service lifetime: In previous versions of .NET, service instances were either "transient" (a new instance is created every time it is requested) or "singleton" (a single shared instance is used for all requests). .NET 6 introduces a new "scoped" lifetime, which creates a new instance of the service for each logical unit of work (e.g., a request in a web application).

To use the scoped lifetime, you can register your service with the AddScoped method of the IServiceCollection interface,

services.AddScoped<IService, Service>();

Step 2

Injection of open generic types: .NET 6 allows you to inject open generic types (e.g., List<T>) as dependencies. This can be useful when you want to inject a generic type that has multiple type parameters and you don't want to specify all of them at registration time.

To inject an open generic type, you can use the MakeGenericType method of the Type class to create a closed generic type, and then register it with the AddScoped method,

Type openGenericType = typeof(List<>);
Type closedGenericType = openGenericType.MakeGenericType(typeof(int));
services.AddScoped(closedGenericType, provider => Activator.CreateInstance(closedGenericType));

Step 3

Service provider extension methods: .NET 6 introduces several extension methods on the IServiceProvider interface that make it easier to resolve services and invoke methods on them. These methods include GetRequiredService<T>, GetRequiredService<T>(Type), GetRequiredServices<T>, GetRequiredServices<T>(Type), GetServiceOrCreateInstance<T>, and GetServiceOrCreateInstance<T>(Type).

Here's an example of using the GetRequiredService<T> method to resolve a service and invoke a method on it,

IServiceProvider serviceProvider = ...;
IService service = serviceProvider.GetRequiredService<IService>();
service.DoSomething();

Step 4

Support for injecting service providers: .NET 6 allows you to inject the IServiceProvider interface itself as a dependency. This can be useful if you need to resolve other services dynamically at runtime.

To inject the service provider, you can register it with the AddSingleton<IServiceProvider> method,

services.AddSingleton<IServiceProvider>(provider => provider);

Step 5

Support for injecting Func<T> and Lazy<T>: .NET 6 allows you to inject Func<T> and Lazy<T> as dependencies, which can be useful when you want to defer the creation of a service until it is actually needed.

Here is a sample console application that demonstrates how to use the new dependency injection features in .NET 6,

using System;
using Microsoft.Extensions.DependencyInjection;
namespace SampleApp {
    class Program {
        static void Main(string[] args) {
            // Create the service collection
            var services = new ServiceCollection();
            // Register the services
            services.AddScoped < IService, Service > ();
            services.AddScoped(typeof(List < > ), provider => Activator.CreateInstance(typeof(List < > ).MakeGenericType(typeof(int))));
            services.AddSingleton < IServiceProvider > (provider => provider);
            services.AddTransient(provider => new Lazy < IService > (provider.GetRequiredService < IService > ));
            services.AddTransient(provider => new Func < IService > (provider.GetRequiredService < IService > ));
            // Build the service provider
            var serviceProvider = services.BuildServiceProvider();
            // Resolve the services and invoke their methods
            var service = serviceProvider.GetRequiredService < IService > ();
            service.DoSomething();
            var openGenericType = typeof(List < > );
            var closedGenericType = openGenericType.MakeGenericType(typeof(int));
            var list = (System.Collections.IList) serviceProvider.GetRequiredService(closedGenericType);
            list.Add(1);
            list.Add(2);
            list.Add(3);
            Console.WriteLine(string.Join(", ", list));
            var serviceProvider2 = serviceProvider.GetRequiredService < IServiceProvider > ();
            var service2 = serviceProvider2.GetRequiredService < IService > ();
            service2.DoSomething();
            var lazyService = serviceProvider.GetRequiredService < Lazy < IService >> ();
            lazyService.Value.DoSomething();
            var funcService = serviceProvider.GetRequiredService < Func < IService >> ();
            funcService().DoSomething();
        }
    }
    public interface IService {
        void DoSomething();
    }
    public class Service: IService {
        public void DoSomething() {
            Console.WriteLine("Doing something...");
        }
    }
}

In this sample app, we create a service collection and register several services with different lifetimes,

  • The IService interface is registered with the AddScoped method, which means a new instance of the Service class will be created for each logical unit of work.
  • The open generic type List<T> is registered with the AddScoped method, using the MakeGenericType method to create a closed generic type.
  • The IServiceProvider interface is registered with the AddSingleton method, which means a single shared instance of the service provider will be used for all requests.
  • The Lazy<IService> and Func<IService> types are registered with the AddTransient method, which means a new instance will be created every time they are requested.

We then build the service provider and use it to resolve the services and invoke their methods.

To build the service provider, you can use the BuildServiceProvider method of the IServiceCollection interface. This method creates a new instance of the ServiceProvider class, which implements the IServiceProvider interface and manages the lifetime of the registered services.

// Create the service collection
var services = new ServiceCollection();

// Register the services
services.AddScoped<IService, Service>();
services.AddScoped(typeof(List<>), provider => Activator.CreateInstance(typeof(List<>).MakeGenericType(typeof(int))));
services.AddSingleton<IServiceProvider>(provider => provider);
services.AddTransient(provider => new Lazy<IService>(provider.GetRequiredService<IService>));
services.AddTransient(provider => new Func<IService>(provider.GetRequiredService<IService>));

// Build the service provider
var serviceProvider = services.BuildServiceProvider();

We can then use the serviceProvider variable to resolve the services and invoke their methods.

var service = serviceProvider.GetRequiredService < IService > ();
service.DoSomething();
var openGenericType = typeof(List < > );
var closedGenericType = openGenericType.MakeGenericType(typeof(int));
var list = (System.Collections.IList) serviceProvider.GetRequiredService(closedGenericType);
list.Add(1);
list.Add(2);
list.Add(3);
Console.WriteLine(string.Join(", ", list));
var serviceProvider2 = serviceProvider.GetRequiredService < IServiceProvider > ();
var service2 = serviceProvider2.GetRequiredService < IService > ();
service2.DoSomething();
var lazyService = serviceProvider.GetRequiredService < Lazy < IService >> ();
lazyService.Value.DoSomething();
var funcService = serviceProvider.GetRequiredService < Func < IService >> ();
funcService().DoSomething();

Conclusion

.NET 6 introduces several new features related to dependency injection (DI) that can make it easier to manage the lifecycle of services and resolve dependencies in your applications. These features include the ability to use the "scoped" lifetime for services, inject open generic types, use extension methods on the IServiceProvider interface, inject the IServiceProvider itself as a dependency, and inject Func<T> and Lazy<T> types. By using these features, you can improve the flexibility and maintainability of your DI-based applications.


Recommended Free Ebook
Similar Articles