1. Ioc 与 DI

Ioc 和DI 这两个词大家都应该比较熟悉,这两者已经在各种开发语言各种框架中普遍使用,成为框架中的一种基本设施了。

Ioc 是控制反转, Inversion of Control 的缩写,DI 是依赖注入,Inject Dependency 的缩写。

所谓控制反转,反转的是类与类之间依赖的创建。类型A依赖于类型B时,不依赖于具体的类型,而是依赖于抽象,不在类A中直接 new 类B的对象,而是通过外部传入依赖的类型对象,以实现类与类之间的解耦。所谓依赖注入,就是由一个外部容器对各种依赖类型对象进行管理,包括对象的创建、销毁、状态保持等,并在某一个类型需要使用其他类型对象的时候,由容器进行传入。

下图是一张网图,是关于Ioc解耦比较经典的图示过程了。至于依赖解耦的好处,就不在这里细讲了,如果有对依赖注入基本概念不理解的,可以稍微搜索一下相关的文章,也可以参考 ASP.NET Core 依赖注入 | Microsoft Learn 官方文档中的讲解。

Ioc是一种设计思想,而DI是这种思想的具体实现。依赖注入是一种设计模式,是对面向对象编程五大基本原则中的依赖倒置原则的实践,其中很重要的一个点就是 Ioc 容器的实现。

2. .NET Core 依赖注入的基本用法

在 .NET Core 平台下,有一套自带的轻量级Ioc框架,如果是ASP.NET Core项目,更是在使用主机的时候自动集成了进去,我们在startup类中的ConfigureServices方法中的代码就是往容器中配置依赖注入关系,如果是控制台项目的话,还需要自己去集成。除此之外,.NET 平台下还有一些第三方依赖注入框架,如 Autofac、Unity、Castle Windsor等。

这里先不讨论第三方框架的内容,先简单介绍一下.Net Core平台自带的Ioc框架的使用。

2.1 服务

依赖项注入术语中,服务通常是指向其他对象提供服务的对象,既可以作为其他类的依赖项,也可能依赖于其他服务。服务是Ioc容器管理的对象。

2.2 服务生命周期

使用了依赖注入框架之后,所有我们注入到容器中的类型的创建、销毁工作都由容器来完成,那么容器什么时候创建一个类型实例,什么时候销毁一个类型实例呢?这就涉及到注入服务的生命周期了。根据我们的需要,我们可以向容器中注册服务的时候,对服务的生命周期进行设置。服务的生命周期有以下三种:

(1) 单例 Singleton

注册成单例模式的服务,整个应用程序生命周期以内只创建一个实例。在应用内第一个使用到该服务时创建,在应用程序停止时销毁。

在某些情况下,对于某些特殊的类,我们需要注册成单例模式,这可以减少实例初始化的消耗,还能实现跨 Service 事务的功能。

(2)范围(或者作用域) Scoped

在同一个范围内只初始化一个实例 。在 web 应用中,可以理解为每一个 request 级别只创建一个实例,同一个 http request 会在一个 scope 内。

(3)多例 Tranisent

每一次使用到服务时都会创建一个新的实例,每一次对该依赖的获取都是一个新实例。

2.3 服务注册

在ASP.NET Core这样的web应用框架中,在使用主机的时候就自动集成了依赖注入框架,之后我们可以通过 IServiceCollection 对象来注册依赖注入关系。前面入口文件一篇讲过,.NET 6 之前可以在 Startup 类中的 ConfigureServices 方法中进行注册,该方法传入IServiceCollection参数,.NET 6 之后,可以通过 WebApplicationBuilder 对象的 Services属性进行注册。

服务注册常用的方法如下:

  • Add 方法

    通过参数 ServiceDescriptor 将服务类型、实现类型、生命周期等信息传入进去,是服务注册最基本的方法。其中 ServiceDescriptor 参数又有多种变形。

    // 最基本的服务注册方法,除此之外还有其他各种变形
    builder.Services.Add(new ServiceDescriptor(typeof(IRabbit), typeof(Rabbit), ServiceLifetime.Transient));
    builder.Services.Add(ServiceDescriptor.Scoped<IRabbit, Rabbit>());
    builder.Services.Add(ServiceDescriptor.Singleton(typeof(IRabbit), (services) => new Rabbit()));
  • Add{lifetime}扩展方法

    基于 Add 方法的扩展方法,包括以下几种,每种都有多个重载:

    // 基于生命周期的扩展方法,以下为实例,正式开发中不可能将一个类型注册为多个生命周期,会抛出异常
    builder.Services.AddTransient<IRabbit, Rabbit>();
    builder.Services.AddTransient(typeof(IRabbit), typeof(Rabbit));
    builder.Services.AddScoped<IRabbit, Rabbit>();
    builder.Services.AddSingleton<IRabbit, Rabbit>();
  • TryAdd{lifetime}扩展方法

    对于 Add{lifetime} 方法的扩展,位于命名空间 Microsoft.Extensions.DependencyInjection.Extensions 下。

    与 Add{lifetime} 方法相比,差别在于当使用 Add{lifetime} 方法将同样的服务注册了多次时,在使用 IEnumerable<{Service}> 解析服务时,就会产生多个实例的副本,这可能会导致一些意料之外的 bug,特别是单例生命周期的服务。

    // 同一个服务同一个实现注入多次
    builder.Services.AddSingleton<IRabbit, Rabbit>();
    builder.Services.AddSingleton<IRabbit, Rabbit>(); [ApiController]
    [Route("[controller]")]
    public class InjectTestController : ControllerBase
    {
    private readonly IEnumerable<IRabbit> _rabbits;
    public InjectTestController(IEnumerable<IRabbit> rabbits)
    {
    _rabbits = rabbits; [HttpGet("")]
    public Task InjectTest()
    {
    // 2个IRabbit实例
    Console.WriteLine(_rabbits.Count());
    var rabbit1 = _rabbits.First();
    var rabbit2 = _rabbits.ElementAt(1);
    // 都是 Rabbit 类型
    Console.WriteLine(rabbit1 is Rabbit);
    Console.WriteLine(rabbit2 is Rabbit);
    // 两个实例不是同一个
    Console.WriteLine(rabbit1 == rabbit2);
    return Task.CompletedTask;
    }
    }

    调用接口后,打印输出结果如下:

    而使用 TryAdd{lifetime} 方法,当DI容器中已存在指定类型的服务时,则不进行任何操作;反之,则将该服务注入到DI容器中。

    将服务注册改成以下代码:

    builder.Services.AddTransient<IRabbit, Rabbit>();
    // 由于上面已经注册了服务类型 IRabbit,所以下面的代码不不会执行任何操作(与生命周期无关)
    builder.Services.TryAddTransient<IRabbit, Rabbit>();
    builder.Services.TryAddTransient<IRabbit, Rabbit1>();

    在上面的控制器中新增以下方法:

    [HttpGet(nameof(InjectTest1))]
    public Task InjectTest1()
    {
    // 只有1个IRabbit实例
    Console.WriteLine(_rabbits.Count());
    var rabbit1 = _rabbits.First();
    // 都是 Rabbit 类型
    Console.WriteLine(rabbit1 is Rabbit);
    return Task.CompletedTask;
    }

    调用接口后,打印输出结果如下:

  • TryAddEnumerable 方法

    与 TryAdd 对应,区别在于TryAdd仅根据服务类型来判断是否要进行注册,而TryAddEnumerable则是根据服务类型和实现类型一同进行判断是否要进行注册,常常用于注册同一服务类型的多个不同实现。

    将服务注册改成以下代码:

    builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IRabbit, Rabbit>());
    builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IRabbit, Rabbit1>());
    // 未进行任何操作,因为 IRabbit 服务的 Rabbit实现在上面已经注册了
    builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IRabbit, Rabbit>());

    在上面的控制器新增一个方法:

    [HttpGet(nameof(InjectTest2))]
    public Task InjectTest2()
    {
    // 2个IRabbit实例
    Console.WriteLine(_rabbits.Count());
    var rabbit1 = _rabbits.First();
    var rabbit2 = _rabbits.ElementAt(1);
    // 第一个是 Rabbit 类型,第二个是 Rabbit1类型
    Console.WriteLine(rabbit1 is Rabbit);
    Console.WriteLine(rabbit2 is Rabbit1);
    return Task.CompletedTask;
    }

    调用接口后,控制台打印如下:

  • Repalce 与 Remove 方法

    当我们想要从Ioc容器中替换或是移除某些已经注册的服务时,可以使用Replace和Remove。

    // 将容器中注册的IRabbit实现替换为 Rabbit1
    builder.Services.Replace(ServiceDescriptor.Transient<IRabbit, Rabbit1>());
    // 从容器中 IRabbit 注册的实现 Rabbit1
    builder.Services.Remove(ServiceDescriptor.Transient<IRabbit, Rabbit1>());
    // 移除 IRabbit服务的所有注册
    builder.Services.RemoveAll<IRabbit>();
    // 清空容器中的所有服务注册
    builder.Services.Clear();

以上是 .NET Core 框架自带的 Ioc 容器的一些基本概念和依赖关系注入的介绍,下一章是注入到容器中的服务使用部分。

参考文章:

ASP.NET Core 依赖注入 | Microsoft Learn

理解ASP.NET Core - 依赖注入(Dependency Injection)

ASP.NET Core 系列:

目录:ASP.NET Core 系列总结

上一篇:ASP.NET Core - 自定义中间件

ASP.NET Core - 依赖注入(一)的更多相关文章

  1. # ASP.NET Core依赖注入解读&使用Autofac替代实现

    标签: 依赖注入 Autofac ASPNETCore ASP.NET Core依赖注入解读&使用Autofac替代实现 1. 前言 2. ASP.NET Core 中的DI方式 3. Aut ...

  2. 实现BUG自动检测 - ASP.NET Core依赖注入

    我个人比较懒,能自动做的事绝不手动做,最近在用ASP.NET Core写一个项目,过程中会积累一些方便的工具类或框架,分享出来欢迎大家点评. 如果以后有时间的话,我打算写一个系列的[实现BUG自动检测 ...

  3. [译]ASP.NET Core依赖注入深入讨论

    原文链接:ASP.NET Core Dependency Injection Deep Dive - Joonas W's blog 这篇文章我们来深入探讨ASP.NET Core.MVC Core中 ...

  4. asp.net core 依赖注入几种常见情况

    先读一篇注入入门 全面理解 ASP.NET Core 依赖注入, 学习一下基本使用 然后学习一招, 不使用接口规范, 直接写功能类, 一般情况下可以用来做单例. 参考https://www.cnblo ...

  5. ASP.NET Core依赖注入——依赖注入最佳实践

    在这篇文章中,我们将深入研究.NET Core和ASP.NET Core MVC中的依赖注入,将介绍几乎所有可能的选项,依赖注入是ASP.Net Core的核心,我将分享在ASP.Net Core应用 ...

  6. 自动化CodeReview - ASP.NET Core依赖注入

    自动化CodeReview系列目录 自动化CodeReview - ASP.NET Core依赖注入 自动化CodeReview - ASP.NET Core请求参数验证 我个人比较懒,能自动做的事绝 ...

  7. ASP.NET Core 依赖注入最佳实践——提示与技巧

    在这篇文章,我将分享一些在ASP.NET Core程序中使用依赖注入的个人经验和建议.这些原则背后的动机如下: 高效地设计服务和它们的依赖. 预防多线程问题. 预防内存泄漏. 预防潜在的BUG. 这篇 ...

  8. ASP.NET Core依赖注入最佳实践,提示&技巧

    分享翻译一篇Abp框架作者(Halil İbrahim Kalkan)关于ASP.NET Core依赖注入的博文. 在本文中,我将分享我在ASP.NET Core应用程序中使用依赖注入的经验和建议. ...

  9. ASP.NET Core依赖注入解读&使用Autofac替代实现【转载】

    ASP.NET Core依赖注入解读&使用Autofac替代实现 1. 前言 2. ASP.NET Core 中的DI方式 3. Autofac实现和自定义实现扩展方法 3.1 安装Autof ...

  10. ASP.NET Core 依赖注入基本用法

    ASP.NET Core 依赖注入 ASP.NET Core从框架层对依赖注入提供支持.也就是说,如果你不了解依赖注入,将很难适应 ASP.NET Core的开发模式.本文将介绍依赖注入的基本概念,并 ...

随机推荐

  1. AI赋能音乐创作,人人都是音视频创作者

    华为HMS Core音频编辑服务(Audio Editor Kit)依托自身AI技术的研发优势,上线全新的歌声合成音色及伴奏,给音视频创作者提供更多的创作可能.在短视频场景中,用户自定义歌词的歌声结合 ...

  2. JavaScript中的Error错误对象与自定义错误类型

    Error Error是JavaScript语言中的一个标准的内置对象,专门用于处理JS开发中的运行时错误. 当我们的JS代码在运行过程中发生错误的话,就会抛出Error对象,整个程序将会中断在错误发 ...

  3. vscode+springboot+gradle

    vscode+springboot+gradle 项目搭建 demo 目标:项目中抛弃所有xml格式文件 啰嗦: 一直在用maven作为项目的依赖包管理,最近看到基于Java17 的 Spring f ...

  4. 《不一般的 DFT》阅读随笔

    感觉上前置知识是毛啸 16 年的论文? 我手头也有,到时候发现有 at 到的地方就插一嘴说一句 srds 先这篇是因为有纸质版的这篇 感觉上大篇幅在讲复杂度模数大小相关的做法. 1 引言 我这写个啥? ...

  5. docker入门(利用docker部署web应用)

    第一章 什么是docker1.1 docker的发展史2010年几个年轻人成立了一个做PAAS平台的公司dotCloud.起初公司发展的不错,不但拿到过一些融资,还获得了美国著名孵化器YCombina ...

  6. 使用 Visual Studio 2022 调试Dapr 应用程序

    使用Dapr 编写的是一个多进程的程序,使用Visual Studio 调试起来可能会比较困难,因为 Visual Studio 默认只会把你当前设置的启动项目的启动调试. 好在有Visual Stu ...

  7. 如何通过 C#/VB.NET 将 PDF 转为 Word

    众所周知,PDF 文档支持特长文件,集成度和安全可靠性都较高,可有效防止他人对 PDF 内容进行更改,所以在工作中深受大家喜爱.但是在工作中,我们不可避免的会对 PDF 文档进行修改或再编辑,这时我们 ...

  8. ModuleNotFoundError: No module named 'MySQLdb'

    执行命令 python manage.py makemigrations时抛出以下错误 Traceback (most recent call last): File "D:\Program ...

  9. cs231n__3. LostFunction

    CS231n 3.1 Lost Function 我们上次提到,要如何选择最优的W呢? 这就是要选择几种损失函数了. 我们要找到一种可行的方法来选择最优的W 先看简单的3个样本的例子 正式定义损失函数 ...

  10. YonBuilder移动开发平台功能大盘点

    YonBuilder是面向企业组织和个人开发者的低代码开发平台,实现无代码.低代码.专业代码开发三种模式.提供元数据驱动和画布构建两种开发方式,通过点击拖拽+自动化代码生成和移动多端编译的技术,与开放 ...