

统一声明:
1.本站联系方式QQ:709466365 TG:@UXWNET 官方TG频道:@UXW_NET 如果有其他人通过本站链接联系您导致被骗,本站一律不负责! 2.需要付费搭建请联系站长QQ:709466365 TG:@UXWNET 3.免实名域名注册购买- 游侠云域名 4.免实名国外服务器购买- 游侠网云服务
这篇文章正是为新手准备的“落地指南”——我们不会讲复杂的原理,而是把实现过程拆成“创建中间件类→编写处理逻辑→注册到项目”的清晰步骤,每一步都附具体代码和避坑提示。比如如何用RequestDelegate
处理请求上下文、如何通过Use
或Map
配置中间件顺序,甚至连“如何测试中间件是否生效”都给了具体方法。哪怕你是第一次碰中间件,跟着流程走也能快速做出一个能实际运行的版本,轻松解决“想定制功能却不知从哪下手”的困惑。读完这篇,你会发现:自定义中间件没那么难,反而能让你的API更灵活、更好维护。
你有没有过这种情况?做ASP.NET Core Web API的时候,想给所有请求加个统一的日志记录,或者校验请求里的Token,但是要么在每个控制器里重复写代码,要么找了一堆教程还是没搞懂怎么弄?其实这些需求正好是自定义中间件的拿手好戏——它能把你要的逻辑“插”到所有请求的处理流程里,不用改现有代码。今天我就把去年帮朋友做电商API中间件的经验拆成明明白白的步骤,你跟着做,半小时就能搞定自己的第一个中间件。
为什么你需要学自定义中间件?先搞懂它的作用
先不说代码,我先给你讲个真实例子:去年朋友做了个电商API项目,刚开始他们在每个控制器的Action里都写日志——比如每个接口第一行都是_logger.LogInformation("收到请求:{path}", Request.Path)
,结果二十多个控制器,改起来麻烦得要命。后来我帮他写了个日志中间件,把这些重复代码全抽到中间件里,之后不管加多少新接口,都不用再写一遍日志逻辑了。你看,这就是中间件最实在的价值:复用、解耦,把通用逻辑从控制器里“抽”出来,让代码更干净。
其实中间件是ASP.NET Core的“核心骨架”——你平时用的Static Files
(处理静态文件)、Authentication
(身份验证)、Authorization
(授权)都是中间件,它们像一串“管道”,把HTTP请求从客户端接过来,一步步处理后再返回响应。自定义中间件就是你自己写一个“管道节点”,插入到这个流程里,比如:想记录所有请求的耗时?想拦截没有Token的请求?想统一处理跨域问题?这些都能靠它解决。
微软官方文档(https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0 rel=”nofollow”)里说得很清楚:“中间件是组装到应用管道中以处理请求和响应的软件组件”。每个中间件都有两个选择:要么把请求传给下一个中间件(调用_next(context)
),要么直接返回响应(比如权限校验不通过,直接返回403)。举个更直观的例子:你用UseExceptionHandler
处理异常,本质就是一个中间件——它能“接住”后面中间件抛出的错误,返回友好的提示页。自定义中间件和它的逻辑一模一样,只是换成了你自己的需求而已。
我再问你个问题:你有没有在控制器里写过权限校验的代码?比如每个Action开头都检查HttpContext.User.Identity.IsAuthenticated
?如果有,那你肯定懂这种“重复劳动”的痛苦——而用中间件的话,只需要写一次逻辑,就能覆盖所有接口。这就是为什么我说,学自定义中间件不是“额外技能”,是帮你节省时间的“效率工具”。
三步搞定自定义中间件:从代码到运行的完整流程
别慌,我把整个过程拆成了“创建类→写逻辑→注册”三步,每一步都有具体代码和我踩过的坑,你跟着做就行。
第一步:创建中间件类——遵循“约定大于配置”的规则
中间件类的命名很简单,一般叫“XXXMiddleware”(比如日志中间件就叫CustomLoggerMiddleware
)。然后有两个“必须满足”的条件:
RequestDelegate
类型的参数(通常命名为_next
)——这是下一个中间件的“入口”; InvokeAsync
方法(异步方法)——这是中间件执行逻辑的地方。 我直接给你写个日志中间件的例子,你照着改就行:
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
public class CustomLoggerMiddleware
{
// 下一个中间件的引用
private readonly RequestDelegate _next;
// 注入的日志服务(需要用ILogger)
private readonly ILogger _logger;
// 构造函数:注入依赖
public CustomLoggerMiddleware(RequestDelegate next, ILogger logger)
{
_next = next;
_logger = logger;
}
// 核心方法:处理请求和响应
public async Task InvokeAsync(HttpContext context)
{
// ① 请求到达时的逻辑(调用下一个中间件之前)
_logger.LogInformation($"请求来了→路径:{context.Request.Path},方法:{context.Request.Method},时间:{DateTime.Now:yyyy-MM-dd HH:mm:ss}");
// ② 调用下一个中间件(必须写!否则请求会“卡住”)
await _next(context);
// ③ 响应返回时的逻辑(调用下一个中间件之后)
_logger.LogInformation($"响应走了→状态码:{context.Response.StatusCode},耗时:{DateTime.Now
context.RequestStarted:hh\:mm\:ss}");
}
}
这里有个我踩过的坑:InvokeAsync
的名字绝对不能错!我第一次写的时候写成了Invoke
(同步方法),结果运行的时候直接报错——后来查微软文档才知道,.NET Core只认InvokeAsync
这个异步方法名。
如果你需要注入其他服务(比如数据库上下文、配置项),直接加到构造函数里就行——和控制器的依赖注入逻辑一样。
第二步:写处理逻辑——别忘调用_next(context)
,否则请求会“死循环”
中间件的逻辑分两部分:请求到达时(_next
之前)和响应返回时(_next
之后)。我再给你举两个实用例子,帮你理解怎么用:
例子1:统计请求耗时(用_next
之后的逻辑)
如果你想知道每个接口的处理时间,只需要在_next
前后加个计时器:
public async Task InvokeAsync(HttpContext context)
{
var stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
await _next(context); // 执行后续中间件
stopwatch.Stop();
_logger.LogInformation($"请求 {context.Request.Path} 耗时:{stopwatch.ElapsedMilliseconds} 毫秒");
}
我之前给一个社区API做过这个功能——他们之前不知道哪些接口慢,用了这个中间件后,很快发现一个关联查询接口耗时2秒,优化后降到了200毫秒。
例子2:权限校验(用_next
之前的逻辑)
如果想拦截没有Token的请求,直接返回401,逻辑是这样的:
public async Task InvokeAsync(HttpContext context)
{
// 检查请求头里有没有Token
if (!context.Request.Headers.ContainsKey("Authorization"))
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
await context.Response.WriteAsync("缺少Token");
return; // 不调用_next,直接返回
}
// 有Token的话,继续执行下一个中间件
await _next(context);
}
这里要注意:如果不调用_next(context)
,请求就不会走到后面的控制器——比如上面的例子,没有Token的请求会直接被拦截,不会执行任何业务逻辑。这比在控制器里做校验高效多了,因为“早拦截早处理”。
再强调一遍:_next(context)
是必须的!我之前写中间件的时候忘了这行,结果Postman发请求一直转圈,直到超时——后来调试才发现,请求卡在了我的中间件里,没传到后面的MVC中间件。
第三步:注册中间件——顺序错了,逻辑就乱了
写完代码,最后一步是把中间件“插”到请求管道里。注册的位置在Program.cs
(.NET 6+)或者Startup.cs
的Configure
方法里,顺序非常重要!
我先给你写个Program.cs
的例子:
var builder = WebApplication.CreateBuilder(args);
// 添加服务(比如控制器、日志)
builder.Services.AddControllers();
builder.Services.AddLogging();
var app = builder.Build();
// 👇 注册中间件的顺序决定了执行顺序 👇
app.UseCustomLogger(); // ① 自己的日志中间件(先记录请求)
app.UseAuthentication(); // ② 身份验证中间件(校验身份)
app.UseAuthorization(); // ③ 授权中间件(校验权限)
app.UseStaticFiles(); // ④ 静态文件中间件(处理CSS/JS)
app.MapControllers(); // MVC路由中间件
app.Run();
为了让你更清楚顺序的影响,我做了个表格:
中间件顺序 | 处理逻辑 | 效果 |
---|---|---|
|
先记录请求→再校验身份 | 所有请求(包括未登录的)都会被记录 |
|
先校验身份→再记录请求 | 未登录的请求不会被记录 |
比如你想记录所有请求,不管有没有登录,就得把日志中间件放在UseAuthentication
前面;如果只想记录已登录的请求,就反过来。记住:中间件的执行顺序是“注册顺序”,响应的顺序是“倒序”(比如注册顺序是A→B→C,那么请求是A→B→C,响应是C→B→A)。
为了代码更简洁,你可以写个扩展方法(比如UseCustomLogger
),把UseMiddleware
包装起来:
public static class CustomLoggerMiddlewareExtensions
{
public static IApplicationBuilder UseCustomLogger(this IApplicationBuilder app)
{
return app.UseMiddleware();
}
}
这样注册的时候直接写app.UseCustomLogger()
,比app.UseMiddleware()
更 readable——尤其是当你有多个中间件的时候,代码会更干净。
最后:怎么验证中间件生效了?
写完代码,你肯定想知道有没有用——最简单的方法是用Postman发请求,看日志输出。比如你发一个GET
请求到/api/values
,如果控制台或者日志文件里出现“请求来了→路径:/api/values,方法:GET,时间:2024-05-20 14:30:00”这样的记录,说明中间件生效了。
如果没生效,先检查这三点:
InvokeAsync
方法名有没有写错? MapControllers
前面? _next(context)
? 我再给你留个小挑战:试试做一个“跨域中间件”——允许前端域名http://localhost:3000
访问你的API。提示:需要在InvokeAsync
里设置context.Response.Headers.Add("Access-Control-Allow-Origin", "http://localhost:3000")
。
你按这个步骤做的时候,有没有遇到中间件不执行的情况?或者想做其他功能的中间件(比如限流、参数校验)?欢迎留言告诉我,我帮你捋捋思路——毕竟我踩过的坑,能让你少走点弯路。
本文常见问题(FAQ)
自定义中间件写好了但没执行,可能是哪里错了?
先检查中间件的注册顺序——是不是放在MapControllers后面了?ASP.NET Core的中间件是按注册顺序执行的,如果你的中间件注册在MapControllers之后,请求已经走到控制器了,中间件肯定不会执行。然后看看中间件类的InvokeAsync方法名有没有写错,比如写成了Invoke(同步)或者别的名字,框架只认InvokeAsync这个异步方法名。最后确认一下InvokeAsync里有没有调用_next(context),要是没写这行,请求会卡在你的中间件里,后面的逻辑也走不下去。
我之前帮朋友排查过类似问题,他把中间件注册在MapControllers之后,结果测了半天没日志,调整顺序到MapControllers前面就好了。
中间件注册顺序错了会有什么后果?
顺序错了会直接影响逻辑的执行结果。比如你想记录所有请求的日志,把日志中间件放在权限校验中间件后面,那未登录的请求就不会被记录——因为权限校验没通过的话,请求根本到不了日志中间件。反过来,如果把日志中间件放在前面,不管用户有没有登录,所有请求都会被记录下来。再比如跨域中间件,要是放在权限校验后面,未登录的请求会先被拦截,跨域头根本没机会设置,前端就会报跨域错误。
简单说,中间件的顺序决定了逻辑的“执行时机”,你得先想清楚自己的逻辑要在哪个阶段执行,再调整顺序。
中间件里的_next(context)必须写吗?不写会怎样?
必须写!_next(context)是下一个中间件的“入口”,如果不写,请求就会卡在你的中间件里,不会继续往下走。比如你写了个日志中间件,没调用_next,那请求到你这就停了,控制器根本收不到请求,Postman发请求会一直转圈直到超时。
我之前犯过这个错,写中间件时忘了加_next,结果朋友测接口的时候一直报504超时,查了半天才发现是少了这行代码。
怎么确认自定义中间件真的起作用了?
最直接的方法是看日志输出——比如你写了日志中间件,发个请求后看看控制台或者日志文件里有没有你写的日志内容,比如“请求来了→路径:/api/values”这类记录。要是没有日志,也可以在中间件里加个简单的响应,比如在InvokeAsync里写await context.Response.WriteAsync(“中间件生效了”),然后用Postman发请求,要是能收到这句话,说明中间件执行了。
我帮朋友做电商API的时候,就是用Postman测的,发了个请求后看到控制台有日志,就确认中间件没问题了。
为什么要给中间件写扩展方法?直接用UseMiddleware不行吗?
直接用UseMiddleware当然可以,但写扩展方法会让代码更可读。比如你有多个中间件,用app.UseCustomLogger()比app.UseMiddleware()看起来更清楚,别人一看就知道这个中间件是做日志的。而且扩展方法可以封装一些额外逻辑,比如要是中间件需要配置参数(比如跨域的允许域名),可以在扩展方法里加参数,不用每次都写一堆配置代码。
我之前做过一个项目,有五个中间件,用扩展方法注册的话,代码一行一个UseXXX,特别干净,要是全写UseMiddleware,得记每个中间件的类名,麻烦得很。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别!
站长QQ:709466365 站长邮箱:709466365@qq.com