Part4 – (42) 中间件的概念
Why 中间件
中间件是ASP.NET Core的核心组件,MVC框架、响应缓存、身份验证、CORS、Swagger等都是内置中间件。
什么是中间件
1、广义上来讲:Tomcat、WebLogic、Redis、IIS;狭义上来讲,ASP.NET Core中的中间件指ASP.NET Core中的一个组件。
2、中间件由前逻辑、next、后逻辑3部分组成,前逻辑为第一段要执行的逻辑代码、next为指向下一个中间件的调用、后逻辑为从下一个中间件执行返回所执行的逻辑代码。每个HTTP请求都要经历一系列中间件的处理,每个中间件对于请求进行特定的处理后,再转到下一个中间件,最终的业务逻辑代码执行完成后,响应的内容也会按照处理的相反顺序进行处理,然后形成HTTP响应报文返回给客户端。
3、中间件组成一个管道,整个ASP.NET Core的执行过程就是HTTP请求和响应按照中间件组装的顺序在中间件之间流转的过程。开发人员可以对组成管道的中间件按照需要进行自由组合。
中间件的三个概念
Map、Use和Run。Map用来定义一个管道可以处理哪些请求,Use和Run用来定义管道,一个管道由若干个Use和一个Run组成,每个Use引入一个中间件,而Run是用来执行最终的核心应用逻辑。
Part4 – (43) 中间件的基本使用
创建一个空的.Net Core项目,看看里面有什么?
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapGet("/zcq", () => "Hello ZCQ!");
app.Run();
简单的自定义中间件
为了能够更清晰地了解中间件,我们创建一个空的ASP.NET Core项目,然后手动添加中间件。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Map("/test", async appbuilder => {
appbuilder.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/>");
});
appbuilder.Use(async (context, next) => {
await context.Response.WriteAsync("2 Start<br/>");
await next.Invoke();
await context.Response.WriteAsync("2 End<br/>");
});
//最后一个中间件
appbuilder.Run(async ctx => {
await ctx.Response.WriteAsync("hello middleware <br/>");
});
});
app.Run();
Part4 – (44) 中间件类
简单的自定义中间件
1、如果中间件的代码比较复杂,或者我们需要重复使用一个中间件的话,我们最好把中间件的代码放到一个单独的“中间件类”中。
2、中间件类是一个普通的.NET类,它不需要继承任何父类或者实现任何接口,但是这个类需要有一个构造方法,构造方法至少要有一个RequestDelegate类型的参数,这个参数用来指向下一个中间件。这个类还需要定义一个名字为Invoke或InvokeAsync的方法,方法至少有一个HttpContext类型的参数,方法的返回值必须是Task类型。中间件类的构造方法和Invoke(或InvokeAsync)方法还可以定义其他参数,其他参数的值会通过依赖注入自动赋值。
检查请求中是否有password=123的查询字符串,而且会把请求报文体按照Json格式尝试解析为dynamic类型的对象,并且把dynamic对象放入context.Items中供后续的中间件或者Run使用。
public class CheckAndParsingMiddleware
{
private readonly RequestDelegate next;
//至少要有一个RequestDelegate类型的参数在构造函数中
public CheckAndParsingMiddleware(RequestDelegate next)
{
this.next = next;
}
//至少要有一个InvokeAsync方法
public async Task InvokeAsync(HttpContext context)
{
string pwd = context.Request.Query["password"];
if (pwd == "123")
{
if (context.Request.HasJsonContentType())
{
var reqStream = context.Request.BodyReader.AsStream();
dynamic? jsonObj = DJson.Parse(reqStream);//需要安装Dynamic.Json这个Nuget包 把Json反序列化成dynamic类型
context.Items["BodyJson"] = jsonObj;
}
await next(context);
}
else context.Response.StatusCode = 401;
}
}
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Map("/test", async appbuilder => {
appbuilder.UseMiddleware<CheckAndParsingMiddleware>();
appbuilder.Run(async ctx => {
ctx.Response.ContentType = "text/html";
ctx.Response.StatusCode = 200;
dynamic? jsonObj = ctx.Items["BodyJson"];//取出来 dynamic
int i = jsonObj.i;
int j = jsonObj.j;
await ctx.Response.WriteAsync($"{i}+{j}={i+j}");
});
});
app.Run();
Part4 – (45) 自己动手模仿Web API中间件
框架由MyStaticFilesMiddleware、MyWebAPIMiddleware、NotFoundMiddleware这3个中间件组成。
using MiniWebAPI;
using MiniWebAPIDemo1;
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
ActionLocator locator = new ActionLocator(services, Assembly.GetEntryAssembly()!);
services.AddSingleton(locator);
services.AddMemoryCache();
ActionFilters.Filters.Add(new MyActionFilter1());
var app = builder.Build();
app.UseMiddleware<MyStaticFilesMiddleware>();
app.UseMiddleware<MyWebAPIMiddleware>();
app.UseMiddleware<NotFoundMiddleware>();
app.Run();
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
namespace MiniWebAPI
{
/// <summary>
/// 对网站中的静态文件进行处理的中间件
/// </summary>
public class MyStaticFilesMiddleware
{
private readonly RequestDelegate next;
private readonly IWebHostEnvironment hostEnv;
public MyStaticFilesMiddleware(RequestDelegate next, IWebHostEnvironment hostEnv)
{
this.next = next;
this.hostEnv = hostEnv;
}
public async Task InvokeAsync(HttpContext context)
{
string path = context.Request.Path.Value ?? "";
//根据路径获取wwwroot文件夹下的静态文件
var file = hostEnv.WebRootFileProvider.GetFileInfo(path);
if (!file.Exists||!ContentTypeHelper.IsValid(file))
{
await next(context);
return;
}
context.Response.ContentType = ContentTypeHelper.GetContentType(file);
context.Response.StatusCode = 200;
using var stream = file.CreateReadStream();
byte[] bytes = await ToArrayAsync(stream);
await context.Response.Body.WriteAsync(bytes);
}
private static async Task<byte[]> ToArrayAsync(Stream stream)
{
using MemoryStream memStream = new MemoryStream();
await stream.CopyToAsync(memStream);
memStream.Position = 0;
byte[] bytes = memStream.ToArray();
return bytes;
}
}
}
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
using System.Text.Json;
namespace MiniWebAPI
{
/**Web API的核心类
* 对于MVC的控制器类做如下简单的约定:
*1)控制器的类名以Controller结尾,除去结尾的Controller就是控制器的名字;
*2)控制器中所有的public方法就是控制器方法,方法的名字就是控制器的名字;
*3)请求路径固定为”/控制器名字/控制方法名字”;
*4)控制器的方法不支持重载,不支持通过[HttpGet]等方式绑定特定的Http谓词;
*5)控制器的方法可以没有参数,如果有参数就只支持一个参数。控制器方法的参数除非是HttpContext类型,否则请求报文体会按照Json格式进行反序列化;
*6)控制器的方法的返回值会被序列化为Json字符串,然后发送到响应报文中,不支持IActionResult等类型的返回值;
*/
public class MyWebAPIMiddleware
{
private readonly RequestDelegate next;
private readonly ActionLocator actionLocator;
public MyWebAPIMiddleware(RequestDelegate next, ActionLocator actionLocator)
{
this.next = next;
this.actionLocator = actionLocator;
}
//在运行的时候才能拿到能用的IServiceProvider
//所以IServiceProvider不通过构造函数注入
public async Task InvokeAsync(HttpContext context, IServiceProvider sp)
{
(bool ok,string? ctrlName,string? actionName)=PathParser.Parse(context.Request.Path);
if(ok==false)
{
await next(context);
return;
}
//使用控制器的名字和操作方法的名字来加载控制器方法对应的MethodInfo类型的对象
var actionMethod = actionLocator.LocateActionMethod(ctrlName!, actionName!);
if(actionMethod ==null)
{
await next(context);
return;
}
Type controllerType = actionMethod.DeclaringType!;
object controllerInstance = sp.GetRequiredService(controllerType);
var paraValues = BindingHelper.GetParameterValues(context, actionMethod);
foreach(var filter in ActionFilters.Filters)
{
filter.Execute();
}
var result = actionMethod.Invoke(controllerInstance, paraValues);
//限定返回值只能是普通类型,不能是IActionResult等
string jsonStr = JsonSerializer.Serialize(result);
context.Response.StatusCode = 200;
context.Response.ContentType = "application/json; charset=utf-8";
await context.Response.WriteAsync(jsonStr);
}
}
}
using Microsoft.AspNetCore.Http;
namespace MiniWebAPI
{
/// <summary>
/// 如果一个请求不能被管道中任何一个中间件处理,也就是请求的地址不存在,
/// 则ASP.NET Core会向客户端写入状态码为404的响应。
/// 为了能够显示自定义的报错信息,我开发了这个中间件类。
/// 我们一般把其他中间件放到前面,而把NotFoundMiddleware放到管道中的最后。
/// 这样,如果任何一个中间件能够处理这个请求,这个请求都会被处理,
/// 然后短路请求,而如果没有任何一个中间件能够处理这个请求,
/// 这个请求就会最终由NotFoundMiddleware处理。
/// </summary>
public class NotFoundMiddleware
{
private readonly RequestDelegate next;
public NotFoundMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task InvokeAsync(HttpContext context)
{
context.Response.StatusCode = 404;
context.Response.ContentType = "text/html;charset=utf-8";
await context.Response.WriteAsync("请求来到了一片未知的荒原");
}
}
}
最后,在控制器中可以正常使用:
using Microsoft.Extensions.Caching.Memory;
using 简单的mvc框架.Models;
namespace abc
{
public class Test1Controller
{
private IMemoryCache memoryCache;
public Test1Controller(IMemoryCache memoryCache)
{
this.memoryCache = memoryCache;
}
public Person IncAge(Person p)
{
p.Age++;
return p;
}
public object[] Get2(HttpContext ctx)
{
DateTime now = memoryCache.GetOrCreate("Now", e => DateTime.Now);
string name = ctx.Request.Query["name"];
return new object[] { "hello" + name, now };
}
}
}
Part4 – (46) Markdown转换器中间件
需求:
1、什么是Markdown;不被浏览器支持;所以,编写一个在服务器端把Markdown转换为HTML的中间件。
2、我们开发的中间件是构建在ASP.NET Core内置的StaticFiles中间件之上,并且在它之前运行,所有的*.md文件都被放到wwwroot文件夹下,当我们请求wwwroot下其他的静态文件的时候,StaticFiles中间件会把它们返回给浏览器,而当我们请求wwwroot下的*.md文件的时候,我们编写的中间件会读取对应的*.md文件并且把它们转换为HTML格式返回给浏览器。
文本编码检测
调用Ude.NetStandard这个NuGet包中的CharsetDetector类来探测文件的编码。
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;
}
// 检测流的编码
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;
}
// 读取文本内容
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);
}
}
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMiddleware<MarkDownViewerMiddleware>();//放在静态文件的前面
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Part4 – (47) Filter与Middleware的区别
关系:
中间件是ASP.NET Core这个基础提供的功能,而Filter是ASP.NET Core MVC中提供的功能。ASP.NET Core MVC是由MVC中间件提供的框架,而Filter属于MVC中间件提供的功能。
区别:
1、中间件可以处理所有的请求,而Filter只能处理对控制器的请求;中间件运行在一个更底层、更抽象的级别,因此在中间件中无法处理MVC中间件特有的概念。
2、中间件和Filter可以完成很多相似的功能。“未处理异常中间件”和“未处理异常Filter”;“请求限流中间件”和“请求限流Filter”的区别。
3、优先选择使用中间件;但是如果这个组件只针对MVC或者需要调用一些MVC相关的类的时候,我们就只能选择Filter。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MiniWebAPI
{
public class ActionFilters
{
public static List<IMyActionFilter> Filters = new List<IMyActionFilter>();
}
public interface IMyActionFilter
{
void Execute();
}
}
using MiniWebAPI;
namespace MiniWebAPIDemo1
{
public class MyActionFilter1 : IMyActionFilter
{
public void Execute()
{
Console.WriteLine("Filter 1执行啦");
}
}
}
foreach(var filter in ActionFilters.Filters)
{
filter.Execute();
}
using MiniWebAPI;
using MiniWebAPIDemo1;
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
ActionLocator locator = new ActionLocator(services, Assembly.GetEntryAssembly()!);
services.AddSingleton(locator);
services.AddMemoryCache();
ActionFilters.Filters.Add(new MyActionFilter1()); //自己框架内实现Filter 中间件内部实现
var app = builder.Build();
app.UseMiddleware<MyStaticFilesMiddleware>();
app.UseMiddleware<MyWebAPIMiddleware>();
app.UseMiddleware<NotFoundMiddleware>();
app.Run();
本文版权归个人技术分享站点所有,发布者:chaoqiang,转转请注明出处:https://www.zhengchaoqiang.com/1545.html