中间件

概念

我怕我解释不清楚,这里直接上课件


基本使用

我们建立一个空的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