ASP.NET Core 依赖注入
依赖注入(Dependency Injection,简称 DI)就是:一个类需要用到别的东西时,不自己“new”,而是让外部把它“送进来”。
1. 没有 DI 的问题
假设 OrderService 需要发通知,它自己创建了一个 EmailSender:
public class OrderService
{
private readonly EmailSender _sender = new EmailSender();
public void CreateOrder()
{
// 业务逻辑...
_sender.Send("订单已创建");
}
}
看起来能用,但问题很快出现:
OrderService被“焊死”在EmailSender上,想换成短信就得改代码。- 写单元测试时,一调用就真的发邮件,无法替换成假的实现。
EmailSender如果自己也依赖别的东西,创建过程会越来越复杂。
根源在于:OrderService 自己决定了用哪个具体实现。
2. 用 DI 改造
第一步,把依赖抽象成接口:
public interface IMessageSender
{
void Send(string message);
}
public class EmailSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine($"发送邮件:{message}");
}
}
第二步,OrderService 不再自己 new,而是通过构造函数“接收”依赖:
public class OrderService
{
private readonly IMessageSender _sender;
public OrderService(IMessageSender sender)
{
_sender = sender;
}
public void CreateOrder()
{
// 业务逻辑...
_sender.Send("订单已创建");
}
}
现在 OrderService 只关心“有一个能发消息的东西”,而不关心它到底是邮件还是短信。具体用哪个,交给外部决定。
3. 容器负责存储依赖
在 ASP.NET Core 里,负责创建对象、并把依赖送进去的角色叫 容器(IoC Container),也叫 服务提供者(Service Provider)。
你只需要在 Program.cs 里告诉容器:“遇到 IMessageSender,就给我一个 EmailSender”。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IMessageSender, EmailSender>();
builder.Services.AddScoped<OrderService>();
var app = builder.Build();
之后当某个地方需要 OrderService 时,容器会:
- 发现
OrderService的构造函数需要IMessageSender; - 找到注册的
EmailSender并创建它; - 把
EmailSender送进OrderService; - 把创建好的
OrderService交给你。
这一整套“自动帮你组装对象”的过程,就是依赖注入的核心价值。
4. DI 解决的问题
初学阶段,重点记住下面这些作用,尤其是**“可替换”**这一条。
4.1 依赖可以随时替换(最重要)
因为 OrderService 依赖的是接口 IMessageSender,你可以在不改动 OrderService 一行代码的情况下换掉具体实现:
// 默认:所有业务都用邮件通知
builder.Services.AddScoped<IMessageSender, EmailSender>();
// 换成短信,只改这一行注册即可
builder.Services.AddScoped<IMessageSender, SmsSender>();
4.2 特殊需求替换通用逻辑
真实项目里经常出现这种情况:大部分场景用通用实现,某个特殊需求要用不一样的逻辑。
比如平时用真实的邮件发送,但某个大客户要求“通知必须走他们自建的内部网关”,这时只需要写一个新的实现类,然后替换注册:
// 通用实现
public class EmailSender : IMessageSender { /* ... */ }
// 针对特定客户的特殊实现
public class CustomerGatewaySender : IMessageSender
{
public void Send(string message)
{
// 调用该客户专用网关
}
}
// 通用环境
builder.Services.AddScoped<IMessageSender, EmailSender>();
// 该客户的部署环境:只替换注册,业务代码完全不动
builder.Services.AddScoped<IMessageSender, CustomerGatewaySender>();
OrderService 完全感知不到这次替换——它只知道“我调用了 Send”。这就是 DI 最有价值的地方:用“替换实现”代替“修改逻辑”。
4.3 方便写测试
测试时可以送进一个假的实现,不真正发消息:
public class FakeSender : IMessageSender
{
public string? LastMessage { get; private set; }
public void Send(string message) => LastMessage = message;
}
var fake = new FakeSender();
var service = new OrderService(fake);
service.CreateOrder();
// 断言业务确实触发了通知
Assert.Equal("订单已创建", fake.LastMessage);