ASP.NET Core 中的依赖注入:它为何存在?它实际解决了什么问题?
Source: Dev.to
请提供您希望翻译的完整文本内容,我将为您翻译成简体中文并保持原有的格式、Markdown 语法以及技术术语不变。
介绍
如果你使用过 ASP.NET Core,可能已经在不经意间使用了依赖注入(DI)。你在 Program.cs 中注册服务,将它们注入到控制器中,然后继续开发。但 DI 到底是为了解决什么问题而存在的? 它在解决什么问题?让我们用代码来正式回答这个问题。
1. 问题:紧耦合
一个非常常见的初学者模式看起来是这样的:
public class PostsController : ControllerBase
{
private readonly PostsService _service = new PostsService();
[HttpGet]
public IEnumerable Get()
{
return _service.GetPosts();
}
}
public class PostsService
{
public IEnumerable GetPosts()
{
return new[] { "Post A", "Post B" };
}
}
乍一看,这似乎没问题。代码可以运行,API 也能正常工作。
为什么这会成为问题
❌ 更改代价高
如果明天 PostsService 需要访问数据库、需要新的实现,或者需要一个用于测试的 mock,你必须 编辑控制器代码。这意味着:
- 要修改多个文件
- 可能引入 bug
- 违背 对扩展开放、对修改关闭 原则
❌ 测试痛苦
因为控制器自行创建依赖,你无法轻松替换它。你被迫:
- 连接真实数据库
- 调用真实 API
- 或者编写 hack 来绕过逻辑
这就是典型的紧耦合。
2. 依赖注入的核心思想(一句话)
不要在类内部创建依赖。应当请求它们。
不是说“我会决定使用哪个服务”,而是说“给我一个符合此契约的东西”。
3. 解决方案:抽象 + 构造函数注入
定义契约(接口)
public interface IPostsService
{
IEnumerable GetPosts();
}
实现契约
public class PostsService : IPostsService
{
public IEnumerable GetPosts()
{
return new[] { "Post A", "Post B" };
}
}
将其注入控制器
[ApiController]
[Route("api/posts")]
public class PostsController : ControllerBase
{
private readonly IPostsService _service;
public PostsController(IPostsService service)
{
_service = service;
}
[HttpGet]
public IEnumerable Get()
{
return _service.GetPosts();
}
}
有什么变化?
- 控制器不再知道使用的是哪种实现;它只依赖于抽象。
- 解耦从这里开始。
4. 让 ASP.NET Core 解析依赖
在 DI 容器中注册服务:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// One‑line decision
builder.Services.AddScoped();
var app = builder.Build();
app.MapControllers();
app.Run();
如果稍后添加另一个实现:
public class PostsServiceNew : IPostsService
{
public IEnumerable GetPosts()
{
return new[] { "Post X", "Post Y" };
}
}
你可以这样切换:
builder.Services.AddScoped();
无需更改控制器,也不会产生连锁影响。
5. DI 容器实际做了什么
在 ASP.NET Core 背后:
- 创建控制器。
- 发现它需要
IPostsService。 - 在容器中查找注册信息。
- 实例化正确的实现。
- 自动注入它。
你并没有失去控制——你 将责任转移给了框架。这就是 控制反转(IoC)。
6. 与 SOLID 的关系(简要)
- 抽象 – 面向接口编程,而不是面向实现。
- 解耦 – 控制器不需要知道或关心使用的是哪个服务。
- 依赖倒置原则 (DIP) – 高层模块依赖抽象,而不是具体类。
- 控制反转 (IoC) – 框架为你创建并连接对象。
DI 是这些原则的实际应用。
7. 为什么 DI 让测试变得容易
因为依赖是通过注入的,你可以传入一个假的实现:
public class FakePostsService : IPostsService
{
public IEnumerable GetPosts()
{
return new[] { "Fake Post" };
}
}
var controller = new PostsController(new FakePostsService());
var result = controller.Get();
没有数据库,没有基础设施——只有纯粹的逻辑。这正是 DI 真正发挥价值的地方。
8. 何时使用依赖注入 ✅
- 需要可替换的实现。
- 重视可测试性。
- 构建中到大型应用程序。
- 预期需求会变化。
9. 当 DI 显得大材小用时 ❌
- 非常小的脚本。
- 一次性控制台应用。
- 永远不会增长或进行测试的代码。
DI 是一种工具——而不是规则。