总览
HTTP 请求进入管道后,依次经过一串中间件,每个中间件在 next(context) 调用前后都有执行机会。顺序错了,行为就错。这一篇讲清楚内置了哪些中间件、推荐的排列顺序、以及每一步的依赖关系。
内置中间件
| 中间件 | 注册方式 | 作用 |
|---|---|---|
| 异常处理 | host.use<ExceptionHandlerMiddleware>() | try-catch 包住下游,未处理异常统一写 500 |
| 健康检查 | host.useHealthChecks("/health") | 暴露 /health 端点供探活,命中即短路 |
| 静态文件 | host.useFileServer() | 提供静态资源访问,命中文件即短路 |
| 路由匹配 | host.useRouting() | 匹配 URL 到 Endpoint,挂到 HttpContext 上 |
| 终结点执行 | host.useEndpoints() / 自动 | 取出 Endpoint.delegate 执行;app.run() 自动补 |
| CORS | host.useCors("策略名") | 处理跨域预检和响应头,读 Endpoint.metadata 中的 CORS 策略 |
| 身份认证 | host.useAuthentication() | 验 JWT / Cookie,解析出 User 挂到 context.user |
| 身份授权 | host.useAuthorization() | 读 Endpoint.metadata 中的权限要求,对 User 验策略 |
| MVC | host.mapControllers() | 扫描 [Route] 控制器,注册为终结点 |
| OpenAPI | host.mapOpenApi() / host.useOpenApiUI() | 生成 /openapi/v1.json 和 API 文档页 |
推荐顺序
ExceptionHandlerMiddleware ← 最外层 try-catch,兜底
useHealthChecks ← 健康检查,提前短路
useFileServer ← 静态文件,命中即短路
useRouting ← 匹配 URL,挂 Endpoint
useCors ← 跨域处理
useAuthentication ← 验 token,挂 User
useAuthorization ← 验权限,放行或拦截
(自定义中间件) ← 日志、审计、限流等
(终结点执行) ← Endpoint.delegate.invoke每一步的依赖关系
ExceptionHandlerMiddleware — 最外层兜底
用 try-catch 包住整个下游管道。任何中间件或终结点抛出的未处理异常,穿透回来被 catch 住,统一写成 500 响应。必须在最外层,否则它下游的中间件抛异常就没人接了。
useHealthChecks — 路由之前短路
健康检查端点(/health)不需要走路由匹配,也不需要鉴权。放在路由之前:命中就直接返回 200,next() 不调,后面的路由、认证、授权全部跳过。
useFileServer — 路由之前短路
静态文件(CSS、JS、图片)同样不需要路由。放在路由之前:命中文件直接返回内容,不命中则 next() 继续。
useRouting — 匹配 URL,挂 Endpoint
路由必须在认证和授权之前,因为 useAuthorization 需要读 context.getEndpoint()?.metadata 才知道当前请求需要什么权限。没有路由匹配,Endpoint 就不存在,metadata 也无从读起。
这也是为什么路由中间件和执行中间件拆成两个——留下空档给认证授权。
useCors — 处理跨域
CORS 中间件需要读 Endpoint.metadata 中的 CORS 策略名(由 .requireCors("策略名") 写入),所以放在路由之后。
为什么在授权之前?CORS 预检请求(OPTIONS)不携带 cookie 和 Authorization 头,此时 context.user 必然是空的。如果授权在 CORS 前面,预检请求会被 401 拦掉——但预检的目的就是要知道"能不能跨域访问",应该先让 CORS 处理完了再说。
useAuthentication — 验 token,挂 User
从请求头解析 JWT / Cookie → 构造 ClaimsPrincipal → 写入 context.user。它不拒绝请求——即使没有 token,也只是 context.user 保持匿名状态,把"让不让过"的决定留给下一步。
useAuthorization — 验权限,放行或拦截
读 context.getEndpoint()?.metadata,找到 IAuthorizeData,用 context.user 去对策略。不满足就短路返回 401 或 403。必须在认证之后(需要 user)、路由之后(需要 endpoint)。
自定义中间件 — 日志、审计、限流
业务特定的横切关注点插在授权之后、执行之前。此时 Endpoint 已确定、User 已解析、权限已通过——信息最完整,做日志或审计最合适。
终结点执行
EndpointMiddleware 读出 endpoint.delegate,执行真正的业务逻辑。这是管道的终点。
一句话总结
路由在最前——后面的中间件要读 Endpoint.metadata
认证在授权前——授权判断依赖 User
CORS 在授权前——预检请求不能先被 401 拦掉
异常处理在最外——兜住所有下游抛出的异常
健康检查和静态文件在路由前——提前短路,节省开销