Dependency injection in .NET Core的最佳实践

我们知道依赖注入(DI)是一种实现对象及其协作者或依赖关系之间松散耦合的技术。 ASP.NET Core包含一个简单的内建容器来支持构造器注入。
我们试图将DI的最佳实践带到.NET Core应用程序中,这表现在以下方面:

  1. 构造器注入
  2. 注册组件
  3. DI in testing

构造器注入

我们可以通过方法注入、属性注入、构造器注入的方式来注入具体的实例,一般来说构造器注入的方式被认为是最好的方式,所以在应用程序中将使用构造器注入,请避免使用别的注入方式。一个构造器注入的例子如:

public class CharacterRepository : ICharacterRepository
{
    private readonly ApplicationDbContext _dbContext;

    public CharacterRepository(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }
}

注册组件到容器

在使用DI之前,需要告诉容器组件之间的对应关系,例如:

container.Register<IAService, AService>();

所以当你使用构造器注入的时候,你告诉构造函数需要注入IAService类型的实例,容器会根据你之前注册的对应关系创建AService的实例。
看起来一切都很简单,但在实际应用过程中并没有这么简单,试想在一个项目中,组件有成千上万个,这成千上万个组件之间的对应关系怎么样维护?
一个稍微改进点的策略根据这些组件的职责分类,把某一类组件的对应关系抽取成方法:

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>();
    //...
}

这两个分类有什么特点呢?第一个方法试图把所有的ApplicationService的组件对应关系汇总在一起,第二个方法试图把所有的DomainService的组件对应关系汇总在一起,比起之前已经有了很大的进步。不过随着组件的增加,你需要不断修改这几个方法。

基于公共接口来注册组件

第一个方法已经找到了同一类的组件,既然这些组件的性质是一样的,就可以用同样的接口来表示,定义一个空接口用来表示ApplicationService:

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

一旦这些组件有了公共特点,尝试创建下面的扩展:

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

这句代码的意思是显而易见的,扫描某个程序集,找到所有实现了IApplicationService的类进而把组件的对照关系注册到了容器中。

当组件拥有多个接口

类是可以拥有多个接口的,在实际开发中,这样的设计也是很常见的:

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

利用上面介绍的扩展注册所有Options:

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

尝试通过下面的构造器注入:

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

工作的很好,没有问题。但是当我们试图从容器里拿到所有的IOptions类型:

container.ResolveAll<IOptions>();

你得不到任何IOptions类型的实例,原因在于向容器注册对应关系的过程是一对一的,我们之前的扩展.WithDefaultInterface()只注册了AlipayOptions和IAlipayOptions的关系,如果想通过上面的方式拿到所有继承了IOptions的实例,则需要使用另一个扩展:

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

把注册文件放在正确的位置

我们通过分层的方式隔离了不同职责的程序集,最终Web/API项目将会引用这些低层的程序集。要想把 Web/API启动起来,需要把所有程序集定义的组件注册在Web/API项目的容器中。我们把Web/API这种能够启动的程序集叫做客户端。
所以一个典型的客户端需要通过下面代码来注册DI容器:

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

这段代码描述了一个现象,Web/API客户端对低层的组件对应关系一清二楚,违反了Tell, Don‘t Ask Priciple. 正确的做法是:
Web/API客户端告诉低层组件,帮我安装你所在的程序集中所有的组件对应关系。

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

具体的组件对应关系应该定义在相应的程序集中。
这一节的思想都来源于Windsor Castle

DI in testing

人们在不断讨论单元测试的各种风格和差异,类似于通过Mock来管理依赖的单元测试被认为是一种反模式。见:To Kill a Mockingtest, 而DI的另一个功能在于便于写出有价值和有效的单元测试。
当你选择测试一个组件时,实际上要花很多的时间来准备依赖数据,这是显而易见的,因为组件并不是独立存在的。试想如果你能从容器中拿到这个组件,容器就会将所有的依赖关系创建好。
但是问题来了,比如说你的被测试组件依赖了一个能够给第三方发送请求的组件,这显然并不是你所期望的,你只需要注册一个假的事先准备好的组件即可。
对ApplicationServiceTests的组件注册如下:

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

一个对SearchService的测试如下:

[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);
}

原文地址:https://www.cnblogs.com/xiandnc/p/9407856.html

时间: 2024-10-08 06:18:58

Dependency injection in .NET Core的最佳实践的相关文章

asp.net core 系列之Dependency injection(依赖注入)

这篇文章主要讲解asp.net core 依赖注入的一些内容. ASP.NET Core支持依赖注入.这是一种在类和其依赖之间实现控制反转的一种技术(IOC). 一.依赖注入概述 1.原始的代码 依赖就是一个对象的创建需要另一个对象.下面的MyDependency是应用中其他类需要的依赖: public class MyDependency { public MyDependency() { } public Task WriteMessage(string message) { Console

.NET Core 2.1中的HttpClientFactory最佳实践

ASP.NET Core 2.1中出现一个新的HttpClientFactory功能, 它有助于解决开发人员在使用HttpClient实例从其应用程序发出外部Web请求时可能遇到的一些常见问题. 介绍 在.NETCore平台的2.1新增了HttpClientFactory,虽然HttpClient这个类实现了disposable,但使用它的时候用声明using包装块的方式通常不是最好的选择.处理HttpClient,底层socket套接字不会立即释放.该HttpClient类是专为多个请求重复使

Dependency injection in ASP.NET Core

原文 ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies. For more information specific to dependency injection within MVC con

[转]ASP.NET Core Web API 最佳实践指南

原文地址: ASP.NET-Core-Web-API-Best-Practices-Guide 转自 介绍# 当我们编写一个项目的时候,我们的主要目标是使它能如期运行,并尽可能地满足所有用户需求. 但是,你难道不认为创建一个能正常工作的项目还不够吗?同时这个项目不应该也是可维护和可读的吗? 事实证明,我们需要把更多的关注点放到我们项目的可读性和可维护性上.这背后的主要原因是我们或许不是这个项目的唯一编写者.一旦我们完成后,其他人也极有可能会加入到这里面来. 因此,我们应该把关注点放到哪里呢? 在

OSGi原理与最佳实践:第一章 OSGi框架简介(2)

OSGi原理与最佳实践:第一章 OSGi框架简介(2) 由  ValRay 发布 已被浏览4884次 共有3条评论 已被3个人收藏 2013-08-16 21:23 顶(0) 踩(0) osgi原理与最佳实践 1.1.4 开发传统类型的应用 1.1.4.1 B/S 我们首先来看一下,如何基于 OSGi 来开发 B/S 结构的应用.B/S 结构应用程序的开发,可有两个选择:一个是在 OSGi 的框架中嵌入 Http 服务器,另外一个是在 Servlet 容器中嵌入 OSGi 框架.下面分别介绍这两

OSGi原理与最佳实践:第一章 OSGi框架简介(5)Spring-DM

OSGi原理与最佳实践:第一章 OSGi框架简介(5)Spring-DM 由  ValRay 发布 已被浏览8409次 共有3条评论 已被2个人收藏 2013-08-16 21:29 顶(1) 踩(0) osgi原理与最佳实践 1.3 Spring-DM 1.3.1 简介 Spring-DM 指的是 Spring Dynamic Modules.Spring-DM 的主要目的是能够方便地将 Spring 框架 和OSGi框架结合在一起,使得使用Spring的应用程序可以方便简单地部署在OSGi环

Atitit.列表页and查询条件的最佳实践(1)------设定搜索条件and提交查询and返回json数据

Atitit.列表页and查询条件的最佳实践(1)------设置查询条件and提交查询and返回json数据 1. 1.?配置条件字段@Conditional 1 1 2. 2.?配置条件字段显示类型为[email protected](displayType?=?displayType.rang,?rangStart?=?rang.start,?rangEnd?=?rang.end,op=op.range) 1 3. #----show  condition  page  list 1 4.

Django 1.6 最佳实践: 如何设置django项目的设置(settings.py)和部署文件(requirements.txt)

Django 1.6 最佳实践: 如何设置django项目的设置(settings.py)和部署文件(requirements.txt) 作者: Desmond Chen,发布日期: 2014-05-17, 修改日期: 2014-05-18 在Django 1.6中的settings.py中可以修改130多项设置, 但大多数都继承自默认值. 设置是在web服务器启动时首次载入的, 服务器重启时重新载入, 因此, 程序员们应尽量避免修改正式服务器上使用的settings.py文件. 以下是一些我们

atitit. 统计功能框架的最佳实践(1)---- on hibernate criteria

atitit. 统计功能框架的最佳实践(1)---- on hibernate criteria 1. 关键字 1 2. 统计功能框架普通有有些条件选项...一个日期选项..一个日期类型(日,周,月份,年等) 1 3. 元数据的位置,不需要绑定class 1 4. 设置聚合字段... @reduce(" sum(timLen) "),@reduce(" Avg(timLen) ") 2 5. 设置groupby  字段  @GroupBy 2 6. 设置groupb