ASP.NETCore学习记录(二) —— ASP.NET Core 中间件

本文深入解析ASP.NET Core的中间件概念,探讨中间件如何处理请求和响应,以及如何通过IApplicationBuilder构建请求管道。文章详细介绍了Run、Map与Use方法的使用,并通过实战示例展示如何创建自定义中间件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 ASP.NET Core 中间件


目录:

 


我们知道在 ASP.NET 中,有一个面向切面的请求管道,由22个主要的事件构成,能够让我们在往预定的执行顺序里面添加自己的处理逻辑。一般采取两种方式:一种是直接在 Global.asax 中对应的方法中直接添加代码。一种是是在 web.config 中通过注册 HttpModule 来实现对请求管道事件监听,并通过 HttpHandler 进入到我们的应用程序中。而在 ASP.NET Core 中,对请求管道进行了重新设计,通过使用一种称为中间件的方式来进行管道的注册,同时也变得更加简洁和强大。关于 HTTP请求流程和管道模型可以看这篇博客 https://www.cnblogs.com/xuhuale/p/10030878.html


什么是中间件 ?

中间件 (Middleware)组装到应用程序管道中以处理请求和响应的组件。 管道内的每个组件都可以选择是否将请求交给下一个组件,并在管道中调用下一个组件之前和之后执行某些操作。请求委托被用来建立请求管道,请求委托处理每一个HTTP请求。

请求委托通过使用 IApplicationBuilder 类型的 RunMap 以及 Use 扩展方法来配置,并在 Startup 类中传给 Configure方法。每个单独的清求委托都可以被指定为一个内嵌匿名方法,或其定义在一个可重用的类中。这些可重用的类被称作 “中间件”或“中间件组件”。每个位于请求管道内的中间件组件负责调用管道中的下一个组件,或适时短路调用链。

ASP.NET请求管道由一系列的请求委托所构成,它们一个接着一个被调用,如图所示(该执行线程按黑色箭头的顺序执行)。

 

微软官方的一个中间件管道请求图
微软官方的一个中间件管道请求图

 

每个委托:

  • 均可在下一个委托前后执行操作
  • 委托还可以决定是否将请求传递给下一个委托(任何委托都能选择停止传递到下一个委托,转而自己处理该请求,这就是请求管道的短路)。

短路是一种有意义的设计,因为它可以避免不必要的工作。比如说:

  • 静态文件中间件可以返回静态文件请求并使管道的其余部分短路。
  • 授权中间件只有通过身份验证之后才能调用下一个中间件,否则就会被他短路。

先在管道中调用异常处理委托,以便它们可以捕获在管道的后期阶段所发生的异常。

返回顶部


IApplicationBuilder

笔记一中,我们就简单介绍过 IApplicationBuilder,在 Startup 类的 Configure方法中,第一个参数便是 IApplicationBuilder

首先,IApplicationBuilder 是用来构建请求管道的,而所谓请求管道,本质上就是对 HttpContext 的一系列操作,即通过对 Request 的处理,来生成 Reponse。在 ASP.NET Core 中定义了一个 RequestDelegate 委托,来表示请求管道中的一个步骤,而对请求管道的注册是通过 Func<RequestDelegate, RequestDelegate> 类型的委托(也就是中间件)来实现的。

返回顶部


使用 IApplicationBuilder 创建中间件

可以先看一下 VisualStudio2017 中默认Web(MVC)站点模板关于请求管道设置的例子。

 


 

 

Startup.csConfigure 方法默认增加了下列这些中间件组件:

  • 异常/错误处理(同时针对开发环境和非开发环境)
  • Https重定向
  • 静态文件服务器
  • Cookie 策略实施
  • MVC
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        // 开发环境
        app.UseDeveloperExceptionPage(); 
    }
    else
    {	
        // 非开发环境
        app.UseExceptionHandler("/Home/Error"); 
        app.UseHsts();
    }

    app.UseHttpsRedirection();// Https重定向
    app.UseStaticFiles();   // 静态文件
    app.UseCookiePolicy();  // 使用Cookie策略

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    }); // MVC
}

上面的代码在非开发环境中,UseExceptionHandler 是第一个被加入到管道中的中间件,因此将会捕获之后调用组件中出现的任何异常,然后跳转到设置的异常页(/Home/Error)。

使用 UseHttpsRedirection 中间件,可以轻松实施HTTPS,将HTTP重定向到HTTPS(ASP.NET Core 2.1具有新功能)。

然后就是静态文件中间件 UseStaticFiles,静态文件中间件不提供授权检查,由它提供的任何文件,包括那些位于wwwroot下的文件都是公开可被访问的。UseCookiePolicy 是使用ASP.NET Core中的Cookie策略(详解Microsoft.AspNetCore.CookiePolicy),管道的最后执行的也就是MVC框架。

顺序
Startup.Configure 方法添加中间件组件的顺序定义了针对请求调用这些组件的顺序,以及响应的相反顺序。 此排序对于安全性、性能和功能至关重要。请求在每一步都可能被短路,所以我们要以正确的顺序添加中间件,如异常处理,我们需要添加在最开始,这样我们就能第一时间捕获异常,以及后续中间可能发生的异常,然后最终做处理返回。

最简单的ASP.NET应用程序(空白Web模板)是使用单个请求委托来处理所有请求。事实上,在这种情况下,并不存在所谓的“管道”,针对每个HTTP请求都调用单个匿名函数来处理。

public void Configure(IApplicationBuilder app)
{
    // 使用单个请求委托来处理所有请求
    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Hello World!");
    });
}

上方代码中 app.Run() 会中断管道,调用单个匿名函数来处理HTTP请求。在下面的例子中,Run了两个委托,只有第一个委托(“Hello,World!”)会被运行。

public void Configure(IApplicationBuilder app)
{
    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Hello World!");
    });

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Welcome!");
    });
}

通过运行项目,发现确实只有在第一个委托执行了,app.Run 终止了管道。

 


 

 

你可以将多个请求委托 app.Use 连接在一起,next参数表示管道内的下一个请求委托。在管道中,可以通过不调用next参数来终止或短路管道。通常可以在执行下一个委托之前和之后执行一些操作,如下例所示:

public void Configure(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Handing Request.\r\n");
        
        // 调用管道中的下一个委托
        await next.Invoke();
        await context.Response.WriteAsync("Finish Handing Request.\r\n");
    });

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Hello from Middleware\r\n");
    });
}

运行项目,在浏览器中访问出现如下结果:

 


 

 

返回顶部


Run、Map 与 Use 方法

你可以使用 RunMapUse 方法配置 HTTP 管道。

Run 方法会短路管道,因为它不会调用 next 请求委托。因此 Run 方法一般只在管道底部被调用。Run 方法是一种惯例约定,有些中间件组件可能会暴露它们自己的 Run[Middleware]方法,而这些方法只能在管道末尾处运行。

Use 前面已经简单介绍通过 Use 构建请求管道的例子,Use 方法亦可使管道短路(即不调用 next 请求委托)。

Map 扩展方法用匹配基于请求路径的请求委托,Map只接受路径,并配置单独的中间件管道的功能。 Map* 方法可以基于给定请求路径的匹配项来创建请求管道分支。 如果请求路径以给定路径开头,则执行分支。如下面例子中:

public void Configure(IApplicationBuilder app)
{
    app.Map("/map1", HandleMapTest1);
    app.Map("/map2", HandleMapTest2);
    app.Run(async context =>
    {
        await context.Response.WriteAsync("<p> Hello from non-Map delegate. </p>");
    });
}

private static void HandleMapTest1(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("<p> Map Test 1 </p>");
    });
}

private static void HandleMapTest2(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("<p> Map Test 2 </p>");
    });
}

任何基于路径 /map1 的请求都会被管道中所配置 HandleMapTest1 方法处理。基于路径 /map2 的请求都会被管道中所配置 HandleMapTest2 方法处理。

 


 

 

下表显示上例来自 http://localhost:52831 的请求和响应。

请求响应
http://localhost:52831Hello from non-Map delegate.
http://localhost:52831/map1Map Test 1
http://localhost:52831/map2Map Test 2
http://localhost:52831/map3Hello from non-Map delegate.

除了基于路径的映射外,MapWhen 方法还支持基于谓语的中间件分支,允许以非常灵活的方式构建单独的管道。任何 Func<HttpContext, bool> 类型的谓语都可以被用于将请求映射到新的管道分支。下例中使用了一个简单的谓语来检测查洵字符串中变量 branch 是否存在:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch);
        app.Run(async context =>
        {
            await context.Response.WriteAsync("<p> Hello from non-Map delegate. </p>");
        });
    }

    private static void HandleBranch(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            var branchVer = context.Request.Query["branch"];
            await context.Response.WriteAsync($"Branch used = {branchVer}");
        });
    }
}

使用了上面的设置后,任何包含请求字符 branch 的请求将使用定义于 HandleBranch 方法内的管道,如果没有包含查询字符串 branch 的请求,将被 app.Run 所定义的委托处理。

 


 

 

请求响应
http://localhost:52831Hello from non-Map delegate.
http://localhost:52831/?branch=1Branch used = 1
http://localhost:52831/?branch=2Branch used = 2
http://localhost:52831/?branchBranch used =

另外还可以嵌套 Maps:

app.Map("/level1", level1App => {
   level1App.Map("/level2a", level2AApp => {
       // "/level1/level2a"
       //...
   });
   level1App.Map("/level2b", level2BApp => {
       // "/level1/level2b"
       //...
   });
});

Map 也可以一次匹配多个片段,例如:

app.Map("/level1/level2", HandleMultiSeg); // "/level1/level2"

以下 Startup.Configure 方法将为常见应用方案添加中间件组件:

中间件描述
身份验证(Authentication)提供身份验证支持
跨域资源共享(CORS)配置跨域资源共享
响应支持(Response Caching)提供缓存响应支持
响应压缩(Response Compression)提供响应压缩支持
路由(Routing)定义和约束请求路由
会话(Session)提供对管理用户会话(session)的支持
静态文件(Static Files)为静态文件和目录浏览提供服务提供支持
URL Rewriting Middleware用于重写 Url,并将请求重定向的支持

更多中间件组件可以到 Microsoft 文档 上查看。

返回顶部


实战中间件

如何实现一个中间件呢,下面我们来实际操作。
中间件遵循 显式依赖原则 ,并在其构造函数中暴露所有的依赖项。中间件能够利用 UseMiddleware<T> 扩展方法的优势,直接通过它们的构造函数注入服务。依赖注入服务是自动完成填充的,扩展所用到的 params 参数数组被用于非注入参数。

下面来实现一个记录IP的中间件。

① 新建一个 ASP.NET Core WebApplication 项目,选择空的模板。

 


 

 

然后为项目添加—个 Microsoft.Extensions.Logging.Console。
NuGet命令行执行(请使用官方源):

Install-Package Microsoft.Extensions.Logging.Console

 


 

 

② 新建一个类 RequestIPMiddleware.es,将中间件委托移动到类:

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace middlewareDemo
{
    public class RequestIPMiddleware
    {
        private readonly RequestDelegate _next;

        private readonly ILogger _logger;

        public RequestIPMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
        {
            _next = next;
            _logger = loggerFactory.CreateLogger<RequestIPMiddleware>();
        }

        public async Task Invoke(HttpContext context)
        {
            _logger.LogInformation("User IP:" + context.Connection.RemoteIpAddress.ToString());
            await _next.Invoke(context);
        }
    }
}

③ 再新建—个 RequestIPExtensions.cs,以下扩展方法通过 IApplicationBuilder 公开中间件:

using Microsoft.AspNetCore.Builder;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace middlewareDemo
{
    public static class RequestIPExtensions
    {
        public static IApplicationBuilder UseRequestIP(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<RequestIPMiddleware>();
        }
    }
}

这样就编写好了一个中间件。
④ 使用中间件。在 Startup.cs 中添加 app.UseRequestIP() 来使用中间件:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole();
    app.UseRequestIP();
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Hello World!");
    });
}

然后运行程序,这里选择使用 Kestrel
访问:http://localhost:5000/

 


 

 

成功运行,到这里我们还可以对这个中间件进行进一步改进,增加更多的功能,如限制访问等。


参考原文

Microsoft 文档 ASP.NET Core 中间件
ASP.NET Core 中间件(Middleware)详解
《ASP.NET Core 跨平台开发从入门到实战》

返回顶部


转载于:https://www.cnblogs.com/anayigeren/p/10327153.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值