ASP.NET Core 中的依赖注入:它为何存在?它实际解决了什么问题?

发布: (2026年2月6日 GMT+8 23:24)
5 min read
原文: Dev.to

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 背后:

  1. 创建控制器。
  2. 发现它需要 IPostsService
  3. 在容器中查找注册信息。
  4. 实例化正确的实现。
  5. 自动注入它。

你并没有失去控制——你 将责任转移给了框架。这就是 控制反转(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 是一种工具——而不是规则。

Back to Blog

相关文章

阅读更多 »