参考微软官方文档 :
https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1
一、ASP.NET Core 中间件简介
1.1 定义:中间件是一种装配到应用管道以处理请求和响应的软件。
每个组件:
- 选择是否将请求传递到管道中的下一个组件。
- 可在管道中的下一个组件前后执行工作。
请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。
使用 Run 、Map 和 Use 扩展方法来配置请求委托。 可将一个单独的请求委托并行指定为匿名方法(称为并行中间件),或在可重用的类中对其进行定义。 这些可重用的类和并行匿名方法即为中间件,也叫中间件组件。 请求管道中的每个中间件组件负责调用管道中的下一个组件,或使管道短路。 当中间件短路时,它被称为“终端中间件”,因为它阻止中间件进一步处理请求。
将 HTTP 处理程序和模块迁移到 ASP.NET Core 中间件介绍了 ASP.NET Core 和 ASP.NET 4.x 中请求管道之间的差异,并提供了更多的中间件示例。
1.2 使用 IApplicationBuilder 创建中间件管道
ASP.NET Core 请求管道包含一系列请求委托,依次调用。 下图演示了这一概念。 沿黑色箭头执行。
每个委托均可在下一个委托前后执行操作。 应尽早在管道中调用异常处理委托,这样它们就能捕获在管道的后期阶段发生的异常。
尽可能简单的 ASP.NET Core 应用设置了处理所有请求的单个请求委托。 这种情况不包括实际请求管道。 调用单个匿名函数以响应每个 HTTP 请求。
public class Startup { public void Configure(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Hello, World!"); }); } }
用 Use 将多个请求委托链接在一起。 next
参数表示管道中的下一个委托。 可通过不调用 next 参数使管道短路。 通常可在下一个委托前后执行操作,如以下示例所示:
public class Startup { public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { // Do work that doesn't write to the Response. await next.Invoke(); // Do logging or other work that doesn't write to the Response. }); app.Run(async context => { await context.Response.WriteAsync("Hello from 2nd delegate."); }); } }
当委托不将请求传递给下一个委托时,它被称为“让请求管道短路”。 通常需要短路,因为这样可以避免不必要的工作。 例如,静态文件中间件可以处理对静态文件的请求,并让管道的其余部分短路,从而起到终端中间件的作用。 如果中间件添加到管道中,且位于终止进一步处理的中间件前,它们仍处理 next.Invoke
语句后面的代码。 不过,请参阅下面有关尝试对已发送的响应执行写入操作的警告。
警告
在向客户端发送响应后,请勿调用 next.Invoke
。 响应启动后,针对 HttpResponse 的更改将引发异常。 例如,设置标头和状态代码更改将引发异常。 调用 next
后写入响应正文:
- 可能导致违反协议。 例如,写入的长度超过规定的
Content-Length
。 - 可能损坏正文格式。 例如,向 CSS 文件中写入 HTML 页脚。
HasStarted 是一个有用的提示,指示是否已发送标头或已写入正文。
Run 委托不会收到 next
参数。 第一个 Run
委托始终为终端,用于终止管道。 Run
是一种约定。 某些中间件组件可能会公开在管道末尾运行的 Run[Middleware]
方法:
public class Startup { public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { // Do work that doesn't write to the Response. await next.Invoke(); // Do logging or other work that doesn't write to the Response. }); app.Run(async context => { await context.Response.WriteAsync("Hello from 2nd delegate."); }); } }
在前面的示例中,Run
委托将 "Hello from 2nd delegate."
写入响应,然后终止管道。 如果在 Run
委托之后添加了另一个 Use
或 Run
委托,则不会调用该委托。
中间件顺序
下图显示了 ASP.NET Core MVC 和 Razor Pages 应用的完整请求处理管道。 你可以在典型应用中了解现有中间件的顺序,以及在哪里添加自定义中间件。 你可以完全控制如何重新排列现有中间件,或根据场景需要注入新的自定义中间件。
上图中的“终结点”中间件为相应的应用类型(MVC 或 Razor Pages)执行筛选器管道。
向 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?}"); }); }
在上述代码中:
- 在使用单个用户帐户创建新的 Web 应用时未添加的中间件已被注释掉。
- 并非所有中间件都需要准确按照此顺序运行,但许多中间件必须遵循这个顺序。 例如:
UseCors
、UseAuthentication
和UseAuthorization
必须按照上述顺序运行。- 由于此错误,
UseCors
当前必须在UseResponseCaching
之前运行。
在某些场景下,中间件将具有不同的排序。 例如,高速缓存和压缩排序是特定于场景的,存在多个有效的排序。 例如:
app.UseResponseCaching();
app.UseResponseCompression();
使用前面的代码,可以通过缓存压缩的响应来保存 CPU,但你可能最终会使用不同的压缩算法(如 gzip 或 brotli)来缓存资源的多个表示形式。
以下排序结合了静态文件以允许缓存压缩的静态文件:
app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();
以下 Startup.Configure
方法将为常见应用方案添加中间件组件:
- 异常/错误处理
- 当应用在开发环境中运行时:
- 开发人员异常页中间件 (UseDeveloperExceptionPage) 报告应用运行时错误。
- 数据库错误页中间件报告数据库运行时错误。
- 当应用在生产环境中运行时:
- 异常处理程序中间件 (UseExceptionHandler) 捕获以下中间件中引发的异常。
- HTTP 严格传输安全协议 (HSTS) 中间件 (UseHsts) 添加
Strict-Transport-Security
标头。
- 当应用在开发环境中运行时:
- HTTPS 重定向中间件 (UseHttpsRedirection) 将 HTTP 请求重定向到 HTTPS。
- 静态文件中间件 (UseStaticFiles) 返回静态文件,并简化进一步请求处理。
- Cookie 策略中间件 (UseCookiePolicy) 使应用符合欧盟一般数据保护条例 (GDPR) 规定。
- 用于路由请求的路由中间件 (UseRouting)。
- 身份验证中间件 (UseAuthentication) 尝试对用户进行身份验证,然后才会允许用户访问安全资源。
- 用于授权用户访问安全资源的授权中间件 (UseAuthorization)。
- 会话中间件 (UseSession) 建立和维护会话状态。 如果应用使用会话状态,请在 Cookie 策略中间件之后和 MVC 中间件之前调用会话中间件。
- 用于将 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 下的文件。 若要了解如何保护静态文件,请参阅 ASP.NET Core 中的静态文件。
如果静态文件中间件未处理请求,则请求将被传递给执行身份验证的身份验证中间件 (UseAuthentication)。 身份验证不使未经身份验证的请求短路。 虽然身份验证中间件对请求进行身份验证,但仅在 MVC 选择特定 Razor Page 或 MVC 控制器和操作后,才发生授权(和拒绝)。
以下示例演示中间件排序,其中静态文件的请求在响应压缩中间件前由静态文件中间件进行处理。 使用此中间件顺序不压缩静态文件。 可以压缩 Razor Pages 响应。
public void Configure(IApplicationBuilder app) { // Static files aren't compressed by Static File Middleware. app.UseStaticFiles(); app.UseRouting(); app.UseResponseCompression(); app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); }); }
对于单页应用程序 (SPA),SPA 中间件 UseSpaStaticFiles 通常是中间件管道中的最后一个。 SPA 中间件处于最后的作用是:
- 允许所有其他中间件首先响应匹配的请求。
- 允许具有客户端侧路由的 SPA 针对服务器应用无法识别的所有路由运行。
若要详细了解 SPA,请参阅 React 和 Angular 项目模板的指南。
转接头中间件顺序
转接头中间件应在其他中间件之前运行。 此顺序可确保依赖于转接头信息的中间件可以使用标头值进行处理。 若要在诊断和错误处理中间件后运行转接头中间件,请参阅转接头中间件顺序。
对中间件管道进行分支
Map 扩展用作约定来创建管道分支。 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:1234
的请求和响应。
请求 | 响应 |
---|---|
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
中删除匹配的路径段,并针对每个请求将该路径段追加到 HttpRequest.PathBase
。
Map
支持嵌套,例如:
app.Map("/level1", level1App => { level1App.Map("/level2a", level2AApp => { // "/level1/level2a" processing }); level1App.Map("/level2b", level2BApp => { // "/level1/level2b" processing }); });
此外,Map
还可同时匹配多个段:
public class Startup { private static void HandleMultiSeg(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Map multiple segments."); }); } public void Configure(IApplicationBuilder app) { app.Map("/map1/seg1", HandleMultiSeg); app.Run(async context => { await context.Response.WriteAsync("Hello from non-Map delegate."); }); } }
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
的请求和响应:
请求 | 响应 |
---|---|
localhost:1234 | Hello from non-Map delegate. |
localhost:1234/?branch=master | Branch used = master |
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
,则在重新加入主管道之前会记录其值。
内置中间件
ASP.NET Core 附带以下中间件组件。 “顺序”列提供备注,以说明中间件在请求处理管道中的放置,以及中间件可能会终止请求处理的条件。 如果中间件让请求处理管道短路,并阻止下游中间件进一步处理请求,它被称为“终端中间件”。 若要详细了解短路,请参阅使用 IApplicationBuilder 创建中间件管道部分。
中间件 | 描述 | 顺序 |
---|---|---|
身份验证 | 提供身份验证支持。 | 在需要 HttpContext.User 之前。 OAuth 回叫的终端。 |
授权 | 提供身份验证支持。 | 紧接在身份验证中间件之后。 |
Cookie 策略 | 跟踪用户是否同意存储个人信息,并强制实施 cookie 字段(如 secure 和 SameSite )的最低标准。 |
在发出 cookie 的中间件之前。 示例:身份验证、会话、MVC (TempData)。 |
CORS | 配置跨域资源共享。 | 在使用 CORS 的组件之前。 由于此错误,UseCors 当前必须在 UseResponseCaching 之前运行。 |
诊断 | 提供新应用的开发人员异常页、异常处理、状态代码页和默认网页的几个单独的中间件。 | 在生成错误的组件之前。 异常终端或为新应用提供默认网页的终端。 |
转接头 | 将代理标头转发到当前请求。 | 在使用已更新字段的组件之前。 示例:方案、主机、客户端 IP、方法。 |
运行状况检查 | 检查 ASP.NET Core 应用及其依赖项的运行状况,如检查数据库可用性。 | 如果请求与运行状况检查终结点匹配,则为终端。 |
标头传播 | 将 HTTP 标头从传入的请求传播到传出的 HTTP 客户端请求中。 | |
HTTP 方法重写 | 允许传入 POST 请求重写方法。 | 在使用已更新方法的组件之前。 |
HTTPS 重定向 | 将所有 HTTP 请求重定向到 HTTPS。 | 在使用 URL 的组件之前。 |
HTTP 严格传输安全性 (HSTS) | 添加特殊响应标头的安全增强中间件。 | 在发送响应之前,修改请求的组件之后。 示例:转接头、URL 重写。 |
MVC | 用 MVC/Razor Pages 处理请求。 | 如果请求与路由匹配,则为终端。 |
OWIN | 与基于 OWIN 的应用、服务器和中间件进行互操作。 | 如果 OWIN 中间件处理完请求,则为终端。 |
响应缓存 | 提供对缓存响应的支持。 | 在需要缓存的组件之前。 UseCORS 必须在 UseResponseCaching 之前。 |
响应压缩 | 提供对压缩响应的支持。 | 在需要压缩的组件之前。 |
请求本地化 | 提供本地化支持。 | 在对本地化敏感的组件之前。 |
终结点路由 | 定义和约束请求路由。 | 用于匹配路由的终端。 |
SPA | 通过返回单页应用程序 (SPA) 的默认页面,在中间件链中处理来自这个点的所有请求 | 在链中处于靠后位置,因此其他服务于静态文件、MVC 操作等内容的中间件占据优先位置。 |
会话 | 提供对管理用户会话的支持。 | 在需要会话的组件之前。 |
静态文件 | 为提供静态文件和目录浏览提供支持。 | 如果请求与文件匹配,则为终端。 |
URL 重写 | 提供对重写 URL 和重定向请求的支持。 | 在使用 URL 的组件之前。 |
WebSockets | 启用 WebSockets 协议。 | 在接受 WebSocket 请求所需的组件之前。 |
二、写入自定义 ASP.NET Core 中间件
首先创建一个ASP.NET Core 3.1 的WebApi 应用程序 MiddlewareDemo的项目
中间件类
通常,中间件封装在类中,并且通过扩展方法公开,在Startup类中Configure方法如下: 如果中间件比较简单可以直接下面使用 app.Use 或者app.Run
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.Use(async (context, next) => { var cultureQuery = context.Request.Query["culture"]; if (!string.IsNullOrWhiteSpace(cultureQuery)) { await context.Response.WriteAsync($"{cultureQuery} \t "); }//调用管道中的下一个委托/中间件 await next(); }); app.Run(async(context)=> { await context.Response.WriteAsync("Hello, World!"); }); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
运行项目首先会在页面显示 Hello, World! ,然后在当前路径后面加入?culture=Dean 页面会显示 Dean Hello, World!
当中间件的业务比较复杂,代码量很大的时候,通常要中间件将委托移动到类:
1、新建一个类RequestCultureMiddleware 代码如下:
using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading.Tasks; namespace MiddlewareDemo { 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)) { await context.Response.WriteAsync($"{cultureQuery} \t "); //var culture = new CultureInfo(cultureQuery); //CultureInfo.CurrentCulture = culture; //CultureInfo.CurrentUICulture = culture; } // Call the next delegate/middleware in the pipeline await _next(context); } } }
注意必须包括中间件类:
- 具有类型为 RequestDelegate 的参数的公共构造函数。
- 名为
Invoke
或InvokeAsync
的公共方法。 此方法必须:- 返回
Task
。 - 接受类型 HttpContext 的第一个参数。
- 返回
构造函数和 Invoke
/InvokeAsync
的其他参数由依赖关系注入 (DI) 填充。
2、 把在上面的Configure方法里的app.Use() 里的中间件注释掉 然后改成
app.UseMiddleware<RequestCultureMiddleware>();
运行后,结果不变
中间件依赖项
中间件应通过在其构造函数中公开其依赖项来遵循显式依赖项原则。 在每个应用程序生存期构造一次中间件。
中间件组件可通过构造函数参数从依赖关系注入 (DI) 解析其依赖项。 UseMiddleware<T> 也可直接接受其他参数。
按请求中间件依赖项
由于中间件是在应用启动时构造的,而不是按请求构造的,因此在每个请求过程中,中间件构造函数使用的范围内生存期服务不与其他依赖关系注入类型共享。 如果必须在中间件和其他类型之间共享范围内服务,请将这些服务添加到 Invoke
方法的签名。 Invoke
方法可接受由 DI 填充的其他参数:
1、新建一个类代码如下:
1 #region 按请求中间件依赖项 2 3 public class GetValue 4 { 5 public static int Value { get; set; } 6 } 7 8 public interface IMyScopedService 9 { 10 11 void SetMyProperty(int value); 12 13 } 14 15 public class MyScopedService : IMyScopedService 16 { 17 public void SetMyProperty(int value) 18 { 19 GetValue.Value = value; 20 } 21 } 22 23 public class CustomMiddleware 24 { 25 private readonly RequestDelegate _next; 26 27 public CustomMiddleware(RequestDelegate next) 28 { 29 _next = next; 30 } 31 32 // IMyScopedService is injected into Invoke 33 public async Task Invoke(HttpContext httpContext, IMyScopedService svc) 34 { 35 svc.SetMyProperty(1000); 36 string str = $" GetValue.Value : { GetValue.Value } \t"; 37 await httpContext.Response.WriteAsync(str, Encoding.UTF8); 38 await _next(httpContext); 39 } 40 } 41 42 43 #endregion
2、在Startup类Configure方法注释掉 app.UseMiddleware<RequestCultureMiddleware>();
3、在注释掉 app.UseMiddleware<RequestCultureMiddleware>(); 下添加下面的
app.UseMiddleware<CustomMiddleware>();
4、运行后页面打印
GetValue.Value : 1000 Hello, World!
中间件扩展方法通过 IApplicationBuilder 公开中间件:
using Microsoft.AspNetCore.Builder; namespace Culture { public static class RequestCultureMiddlewareExtensions { public static IApplicationBuilder UseRequestCulture( this IApplicationBuilder builder) { return builder.UseMiddleware<RequestCultureMiddleware>(); } } }
以下代码通过 Startup.Configure
调用中间件:
public class Startup { public void Configure(IApplicationBuilder app) { app.UseRequestCulture(); app.Run(async (context) => { await context.Response.WriteAsync( $"Hello {CultureInfo.CurrentCulture.DisplayName}"); }); } }
三、基于工厂的中间件(ASP.NET Core 中基于工厂的中间件激活)
参考文档:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/extensibility?view=aspnetcore-3.1
IMiddlewareFactory/IMiddleware 是中间件激活的扩展点。
UseMiddleware 扩展方法检查中间件的已注册类型是否实现 IMiddleware。 如果是,则使用在容器中注册的 IMiddlewareFactory 实例来解析 IMiddleware 实现,而不使用基于约定的中间件激活逻辑。 中间件在应用的服务容器中注册为作用域或瞬态服务。
优点:
- 按客户端请求(作用域服务的注入)激活
- 让中间件强类型化
IMiddleware 按客户端请求(连接)激活,因此作用域服务可以注入到中间件的构造函数中。
IMiddleware
IMiddleware 定义应用的请求管道的中间件。 InvokeAsync(HttpContext, RequestDelegate) 方法处理请求,并返回代表中间件执行的 Task。
3.1使用约定激活的中间件:
1 using Microsoft.AspNetCore.Http; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Threading.Tasks; 6 7 namespace MiddlewareDemo 8 { 9 public class ConventionalMiddleware 10 { 11 private readonly RequestDelegate _next; 12 13 public ConventionalMiddleware(RequestDelegate next) 14 { 15 _next = next; 16 } 17 18 public async Task InvokeAsync(HttpContext context, AppDbContext db) 19 { 20 var keyValue = context.Request.Query["key"]; 21 22 if (!string.IsNullOrWhiteSpace(keyValue)) 23 { 24 db.Add(new Request() 25 { 26 DT = DateTime.UtcNow, 27 MiddlewareActivation = "ConventionalMiddleware", 28 Value = keyValue 29 }); 30 await context.Response.WriteAsync($"ConventionalMiddleware count : {DB.DbList.Count.ToString()} \t"); 31 //await db.SaveChangesAsync(); 32 } 33 34 await _next(context); 35 } 36 } 37 38 39 }
3.2使用 MiddlewareFactory 激活的中间件:
1 using Microsoft.AspNetCore.Http; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Threading.Tasks; 6 7 namespace MiddlewareDemo 8 { 9 public class FactoryActivatedMiddleware : IMiddleware 10 { 11 private readonly AppDbContext _db; 12 13 public FactoryActivatedMiddleware(AppDbContext db) 14 { 15 _db = db; 16 } 17 18 public async Task InvokeAsync(HttpContext context, RequestDelegate next) 19 { 20 var keyValue = context.Request.Query["key"]; 21 22 if (!string.IsNullOrWhiteSpace(keyValue)) 23 { 24 _db.Add(new Request() 25 { 26 DT = DateTime.UtcNow, 27 MiddlewareActivation = "FactoryActivatedMiddleware", 28 Value = keyValue 29 }); 30 31 await context.Response.WriteAsync($"FactoryActivatedMiddleware count : {DB.DbList.Count.ToString()} \t"); 32 //await _db.SaveChangesAsync(); 33 } 34 35 await next(context); 36 } 37 } 38 }
3.3创建AppDbContext类,代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace MiddlewareDemo { public class AppDbContext { public void Add(Request request) { DB.DbList.Add(request); } } public class DB { public static List<Request> DbList = new List<Request>(); } }
添加Request类
public class Request { public DateTime DT { get; set; } public string MiddlewareActivation { get; set; } public string Value { get; set; } }
3.4创建一个IApplicationBuilder的扩展方法MiddlewareExtensions,代码如下:
1 using Microsoft.AspNetCore.Builder; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Threading.Tasks; 6 7 namespace MiddlewareDemo 8 { 9 public static class MiddlewareExtensions 10 { 11 public static IApplicationBuilder UseConventionalMiddleware( 12 this IApplicationBuilder builder) 13 { 14 return builder.UseMiddleware<ConventionalMiddleware>(); 15 } 16 17 public static IApplicationBuilder UseFactoryActivatedMiddleware( 18 this IApplicationBuilder builder) 19 { 20 return builder.UseMiddleware<FactoryActivatedMiddleware>(); 21 } 22 23 // public static IApplicationBuilder UseFactoryActivatedMiddleware( 24 //this IApplicationBuilder builder, bool option) 25 // { 26 // // Passing 'option' as an argument throws a NotSupportedException at runtime. 27 // return builder.UseMiddleware<FactoryActivatedMiddleware>(option); 28 // } 29 } 30 }
3.5在Startup类ConfigureServices方法中注入下面的
services.AddScoped<AppDbContext>();
services.AddTransient<FactoryActivatedMiddleware>();
3.6在Startup类Configure方法中添加下面的
#region 基于工厂的中间件 app.UseConventionalMiddleware(); app.UseFactoryActivatedMiddleware(); //app.UseFactoryActivatedMiddleware(false); #endregion
运行程序,在程序后面加入参数 例如:https://localhost:44349/weatherforecast?key=p
结果为: web页面显示
ConventionalMiddleware count : 1 FactoryActivatedMiddleware count : 2 Hello, World!
代码地址:https://github.com/hudean/MiddlewareDemo.git