一、什么是中间件(Middleware)?

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

  • 选择是否将请求传递给管道中的下一个组件。

  • 可以在调用管道中的下一个组件之前和之后执行工作。

请求委托(Request delegates)用于构建请求管道,处理每个HTTP请求。

请求委托使用RunMapUse扩展方法进行配置。单独的请求委托可以以内联匿名方法(称为内联中间件)指定,或者可以在可重用的类中定义它。这些可重用的类和内联匿名方法是中间件或中间件组件。请求流程中的每个中间件组件都负责调用流水线中的下一个组件,如果适当,则负责链接短路。

将HTTP模块迁移到中间件解释了ASP.NET Core和以前版本(ASP.NET)中的请求管道之间的区别,并提供了更多的中间件示例。

使用 IApplicationBuilder 创建中间件管道

ASP.NET Core请求流程由一系列请求委托组成,如下图所示(执行流程遵循黑色箭头):

每个委托可以在下一个委托之前和之后执行操作。委托还可以决定不将请求传递给下一个委托,这称为请求管道的短路。短路通常是可取的,因为它避免了不必要的工作。例如,静态文件中间件可以返回一个静态文件的请求,并使管道的其余部分短路。需要在管道早期调用异常处理委托,因此它们可以捕获后面管道的异常。

最简单的可能是ASP.NET Core应用程序建立一个请求的委托,处理所有的请求。此案例不包含实际的请求管道。相反,针对每个HTTP请求都调用一个匿名方法。

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
}
}

第一个 app.Run 委托终止管道。

有如下代码:

通过浏览器访问,发现确实在第一个app.Run终止了管道。

您可以将多个请求委托与app.Use连接在一起。 next参数表示管道中的下一个委托。 (请记住,您可以通过不调用下一个参数来结束流水线。)通常可以在下一个委托之前和之后执行操作,如下例所示:

public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("进入第一个委托 执行下一个委托之前\r\n");
//调用管道中的下一个委托
await next.Invoke();
await context.Response.WriteAsync("结束第一个委托 执行下一个委托之后\r\n");
});
app.Run(async context =>
{
await context.Response.WriteAsync("进入第二个委托\r\n");
await context.Response.WriteAsync("Hello from 2nd delegate.\r\n");
await context.Response.WriteAsync("结束第二个委托\r\n");
});
}
}

使用浏览器访问有如下结果:

可以看出请求委托的执行顺序是遵循上面的流程图的。

注意:
响应发送到客户端后,请勿调用next.Invoke。 响应开始之后,对HttpResponse的更改将抛出异常。 例如,设置响应头,状态代码等更改将会引发异常。在调用next之后写入响应体。

  • 可能导致协议违规。 例如,写入超过content-length所述内容长度。

  • 可能会破坏响应内容格式。 例如,将HTML页脚写入CSS文件。

HttpResponse.HasStarted是一个有用的提示,指示是否已发送响应头和/或正文已写入。

顺序

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

Startup.Configure方法(如下所示)添加了以下中间件组件:

  1. 异常/错误处理

  2. 静态文件服务

  3. 身份认证

  4. MVC

public void Configure(IApplicationBuilder app)
{
app.UseExceptionHandler("/Home/Error"); // Call first to catch exceptions
// thrown in the following middleware. app.UseStaticFiles(); // Return static files and end pipeline. app.UseAuthentication(); // Authenticate before you access
// secure resources. app.UseMvcWithDefaultRoute(); // Add MVC to the request pipeline.
}

上面的代码,UseExceptionHandler是添加到管道中的第一个中间件组件,因此它捕获以后调用中发生的任何异常。

静态文件中间件在管道中提前调用,因此可以处理请求和短路,而无需通过剩余的组件。 静态文件中间件不提供授权检查。 由其提供的任何文件,包括wwwroot下的文件都是公开的。

如果请求没有被静态文件中间件处理,它将被传递给执行身份验证的Identity中间件(app.UseAuthentication)。 身份不会使未经身份验证的请求发生短路。 虽然身份认证请求,但授权(和拒绝)仅在MVC选择特定的Razor页面或控制器和操作之后才会发生。

授权(和拒绝)仅在MVC选择特定的Razor页面或Controller和Action之后才会发生。

以下示例演示了中间件顺序,其中静态文件的请求在响应压缩中间件之前由静态文件中间件处理。 静态文件不会按照中间件的顺序进行压缩。 来自UseMvcWithDefaultRoute的MVC响应可以被压缩。

public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles(); // Static files not compressed
app.UseResponseCompression();
app.UseMvcWithDefaultRoute();
}

二、Use, Run, 和 Map

你可以使用UseRunMap配置HTTP管道。Use方法可以使管道短路(即,可以不调用下一个请求委托)。Run方法是一个约定, 并且一些中间件组件可能暴露在管道末端运行的Run [Middleware]方法。Map*扩展用作分支管道的约定。映射根据给定的请求路径的匹配来分支请求流水线,如果请求路径以给定路径开始,则执行分支。

public class Startup
{
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>");
});
}
}

下表显示了使用以前代码的 http://localhost:19219 的请求和响应:

请求

响应

localhost:1234

Hello from non-Map delegate.

localhost:1234/map1

Map Test 1

localhost:1234/map2

Map Test 2

localhost:1234/map3

Hello from non-Map delegate.

当使用Map时,匹配的路径段将从HttpRequest.Path中删除,并为每个请求追加到Http Request.PathBase

MapWhen根据给定谓词的结果分支请求流水线。 任何类型为Func<HttpContext,bool>的谓词都可用于将请求映射到管道的新分支。 在以下示例中,谓词用于检测查询字符串变量分支的存在:

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:19219 的请求和响应:

请求

响应

localhost:1234

Hello from non-Map delegate.

localhost:1234/?branch=1

Branch used = master

Map支持嵌套,例如:

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

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

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

三、内置中间件

ASP.NET Core附带以下中间件组件:

中间件

描述

Authentication

提供身份验证支持

CORS

配置跨域资源共享

Response Caching

提供缓存响应支持

Response Compression

提供响应压缩支持

Routing

定义和约束请求路由

Session

提供用户会话管理

Static Files

为静态文件和目录浏览提供服务提供支持

URL Rewriting Middleware

用于重写 Url,并将请求重定向的支持

三、编写中间件

中间件通常封装在一个类中,并使用扩展方法进行暴露。 查看以下中间件,它从查询字符串设置当前请求的Culture:

public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use((context, next) =>
{
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
return next();
}); app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
}); }
}

您可以通过传递Culture来测试中间件,例如 http://localhost:19219/?culture=zh-CN

以下代码将中间件委托移动到一个类:

using Microsoft.AspNetCore.Http;
using System.Globalization;
using System.Threading.Tasks; namespace Culture
{
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next; public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
} public Task Invoke(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
return this._next(context);
}
}
}

以下通过IApplicationBuilder的扩展方法暴露中间件:

using Microsoft.AspNetCore.Builder;

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

以下代码从Configure调用中间件:

public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseRequestCulture(); app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
}); }
}

中间件应该遵循显式依赖原则,通过在其构造函数中暴露其依赖关系。 中间件在应用程序生命周期构建一次。 如果您需要在请求中与中间件共享服务,请参阅以下请求相关性。

中间件组件可以通过构造方法参数来解析依赖注入的依赖关系。 UseMiddleware也可以直接接受其他参数。

四、每个请求的依赖关系

因为中间件是在应用程序启动时构建的,而不是每个请求,所以在每个请求期间,中间件构造函数使用的作用域生命周期服务不会与其他依赖注入类型共享。 如果您必须在中间件和其他类型之间共享作用域服务,请将这些服务添加到Invoke方法的签名中。 Invoke方法可以接受由依赖注入填充的其他参数。 例如:

public class MyMiddleware
{
private readonly RequestDelegate _next; public MyMiddleware(RequestDelegate next)
{
_next = next;
} public async Task Invoke(HttpContext httpContext, IMyScopedService svc)
{
svc.MyProperty = ;
await _next(httpContext);
}
}

参考资料:https://www.cnblogs.com/stulzq/p/7760648.html

Net core学习系列(七)——Net Core中间件的更多相关文章

  1. 【.Net Core 学习系列】-- EF Core 实践(Code First)

    一.开发环境: VS2015, .Net Core 1.0.0-preview2-003156 二解决方案: 新建项目: File --> New --> Project -->   ...

  2. 【.Net Core 学习系列】-- EF Core实践(DB First)

    一.开发环境: VS2015, .Net Core 1.0.0-preview2-003156 二.准备数据: CREATE DATABASE [Blogging]; GO USE [Blogging ...

  3. EntityFramework Core 学习系列(一)Creating Model

    EntityFramework Core 学习系列(一)Creating Model Getting Started 使用Command Line 来添加 Package  dotnet add pa ...

  4. ASP.NET Core学习系列

    .NET Core ASP.NET Core ASP.NET Core学习之一 入门简介 ASP.NET Core学习之二 菜鸟踩坑 ASP.NET Core学习之三 NLog日志 ASP.NET C ...

  5. Net core学习系列(一)——Net Core介绍

    一.什么是Net Core .NET Core是适用于 windows.linux 和 macos 操作系统的免费.开源托管的计算机软件框架,是微软开发的第一个官方版本,具有跨平台 (Windows. ...

  6. Net core学习系列(二)——Net Core项目文件简介

    一. ASP.NET Core 项目文件夹解读 ASP.NET Core 1.0 发布以来,相较于传统项目编码发布的行为,新项目中的操作已经有了很大的变化,如解析依赖,选择运行平台和Runtime等等 ...

  7. 《ASP.NET Core 高性能系列》静态文件中间件

    一.概述 静态文件(如 HTML.CSS.图片和 JavaScript等文件)是 Web程序直接提供给客户端的直接加载的文件. 较比于程序动态交互的代码而言,其实原理都一样(走Http协议), ASP ...

  8. Net core学习系列(十)——Net Core配置

    一.前言 选项(Options)模式是对配置(Configuration)的功能的延伸.在12章(ASP.NET Core中的配置二)Configuration中有介绍过该功能(绑定到实体类.绑定至对 ...

  9. Net core学习系列(八)——Net Core日志

    一.简介# 日志组件,作为程序员使用频率最高的组件,给程序员开发调试程序提供了必要的信息.ASP.NET Core中内置了一个通用日志接口ILogger,并实现了多种内置的日志提供器,例如 Conso ...

随机推荐

  1. Vue事件修饰符,.capture关键字详解

    .prevent 用于阻止默认事件,点击a标签href可以打开相应的链接,那么给事件加    上此关键字,click.prevent .capture 冒泡顺序    例如 div1中嵌套div2中嵌 ...

  2. 线下AWD平台搭建以及一些相关问题解决

    线下AWD平台搭建以及一些相关问题解决 一.前言 文章首发于tools,因为发现了一些新问题但是没法改,所以在博客进行补充. 因为很多人可能没有机会参加线下的AWD比赛,导致缺乏这方面经验,比如我参加 ...

  3. Easy2game使用

    每个独享IP服务器,开设3-5个高速接口IP,用户可自行选择当地连接速度快的接口接入服务器,服务器再为用户自动分配所绑定的独享IP连接至游戏服务器,可保证连接的稳定性,统一性 打开软件 添加程序 服务 ...

  4. Android Studio 导入 Android 系统模块并编译和调试

    FAQ: AS导入系统模块源码,并且能够编译调试,正常查看java doc ???? Android AOSP基础(五)Android Studio调试系统源码的三种方式http://liuwangs ...

  5. Unity编辑器扩展中,使用Unity自带的GUIStyle

    在进行编辑器扩展时,创建组件的方法一般都会提供GUIStyle参数,可以让我们自定义样式.修改背景图,字体大小,颜色等等. 比如,创建Button组件的方法:public static bool Bu ...

  6. CRM BP函数

    REPORT ZCRM_BP_TEST. """""""""""""& ...

  7. kubelet 预留system、kube资源

    kubelet 预留system.kube资源 Kubernetes 的节点可以按照 Capacity 调度.默认情况下 pod 能够使用节点全部可用容量.这是个问题,因为节点自己通常运行了不少驱动 ...

  8. X509Store 类

    标题:X509Store 类 地址:https://docs.microsoft.com/zh-cn/dotnet/api/system.security.cryptography.x509certi ...

  9. 用Python打造了一个渗透测试暴力探测器

    资源探测的作用 通过资源探测,我们可以在目标系统中发现文件.目录.活动.服务还有相关的参数,为下一步的行动提供信息参考. 一个开源的模糊测试数据库 https://github.com/fuzzdb- ...

  10. MySQL 快速删除大量数据

    千万级数据量 方案1. 直接使用delete 因delete执行速度与索引量成正比,若表中索引量较多,使用delete会耗费数小时甚至数天的时间   方案2. (1)创建临时表,表结构与原表结构相同 ...