4.4 依赖注入与中间件
依赖注入负责管理对象创建和依赖关系,中间件负责处理 HTTP 请求管线。它们是理解 ASP.NET Core 的两条主线。
学习目标
- 能注册和注入服务。
- 能区分 Singleton、Scoped、Transient 生命周期。
- 能解释请求如何经过中间件管线。
- 能写一个简单自定义中间件。
应用场景
- 在 API 中注入业务服务、数据库上下文、日志和配置。
- 为所有请求统一处理异常、认证、CORS、静态文件和路由。
- 给请求添加 TraceId、耗时日志或租户信息。
服务生命周期
| 生命周期 | 创建时机 | 适合对象 |
|---|---|---|
| Singleton | 应用启动后复用同一个实例 | 无状态缓存、配置读取器 |
| Scoped | 每个请求一个实例 | DbContext、业务服务 |
| Transient | 每次注入创建新实例 | 轻量、无状态工具类 |
案例:注册服务
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
builder.Services.AddScoped<TodoService>();
builder.Services.AddSingleton<SystemClock>();
var app = builder.Build();
示例:自定义中间件
app.Use(async (context, next) =>
{
var traceId = context.TraceIdentifier;
context.Response.Headers.TryAdd("X-Trace-Id", traceId);
await next(context);
});
中间件按注册顺序执行,await next(context) 之前是进入下一个中间件前的逻辑,之后是响应返回阶段的逻辑。
推荐管线顺序
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/error");
}
app.UseHttpsRedirection();
app.UseCors("Frontend");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
重点难点
- 不要把 Scoped 服务注入 Singleton 服务,否则会产生生命周期错误。
- 中间件顺序会改变行为,例如认证必须在授权之前。
- 不要在每个路由里手写重复横切逻辑,应放到中间件或过滤器。
DbContext不是线程安全对象,不要跨线程并发使用同一个实例。
常见误区
| 误区 | 推荐做法 |
|---|---|
| 所有服务都注册 Singleton | 按状态和依赖选择生命周期 |
| 忽略中间件顺序 | 明确请求进入和返回的顺序 |
业务服务直接 new 依赖对象 | 通过构造函数注入依赖,方便测试 |
练习
- 给每个响应添加
X-Trace-IdHeader。 - 把
TodoService注册为 Scoped,并在 Minimal API 中注入。 - 故意把 Scoped 注入 Singleton,观察启动或运行时错误。