依赖注入在 dotnet core 中实现与使用:1 基本概念
关于 Microsoft Extension: DependencyInjection 的介绍已经很多,但是多数偏重于实现原理和一些特定的实现场景。作为 dotnet core 的核心基石,这里准备全面介绍它的概念、原理和使用。
这里首先介绍概念部分。
1. 概念
该项目在 GitHub 的地址:https://github.com/aspnet/Extensions/tree/master/src/DependencyInjection
Microsoft.Extensions.DependencyInjection
是微软对依赖倒置原则的实现。作为 ASP.NET Core 的基石,DependencyInjection
贯穿了整个项目的方方面面,掌握它的使用方式和原理,不仅对理解 ASP.NET Core 有重要意义,也有助于将它运用到其它项目的开发中,帮助提供项目开发的效率和质量。
1.1 问题的场景
在软件开发中,项目通常有多个不同的模块组成,模块之间存在依赖关系。例如,我们考虑一个简化的场景,我们有 3 个关于用户的类:
AccountController,提供用户交互界面
UserService,提供用户管理的业务逻辑
UserRepository,提供用户管理的数据访问
AccountController
内部需要使用 UserService
的实例 来管理用户,而 UserService
内部则需要基于 UserRepository
来提供数据访问。我们称它们之间存在依赖关系。或者表达为,AccountController
依赖于 UserService
,而 UserService
依赖于 UserRepository
。而依赖注入就是用来帮助我们实现依赖管理的有力工具。
1.2 依赖倒置原则 DIP
依赖倒置原则是广为人知的设计原则之一,该原则是实现软件项目中模块的解耦的理论基石。
原则的定义如下:
High level modules should not depend upon low level modules,Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstracts.
翻译过来为:
高层模块不应该依赖低层模块,两者都应该依赖抽象
抽象不应该依赖细节
细节应该依赖抽象
在没有实现依赖倒置原则的时候,我们通过在 AccountController
类中自己通过 new
关键字来创建其依赖的 UserService
对象实例,
public class AccountController {
private readonly UserService _userService;
public AccountController() {
this._userService = new UserService();
}
}
这导致了两个类之间的紧耦合,AccountController
与 UserService
被绑定到一起, 在每次创建 AccountController
的时候,一定会创建一个 UserService
的对象实例,而如果我们需要测试 AccountController
的时候,也就不得不考虑 UserService
,这样一级一级的依赖下来,UserService
又会依赖 UserRepository
,就会发现项目中的类都被绑定在一起, 紧密耦合,难以分拆。
基于依赖倒置的原则,通常会考虑通过接口进行隔离。例如,我们可能会定义一个用户服务的接口:
public interface IUserService
{
}
而用户服务则会实现该接口
public class UserService : IUserService {
}
在 AccountController
类中,则改变成了基于接口来使用 UserService
。
public class AccountController {
private readonly IUserService _userService;
public AccountController() {
this._userService = new UserService();
}
}
虽然在 HomeController
内部,我们可以基于接口编程了,但是这样的作法并没有解决自己通过 new
来获取 UserService
对象实例的问题。
1.3 控制反转 IoC
IoC
是一种著名的实现 DIP 的设计模式。
它的核心思想是:在需要对象实例的时候,不要总考虑自己通过 new
来创建对象,放下依赖对象的创建过程,而是把创建对象的工作交给别人来负责,这个别人我们通常称为 容器 (Container) 或者 服务提供者 (ServiceProvider), 我们后面使用这个 ServiceProvider
来指代它,
在需要对象实例的时候,从这个 ServiceProvider
中获取。
下面是一个广泛使用的示意图。拿总是要拿的,但是从 自己穿上 变成了 给你穿上
在控制反转中,引入了一个 ServiceProvider 来帮助我们获得对象实例。
1.4 依赖注入 DI (DependencyInjection)
DI 是 IoC 模式的一种实现。
《Expert one on one J2EE Development without EJB》第 6 章
IoC 的主要实现方式有两种:依赖查找,依赖注入 (p128)
依赖注入是一种更可取的方式。(p130)
Martin Fowler 的原文:
As a result I think we need a more specific name for this pattern. Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection.
大意是:
已经存在某种模式,该模式被称为 IoC,但 IoC 太过广义,任何框架都 IoC,为了让表意更明确,决定采用 DI 来精确指称它。
DI 的实现有多种,我们这里介绍的是微软官方在 Microsoft Extension 中内置提供的 DependencyInjection。它是 IoC 中一种实现,ASP.NET Core 的整个核心基于它来实现。同时,我们也可以在其它项目中使用,以实现对依赖倒置原则的支持。
2. DependencyInjection 中的基本概念
2.1 服务描述集合 ServiceCollection
在微软的 DI 实现中,所有的服务需要首先注册到一个公共的服务描述集合中,该集合对于整个 DI 来说,只需要一个,服务只需要在此集合中注册一次,即可在以后通过 DI 提供给使用者。
该集合的接口定义为 IServiceCollection
,可以看出来,它其实就是一个用来保存服务注册的集合。
public interface IServiceCollection : IList<ServiceDescriptor>, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable
{
}
系统默认已经实现了一个对 IServiceCollection
的实现,名为 ServiceCollection
。在 ASP.NET Core 中,内部会创建该对象的实例,我们也可以在其它项目中,自己来创建它,很简单,直接 new
出来就可以使用了。
IServiceCollection services = new ServiceCollection ();
在 ASP.NET Core MVC 中,你可能已经见过它了,不需要你来创建,系统已经帮你做了。
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IRepository, MemoryRepository>();
services.AddMvc();
}
2.2 服务 Service
在 DI 语境中,服务特指通过 DI 容器管理的对象实例。这个服务并不一定被称为 **Service,而是可以是任何由 DI 所管理的对象,只是在 DI 这个语境下,我们将其统称为服务。
服务是我们自己定义的,例如前面提到的 AccountController
和 UserService
等等。
我们通过 DI 来获得服务对象实例,管理服务对象的生命周期,对于存在复杂依赖关系的对象, DI 还负责管理这些实例之间的依赖关系。
服务必须首先注册在 DI 中才能使用,但是,注册前需要首先考虑和决定服务的生命周期。
2.3 服务的生命周期
服务对象实例有着不同类型的生命周期。有些对象的生命周期与应用程序相同,在应用程序启动时创建,在应用程序退出时才需要释放。例如我们的数据访问对象实例。有些对象仅仅在当前方法中使用,在方法调用结束之后就应该销毁。服务的生命周期管理用来管理这些需求。
DI 支持三种类型的生命周期:
Singleton,单例,在当前应用程序环境下只有一个实例。例如数据访问服务对象实例。
Scoped,限定范围,一旦退出此范围,在此范围内的服务对象都需要销毁。例如 Web 开发中的请求对象实例。
Transient,瞬态,一次性使用,每次从 DI 中获取,都返回一个新的实例。
Microsoft.Extensions.DependencyInjection.ServiceLifetime
public enum ServiceLifetime
{
Singleton,
Scoped,
Transient
}
服务的生命周期在注册服务的时候确定。在使用的时候,直接获取实例,不再指定服务的生命周期。微软提供了多种扩展方法来便于在注册服务时指定服务的生命周期。例如下面是通过泛型方式来指定单例模式的生命周期。
// 基于接口的注册
services.AddSingleton<IUserService, UserService>();
2.4 服务提供者 ServiceProvider
在需要使用服务对象实例的时候,不是从注册服务的集合中获取,而是需要通过服务提供者来获取,这个服务提供者显然需要来自注册服务的集合。服务提供者的接口定义为 IServiceProvider
,它是 .net 的基础定义之一,不是在该 DI 框架中定义的。
public interface IServiceProvider
{
object GetService(Type serviceType);
}
DI 中的 ServiceCollectionContainerBuilderExtensions
扩展了 IServiceCollection
,提供了获得这个服务提供者 ServiceProvider 的支持。
public static ServiceProvider BuildServiceProvider(this IServiceCollection services)
{
return BuildServiceProvider(services, ServiceProviderOptions.Default);
}
所以,我们通常使用该方法来获取并使用它。
// 创建注册服务的容器
IServiceCollection services = new ServiceCollection ();
// 注册服务,这里指定了单例
services.AddSingleton<IUserService, UserService>();
// 通过容器获得服务提供者
IServiceProvider provider = services.BuildServiceProvider ();
2.5 获取服务对象实例
通过服务提供者来手动获取服务对象实例。通过注册的服务类型,直接调用 GetService
方法即可。
例如,前面我们注册了服务类型 IUserService
的实现类型是 UserService
,那么,可以通过此类型来获取实际实现该接口的对象实例。
// 创建注册服务的容器
IServiceCollection services = new ServiceCollection ();
// 注册服务,这里指定了单例
services.AddSingleton<IUserService, UserService>();
// 通过容器获得服务提供者
IServiceProvider provider = services.BuildServiceProvider ();
// 通过接口获取服务对象实例
IUserService instance = provider.GetService<IUserService> ();
看起来,更加复杂了。在实际使用中,我们很少使用这样的方式来使用 DI,后面我们再深入讨论具体的使用过程。
2.6 构造函数注入
DI 支持构造函数注入。
定义 IUserRepository
接口,并实现 UserRepository
。
public interface IUserRepository {
}
public class UserReposotory: IUserRepository {
}
UserService
通过构造函数依赖 IUserRepository
。
public class UserService : IUserService {
private readonly IUserRepository _userRepository;
public UserService(IUserRepository userRepository) {
this._userRepository = userRepository;
}
}
在通过 DI 获得 UserService
实例的时候,DI 帮助实例化其所依赖的 UserRepository
实例。
在 UserService
的定义中,我们只需要通过构造函数声明所需的依赖即可。
IServiceCollection services = new ServiceCollection ();
// 基于接口的注册
services.AddSingleton<IUserService, UserService>();
services.AddSingleton<IUserRepository, UserReposotory>();
IServiceProvider provider = services.BuildServiceProvider ();
IUserService instance = provider.GetService<IUserService> ();
依赖注入在 dotnet core 中实现与使用:1 基本概念的更多相关文章
- 依赖注入在 dotnet core 中实现与使用:2 使用 Extensions DependencyInjection
既然是依赖注入容器,必然会涉及到服务的注册,获取服务实例,管理作用域,服务注入这四个方面. 服务注册涉及如何将我们的定义的服务注册到容器中.这通常是实际开发中使用容器的第一步,而容器本身通常是由框架来 ...
- 依赖注入在 dotnet core 中实现与使用:4. 集成 Autofac
本示例使用 .net core 5 rc-1 实现. 1. 添加 Nuget 包引用 使用 Autofac 当然要添加 Autofac 的 Nuget 包,主要涉及到两个: Autofac.Exten ...
- 依赖注入在 dotnet core 中实现与使用:3 使用 Lazy<T> 延迟实例化
有些对象我们并不想一开始就实例化,由于性能或者功能的考虑,希望等到使用的时候再实例化.考虑存在一个类 A, 它使用了依赖的类 B,在 A 中,只有某些不常用到的方法会涉及调用 B 中的方法,多数情况下 ...
- Dotnet Core中使用AutoMapper
官网:http://automapper.org/ 文档:https://automapper.readthedocs.io/en/latest/index.html GitHub:https://g ...
- 依赖注入[8]: .NET Core DI框架[服务消费]
包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IServiceProvider对象.当需要消费某个服务实例的时候,我们只需要指定服务类型调用IServicePr ...
- 依赖注入[7]: .NET Core DI框架[服务注册]
包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IServiceProvider对象.服务注册就是创建出现相应的ServiceDescriptor对象并将其添加到 ...
- 依赖注入[6]: .NET Core DI框架[编程体验]
毫不夸张地说,整个ASP.NET Core框架是建立在一个依赖注入框架之上的,它在应用启动时构建请求处理管道过程中,以及利用该管道处理每个请求过程中使用到的服务对象均来源于DI容器.该DI容器不仅为A ...
- .NET CORE学习笔记系列(2)——依赖注入[7]: .NET Core DI框架[服务注册]
原文https://www.cnblogs.com/artech/p/net-core-di-07.html 包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IS ...
- .NET CORE学习笔记系列(2)——依赖注入[6]: .NET Core DI框架[编程体验]
原文https://www.cnblogs.com/artech/p/net-core-di-06.html 毫不夸张地说,整个ASP.NET Core框架是建立在一个依赖注入框架之上的,它在应用启动 ...
随机推荐
- Facebook Libra - 第一笔交易
第一笔交易 假定 运行的是Linux或者macOS系统 网络连接正常 git已安装 macOS中安装了Homebrew Linux中安装了yum或者apt-get 提交一笔交易的步骤 克隆并构建Lib ...
- 剑指offer第二版-6.从尾到头打印链表
描述:输入一个链表的头节点,从尾到头打印每个节点的值. 思路:从尾到头打印,即为“先进后出”,则可以使用栈来处理:考虑递归的本质也是一个栈结构,可递归输出. 考点:对链表.栈.递归的理解. packa ...
- 开设“C程序答疑解惑”的初衷
博主经常在QQ群里.论坛里看到好多C语言初学者,甚至是有一定编程经验的人,咨询在编程中遇到的一些稀奇古怪的问题.博主对这些问题做过分析汇总,有些问题确实隐蔽的非常深,像break关键字用的不对啦,局部 ...
- python对Excel的读取
在python自动化中,经常会遇到对数据文件的操作,比如添加多名员工,但是直接将员工数据写在python文件中,不但工作量大,要是以后再次遇到类似批量数据操作还会写在python文件中吗? 应对这一问 ...
- C#拼装JSON数组简易方法
下面是我们想要拼接出来的JSON字符串,返回给前台 {"success":"true","msg":"","d ...
- 关于vue项目font字体图标库导入未显示的问题
运行项目时,弹出以下信息:
- koa2服务端使用jwt进行鉴权及路由权限分发
大体思路 后端书写REST api时,有一些api是非常敏感的,比如获取用户个人信息,查看所有用户列表,修改密码等.如果不对这些api进行保护,那么别人就可以很容易地获取并调用这些 api 进行操作. ...
- Socket 连接问题之大量 TIME_WAIT
简评:最近项目就出现了大量短连接导致建立新连接超时问题,最后是通过维护长连接解决的. 代理或者服务器设备都有端口限制,如果使用 TCP 连接,连接数量达到端口限制,在这种情况下,将不能创建新的连接. ...
- asn1学习笔记 约束
继续看asn1语法详解,今天主要看了约束部分,包含 1.单值约束,包含枚举类型 enumerated . 如: Two ::= INTEGER(2) Day ::= ENUMERATED { mond ...
- 聊聊HTML5中的Web Notification桌面通知
有的时候我们会在桌面右下角看到这样的提示: 这种桌面提示是HTML5新增的 Web Push Notifications 技术. Web Notifications 技术使页面可以发出通知,通知将被显 ...