.Net Core 教程 Part4 – (42)(43)(44)(45)(46)(47)中间件

Part4 – (42) 中间件的概念

Why 中间件

中间件是ASP.NET Core的核心组件,MVC框架、响应缓存、身份验证、CORS、Swagger等都是内置中间件

.Net Core 教程 Part4 – (42)(43)(44)(45)(46)(47)中间件
中间件的工作原理

什么是中间件

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是用来执行最终的核心应用逻辑

.Net Core 教程 Part4 – (42)(43)(44)(45)(46)(47)中间件

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();
.Net Core 教程 Part4 – (42)(43)(44)(45)(46)(47)中间件
执行结果

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中间件提供的功能。

.Net Core 教程 Part4 – (42)(43)(44)(45)(46)(47)中间件
中间件与筛选器

区别

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

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
chaoqiangchaoqiang
上一篇 2022-01-12 07:34
下一篇 2022-01-15 21:46

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

近期个人博客正在迁移中,原博客请移步此处,抱歉!