跳到主要内容

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-Id Header。
  • TodoService 注册为 Scoped,并在 Minimal API 中注入。
  • 故意把 Scoped 注入 Singleton,观察启动或运行时错误。