.NET Core中间件的使用方法总结

中间件数据库容器

.NetCore的开发过程中,与中间件打交道是常态,中间件其实也不难理解。接下来我们就来总结下中间件的相关理论和实际操作。

picture.image

微软官方定义中间件:

中间件是一种装配到应用管道以处理请求和响应的软件。每个组件:

1、选择是否将请求传递到管道中的下一个组件。

2、可在管道中的下一个组件前后执行工作。

请求委托用于生成请求管道。请求委托处理每个 HTTP 请求。

使用 RunMap 和 Use 扩展方法来配置请求委托。可将一个单独的请求委托并行指定为匿名方法(称为并行中间件),或在可重用的类中对其进行定义。这些可重用的类和并行匿名方法即为中间件,也叫中间件组件。请求管道中的每个中间件组件负责调用管道中的下一个组件,或使管道短路。当中间件短路时,它被称为“终端中间件”,因为它阻止中间件进一步处理请求。

picture.image

简单概括:中间件是一类装配在应用管道的代码,负责处理请求和响应。每个中间件都可在管道中的下一个组件前后执行工作,并选择是否将请求传递到管道中的下一个中间件。可在Startup.Configure方法中可以进行中间件的装配。

picture.image

picture.image

picture.image

中间件管道模型

ASP.NET Core 请求管道包含一系列请求委托,依次调用。每个委托均可在下一个委托前后执行操作。 应尽早在管道中调用异常处理委托,这样它们就能捕获在管道的后期阶段发生的异常。 下图演示了这一概念。沿黑色箭头执行。

picture.image

(上图为中间件管道模型最经典的 “俄罗斯套娃”图)

picture.image

picture.image

最简单的中间件

处理请求的单个请求委托尽可能简单。一个中间件可以是匿名方法的显示嵌入到管道中,也可以封装为单独的类便于重用,嵌入式的中间件就像这样:

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

中间件的配置

配置中间件会使用到三个扩展方法:

  • Use

  • Run

  • Map

    Use

Use用来将多个中间件按添加顺序链接到一起:

  
app.Use(async (context, next) =>  
{  
 await context.Response.WriteAsync("middleware1 begin\r\n");  
 await next.Invoke();  
 await context.Response.WriteAsync("middleware1 end\r\n");  
});  
  
app.Use(async (context, next) =>  
{  
 await context.Response.WriteAsync("middleware2 begin\r\n");  
 await next.Invoke();  
 await context.Response.WriteAsync("middleware2 end\r\n");  
});  
  
app.Run(async context =>  
{  
 await context.Response.WriteAsync("end of pipeline.\r\n");  
});

这三个中间件配置到管道后,输出的结果与管道模型的图示是一致的:

  
middleware1 begin  
middleware2 begin  
end of pipeline.  
middleware2 end  
middleware1 end

可以看到除了最后一个中间件,前面的中间件都调用了await next.Invoke(),next参数表示管道中的下一个委托,如果不调用 next,后面的中间件就不执行,这称为管道的短路。

通常中间件都应该自觉调用下一个中间件,但有的中间件会故意造成短路,比如授权中间件、静态文件中间件等。

Run

Run的委托中没有next参数,这就意味着它会称为最后一个中间件(终端中间件),此外可以故意短路的Use委托也可能会成为终端中间件。

Map,提供了创建管道分支的能力

Map扩展用作约定来创建管道分支,会基于给定请求路径的匹配项来创建请求管道分支,如果请求路径以给定路径开头,则执行分支。

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

MapWhen 基于给定谓词的结果创建请求管道分支。Func<HttpContext, bool> 类型的任何谓词均可用于将请求映射到管道的新分支。在以下示例中,谓词用于检测查询字符串变量 branch 是否存在:

  
public class Startup  
{  
 private static void HandleBranch(IApplicationBuilder app)  
 {  
 app.Run(async context =>  
 {  
 var branchVer = context.Request.Query["branch"];  
 await context.Response.WriteAsync($"Branch used = {branchVer}");  
 });  
 }  
  
 public void Configure(IApplicationBuilder app)  
 {  
 app.MapWhen(context => context.Request.Query.ContainsKey("branch"),  
 HandleBranch);  
  
 app.Run(async context =>  
 {  
 await context.Response.WriteAsync("Hello from non-Map delegate. <p>");  
 });  
 }  
}

下表使用前面的代码显示来自 http://localhost:1234 的请求和响应:

表 2

请求响应
localhost:1234Hello from non-Map delegate.
localhost:1234/?branch=mainBranch used = main

UseWhen 也基于给定谓词的结果创建请求管道分支。与 MapWhen 不同的是,如果这个分支不发生短路或包含终端中间件,则会重新加入主管道:

  
public class Startup  
{  
 private void HandleBranchAndRejoin(IApplicationBuilder app, ILogger<Startup> logger)  
 {  
 app.Use(async (context, next) =>  
 {  
 var branchVer = context.Request.Query["branch"];  
 logger.LogInformation("Branch used = {branchVer}", branchVer);  
  
 // Do work that doesn't write to the Response.  
 await next();  
 // Do other work that doesn't write to the Response.  
 });  
 }  
  
 public void Configure(IApplicationBuilder app, ILogger<Startup> logger)  
 {  
 app.UseWhen(context => context.Request.Query.ContainsKey("branch"),  
 appBuilder => HandleBranchAndRejoin(appBuilder, logger));  
  
 app.Run(async context =>  
 {  
 await context.Response.WriteAsync("Hello from main pipeline.");  
 });  
 }  
}

在前面的示例中,响应 "Hello from main pipeline." 是为所有请求编写的。如果请求中包含查询字符串变量 branch,则在重新加入主管道之前会记录其值。

picture.image

自定义中间件

在开始封装前,先了解一下对中间件的要求:

中间件必须具有类型为RequestDelegate的参数的公共构造函数,之前代码中看到next.Invoke(),其中next就是下一个中间件的RequestDelegate;

名为 Invoke 或 InvokeAsync 的公共方法。而且这个方法的返回类型为Task,且第一个参数的必须是HttpContext,其它的参数由DI容器解析。

基于上述要求,编写的中间件为:

  
public class RequestCultureMiddleware  
{  
 private readonly RequestDelegate _next;  
  
 public RequestCultureMiddleware(RequestDelegate next)  
 {  
 _next = next;  
 }  
  
 public async Task InvokeAsync(HttpContext context)  
 {  
 var cultureQuery = context.Request.Query["culture"];  
 if (!string.IsNullOrWhiteSpace(cultureQuery))  
 {  
 var culture = new CultureInfo(cultureQuery);  
  
 CultureInfo.CurrentCulture = culture;  
 CultureInfo.CurrentUICulture = culture;  
 }  
  
 // Call the next delegate/middleware in the pipeline  
 //await _next.Invoke(context);  
 await _next(context);  
 }  
}

然后就可以在Startup的Configure里使用了:

  
app.UseMiddleware<RequestCultureMiddleware>();

还可以进一步封装为IApplicationBuilder的扩展方法:

  
public static class RequestCultureMiddlewareExtensions  
{  
 public static IApplicationBuilder UseRequestCulture(  
 this IApplicationBuilder builder)  
 {  
 return builder.UseMiddleware<RequestCultureMiddleware>();  
 }  
}

注:封装扩展方法,都是定义一个静态类和静态方法,静态方法里,第一个参数都为你需要给哪个类/接口 扩展方法,这里是为 IApplicationBuilder做扩展方法,所以第1个参数为this IApplicationBuilder 。

然后就可以像内置的中间件一样了:

  
app.UseRequestCulture();

如果需要依赖项则可使用依赖注入:

  
public class CustomMiddleware  
{  
 private readonly RequestDelegate _next;  
  
 public CustomMiddleware(RequestDelegate next)  
 {  
 _next = next;  
 }  
  
 // IMyScopedService is injected into Invoke  
 public async Task Invoke(HttpContext httpContext, IMyScopedService svc)  
 {  
 svc.MyProperty = 1000;  
 await _next(httpContext);  
 }  
}

picture.image

picture.image

中间件顺序

下图显示了 ASP.NET Core MVC 和 Razor Pages 应用的完整请求处理管道。你可以在典型应用中了解现有中间件的顺序,以及在哪里添加自定义中间件。你可以完全控制如何重新排列现有中间件,或根据场景需要注入新的自定义中间件。

picture.image

上图中的“终结点”中间件为相应的应用类型(MVC 或 Razor Pages)执行筛选器管道。

picture.image

Startup.Configure 方法添加中间件组件的顺序定义了针对请求调用这些组件的顺序,以及响应的相反顺序。此顺序对于安全性、性能和功能至关重要。

下面的 Startup.Configure 方法按照典型的建议顺序增加与安全相关的中间件组件:

  
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
{  
 if (env.IsDevelopment())  
 {  
 app.UseDeveloperExceptionPage();  
 app.UseDatabaseErrorPage();  
 }  
 else  
 {  
 app.UseExceptionHandler("/Error");  
 app.UseHsts();  
 }  
  
 app.UseHttpsRedirection();  
 app.UseStaticFiles();  
 // app.UseCookiePolicy();  
  
 app.UseRouting();  
 // app.UseRequestLocalization();  
 // app.UseCors();  
  
 app.UseAuthentication();  
 app.UseAuthorization();  
 // app.UseSession();  
 // app.UseResponseCompression();  
 // app.UseResponseCaching();  
  
 app.UseEndpoints(endpoints =>  
 {  
 endpoints.MapRazorPages();  
 endpoints.MapControllerRoute(  
 name: "default",  
 pattern: "{controller=Home}/{action=Index}/{id?}");  
 });  
}

picture.image

picture.image

常见的中间件组件

1、异常/错误处理

(1)当应用在开发环境中运行时:

开发人员异常页中间件 (UseDeveloperExceptionPage) 报告应用运行时错误。

数据库错误页中间件报告数据库运行时错误。

(2)当应用在生产环境中运行时:

异常处理程序中间件 (UseExceptionHandler) 捕获以下中间件中引发的异常。

HTTP 严格传输安全协议 (HSTS) 中间件 (UseHsts) 添加 Strict-Transport-Security 标头。

2、HTTPS 重定向中间件 (UseHttpsRedirection) 将 HTTP 请求重定向到 HTTPS。

3、静态文件中间件 (UseStaticFiles) 返回静态文件,并简化进一步请求处理。

4、Cookie 策略中间件 (UseCookiePolicy) 使应用符合欧盟一般数据保护条例 (GDPR) 规定。

5、用于路由请求的路由中间件 (UseRouting)。

6、身份验证中间件 (UseAuthentication) 尝试对用户进行身份验证,然后才会允许用户访问安全资源。

7、用于授权用户访问安全资源的授权中间件 (UseAuthorization)。

8、会话中间件 (UseSession) 建立和维护会话状态。如果应用使用会话状态,请在 Cookie 策略中间件之后和 MVC 中间件之前调用会话中间件。

9、用于将 Razor Pages 终结点添加到请求管道的终结点路由中间件(带有 MapRazorPages 的 UseEndpoints)。

  
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
{  
 if (env.IsDevelopment())  
 {  
 app.UseDeveloperExceptionPage();  
 app.UseDatabaseErrorPage();  
 }  
 else  
 {  
 app.UseExceptionHandler("/Error");  
 app.UseHsts();  
 }  
  
 app.UseHttpsRedirection();  
 app.UseStaticFiles();  
 app.UseCookiePolicy();  
 app.UseRouting();  
 app.UseAuthentication();  
 app.UseAuthorization();  
 app.UseSession();  
  
 app.UseEndpoints(endpoints =>  
 {  
 endpoints.MapRazorPages();  
 });  
}

在前面的示例代码中,每个中间件扩展方法都通过 Microsoft.AspNetCore.Builder 命名空间在 IApplicationBuilder 上公开。

UseExceptionHandler 是添加到管道的第一个中间件组件。因此,异常处理程序中间件可捕获稍后调用中发生的任何异常。

尽早在管道中调用静态文件中间件,以便它可以处理请求并使其短路,而无需通过剩余组件。静态文件中间件不提供授权检查。可公开访问由静态文件中间件服务的任何文件,包括 wwwroot 下的文件。

2021沐雪.NetCore版本SaaS多租户商城系统源码 小程序商城源码

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
vivo 容器化平台架构与核心能力建设实践
为了实现规模化降本提效的目标,vivo 确定了基于云原生理念构建容器化生态的目标。在容器化生态发展过程中,平台架构不断演进,并针对业务的痛点和诉求,持续完善容器化能力矩阵。本次演讲将会介绍 vivo 容器化平台及主要子系统的架构设计,并分享重点建设的容器化核心能力。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论