一、今日学习目标
- 理解 请求管道 执行顺序
- 掌握
Use / Run / Map区别 - 学会 自定义中间件
- 写两个企业常用中间件:
- 请求日志中间件
- 简单权限校验中间件
二、核心面试题 + 标准答案
1. 什么是 ASP.NET Core 管道?
答案: HTTP 请求从进入到响应离开,经过的一系列中间件组件 叫请求管道。请求依次经过中间件,响应原路返回,类似俄罗斯套娃。
2. Use / Run / Map 区别(高频必问)
- Use :注册中间件,可以交给下一个 (调用
await next()) - Run :终端中间件 ,执行后直接返回,不往后走
- Map :按路径分支,匹配某个路径才执行
3. 中间件执行顺序?
**答案:**配置顺序 = 执行顺序。先注册的先接收请求,后响应。
4. 中间件 vs 过滤器?
- 中间件:全局,针对所有请求,更底层
- 过滤器:针对 Controller/Action,属于 MVC 框架
- 性能:中间件更高
5. 中间件生命周期?
答案: 默认 Singleton ,所以不要在中间件里写 Scoped 服务 ,要用 IServiceProvider 手动创建作用域获取。
6. 自定义中间件两种方式?
- 匿名委托(
app.Use) - 独立类(
RequestMiddleware,标准企业写法)
三、核心语法速记
cs
// 分支
app.Map("/test", builder => builder.Run(...));
// 短路
app.Run(async context => await context.Response.WriteAsync("end"));
// 标准中间件
app.Use(async (context, next) =>
{
// 请求前
await next();
// 响应后
});
四、Day10 实战练习(必须完成)
练习 1:请求日志中间件
- 记录:请求路径、方法、耗时、状态码
- 输出到控制台 / Serilog
练习 2:简单权限校验中间件
- 判断请求头是否包含
Token: admin - 没有则直接返回 401
- 有则继续执行后续管道
四、练习代码
WeatherController.cs
cs
[ApiController]
[Route("api/[controller]")]
public class WeatherController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("正常接口数据");
}
[HttpPost("login")]
public IActionResult Login()
{
return Ok("登录成功,返回 Token:admin");
}
}
appsettings.json
cs
"Serilog": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{ "Name": "Console" },
{
"Name": "File",
"Args": {
//文件保存路径及文件名
"path": "Logs/log-.txt",
//滚动策略:以天为单位,每天生成一个新日志文件,如 log-20260410.log
"rollingInterval": "Day",
//输出模板:2026-04-10 12:54:27 [DBG] 调试日志:服务启动成功
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss}[{Level:u3}]{Message:lj}{NewLine}{Exception}"
}
}
]
}
Program.cs
cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
// ==========================
// 1. 日志中间件(Use)
// 记录:请求路径、方法、耗时、状态码
// 输出到控制台 / Serilog
// ==========================
app.Use(async (context, next) =>
{
// 请求前
var path = context.Request.Path;
var method = context.Request.Method;
var start = DateTime.UtcNow; //开始时间
Console.WriteLine($"[请求进入] {method} {path}");
await next();// 调用下一个中间件
// 响应后
var cost = (DateTime.UtcNow - start).TotalMilliseconds; //计算耗时
var code = context.Response.StatusCode; //响应状态
Console.WriteLine($"[请求结束] {method} {path} | 状态码:{code} | 耗时:{cost:F2}ms");
});
// ==========================
// 2. 权限校验中间件
// 判断请求头是否包含 Token: admin
// 没有则直接返回 401
// 有则继续执行后续管道
// ==========================
app.Use(async (context, next) =>
{
var path = context.Request.Path;
// 登录接口不校验
if (path.StartsWithSegments("/api/weather/login")) //StartsWithSegments:用于按路径段(segment)精确匹配 URL 前缀的方法,常用于中间件、路由或 API 版本控制等场景。
{
await next();
return;
}
var token = context.Request.Headers["Token"].FirstOrDefault();
if (token != "admin")
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("无权限,请在请求头带 Token:admin");
return; //短路不往后走
}
await next();
});
// ==========================
// 3. Map 分支演示
// ==========================
app.Map("/begin", x =>
{
x.Run(async c =>
{
await c.Response.WriteAsync("Hello from Map");
});
});
// ==========================
// 4. Run 终端中间件
// ==========================
app.Map("/end", x =>
{
x.Run(async c => await c.Response.WriteAsync("This is the end."));
});
app.MapControllers();
app.Run();