Best practices for Dependency injection in. NET Core

Source: Internet
Author: User

We know that Dependency injection (DI) is a technique that implements loose coupling between an object and its collaborators or dependencies. ASP. NET core contains a simple built-in container to support constructor injection.
We are trying to bring the best practices of Di into a. NET core application, which is reflected in the following areas:

    1. Constructor injection
    2. Registering components
    3. DI in testing
Constructor injection

We can inject concrete instances by way of method injection, attribute injection, constructor injection, in general, constructor injection is considered the best way, so the constructor is injected in the application, so avoid using another injection method. An example of a constructor injection is:

public class CharacterRepository : ICharacterRepository{    private readonly ApplicationDbContext _dbContext;    public CharacterRepository(ApplicationDbContext dbContext)    {        _dbContext = dbContext;    }}
Registering Components to Containers

Before using Di, you need to tell the corresponding relationship between the container components, for example:

container.Register<IAService, AService>();

So when you use the constructor to inject, you tell the constructor to inject an instance of the Iaservice type, and the container creates an instance of Aservice based on the corresponding relationship you previously registered.
Everything seems simple, but in practice it's not that simple, just imagine that there are thousands of components in a project, how is the correspondence between thousands of components maintained?
A slightly improved strategy based on the responsibilities of these components, the corresponding relationship of a class of components is extracted into a method:

private void RegisterApplicationServices(Container container){    container.Register<IAApplicationService, AApplicationService>();    container.Register<IBApplicationService, BApplicationService>();    //...}private void RegisterDomainServices(Container container){    container.Register<IADomainService, ADomainService>();    container.Register<IBDomainService, BDomainService>();    //...}private void RegisterOtherServices(Container container){    container.Register<IDataTimeSource, DataTimeSource>();    container.Register<IUserFetcher, UserFetcher>();    //...}

What are the characteristics of these two categories? The first method tries to summarize all the Applicationservice component correspondence, and the second method tries to summarize all of the Domainservice's component correspondence, and has made a lot of progress compared to the previous one. However, as the components increase, you need to constantly modify these methods.

Registering components based on a common interface

The first method has found the same class of components, since the properties of these components are the same, you can use the same interface to represent, define an empty interface to represent Applicationservice:

public interface IApplicationService {}public interface IAApplicationService : IApplicationService { //.. }public interface IBApplicationService : IApplicationService { //.. }

Once these components have a common feature, try creating the following extensions:

container.Register(Classes.FromAssembly().BaseOn<IApplicationService>().WithDefaultInterface());

The meaning of this code is obvious, scan an assembly, find all the classes that implement Iapplicationservice, and then register the component's control relationship with the container.

When a component has multiple interfaces

Classes can have multiple interfaces, and in real-world development, such a design is also very common:

public interface IOptions { //... }public interface IAlipayOptions : IOptions { //... }public class AlipayOptions: IAlipayOptions { //... }

Use the extensions described above to register all options:

container.Register(Classes.FromAssembly().BaseOn<IOptions>().WithDefaultInterface());

Try to inject through the following constructor:

public AlipayPayment(IAlipayOptions alipayOptions) { //... }

The work is very good, no problem. But when we try to get all the ioptions types from the container:

container.ResolveAll<IOptions>();

You don't get any instances of the ioptions type, because the process of registering the corresponding relationship with the container is one-to-one, our previous extension. Withdefaultinterface () Only registers the relationship between Alipayoptions and ialipayoptions, and if you want to get all the instances that inherit ioptions in the way above, you need to use another extension:

container.Register(Classes.FromAssembly().BaseOn<IOptions>().WithAllInterfaces());
Put the registration file in the right place

We isolate the assemblies of different responsibilities in a layered manner, and eventually the WEB/API project will reference these lower-level assemblies. To start the WEB/API, you need to register all the assembly-defined components in the container of the WEB/API project. We call this WEB/API-capable assembly called the client.
So a typical client needs to register the DI container with the following code:

container.Register(Classes.FromAssembly().BaseOn<IApplicationService>().WithDefaultInterface());container.Register(Classes.FromAssembly().BaseOn<IDomainService>().WithDefaultInterface());//...// 还有其他无法用公共接口表示的组件,这些组件可能来自于低层服务container.Register<IDateTimeSource, DateTimeSource>();container.Register<IUserFetcher, UserFetcher>();//...

This code describes a phenomenon that web/api the client to the low-level component correspondence, and violates the tell, Don ' t Ask priciple. The correct approach is to:
The WEB/API client tells the lower-level components to help me install all the component correspondence in your assembly.

// 安装所有services.Install(FromAssembly.Contains<IApplicationService>());services.Install(FromAssembly.Contains<IDomainService>());services.Install(FromAssembly.Contains<IOtherService>());

The specific component correspondence should be defined in the corresponding assembly.
The idea of this verse comes from Windsor Castle.

DI in testing

People are constantly discussing the various styles and differences of unit tests, similar to the unit tests that manage dependencies through mocks, which are considered an anti-pattern. See: To Kill a mockingtest, while the other function of Di is to facilitate the writing of valuable and effective unit tests.
When you choose to test a component, it actually takes a lot of time to prepare the dependent data, which is obvious because the component does not exist independently. Just imagine that if you can get this component in your device, the container will create all the dependencies.
But the problem is, for example, your tested component relies on a component that can send requests to a third party, which is obviously not what you expect, you just need to register a fake prepared component.
The components for applicationservicetests are registered as follows:

container.Install(FromAssembly.Contains<FakedComponentsInstaller>());//..Register other components that ApplicationService depend on

A test for Searchservice is as follows:

[Fact]public async void WhenInputDataIsValidShouldGetSearchResult(){    //Arrage    var searchService = _container.Resolve<ISearchService>();    var searchModel = SearchModelBuilder.Default().Build();    //Act    var result = await searchService.Search(searchModel);    //Assert    result.Count.Should().BeGreaterThan(0);}

Best practices for Dependency injection in. NET Core

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.