ASP.NET Core notes - dependency injection

  • Service life cycle
  • The choice of life span in chain injection
  • TryAdd and generic injection
  • Replace built-in service container

ASP.NET Core provides a default dependency injection container, which can be configured in the Startup.ConfigureServices method.

Service life cycle

The default dependency injection container provides three lifecycles:

  • Add transient: each time a request is made to a service container, a new instance will be created. This lifetime is suitable for lightweight, stateless services.
  • In scope (AddScoped), create an instance for each client request.
  • Single instance (AddSingleton) is created on the first request (or when Startup.ConfigureServices is running and the specified instance is registered with the service), and each subsequent request uses the same instance. If the application requires a singleton behavior, it is recommended that the service container be allowed to manage the lifetime of the service. Don't implement the singleton design pattern yourself.

Here are three ways to test the difference: Injection configuration:

services.AddSingleton<ISingletonTest, SingletonTest>();
services.AddTransient<ITransientTest, TransientTest>();
services.AddScoped<IScopedTest, ScopedTest>();

services.AddTransient<ScopeAndTransientTester>();

Controller:

public DefaultController(ISingletonTest singleton, ITransientTest transient, IScopedTest scoped, ScopeAndTransientTester scopeAndTransientTester
    )
{
    this.singleton = singleton;
    this.transient = transient;
    this.scoped = scoped;
    this.scopeAndTransientTester = scopeAndTransientTester;
}

[HttpGet]
public string Get()
{
    return $"singleton={singleton.guid}; \r\nscoped1={scoped.guid};\r\nscoped2={scopeAndTransientTester.ScopedID()};\r\ntransient={transient.guid};\r\ntransient2={scopeAndTransientTester.TransientID()};";
}

ScopeAndTransientTester class for second injection:

public class ScopeAndTransientTester
{
    public ISingletonTest singleton { get; }
    public ITransientTest transient { get; }
    public IScopedTest scoped { get; }

    public ScopeAndTransientTester(ISingletonTest singleton, ITransientTest transient, IScopedTest scoped)
    {
        this.singleton = singleton;
        this.transient = transient;
        this.scoped = scoped;
    }

    public Guid SingletonID()
    {
        return singleton.guid;
    }
    public Guid TransientID()
    {
        return transient.guid;
    }
    public Guid ScopedID()
    {
        return scoped.guid;
    }
}

First request:

singleton=ebece97f-bd38-431c-9fa0-d8af0419dcff; 
scoped1=426eb574-8f34-4bd3-80b3-c62366fd4c74;
scoped2=426eb574-8f34-4bd3-80b3-c62366fd4c74;
transient=98f0da06-ba8e-4254-8812-efc19931edaa;
transient2=c19482f7-1eec-4b97-8cb2-2f66937854c4;

Second request:

singleton=ebece97f-bd38-431c-9fa0-d8af0419dcff; 
scoped1=f5397c05-a418-4f92-8c6d-78c2c8359bb5;
scoped2=f5397c05-a418-4f92-8c6d-78c2c8359bb5;
transient=59ed30fa-609b-46b1-8499-93a95ecd330b;
transient2=d2a8ea1c-ae0b-4732-b0a1-ca186897e676;

Use Guid to represent different instances. Comparing the two requests, it can be seen that the id value of AddSingleton method is the same; the id value of AddScope method is different between the two requests, but it is the same within the same request; the id value of AddTransient method is different between multiple injections within the same request.

TryAdd and generic injection

In addition, there is the TryAdd{Lifetime} method. If you want to add a service only when the same type of service has not been registered, you can use this method. If you use add {lifetime} directly, multiple uses will repeat the registration.

In addition to the usage of add {lifetime} < {service}, {implementation} > (), there is another overloaded writing method:

Add{LIFETIME}(typeof(SERVICE, typeof(IMPLEMENTATION)

Another advantage of this writing method is that generics can be parsed. For example, ILogger is registered automatically by the framework in this way:

services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));

The choice of life span in chain injection

When dependency injection is used in a chained way, the dependency of each request requests its own dependency accordingly. The container resolves these dependencies, builds a dependency tree, and returns a fully resolved service. In chain injection, it should be noted that the life cycle of the dependent party cannot be greater than that of the dependent party. In the previous example, ScopeAndTransientTester has injected all three life cycle services, so it can only be registered as temporary, otherwise an error will be reported at startup:

System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor '*** Lifetime: Singleton ImplementationType: ***': Cannot consume scoped service '***' from singleton 

Replace built-in service container

The built-in service container is designed to meet the needs of the framework and most developers' applications. Generally, it is enough to use the built-in container, unless some specific functions not supported by the built-in container are needed, such as attribute injection, name based injection, sub container, custom lifetime management, func < T > support for lazy initialization, contract based registration, etc.

Keywords: Programming Attribute

Added by sneskid on Sun, 22 Mar 2020 17:42:35 +0200