Deep understanding of dependency injection, Singleton, Scoped, Transient in net core

Keywords: PHP Redis Attribute

http://www.mamicode.com/info-detail-2200461.html

 

Related articles:

Deep understanding of dependency injection, Singleton, Scoped, Transient in net core (I)

Deep understanding of dependency injection, Singleton, Scoped, Transient in net core (II)

Deep understanding of dependency injection, Singleton, Scoped, Transient in net core (3)

Deep understanding of dependency injection, Singleton, Scoped, Transient in net core (4)
 
I. What is Denpendency Injection?
This is also an old-fashioned question. What exactly is dependency injection? Why use it? Beginners are particularly prone to confusion about the concepts of Iversion of Control (IOC) and DI.

1.1 Dependence

Dependencies arise when one class needs collaboration from another to complete its work. For example, in AccountController, we need to complete user-related registration, login and other things. The login is done by EF combined with Idnetity, so we encapsulated an EFLoginService. Here AccountController has an ILoginService dependency.
Here's a design principle: it depends on abstraction, not concrete implementation. So we define an interface for EFLoginService and abstract the behavior of LoginService.

1.2 What is injection

The injection embodies an IOC (the idea of inversion control). Before reversing, let's look at the positive turn first.
AccountController itself instantiates the required dependencies.
private ILoginService<ApplicationUser> _loginService;
public AccountController()
{
  _loginService = new EFLoginService()
}

The Master said, this is not good. You shouldn't create it yourself, but it should be given to you by your caller. So you let the outside world pass these two dependencies to you through the constructor.

 public 
 AccountController(ILoginService<ApplicationUser> loginService)
{
  _loginService = loginService;
}

The process of creating dependencies for others and using them for themselves is understood as injection.

1.3 Why reverse?

In order to minimize the possible problems caused by changing the code when the business changes.
For example, we're going to change from EF to Redis, so we add a RedisLoginService. At this time, we just need to change the original injection place.
 var controller = new AccountController(new EFLoginService());
controller.Login(userName, password);

// Replace the original EF login with Redis

var controller = new AccountController(new RedisLoginService());
controller.Login(userName, password);

1.4 What is a container?

When we used AccountController above, we created an instance of ILoggingServce by ourselves through code. Imagine if there are 100 such places in a system, are we going to do this in 100 places? Control is reversed and dependency creation is transferred to the outside world. The problem now is that there is too much dependency. We need a local unified management system with all the dependencies. Containers are born.
Containers are responsible for two things:
  • The relationship between binding services and instances
  • Get instances and manage them (create and destroy)

2. NET Core DI

2.1 Instance registration

After explaining the key concepts of DI and Ioc, let's first look at the application of. NET Core DI in the console. The core of DI in. NET Core is divided into two components: IServiceCollection and IServiceProvider.
  • IServiceCollection is responsible for registration
  • IServiceProvider is responsible for providing examples
Through the default Service Collection (in Microsoft. Extensions. Dependency Injection namespace), there are three methods:
var serviceCollection = new ServiceCollection()
  .AddTransient<ILoginService, EFLoginService>()
  .AddSingleton<ILoginService, EFLoginService>()
  .AddScoped<ILoginService, EFLoginService>();
All three methods register our instances, but the life cycle of the instances is different. When is the life cycle we will continue in the next section.
The default implementation of Service Collection is to provide a List of Service Descriptors
public interface IServiceCollection : IList<ServiceDescriptor>
{
}

Our AddTransient, AddSignletone, and Coped methods above are extensions to IServiceCollection, all of which add Service Descriptor to this List.

private static IServiceCollection Add(
  IServiceCollection collection,
  Type serviceType,
  Type implementationType,
  ServiceLifetime lifetime)
{
  var descriptor = 
  new ServiceDescriptor(serviceType, implementationType, lifetime);
  collection.Add(descriptor);
  return collection;
}

2.2 A single example of the life cycle of an instance

As we saw above, the example life cycle provided by. NET Core DI includes three types:
  • Transient: Each GetService creates a new instance
  • Scoped: Only one instance is initialized in the same Scope, which can be understood as (only one instance is created at each request level, and the same http request is in a scope)
  • Singleton: Only one instance is created throughout the application lifecycle
 
 
Three enumerated values corresponding to Microsoft. Extensions. Dependency Injection. Service Lifetime
public enum ServiceLifetime
{
  Singleton,
  Scoped,
  Transient
}
In order to better understand the concept of this life cycle, we do a test:
Define a basic IOperation that has an OperationId attribute, and IOperationSingleton is just another interface.
public interface IOperation
{
        Guid OperationId { get; }
}
public interface IOperationSingleton : IOperation { }
public interface IOperationTransient : IOperation{}
public interface IOperationScoped : IOperation{}

Our Operation implementation is simple. We can pass in a Guid in the constructor for assignment, and if not, we can use our own New Guid.

public class Operation : 
  IOperationSingleton,
  IOperationTransient,
  IOperationScoped
{
    private Guid _guid;

    public Operation() {
        _guid = Guid.NewGuid();
    }

    public Operation(Guid guid)
    {
        _guid = guid;
    }

    public Guid OperationId => _guid;
}

In the program, we can call the GetService method of ServiceProvider many times, and get the same instance.

var services = new ServiceCollection();
// Default construction
services.AddSingleton<IOperationSingleton, Operation>();
// Customize null values of incoming Guid
services.AddSingleton<IOperationSingleton>(
  new Operation(Guid.Empty));
// Customize to pass in a New Guid
services.AddSingleton <IOperationSingleton>(
  new Operation(Guid.NewGuid()));

var provider = services.BuildServiceProvider();

// Guid for output singletone1
var singletone1 = provider.GetService<IOperationSingleton>();
Console.WriteLine($"signletone1: {singletone1.OperationId}");

// Guid for output singletone2
var singletone2 = provider.GetService<IOperationSingleton>();
Console.WriteLine($"signletone2: {singletone2.OperationId}");
Console.WriteLine($"singletone1 == singletone2 ? : { singletone1 == singletone2 }");

We registered IOperation Singleton three times, and finally got it two times. You should notice that we always got an instance of Guid from the last one we registered. The preceding one will be overwritten.

2.3 Tranisend for instance life cycle

The IOperation Transient we obtained this time is two different instances.

var services = new ServiceCollection();
services.AddTransient<IOperationTransient, Operation>();
    
var provider = services.BuildServiceProvider();

var transient1 = provider.GetService<IOperationTransient>();
Console.WriteLine($"transient1: {transient1.OperationId}");

var transient2 = provider.GetService<IOperationTransient>();
Console.WriteLine($"transient2: {transient2.OperationId}");
Console.WriteLine($"transient1 == transient2 ? : 
  { transient1 == transient2 }");

2.4 Scoped for instance life cycle

NET Core IServiceProvider provides CreateScope to generate a new ServiceProvider scope in which Scope annotation instances will only be the same. In other words, an object registered with Scope is equivalent to a singleton under the same Service Provider's Scope.
Similarly, we first register three instances of IOperationScoped, IOperationTransient and IOperationSingletone, using the corresponding Scoped, Transient, and Singleton life cycle.
 var services = new ServiceCollection()
.AddScoped<IOperationScoped, Operation>()
.AddTransient<IOperationTransient, Operation>()
.AddSingleton<IOperationSingleton, Operation>();

Next we create a Scope using the ServiceProvider.CreateScope method

var provider = services.BuildServiceProvider();
using (var scope1 = provider.CreateScope())
{
    var p = scope1.ServiceProvider;

    var scopeobj1 = p.GetService<IOperationScoped>();
    var transient1 = p.GetService<IOperationTransient>();
    var singleton1 = p.GetService<IOperationSingleton>();

    var scopeobj2 = p.GetService<IOperationScoped>();
    var transient2 = p.GetService<IOperationTransient>();
    var singleton2 = p.GetService<IOperationSingleton>();

    Console.WriteLine(
        $"scope1: { scopeobj1.OperationId }," +
        $"transient1: {transient1.OperationId}, " +
        $"singleton1: {singleton1.OperationId}");

    Console.WriteLine($"scope2: { scopeobj2.OperationId }, " +
        $"transient2: {transient2.OperationId}, " +
        $"singleton2: {singleton2.OperationId}");
}

Next

scope1: 200d1e63-d024-4cd3-88c9-35fdf5c00956, 
transient1: fb35f570-713e-43fc-854c-972eed2fae52, 
singleton1: da6cf60f-670a-4a86-8fd6-01b635f74225

scope2: 200d1e63-d024-4cd3-88c9-35fdf5c00956, 
transient2: 2766a1ee-766f-4116-8a48-3e569de54259, 
singleton2: da6cf60f-670a-4a86-8fd6-01b635f74225

If a new Scope runs,

scope1: 29f127a7-baf5-4ab0-b264-fcced11d0729, 
transient1: 035d8bfc-c516-44a7-94a5-3720bd39ce57, 
singleton1: da6cf60f-670a-4a86-8fd6-01b635f74225

scope2: 29f127a7-baf5-4ab0-b264-fcced11d0729, 
transient2: 74c37151-6497-4223-b558-a4ffc1897d57, 
singleton2: da6cf60f-670a-4a86-8fd6-01b635f74225
Notice that we have four Transient instances, two Scope instances and one Singleton instance.
What's the use of this?
If you have used Autofac's Instance PerRequest in Mvc, you know that there are objects that can be an instance when a request spans multiple actions or multiple services or repositories, such as the most commonly used DBContext. It can not only reduce the consumption of instance initialization, but also realize the function of cross-Service transactions. (Note: All Services for EF in ASP.NET Core need to be registered as Scoped)
 
The way to do this is to share a Scope throughout the reqeust request lifecycle.

Application of DI in ASP.NET Core

3.1 Initialization in Startup Class

ASP.NET Core can configure DI in ConfigureService of Startup.cs. You should be familiar with this parameter of IServiceCollection.
public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<ILoginService<ApplicationUser>, 
      EFLoginService>();
    services.AddMvc();
)

Some components of ASP.NET Core have already provided some instance bindings, such as AddMvc, which is Mvc Middleware's extension method added to IServiceCollection.

public static IMvcBuilder AddMvc(this IServiceCollection services)
{
    if (services == null)
    {
        throw new ArgumentNullException(nameof(services));
    }

    var builder = services.AddMvcCore();

    builder.AddApiExplorer();
    builder.AddAuthorization();
    AddDefaultFrameworkParts(builder.PartManager);
    ...
}

3.2 Use in Controller

Injection can generally be achieved through constructors or attributes, but the official recommendation is through constructors. This is also known as explicit dependency.
 private ILoginService<ApplicationUser> _loginService;
public AccountController(
  ILoginService<ApplicationUser> loginService)
{
  _loginService = loginService;
}

We just need to write this parameter in the controller's constructor, and Service Provider will inject it into the controller. This step is completed when the controller is initialized by Mvc, and we will go into details later when we introduce Mvc.

3.3 View

In View, you need to declare it again with @inject and give it an individual name.
@using MilkStone.Services;
@model MilkStone.Models.AccountViewModel.LoginViewModel
@inject ILoginService<ApplicationUser>  loginService
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head></head>
<body>
  @loginService.GetUserName()
</body>
</html>

3.4 Get instances through HttpContext

A Requested Service under HttpContext can also be used to retrieve instance objects, but this method is generally not recommended. Also note that GetService<> is a paradigm method, which is not called by default without adding the use of Microsoft. Extension. Dependency Injection.
HttpContext.RequestServices.GetService<ILoginService<ApplicationUser>>();

4. How to replace other Ioc containers

Autofac is also a good choice, but first we need to figure out why we need to replace the default DI container. What's the impact of replacement? The default implementation of. NET Core is sufficient for some small projects, even for large projects, but it will be troublesome, because it only provides the most basic AddXXXX method to bind instance relationships, and needs to be added one by one. If the project may have to add hundreds of lines of this method.
If you are familiar with Autofac, you may have an image of the following code.
builder.RegisterGeneric(typeof(LoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>));
 
builder.RegisterGeneric(typeof(ValidatorBehavior<,>)).As(typeof(IPipelineBehavior<,>));

This will bring some convenience to our initialization. Let's see how to replace Autofac to ASP.NET Core. We just need to change the return value of ConfigureService in the Startup class from void to IServiceProvider. The return is an AutoService Provider.

public IServiceProvider ConfigureServices(
  IServiceCollection services){
    services.AddMvc();
    // Add other framework services

    // Add Autofac
    var containerBuilder = new ContainerBuilder();
    containerBuilder.RegisterModule<DefaultModule>();
    containerBuilder.Populate(services);
    var container = containerBuilder.Build();
    return new AutofacServiceProvider(container);
}

4.1 What's the change?

One of the big changes is that Instance PerRequest, the original life cycle of Autofac, will no longer be effective. As we said earlier, the entire request lifecycle is managed by ASP.NET Core, so this one of Autofac will no longer work. We can use Instance Per Lifetime Scope, which is also useful, corresponding to the Scoped in our ASP.NET Core DI.

Deep understanding of dependency injection, Singleton, Scoped, Transient in net core (4)

Label: function   services   return   dcl   null   injection   bfc   addm   sig   

Original address: https://www.cnblogs.com/gdsblog/p/8465401.html

Posted by cheesemunger on Sat, 27 Jul 2019 03:46:17 -0700