Location>code7788 >text

Net dependency injection in-depth exploration, to do a DI extension, to achieve a simple and flexible automatic dependency injection framework

Popularity:493 ℃/2024-09-30 14:58:13

I. Dependency Injection Knowledge

1.1, the principle and advantages of dependency injection

  • Dependency Injection (DI), is the realization of the IOC control reversal idea. By a DI container, to unify the management of all the service life cycle, service creation, destruction, acquisition, are handled by the DI container.
  • Dependency injection, to a large extent, decoupled the dependency relationship between services, services rely on each other is an abstraction (rely on the service / service interface "type"), rather than relying on a concrete implementation (the service does not have to pay attention to the creation of the service he depends on, only through the constructor declaration of the dependent service type you can get the dependency of the service instance , the The actual service instance is created by the container).
  • When acquiring a service, DI is able to parse the dependency tree of all services, and creates all directly and indirectly dependent services (the timing of creation is different for different lifecycle types, see 1.3), which can be used directly.
  • Advantages: decoupling, life cycle management, improved maintainability, service replaceability. (Dependency between services is only the type, not concerned about the creation of dependent services, so any service changes, the impact between is very small, and dependent interface services, the implementation of the interface can be directly replaced at the time of registration.)
  • The disadvantages of creating services with new() in the .net framework: services depend on each other for specific implementations, dependent services need to be created out manually, and the impact of any service change is very much in the same place. When the service dependency level is very deep, the outer service uses the bottom service, you need to create all the dependent services one by one from low to. When one of the service constructor changes, all the places that use it, the creation needs to be changed, the impact will be very large, not conducive to maintenance. And you need to manually control the lifecycle of some unmanaged resource services, you need to pay attention to memory leaks.

1.2. .Net service registration

  • 1.2.1, Service Type/Service Interface Type Registration
// Register the service as an interface
<IMyService, MyService>(); // generic registration
<IMyService, MyService>(); // Register the service as an interface.
<IMyService, MyService>();; // Pan-registration.

<MyService>(); // register the service directly

(typeof(IMyService),typeof(MyService)); // type registration
(typeof(MyService));; // Register the service directly.
  • 1.2.2. Service instance registration (only for singleton model services)
MyService myService = new MyService(); // First create an instance, then register it as a singleton service
(myService); // Register it directly
<IMyService>(myService); // Register it as an interface.
  • 1.2.3、ServiceDescriptor Use the service description class, registration
(new ServiceDescriptor(typeof(IMyService), typeof(MyService), )); // Type of service registered, type of implementation of the service, life cycle of the service
((typeof(IMyService), typeof(MyService))); // The registered service type, the service implementation type, and the service lifecycle.

// Replace the service, if it already exists; if it doesn't, add it.
((typeof(IMyService), typeof(MyService))); // Replace: if the IMyService service already exists, replace it; if it doesn't, add it.

1.3. Three types of service lifecycle, ServiceLifetime enumeration

Singleton pattern

  • Get that service from the DI and get the same instance at all times.
  • Single instance service, generally created when the program is run, can also be specified to be created when the service is accessed for the first time. Single-case service The program will not be automatically released, and generally does not need to be released; however, you need to pay attention to memory issues, and if it contains unmanaged resources, you need to pay attention to avoid memory leakage problems.
  • A single instance service cannot be directly dependent on a scoped service. Because a single instance service is created at program runtime, when there is no service scope, relying directly on a scoped service will throw an exception at program runtime.
  • If you have a single instance service and need to use a scoped service, you usually need to create a service scope at the right time and get the scoped service through the DI of the service scope. This scope also needs to be released manually at the right time.

Scoped Scope Patterns

  • DI's get the same instance in the same service scope.
  • When a service scope is created, all scoped services are created. When the scope is released, it is released.
  • Net Web Programs Underneath, all controller classes are automatically registered as scoped services. Each time an http request is initiated, a service scope is automatically created for the current http request and released at the end of the request.

Transient Transient mode

  • From the DI, a new instance is created each time the service is fetched, and it is not the same instance each time the service is fetched.
  • The GC automatically recycles and releases the service when it is no longer referenced, or it releases the transient service when the service scope ends.

1.6. Service Injection, Service Acquisition

  • Conventional is constructor injection , there are third-party dependency injection frameworks such as : Autofac , etc. , the implementation of the property injection . Net recommended practice is still conventional constructor injection.
  • With IServiceProvider, you can get all the registered services.
public class EFCoreController : ControllerBase
_myTestService_; _myTestService_; _myTestService
    IMyTestService _myTestService; // The IMyTestService is a singleton model.
    IServiceProvider _serviceProvider; // DI service provider, which is a singleton pattern. Through him you can get all single instance, transient services in DI. If you are in a service scope, you can also get all the scoped services. Here in the controller, it is within the service scope of the http request.
    public EFCoreController(
        IServiceProvider serviceProvider,
        IMyTestService myTestService)
    {
        _myTestService = serviceProvider.
        _myTestService = myTestService.
        _serviceProvider.GetService<T>(); // Get the service from the DI, if the service is not created and cannot be obtained, return null
        _serviceProvider.GetRequiredService<T>(); // Get the service from the DI, if the service is not created and can't be retrieved, throw an exception
    }
}

1.5 Advanced Usage of Dependency Injection

  • Manually create the service scope and get the scoped service instance (typically used in a singleton service where the scoped service is needed)
    (e.g., RabbitMQ publish-subscribe, register RabbitMQ consumers in the BackgroundService background task (the background task is a singleton pattern), RabbitMQ publishes an event, and when the consumer in the background task receives the message, it can manually create a service scope and manually specialize it by using other information such as people information in the message) Some stateful services, such as user information contexts. The service scope is released when the message is consumed.)
var app = ();

IServiceProvider rootServiceProvider = ; // A runtime DI for web applications that can access singleton and transient services, but not scoped services.
using (IServiceScope serviceScope = ()) // () Create a service scope, at which point all scoped services are created.
{
    IServiceProvider serviceProvider = ; // Use the DI in the service scope to access the scoped services.
    TestScopeService? testScopeService = <TestScopeService>(); // The test gets the scoped service instance
}

Design a lightweight framework for automatic dependency injection.

2.1 Design objectives

  • By taking the service you want to inject and labeling it with a feature, you can inject it directly into the DI
  • This feature allows you to specify that the service be registered as an implementation of an interface
  • This feature allows you to specify the type of life cycle for this service
  • In DI, one-click injection of all services in the entire assembly that specify the feature

2.2 Examples of use

2.2.1. On the service class to be injected, mark the CoreDI feature, specify the type of service to be registered and the type of service life cycle to be registered.

 [CoreDI(typeof(ITestSigletonService), )] // register the service as an interface to DI (the class must be an implementation of the interface); specify the lifecycle type
 public class TestSingletonService: ITestSigletonService
 {
     public void Test()
     {
         ("Single instance service"); }
     }
 }

 [CoreDI] // Register the class directly without specifying an interface; do not specify a lifecycle type, the default is the scope lifecycle
 public class TestScopeService
 {
     public void Test()
     {
         ("Scope Service"); }
     }
 }

2.2.2. Inject all services of the current assembly class that are labeled with CoreDI features into the DI

(typeof(TestSingletonService).Assembly); 

Designing a Lightweight Automatic Dependency Injection Framework [Code Implementation].

3.1、CoreDIAttribute

/// <summary>
/// characterization Injecting services into theDIcenter
/// </summary>
[AttributeUsage(, AllowMultiple = true)]
public class CoreDIAttribute : Attribute
{
    /// <summary>
    /// The type of interface to inject
    /// </summary>
    public Type? InterfaceType { get; private set; }
    /// <summary>
    /// Service life cycle
    /// </summary>
    public ServiceLifetime ServiceLifetime { get; private set; }

    public CoreDIAttribute(Type? interfaceType = null, ServiceLifetime serviceLifeTime = )
    {
        if (!(serviceLifeTime))
            throw new Exception($"【CoreDI】 The Enum '{nameof(ServiceLifetime)}' value error.");
        InterfaceType = interfaceType;
        ServiceLifetime = serviceLifeTime;
    }
}

3.2、CoreDIServiceExtensions

/// <summary>
/// Dependency Injection Extension
/// </summary> /// Dependency Injection
public static class CoreDIServiceExtensions
{
    /// <summary> /// Dependency Injection
    /// Services with CoreDI features in these assemblies are automatically injected into the DI container
    /// </summary> /// Automatically inject services with CoreDI features from these collections into the DI container.
    public static void AddCoreDIServices(this IServiceCollection services, params Assembly[] assemblies)
    {
        // Find all the services in these assemblies that contain CoreDI features and register them with the DI
        foreach (var assembly in assemblies)
        {
            Dictionary<Type, IEnumerable<CoreDIAttribute>> dic = ().ToDictionary(x => x, x => <CoreDIAttribute>());
            foreach (var item in dic)
            {
                Type type = ;
                IEnumerable<CoreDIAttribute> attributes = ;
                if (() == 0)
                    continue;
                foreach (CoreDIAttribute di in attributes)
                {
                    if ( ! = null && ! ().Any(x => x == )) // If the service is registered as an interface, it must be an implementation of that interface
                        throw new Exception($"[CoreDI] The Service '{type?.Name}' can not be resolved to the Service '{}' ");
                    (new ServiceDescriptor( ? type, type, )); // Register the service with DI using the service descriptor class ServiceDescriptor, replacing it if it exists and adding it if it doesn't; get the specified lifecycle type from the CoreDI feature
                }
            }
        }
    }
}