目录
-
- [一、先搞懂:什么是 DI / IoC?(核心思想)](#一、先搞懂:什么是 DI / IoC?(核心思想))
-
- [1.1 官方定义(简化版)](#1.1 官方定义(简化版))
- [1.2 生活类比:点外卖 vs 自己做饭](#1.2 生活类比:点外卖 vs 自己做饭)
- 核心结论:
- [1.3 DI 核心好处(可维护架构必备)](#1.3 DI 核心好处(可维护架构必备))
- [二、流程图:ASP.NET Core DI 完整工作流程](#二、流程图:ASP.NET Core DI 完整工作流程)
- [三、代码实战:从 "烂代码" 到 "DI 最佳实践"](#三、代码实战:从 “烂代码” 到 “DI 最佳实践”)
-
- [3.1 反例:不使用 DI(高度耦合,禁止在项目中使用)](#3.1 反例:不使用 DI(高度耦合,禁止在项目中使用))
- [3.2 正例:使用 DI(ASP.NET Core 官方最佳实践)](#3.2 正例:使用 DI(ASP.NET Core 官方最佳实践))
-
- [步骤 1:定义接口(面向接口编程,DI 核心规范)](#步骤 1:定义接口(面向接口编程,DI 核心规范))
- [步骤 2:定义业务服务,通过构造函数注入依赖](#步骤 2:定义业务服务,通过构造函数注入依赖)
- [步骤 3:在 Program.cs 中注册服务(最重要一步)](#步骤 3:在 Program.cs 中注册服务(最重要一步))
- [步骤 4:控制器注入使用](#步骤 4:控制器注入使用)
- [四、ASP.NET Core DI 三种生命周期(必考 + 必掌握)](#四、ASP.NET Core DI 三种生命周期(必考 + 必掌握))
-
- [4.1 三种生命周期对比](#4.1 三种生命周期对比)
- [4.2 代码注册方式](#4.2 代码注册方式)
- [五、DI 注入的 3 种方式(官方推荐优先级)](#五、DI 注入的 3 种方式(官方推荐优先级))
-
- [1. 构造函数注入(官方唯一推荐 ✅)](#1. 构造函数注入(官方唯一推荐 ✅))
- [2. 属性注入(不推荐 ⚠️)](#2. 属性注入(不推荐 ⚠️))
- [3. 方法注入(极少用 🚫)](#3. 方法注入(极少用 🚫))
- [六、新手常踩的 7 个大坑(90% 开发者都中过)](#六、新手常踩的 7 个大坑(90% 开发者都中过))
-
- [坑 1:没有注册服务 → 直接报 InvalidOperationException](#坑 1:没有注册服务 → 直接报 InvalidOperationException)
- [坑 2:生命周期错误(最常见)](#坑 2:生命周期错误(最常见))
- [坑 3:手动 new 服务,还希望 DI 注入](#坑 3:手动 new 服务,还希望 DI 注入)
- [坑 4:构造函数注入为 null,直接空指针](#坑 4:构造函数注入为 null,直接空指针)
- [坑 5:循环依赖(A 注入 B,B 注入 A)](#坑 5:循环依赖(A 注入 B,B 注入 A))
- [坑 6:滥用 Singleton 导致线程安全问题](#坑 6:滥用 Singleton 导致线程安全问题)
- [坑 7:服务没有实现接口,直接注册类(不规范)](#坑 7:服务没有实现接口,直接注册类(不规范))
- [七、企业级 DI 编码规范(可维护性必备)](#七、企业级 DI 编码规范(可维护性必备))
- 八、总结
开启 ASP.NET 架构规范 + 可维护性 专栏第一期:依赖注入(DI) 。这是 ASP.NET Core 最核心、最基础、面试必问、项目必用的技术,也是写出松耦合、可测试、易维护 代码的基石。
很多新手觉得 DI 很抽象,其实它本质就一句话:自己不 new 对象,交给框架去管理 。我会用生活类比 + 完整代码 + 流程图 + 避坑指南,把 DI 讲得明明白白。

一、先搞懂:什么是 DI / IoC?(核心思想)
1.1 官方定义(简化版)
- IoC(Inversion of Control)控制反转:把对象的创建、生命周期管理,从 "自己手动控制" 反转给 "框架自动控制"。
- DI(Dependency Injection)依赖注入:IoC 的具体实现方式 ------ 框架自动把需要的对象 "注入" 到类里,不用自己实例化。
1.2 生活类比:点外卖 vs 自己做饭
- 不使用 DI(传统写法) :自己买菜、切菜、炒菜、洗碗 = 自己 new 对象、管理对象、销毁对象,累、耦合高、换做法麻烦。
- 使用 DI(最佳实践) :打开外卖软件,点单,饭直接送到家 = 告诉框架我需要什么对象,框架直接给你,不用管怎么创建。
核心结论:
DI 就是 "不用自己 new,框架自动给",实现代码解耦,提升可维护性。
1.3 DI 核心好处(可维护架构必备)
1.松耦合 :类与类之间不硬编码依赖,替换功能不用改大量代码
2.可测试性 :单元测试可以轻松替换 mock 对象
3.统一管理对象 :框架控制生命周期,避免内存泄漏
3.代码规范整洁:符合单一职责,可读性极高
二、流程图:ASP.NET Core DI 完整工作流程
我用最清晰的流程告诉你,DI 在框架里到底怎么跑的:
定义服务接口/类
Program.cs 注册服务
框架创建 ServiceProvider 容器
控制器/类声明构造函数依赖
框架自动从容器获取对象
自动注入到使用方
按照生命周期管理对象
一句话流程:
定义 → 注册 → 容器管理 → 声明依赖 → 自动注入 → 生命周期管理
三、代码实战:从 "烂代码" 到 "DI 最佳实践"
我们用一个最常见的业务场景:用户服务 + 日志记录。
3.1 反例:不使用 DI(高度耦合,禁止在项目中使用)
csharp
// 日志类
public class MyLogger
{
public void Log(string msg) => Console.WriteLine($"日志:{msg}");
}
// 用户服务
public class UserService
{
// 自己 new 对象!高度耦合!
private readonly MyLogger _logger = new MyLogger();
public void AddUser()
{
_logger.Log("添加用户成功");
Console.WriteLine("执行添加用户逻辑");
}
}
// 控制器
public class UserController : Controller
{
private readonly UserService _userService = new UserService();
public IActionResult Add()
{
_userService.AddUser();
return Ok();
}
}
问题:
- 高度耦合,MyLogger 一改,所有地方都要改
- 无法单元测试
- 手动 new 对象,无法控制生命周期
- 完全不符合架构规范
3.2 正例:使用 DI(ASP.NET Core 官方最佳实践)
步骤 1:定义接口(面向接口编程,DI 核心规范)
csharp
/// <summary>
/// 日志接口(面向接口,不面向实现)
/// </summary>
public interface ILogger
{
void Log(string message);
}
/// <summary>
/// 日志实现类
/// </summary>
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"[DI 注入日志] {message}");
}
}
步骤 2:定义业务服务,通过构造函数注入依赖
csharp
/// <summary>
/// 用户服务
/// </summary>
public interface IUserService
{
void AddUser();
}
public class UserService : IUserService
{
// 构造函数注入:DI 最标准、最常用的方式
private readonly ILogger _logger;
// 关键:自己不 new,框架自动传进来
public UserService(ILogger logger)
{
_logger = logger;
}
public void AddUser()
{
_logger.Log("用户新增成功");
Console.WriteLine("UserService:执行业务逻辑");
}
}
步骤 3:在 Program.cs 中注册服务(最重要一步)
csharp
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
// ------------ DI 服务注册 ------------
// 注册日志
builder.Services.AddScoped<ILogger, ConsoleLogger>();
// 注册用户服务
builder.Services.AddScoped<IUserService, UserService>();
var app = builder.Build();
步骤 4:控制器注入使用
csharp
public class UserController : Controller
{
private readonly IUserService _userService;
// 控制器也通过构造函数注入
public UserController(IUserService userService)
{
_userService = userService;
}
public IActionResult Index()
{
_userService.AddUser();
return View();
}
}
运行结果:
plaintext
[DI 注入日志] 用户新增成功
UserService:执行业务逻辑
✅** 完美实现:无耦合、可维护、可测试、架构规范**
四、ASP.NET Core DI 三种生命周期(必考 + 必掌握)
DI 最重要知识点之一:对象存活时间,注册错会导致 Bug、内存泄漏、线程安全问题。
4.1 三种生命周期对比
表格
| 类型 | 全称 | 中文 | 创建时机 | 核心特点 | 使用场景 |
|---|---|---|---|---|---|
| Singleton | 单例 | 全局唯一 | 程序启动一次 | 整个应用只有一个实例 | 配置、日志、工具类 |
| Scoped | 作用域 | 一次请求 | 每次请求创建 | 请求内唯一,请求结束销毁 | 业务服务、Repository、工作单元 |
| Transient | 瞬时 | 用完即毁 | 每次使用都创建 | 每次注入都新建实例 | 轻量级无状态服务 |
4.2 代码注册方式
csharp
// 单例
builder.Services.AddSingleton<ILogger, ConsoleLogger>();
// 作用域(最常用)
builder.Services.AddScoped<IUserService, UserService>();
// 瞬时
builder.Services.AddTransient<ITempService, TempService>();
企业规范:
默认优先使用 Scoped,Singleton 谨慎用(线程不安全),Transient 用于轻量级服务。
五、DI 注入的 3 种方式(官方推荐优先级)
ASP.NET Core 支持 3 种注入方式,****企业规范只推荐第一种。
1. 构造函数注入(官方唯一推荐 ✅)
csharp
public class UserService
{
private readonly ILogger _logger;
// 依赖必须,必须注入
public UserService(ILogger logger)
{
_logger = logger;
}
}
优点:依赖明确、强制检查、线程安全、测试方便
2. 属性注入(不推荐 ⚠️)
csharp
public ILogger Logger { get; set; }
缺点:容易为 null、依赖不明确、破坏封装
3. 方法注入(极少用 🚫)
仅用于特殊场景,普通业务绝对不要用
企业最佳实践:只使用构造函数注入,禁止属性注入!
六、新手常踩的 7 个大坑(90% 开发者都中过)
这一节价值千金,帮你避开项目中 99% 的 DI 异常。
坑 1:没有注册服务 → 直接报 InvalidOperationException
plaintext
InvalidOperationException: Unable to resolve service for type 'ILogger'
原因: 只写了接口,没在 Program.cs 注册
解决: 必须 AddScoped/AddSingleton 注册
坑 2:生命周期错误(最常见)
- 单例(Singleton)注入 作用域(Scoped)服务 → 程序直接崩溃
- 原因:长生命周期引用短生命周期,会导致对象已释放
规范 :生命周期只能同级或向下引用- Singleton 可引用:Singleton
- Scoped 可引用:Scoped、Singleton、Transient
- Transient 可引用:全部
坑 3:手动 new 服务,还希望 DI 注入
csharp
// 错误!自己 new 的对象,框架不会帮你注入依赖
var userService = new UserService();
**解决:**所有需要 DI 的对象,必须交给框架管理,禁止手动 new
坑 4:构造函数注入为 null,直接空指针
原因 :没注册 + 循环依赖
解决:检查注册、检查循环依赖
坑 5:循环依赖(A 注入 B,B 注入 A)
plaintext
A -> B -> A
解决:拆分服务,使用 Lazy 或中介者模式,解除循环
坑 6:滥用 Singleton 导致线程安全问题
原因 :单例全局唯一,多线程同时修改会脏数据
规范:单例只存无状态、只读对象
坑 7:服务没有实现接口,直接注册类(不规范)
不推荐:
csharp
builder.Services.AddScoped<ConsoleLogger>();
推荐:
csharp
builder.Services.AddScoped<ILogger, ConsoleLogger>();
原因:面向接口编程,方便替换、测试、解耦
七、企业级 DI 编码规范(可维护性必备)
我把多年大厂架构规范总结成 6 条:
1.**面向接口编程:**所有业务服务必须定义接口
2.只使用构造函数注入 ,禁止属性注入
3.默认使用 Scoped 生命周期
4.服务必须在 Program.cs 集中注册
5.禁止在服务中手动 new 其他依赖服务
6.单例服务必须保证线程安全
遵守这套规范,你的代码会:
新人一看就懂
- 维护成本极低
- 单元测试轻松写
- 架构完全符合 ASP.NET Core 设计哲学
八、总结
DI 依赖注入是 ASP.NET Core 架构的灵魂:
- IoC 是思想:把对象控制权交给框架
- DI 是实现:自动注入依赖,不用自己 new
- 三种生命周期:Singleton / Scoped / Transient
- 一种最佳注入方式:构造函数注入
- 核心目标:松耦合、可测试、可维护
学会 DI,你才算真正入门 ASP.NET Core 架构设计。
欢迎在评论区留言:你踩过 DI 最大的坑是什么?
我会一一回复
如果觉得这篇文章对你有帮助,欢迎点赞、收藏、关注,我们下期继续深入 ASP.NET 架构最佳实践!