中间件
概念
我怕我解释不清楚,这里直接上课件



基本使用
我们建立一个空的asp.net core项目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build();
//app.MapGet("/", () => "Hello World!"); app.Map("/test", async (pipeBuilder) => { pipeBuilder.Use(async (context, next) => { context.Response.ContentType = "text/html"; await context.Response.WriteAsync("1 Start <br/>"); await next.Invoke(); await context.Response.WriteAsync("1 End <br/>"); }); pipeBuilder.Use(async (context, next) => { await context.Response.WriteAsync("2 Start <br/>"); await next.Invoke(); await context.Response.WriteAsync("2 End <br/>"); }); pipeBuilder.Run(async (context) => { await context.Response.WriteAsync("Run <br/>"); }); });
app.Run(); }
|
以上仅为了演示,一般都在Run中输出内容,如果在Use中输出了内容,就不要next了,不然容易引起混乱。
简单的自定义中间件
1、如果中间件的代码比较复杂,或者我们需要重复使用一个中间件的话,我们最好把中间件的代码放到一个单独的“中间件类”中。
2、中间件类是一个普通的类,不需要继承任何父类或者实现任何接口,但是这个类需要有一个构造方法,构造方法至少要有一个RequestDelegate类型的参数(就是上面的next),这个参数用来指向下一个中间件。这个类还需要定义一个名字为Invoke或者InvokeAsync的方法,方法至少有一个HttpContext类型的参数,方法的返回值必须是Task类型。中间件类的构造方法和Invoke(或者InvokeAsync)方法还可以定义其他参数,其他参数的值会通过依赖注入自动赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| //自定义中间件类 public class CheckAndParsingMiddleware { private readonly RequestDelegate next; public CheckAndParsingMiddleware(RequestDelegate next) { this.next = next; } public async Task InvokeAsync(HttpContext context) { string password = context.Request.Query["password"]; if(password == "123") { if (context.Request.HasJsonContentType()) { //如果请求中有json就往下执行(浏览器看不出来,去postman上看)
//如果password为123就将请求报文转为dynamic类型 var reqStream = context.Request.BodyReader.AsStream();//转成流的格式 //需要安装Dynamic.json包 //目前(.net6)System.Text.Json不支持把json反序列化为dynamic类型 dynamic? jsonObj = DJson.Parse(reqStream); context.Items["BodyJson"] = jsonObj; } await next.Invoke(context); } else { context.Response.StatusCode = 401; } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| //Program.cs app.Map("/test", async (pipeBuilder) => { pipeBuilder.UseMiddleware<CheckAndParsingMiddleware>(); pipeBuilder.Use(async (context, next) => { context.Response.ContentType = "text/html"; await context.Response.WriteAsync("2 Start <br/>"); await next.Invoke(); await context.Response.WriteAsync("2 End <br/>"); }); pipeBuilder.Run(async (context) => { await context.Response.WriteAsync("Run <br/>"); //context.Item可以在同一个请求中跨中间件进行消息传递 dynamic obj = context.Items["BodyJson"]; if(obj != null) { await context.Response.WriteAsync($"{obj.ToString()}<br/>"); } }); });
|
案例(Markdown渲染中间件)
Markdown不被浏览器支持,所以编写一个在服务器端把Markdown转换为HTML的中间件。
我们开发的中间件是构件在ASP.NET Core内置的StaticFiles中间件之上,并且在它之前运行,所有的*.md文件都被放到wwwroot文件夹下,当我们请求wwwroot下其他静态文件时,StaticFiles中间件会把它们返回给浏览器,而当我们请求wwwroot下的*.md文件时,我们编写的中间件会读取对应的*.md文件,并把它们转换为HTML格式返回给浏览器
检测文本编码:
安装Ude.NetStandard包。
通过CharsetDetector类探测文件编码。
Markdown->HTML:
安装MarkdownSharp包。
通过Markdown实例的Transform()方法将md文本转换为html。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| using MarkdownSharp; using Microsoft.Extensions.Caching.Memory; using System.Text; using Ude;
public class MarkDownViewerMiddleware { private readonly RequestDelegate next; private readonly IWebHostEnvironment hostEnv; private readonly IMemoryCache memCache;
public MarkDownViewerMiddleware(RequestDelegate next, IWebHostEnvironment hostEnv, IMemoryCache memCache) { this.next = next; this.hostEnv = hostEnv; this.memCache = memCache; }
/// <summary> /// 检测流的编码 /// </summary> /// <param name="stream"></param> /// <returns></returns> private static string DetectCharset(Stream stream) { CharsetDetector charDetector = new(); charDetector.Feed(stream); charDetector.DataEnd(); string charset = charDetector.Charset ?? "UTF-8"; stream.Position = 0; return charset; }
/// <summary> /// 读取文本内容 /// </summary> /// <param name="stream"></param> /// <returns></returns> private static async Task<string> ReadText(Stream stream) { string charset = DetectCharset(stream); using var reader = new StreamReader(stream, Encoding.GetEncoding(charset)); return await reader.ReadToEndAsync(); }
public async Task InvokeAsync(HttpContext context) { string path = context.Request.Path.Value ?? ""; if (!path.EndsWith(".md")) { await next(context); return; } var file = hostEnv.WebRootFileProvider.GetFileInfo(path); if (!file.Exists) { await next(context); return; } context.Response.ContentType = $"text/html;charset=UTF-8"; context.Response.StatusCode = 200; string cacheKey = nameof(MarkDownViewerMiddleware) + path + file.LastModified; var html = await memCache.GetOrCreateAsync(cacheKey, async ce => { ce.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1); using var stream = file.CreateReadStream(); string text = await ReadText(stream); Markdown markdown = new Markdown(); return markdown.Transform(text); }); await context.Response.WriteAsync(html); } }
|
1 2 3 4 5
| //Program.cs //注意中间件的位置 app.UseHttpsRedirection(); app.UseMiddleware<MarkDownViewerMiddleware>(); app.UseStaticFiles();
|
中间件和Filter的区别

区别:
1、中间件可以处理所有的请求,而Filter只能处理对控制器的请求;中间件运行在一个更底层、更抽象的级别,因此在中间件中无法处理MVC中间件特有的概念。
2、中间件和Filter可以完成很多相似的功能。“未处理异常中间件”和“未处理异常Filter”;“请求限流中间件”和“请求限流FIlter”
3、优先选择使用中间件;但是如果这个组件只针对MVC或者需要调用一些MVC相关的类的时候,我们就只能选择Filter