Dependency injection in .NET Core的最佳实践
我们知道依赖注入(DI)是一种实现对象及其协作者或依赖关系之间松散耦合的技术。 ASP.NET Core包含一个简单的内建容器来支持构造器注入。
我们试图将DI的最佳实践带到.NET Core应用程序中,这表现在以下方面:
构造器注入
注册组件
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
.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com
Dependency injection in .NET Core的最佳实践相关推荐
- 关于单元测试的思考--Asp.Net Core单元测试最佳实践
https://www.cnblogs.com/yubaolee/p/DotNetCoreUnitTest.html 在我们码字过程中,单元测试是必不可少的.但在从业过程中,很多开发者却对单元测试望而 ...
- everythingtoolbar.dll”或它的一个依赖项。_ASP.NET Core依赖注入最佳实践、提示和技巧...
译者前言 本文译自ABP框架的开发博客<ASP.NET Core Dependency Injection Best Practices, Tips & Tricks>一文(原作者 ...
- Dependency injection in ASP.NET Core
原文 github地址 ASP.NET Core supports the dependency injection (DI) software design pattern, which is a ...
- .NET Core开发实战(定义API的最佳实践)Source Generators版
前言 极客时间上的<.NET Core开发实战>是一门非常好的课程,作者肖伟宇在第31课(https://time.geekbang.org/course/detail/100044601 ...
- ASP.NET Core 性能优化最佳实践
本文提供了 ASP.NET Core 的性能最佳实践指南. 译文原文地址:https://docs.microsoft.com/en-us/aspnet/core/performance/perfor ...
- 可能是Asp.net Core On host、 docker、kubernetes(K8s) 配置读取的最佳实践
写在前面 为了不违反广告法,我竭尽全力,不过"最佳实践"确是标题党无疑,如果硬要说的话 只能是个人最佳实践. 问题引出 可能很多新手都会遇到同样的问题:我要我的Asp.net ...
- Dotnet core使用JWT认证授权最佳实践(二)
最近,团队的小伙伴们在做项目时,需要用到JWT认证.遂根据自己的经验,整理成了这篇文章,用来帮助理清JWT认证的原理和代码编写操作. 第一部分:Dotnet core使用JWT认证授权最佳实践(一) ...
- .NET Core开发实战(第15课:选项框架:服务组件集成配置的最佳实践)--学习笔记...
15 | 选项框架:服务组件集成配置的最佳实践 这一节讲解如何使用选项框架来处理服务和配置的关系 选项框架的特性: 1.支持单例模式读取配置 2.支持快照 3.支持配置变更通知 4.支持运行时动态修改 ...
- ASP.NET Core Web API 最佳实践指南
原文地址: ASP.NET-Core-Web-API-Best-Practices-Guide 介绍 当我们编写一个项目的时候,我们的主要目标是使它能如期运行,并尽可能地满足所有用户需求. 但是,你难 ...
最新文章
- 开发安全的 API 所需要核对的清单
- C# IPGlobalStatistics获取本机网络流量信息
- 2014年英语一阅读理解Text1
- mysql的存储引擎详解_MySQL常见存储引擎详解
- Jupyter Notebook命令行启动报错: DLL load failed
- Bootstrap精巧布局
- Spring源码解析一(框架梳理)
- Navicat15注册时报错 rsa public key not find
- matlab mac问题,mac版的matlab出现问题,有大神帮忙看一下吗
- 【图像处理】python实现对图像进行二值化处理
- php测速,speedtest-x :一款PHP网页测速工具
- ORB-SLAM 全文翻译
- 商法——企业法律风险防范
- 网络的形成-从原始部落到现代化世界
- nasm ces纠正性训练 nsca-cpt体能训练 pes cscs 体能训练 acsm
- prettier简单介绍
- 数据结构与算法:树 二叉树入门(一)
- Apache Curator实战
- No.002 关于Python函数返回值的三种情况
- QN8035 FM收音机芯片 驱动程序
热门文章
- Android帧缓冲区(Frame Buffer)硬件抽象层(HAL)模块Gralloc的实现原理分析(2)...
- [js高手之路]使用原型对象(prototype)需要注意的地方
- 使用JUnitParams简化Parameterized tests
- java获取ResultSet长度
- VForum 2008系列之六:分论坛视频-虚拟世界中的应用
- AspNet Core 6.0 Json写默认首字母小写(camelCase)问题
- 手把手教你构建WPF官方开源框架源代码
- aspnetcore 应用 接入Keycloak快速上手指南
- WPF实现实现圆形菜单
- 全网最通透的“闭包”认知 · 跨越语言