C#如何在ASP.NET Core中间件中处理请求?完整教程解析

来源:站长论坛作者:深圳SEO公司头衔:草根站长
导读:本期聚焦于小伙伴创作的《C#如何在ASP.NET Core中间件中处理请求?完整教程解析》,敬请观看详情。请求管道里的中间件决定了ASP.NET Core应用如何响应每一次HTTP调用。不少初学者误以为只有控制器才能处理请求,其实自定义中间件可以在管道任意位置拦截、修改或短路请求。本文从RequestDelegate委托机制讲起,说明如何通过Invoke方法读取HttpContext中的请求头与正文,怎样在next委托前后插入逻辑实现计时与鉴权,并给出短路返回JSON的典型写法。同时对比了基于约定类的中间件与匿名中间件委托的适用场景,指出常见内存泄漏与流不可重读坑点,帮助你在真实项目中写出稳定可维护的管道组件。

在ASP.NET Core中,中间件是组成请求处理管道的核心单元。每一个中间件都像一个接力选手,可以选择把请求传给下一位,也可以直接结束响应。理解中间件的运行机制,是构建鉴权、日志、异常处理等横切功能的基础。

C#如何在ASP.NET Core中间件中处理请求?完整教程解析

中间件的基本工作原理

ASP.NET Core的请求管道由一组有序的中间件构成,框架使用RequestDelegate来表示下一个中间件的委托。当你注册一个中间件时,实际上就是向管道中插入一段逻辑,这段逻辑接收HttpContext并决定是否调用后续委托。

最基础的中间件写法是通过app.Use传入一个接收HttpContextRequestDelegate的匿名方法。下面代码展示了一个打印请求路径后再调用后续中间件的最小示例:

app.Use(async (context, next) =>
{
    // 在调用下一个中间件前记录请求路径
    var path = context.Request.Path.Value;
    Console.WriteLine($"请求进入:{path}");

    // 交给管道中的下一个中间件
    await next();

    // 后续中间件执行完毕后回到这里
    Console.WriteLine($"响应完成:{path}");
});

这种写法适合逻辑简单、仅在某处使用一次的场景。它的优势是直观,不需要单独建类;缺点是如果逻辑复杂,匿名方法会让Program.cs变得臃肿,且不利于单元测试。

使用约定类封装中间件

当处理逻辑需要复用或变得复杂时,推荐按照约定创建一个中间件类。该类需包含公共构造函数接收RequestDelegate,并暴露返回TaskInvokeAsync方法。

以下示例实现了一个统计请求耗时的中间件。它在调用next之前记录开始时间,在之后计算并写入响应头。注意_next字段保存了管道后续委托,不能在构造之外修改。

public class TimingMiddleware
{
    private readonly RequestDelegate _next;

    public TimingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var start = DateTime.UtcNow;

        // 先执行后续管道
        await _next(context);

        var cost = DateTime.UtcNow - start;
        // 将耗时写入响应头,方便前端或网关观测
        context.Response.Headers.Add("X-Process-Time", cost.TotalMilliseconds.ToString());
    }
}

// 注册扩展方法
public static class TimingMiddlewareExtensions
{
    public static IApplicationBuilder UseTiming(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<TimingMiddleware>();
    }
}

使用约定类后,注册只需一行app.UseTiming();。这种结构清晰,能把字段注入(如配置、日志器)通过构造函数完成。但要小心:中间件实例在应用生命周期内是单例的,因此不要在其中保存请求级状态,否则会造成数据串号。

在中间件中读取与修改请求

很多业务需要在中间件里读取请求正文,例如签名验证或报文转换。由于HTTP请求体是一个只进流,直接读取会导致后续模型绑定失败,必须借助EnableBuffering允许重读。

下面的代码演示了如何在中间件中安全地读取JSON正文并原样归还流位置:

app.Use(async (context, next) =>
{
    // 允许请求体可重读
    context.Request.EnableBuffering();

    using var reader = new StreamReader(
        context.Request.Body,
        encoding: System.Text.Encoding.UTF8,
        detectEncodingFromByteOrderMarks: false,
        leaveOpen: true);

    var body = await reader.ReadToEndAsync();
    // 此处可做签名校验或日志落盘
    Console.WriteLine($"正文长度:{body.Length}");

    // 将流位置重置,供后续中间件或控制器读取
    context.Request.Body.Position = 0;

    await next();
});

如果不调用EnableBufferingReadToEndAsync会消耗流且无法复位,MVC控制器将无法再绑定参数。这是中间件处理请求正文时最容易踩的坑之一。

短路请求与返回自定义响应

中间件不一定总要调用next。当检测到未授权或参数非法时,可直接写入响应并结束管道,这称为短路。短路能减少不必要的下游开销。

下面示例在请求头缺少令牌时直接返回401和JSON错误信息,不再执行后续任何中间件:

app.Use(async (context, next) =>
{
    if (!context.Request.Headers.ContainsKey("X-Token"))
    {
        context.Response.StatusCode = 401;
        context.Response.ContentType = "application/json";
        await context.Response.WriteAsync(
            "{"error":"缺少访问令牌"}");
        // 不调用 next,直接结束
        return;
    }

    await next();
});

短路逻辑应放在管道靠前位置,例如放在路由和端点之前。但要注意,如果前面已有身份验证中间件,应优先使用框架内置方案,避免重复造轮子。自定义短路适合做轻量的网关层拦截。

中间件注册顺序的影响

中间件的执行顺序严格遵循注册顺序。先注册的中间件先接触请求,后接触响应。例如异常处理中间件必须放在最前,才能捕获后续所有错误。

常见推荐顺序为:异常处理、HTTPS重定向、静态文件、路由、鉴权、端点。若把日志记录放最前,它能记录所有请求;若放鉴权之后,则未通过鉴权的请求不会被记录。下面用表格说明两种顺序差异:

注册顺序请求阶段可见性适用场景
日志在最前全部请求含被拒请求安全审计、流量统计
日志在鉴权后仅合法请求业务性能分析

理解顺序差异能避免“为什么日志没打”之类的排查盲区。在Program.cs中调整app.UseXxx的先后即可控制行为,无需改动中间件内部代码。

依赖注入与作用域注意事项

中间件构造函数只能注入单例或瞬态服务,不能直接注入作用域服务(如EF Core的DbContext)。如果需要在中间件中使用作用域服务,应通过InvokeAsync的参数或context.RequestServices解析。

以下代码展示通过方法参数注入作用域服务的安全写法:

public class ScopedMiddleware
{
    private readonly RequestDelegate _next;

    public ScopedMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context, IMyScopedService svc)
    {
        // svc 每次请求都是新的作用域实例
        var data = await svc.GetAsync();
        context.Items["data"] = data;

        await _next(context);
    }
}

这样框架会自动创建对应请求的作用域并释放资源,避免内存泄漏。若错误地在构造函数中捕获作用域服务,会导致服务被提升为单例,引发并发与数据不一致问题。

总结与实践建议

掌握ASP.NET Core中间件请求处理,关键在理解RequestDelegate链条、HttpContext的读写时机以及注册顺序。简单逻辑用app.Use匿名委托,复杂功能用约定类并配合扩展方法注册。

在真实项目中,建议把鉴权短路、请求日志、异常捕获做成独立中间件,并放在管道头部;请求正文读取务必启用缓冲并复位流;作用域服务走方法参数注入。遵循这些准则,你的中间件将既灵活又稳定。

C#ASP.NET_Core中间件修改时间:2026-07-31 14:42:35

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。