[. NET Core 3.0] framework 9 - dependency injection and IoC

preface

1. Important: if you realize decoupling, that is, the api layer only references IService and IRepository, you need to clean up the solution and recompile the project every time you modify the service layer, because the dll of your api layer is still the unmodified code at this time.

2. Important +: Please note that the purpose of dependency injection is not to decouple, but to control inversion. Generally speaking, we don't need to go to the new Service instance ourselves, so we don't need to decouple (for example, I don't refer to the Service layer and Repository layer mentioned below). In my next DDD series, dependency injection is not decoupled, Because I use the built-in injection, not the reflection dll of Autofac. My purpose of decoupling is to make everyone better understand how services are injected into the host container.

After that, last time I talked about the preliminary discussion on the overall construction of 8 | API project 6.3 asynchronous generics + dependency injection. In the later title, I removed the word warehouse, because it seems that everyone has very different views on this model. Um ~ maybe I'm not good at learning and didn't mention the benefits. Now I'm learning about driver design in the field of DDD, With good inspiration, let's share it with you.

Although the project can run as a whole, I still have a few small knowledge points to say, mainly
1. Knowledge of dependency injection and AOP;
2. Cross domain proxy and other issues (because Vue is developed based on Node and is not at the same address as the background API interface);
3. Small problems related to DTO of entity class;
4. Redis cache, etc;
5. Deploy various pits in the server; Although they are all very small knowledge points, I'll tell you all. Well, let's start today's explanation;

Zero. Green part completed today

1, Understanding and thinking of dependency injection

Update (19-04-17): if you want to understand dependency injection well, you can start from the following aspects:

1. How do references between projects work? For example, why does the api layer only refer to the service layer? Why can multi-layer classes such as repository and model be used?

2. When the project starts, that is, when it runs, how to dynamically obtain and access the instance of each object? That's the principle of new

3. There are n classes in the project, corresponding to m
Examples, etc. where are these services? Certainly every project has its own piece. If the project does not start, there must be no such services in memory.

4. What are the benefits of using * * interfaces (for abstraction) * *?

5. At the later stage of the project, the implementation classes of all interfaces should be modified according to the business needs, such as IA = new A(); Change all to IA = new B();

6. The importance of reflection, why use reflection dll?

If each of these can be explained clearly, you must know what dependency injection does.

When it comes to dependency, I think of an example on the Internet. Dependency injection is similar and different from the factory model:

(1) In primitive society, there was no social division of labor. The person who needs an axe (the caller) can only grind an axe (the callee) by himself. The corresponding situation is that the caller in the software program creates the callee himself.

(2) Entering the industrial society, factories appear. Axes are no longer finished by ordinary people, but are produced in factories. At this time, people who need axes (callers) find factories and buy axes without caring about the manufacturing process of axes. Simple factory design mode of corresponding software program.

(3) In the "distribution on demand" society, people who need axes don't need to find factories. We just need to work. The axes have been automatically prepared for us and can be used directly.

First of all, we need to know what control reversal IOC is. Take chestnuts for example. When I developed a simple mall before, there was an order form in the order module, and there must be an order details table. In addition, there was a commodity information table in the order details table, which was also associated with a price specification table, or other logistics information and merchant information. Of course, We can put it in a large table, but you won't do so. Because it's too large, it must be divided into tables, which will inevitably lead to the situation of setting classes in classes. This is dependency. For example, the order table above depends on the detail table. When instantiating the order entity class, we also need to manually instance the detail table. Of course, it will be generated automatically in the EF framework. However, if a programmer corrects the entity class of the detail table wrong, the order table will crash. Oh, no! I have encountered such a situation.

How to solve this problem, there is control reversal. I saw a good explanation on the Internet:

1. Before the introduction of IOC, object A depends on object B. when object A initializes or runs to A certain point, A directly uses the new keyword to create an instance of B. the program is highly coupled and inefficient. Whether creating or using B objects, the control is in its own hands.

2. After the software system introduces the IOC container, this situation is completely changed. Due to the addition of the IOC container, the direct connection between object A and object B is lost. Therefore, when object A runs to the point where object B is needed, the IOC container will actively create an object B and inject it into the place where object A needs it.

3. Dependency injection means that if another object needs to be called for assistance during program operation, the callee does not need to be created in the code, but depends on external injection. Spring's dependency injection has almost no requirements for callers and callees, and fully supports the management of dependencies between POJO s. There are usually two types of dependency injection:
·Set point injection.
·Structural injection.
This is the way of dependency injection.

What is inversion of control (IoC)

Inversion of Control, abbreviated as IoC, is not a technology, but a design idea.

In short, it is to decompose the complex system into cooperative objects. After these object classes are encapsulated, the internal implementation is transparent to the outside, which reduces the complexity of solving problems, and can be reused and extended flexibly. The view proposed by IOC theory is generally as follows: decoupling between objects with dependencies is realized by means of "third party", as shown in the following figure:

As you can see, due to the introduction of the "third party" in the middle, that is, the IOC container, there is no coupling relationship between the four objects A, B, C and D. the transmission between gears all depends on the "third party", and the control of all objects is handed over to the "third party" IOC container. Therefore, the IOC container has become the key core of the whole system, It plays A role similar to "adhesive" and binds all objects in the system together. Without this "adhesive", objects will lose contact with each other. This is the reason why some people compare IOC container to "adhesive".

Let's do another experiment: remove the IOC container in the middle of the figure above, and then take a look at the system:

The picture we see now is all that we need to complete to realize the whole system. At this time, the four objects A, B, C and d have no coupling relationship and no connection with each other. In this way, when you implement A, you don't need to consider B, C and D at all, and the dependency between objects has been reduced to the minimum. Therefore, if the IOC container can be implemented, it will be A wonderful thing for system development. Each member participating in the development only needs to implement his own class, which has nothing to do with others!

In the above article, we have learned what is dependency inversion, inversion of control (IOC) and dependency injection (DI). There are many explanations on the Internet, but I won't explain it here. In fact, we mainly see that there is dependency

public class A : D
{

    public A(B b)
    {
        // do something   
    }
    C c = new C();
}

For example, as long as the BlogController in our project is instantiated through new, there are dependencies

public async Task<List<Advertisement>> Get(int id)
{
    IAdvertisementServices advertisementServices = new AdvertisementServices();
    return await advertisementServices.Query(d => d.Id == id);
}

Using dependency injection has the following advantages:

  • In the traditional code, each object is responsible for managing the objects it needs to depend on. As a result, if it is necessary to switch the implementation classes of dependent objects, it needs to modify many places. At the same time, over coupling also makes it difficult for objects to carry out unit testing.
  • Dependency injection gives the creation of objects to external management, which well solves the problem of tight coupling of code. It is a way to realize loose coupling of code
    (couple).
  • Loose coupling makes the code more flexible, can better respond to requirements changes, and facilitate unit testing.

For example, chestnuts are about logging

Log recording: sometimes it is necessary to debug and analyze and record log information. At this time, it can be output to console, file, database, remote server, etc; Suppose you initially output to the console and directly instantiate ILogger logger = new ConsoleLogger() in the program, but sometimes you need to output to other files. Maybe you need to change the program to change the ConsoleLogger to FileLogger or NoLogger, new FileLogger() or new SqlLogger(). At this time, it seems bad to constantly change the code, If you use dependency injection, it is particularly comfortable.

2, What are the common IoC frameworks

1. Autofac + native

I often use native injection and Autofac injection.

Autofac: it seems that net is used most at present
Ninject: not many people seem to use it at present
Unity: it's also common
DI from Microsoft core

In fact, the. Net Core has its own lightweight IoC framework,

ASP.NET Core itself has integrated a lightweight IOC container. Developers only need to define the interface and use the corresponding generation in the ConfigureServices method of Startup.cs

The life cycle binding method is sufficient. The common methods are as follows

services.AddTransient<IApplicationService,ApplicationService>//The service is created on every request, and it is best used for lightweight stateless services (such as our Repository and ApplicationService services)
services.AddScoped<IApplicationService,ApplicationService>//The service is created at each request and its life cycle spans the entire request
services.AddSingleton<IApplicationService,ApplicationService>//The Singleton service is created at the first request (or when we specify to create an instance and run the method in ConfigureServices), and each subsequent request will continue to use the created service. If the developer's application needs a Singleton service scenario, it should be designed to allow the service container to operate the service life cycle, rather than manually implementing the Singleton design pattern, and then the developer should operate in the user-defined class.

Of course, the container of. Net Core itself is still relatively simple. If you want more functions and extensions, you still need to use the above framework.

2. Three injection lifecycles

Weight:

AddSingleton→AddTransient→AddScoped

AddSingleton lifecycle:

Project start - project close is equivalent to only one static class

AddScoped lifecycle:

Request start - request end. All the objects obtained in this request are the same

AddTransient lifecycle:

Request acquisition - (GC recycle - active release) the object acquired each time is not the same

Here's a simple DEMO:

1. Four interfaces are defined and implemented respectively. The purpose is to test Singleton, Scope and Transient, as well as the final Service service:

 public interface ISingTest
    {
        int Age { get; set; }
        string Name { get; set; }
    }

 public class SingTest: ISingTest
 {
     public int Age { get; set; }
     public string Name { get; set; }
 }

//--------------------------

 public interface ISconTest
 {
     int Age { get; set; }
     string Name { get; set; }
 }
  public class SconTest: ISconTest
  {
      public int Age { get; set; }
      public string Name { get; set; }
  }

//--------------------------
 public interface ITranTest
 {
     int Age { get; set; }
     string Name { get; set; }
 }
  public class TranTest: ITranTest
  {
      public int Age { get; set; }
      public string Name { get; set; }
  }

//-----------------------
public interface IAService
{
    void RedisTest();
}

 public class AService : IAService
 {
     private ISingTest sing; ITranTest tran; ISconTest scon;
     public AService(ISingTest sing, ITranTest tran, ISconTest scon)
     {
         this.sing = sing;
         this.tran = tran;
         this.scon = scon;
     }
     public void RedisTest()
     {

     }
 }

2. Project injection

3. Controller call

 private ISingTest sing; ITranTest tran; ISconTest scon; IAService aService;
  public ValuesController(ISingTest sing, ITranTest tran, ISconTest scon, IAService aService)
  {
      this.sing = sing;
      this.tran = tran;
      this.scon = scon;
      this.aService = aService;
  }

  // GET api/values
  [HttpGet]
  public ActionResult<IEnumerable<string>> SetTest()
  {
      sing.Age = 18;
      sing.Name = "Xiao Hong";

      tran.Age = 19;
      tran.Name = "Xiao Ming";

      scon.Age = 20;
      scon.Name = "Blue ";

      aService.RedisTest();


      return new string[] { "value1", "value2" };
  }

  // GET api/values/5
  [HttpGet("{id}")]
  public ActionResult<string> Get(int id)
  {
      return "value";
  }

4. Start the test, what happens to the three injection methods

Request SetTest // GET api/values


The object of AddSingleton has not changed

The AddScoped object has not changed

The object of AddTransient has changed

Request / / GET api/values/5

The object of AddSingleton has not changed

The AddScoped object has changed

The object of AddTransient has changed

be careful:

Because the AddScoped object is created at the time of request

Therefore, it cannot be used in AddSingleton object

It cannot even be used in AddTransient objects

So the weight is

AddSingleton→AddTransient→AddScoped

Otherwise, the following exceptions will be thrown

3, Better use of IoC framework -- Autofac

First of all, we need to understand where we want to inject - the Controller API layer. Then, we see that when calling the interface, if you need the methods in it, you need to use two namespaces

[HttpGet("{id}", Name = "Get")]
public async Task<List<Advertisement>> Get(int id)
{            
     //Two namespaces blog.core.iservices need to be referenced; Blog.Core.Services;
     IAdvertisementServices advertisementServices = new AdvertisementServices();
     return await advertisementServices.Query(d => d.Id == id);
}

Next, we need to deal with:

1. Introducing nuget package

Two are introduced into Nuget: Autofac.Extras.DynamicProxy (the dynamic proxy of Autofac, which relies on Autofac, so it is not necessary to introduce Autofac separately) and Autofac.Extensions.DependencyInjection (the extension of Autofac). Note that it is the latest version.

 <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="5.0.0" />
 <PackageReference Include="Autofac.Extras.DynamicProxy" Version="4.5.0" />

2. Configure container and inject service

In the startup.cs file, add a method to configure the Autofac service container

First, we create an interface and corresponding implementation class:

 public interface IAdvertisementServices
 {
     int Test();
 }

 public class AdvertisementServices : IAdvertisementServices
 {
     public int Test()
     {
         return 1;
     }
 }

Then inject the service into the Autofac container:

 public void ConfigureContainer(ContainerBuilder builder)
 {

     var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;

     //Register a class and interface directly
     //On the left is the implementation class, and on the right is the interface
     builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>();


     //Register the component you want to create through reflection
     var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll");
     var assemblysServices = Assembly.LoadFrom(servicesDllFile);

     builder.RegisterAssemblyTypes(assemblysServices)
               .AsImplementedInterfaces()
               .InstancePerLifetimeScope()
               .EnableInterfaceInterceptors();

 }


At this time, we inject the new instantiation process of advertising services into the Autofac container,

At this time, you should understand that the front is the implementation class and the back is the interface. Don't confuse the order.

3. Using the service factory, add the Autofac container to the Host

Why do we do this? You should also see from the above that we have only configured the service and container and have not added it to our project host, so our controller can't get the corresponding service.

We need to configure UseServiceProviderFactory in the Program.cs file

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
    .UseServiceProviderFactory(new AutofacServiceProviderFactory()) //<--NOTE THIS
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

4. Use constructor mode to inject

There are three ways of dependency injection (constructor injection, attribute injection and mode injection). We usually use the constructor to implement injection,

Do you remember the default controller WeatherForecastController? As we said at that time, there is already a dependency injection usage, that is, ILogger. Now let's continue to inject,

 private readonly ILogger<WeatherForecastController> _logger;
 private readonly IAdvertisementServices advertisementServices;

 public WeatherForecastController(ILogger<WeatherForecastController> logger, IAdvertisementServices advertisementServices)
 {
     _logger = logger;
     this.advertisementServices = advertisementServices;
 }

 /// <summary>
 ///Get interface data
 /// </summary>
 /// <returns></returns>
 [HttpGet]
 public string[] Get()
 {
     var ads = advertisementServices.Test();
     return Summaries;
 }

5. Effect debugging has been successful

Then run debugging and find that when the breakpoint just enters, the interface has been instantiated to achieve the purpose of injection.

Note: an error is often encountered here: None of the constructors found with,

Check whether your service uses another repository, but lacks a constructor.

If there is no problem, you need to think about whether there are other injection methods without a third-party framework other than Autofac? Smart as you, netcore really comes with an injection extension.

6. Implementation effect of injection provided by NetCore

Of course, we can use the injection method provided by Asp.net core. It's also very simple. Here's how to use it:

 // Inject service
 services.AddScoped<IAdvertisementServices, AdvertisementServices>();


At this time, we found that it has been successfully injected, and it is very simple. Why do we need to use the third-party extension of Autofac? Let's think about it. We just injected a Service, but there are so many classes in the project. Do you have to add them manually one by one? Of course, the answer is not drop~

4, Injection of the entire dll assembly

1. Service assembly injection mode - not decoupled

Inject all methods of Blog.Core.Services and Blog.Core.Repository assemblies through reflection

Modify the following code. Note that at this time, right-click in the project dependency and add the reference Blog.Core.Services layer and Repository layer to the project, as shown in the following figure. At this time, our program depends on specific services:

The core code is as follows. Note whether this is the Load mode (assembly name) or configure the Autofac container in the startup.cs file.

 public void ConfigureContainer(ContainerBuilder builder)
 {

     var assemblysServices = Assembly.Load("Blog.Core.Services");
   
     builder.RegisterAssemblyTypes(assemblysServices)
               .AsImplementedInterfaces()
               .InstancePerLifetimeScope()
               .EnableInterfaceInterceptors();
 }

Everything else remains the same. The project runs normally. It's OK to change other interfaces. I won't elaborate on the details.

Here, Autofac dependency injection has been completed, and the basic operation is like this. However, you may not really realize the benefits of injection. Take a challenge, look at the content below, and try decoupling the levels!

2. Assembly injection -- implementing hierarchical decoupling

This is a learning idea. You should think more. You may feel bored or useless, but it is still necessary to understand project startup and loading.

1. The project ultimately relies only on abstraction
The final effect is as follows: the project only relies on abstraction, deletes the two implementation layers and references the two interface layers.

2. Configure assembly output for warehousing and service tiers
Change the project generation addresses of Blog.Repository layer and Service layer into relative paths, so that you don't have to copy these two DLLs manually. When F6 is compiled, it is directly generated under the bin of api layer:

"...\Blog.Core\bin\Debug\"

3. Use LoadFile to load the assembly of the service tier

 var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;//Get project path
 var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll");//Get absolute path of injection project
 var assemblysServices = Assembly.LoadFile(servicesDllFile);//Directly use the method of loading files

At this time, the page may start normally after we compile successfully, which proves that we have registered all the services of the Service and Repository Service layers, but there will still be errors when accessing an interface:

This error indicates that our sqlsugar service has not been registered successfully. It must be that our sqlsugar assembly does not have a normal reference. What should we do? Just reference it directly under the api layer.

4. Decouple the Service layer from the Repository layer

Remember BaseServices.cs in Blog.Core.Services? It is still created through new instantiation. Follow the contraller, modify BaseServices, and inject in the constructors of all subclasses:

 public class BaseServices<TEntity> : IBaseServices<TEntity> where TEntity : class, new()
    {
        //public IBaseRepository<TEntity> baseDal = new BaseRepository<TEntity>();
        public IBaseRepository<TEntity> baseDal;//By injecting in the constructor of the subclass, this is the base class without constructor
      //...
   }


    public class AdvertisementServices : BaseServices<Advertisement>, IAdvertisementServices
    {
        IAdvertisementRepository dal;
        public AdvertisementServices(IAdvertisementRepository dal)
        {
            this.dal = dal;
            base.baseDal = dal;
        }
    }


Well, now the whole project has completed the function of direct decoupling. In the future, even if the Repository and Service change, the interface layer does not need to be modified, because the injection has been completed, and the third-party Autofac will instantiate.

5. View injected service data in container
If you want to see whether the injection into the container is successful, you can directly look at the contents of the container ApplicationContainer:

5, No interface project injection

1. Injection in the form of interface

We discussed a lot above, but they are all interface frameworks,

For example: Service.dll and its corresponding IService.dll, Repository.dll and its corresponding IRepository.dll,

In this way, if we use services between multiple layers, we can directly inject the new object we need to use into the container, and then we can use the corresponding interface,

For example, if we want to use the AdvertisementServices class in the controller, we can directly use its interface IAdvertisementServices, so as to better understand the coupling, so that we can easily decouple the Service.dll in the API layer;

If we need to use AdvertisementRepository in the Service class, we will directly use the corresponding interface IAdvertisementRepository. In this way, we will decouple the storage layer from the Service layer.

2. If there is no interface

The case is as follows:

If our project is like this and there is no interface, what will we do

// Service layer class
   public class StudentService
    {
        StudentRepository _studentRepository;
        public StudentService(StudentRepository studentRepository)
        {
            _studentRepository = studentRepository;
        }


        public string Hello()
        {
            return _studentRepository.Hello();
        }

    }


    // Storage layer class
     public class StudentRepository
    {
        public StudentRepository()
        {

        }

        public string Hello()
        {
            return "hello world!!!";
        }

    }


    // controller interface call
    StudentService _studentService;

    public ValuesController(StudentService studentService)
    {
        _studentService = studentService;
    }

In this case, we can't use the above interface injection mode, because we registered the injected service to the interface. AsImplementedInterfaces(), we can't realize decoupling, because there is no interface layer at all, so we can only reference the implementation class layer. This injection:


Through builder. Registerassemblytypes (assemblyrepository); Method is injected directly into the service, nothing else.

3. If it is a separate entity class without an interface

public class Love
    {
        // It must be a virtual method
        public virtual string SayLoveU()
        {
            return "I ♥ U";
        }

    }

//---------------------------

Only virtual methods in this class can be injected
builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love)))
    .EnableClassInterceptors()
    .InterceptedBy(typeof(BlogLogAOP));

6, Multiple class injections are implemented on the same interface

There is no example code here. If you need it, you can see the chestnuts of this blogger: https://www.cnblogs.com/fuyujian/p/4115474.html

I'll write a chestnut and put it here later.

Posted on Sun, 05 Sep 2021 20:43:38 -0400 by freelancer