跟我一起学.NetCore之中间件(Middleware)应用和自定义

前言

Asp.NetCore中的请求管道是通过一系列的中间件组成的,使得请求会根据需求进行对应的过滤和加工处理。在平时开发中会时常引用别人定义好的中间件,只需简单进行app.Usexxx就能完成中间件的注册,但是对于一些定制化需求还得自己进行处理和封装,以下说说中间件的注册应用和自定义中间件;

正文

在上一小节中有简单提到,当注册第三方封装的中间件时,其实本质还是调用了IApplicationBuilder的Use方法;而在开发过程中,会使用以下三种方式进行中间件的注册:

  • Use:通过Use的方式注册中间件,可以控制是否将请求传递到下一个中间件;

  • Run:通过Run的方式注册中间件,一般用于断路或请求管道末尾,即不会将请求传递下去;

  • Map/MapWhen:请求管道中增加分支,条件满足之后就由分支管道进行处理,而不会切换回主管道;Map用于请求路径匹配,而MapWhen可以有更多的条件进行过滤;

  • UseMiddleWare : 一般用于注册自定义封装的中间件,内部其实是使用Use的方式进行中间件注册;

相信都知道我的套路了,光说不练假把式,来一个Asp.NetCore API项目进行以上几种中间件注册方式演示:

图中代码部分将原先默认注册的中间件删除了,用Use和Run的方式分别注册了两个中间件(这里只是简单的显示文字,里面可以根据需求添加相关逻辑),其中用Use注册的方式在上一节中已经提及到,直接将中间件添加链表中,这里就不再赘述了;

对于使用Run方式注册中间,小伙伴们肯定不甘心止于此吧,所以这里直接看Run是如何实现:

namespace Microsoft.AspNetCore.Builder
{public static class RunExtensions{// 也是一个扩展方法,但参数就是一个委托public static void Run(this IApplicationBuilder app, RequestDelegate handler){// 参数校验,如果null就抛出异常if (app == null){throw new ArgumentNullException(nameof(app));}// 传入的委托校验,如果null也是抛出异常if (handler == null){throw new ArgumentNullException(nameof(handler));}// 这里其实只有一个 RequestDelegate执行逻辑,并没有传递功能// 本质也是使用方法Useapp.Use(_ => handler);}}
}

通过代码可知,用Run方式只是将处理逻辑RequestDelegate传入,并没有传递的逻辑,所以Run注册的中间件就会形成断路,导致后面的中间件不能再执行了;

使用Map和MapWhen注册的方式,其实是给管道开一个分支,就像高速公路一样,有匝道,到了对应出口就进匝道了,就不能倒车回来了(倒回来扣你12分);同样,请求管道也是,当条件满足时,请求就走Map对应的分支管道,就不能重新返回主管道了;

代码走一波,在注册中间件的地方增加Map的使用:

Configure全部代码如下:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{// 使用Use注册中间 app.Use(async (context, next) => {await context.Response.WriteAsync("Hello Use1\r\n");// 将请求传递到下一个中间件await next();await context.Response.WriteAsync("Hello Use1 Response\r\n");});// 使用Use注册中间   参数类型不一样app.Use(requestDelegate =>{return async (context) =>{await context.Response.WriteAsync("Hello Use2\r\n");// 将请求传递到下一个中间件await requestDelegate(context);await context.Response.WriteAsync("Hello Use2 Response\r\n");};});// 分支管道,只有匹配到路径才走分支管道app.Map("/Hello", builder =>{builder.Use(async (context, next) =>{await context.Response.WriteAsync("Hello MapUse\r\n");// 将请求传递到分支管道的下一个中间件await next();await context.Response.WriteAsync("Hello MapUse Response\r\n");});// 注册分支管道中间件builder.Run(async context => {await context.Response.WriteAsync("Hello MapRun1~~~\r\n");});// 注册分支管道中间件builder.Run(async context => {await context.Response.WriteAsync("Hello MapRun2~~~\r\n");});});// 使用Run app.Run(async context => {await context.Response.WriteAsync("Hello Run~~~\r\n");});//使用Run注册app.Run(async context => {await context.Response.WriteAsync("Hello Code综艺圈~~~\r\n");});
}

执行看效果:

Map方式注册的分支管道只有路径匹配了才走,否则都会走主管道;

仔细的小伙伴肯定会说,那是在分支管道上用了Run注册中间件了,形成了断路,所以导致不能执行主管道剩下的中间件,好,那我们稍微改改代码:

这样运行访问分支管道时会报错,因为分支管道中没有下一个中间件了,还调用下一个中间件,那肯定有问题;

改了改,如下运行:

进入匝道还想倒回来,12分不要了吗,哈哈哈;

MapWhen注册的分支管道逻辑和Map差不多类似,只是匹配的条件更加灵活而已,可以根据自己需求进行调节匹配,如下:

看到这,小伙伴应该都知道,接下来肯定不会放过Map/MapWhen的实现:

  • Map

    public static IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, Action<IApplicationBuilder> configuration)
    {// 进行参数校验 IApplicationBuilder对象不能为空if (app == null){throw new ArgumentNullException(nameof(app));}// 进行参数校验 传进的委托对象不能为空if (configuration == null){throw new ArgumentNullException(nameof(configuration));}// 匹配的路径末尾不能有"/",否则就抛异常if (pathMatch.HasValue && pathMatch.Value.EndsWith("/", StringComparison.Ordinal)){throw new ArgumentException("The path must not end with a '/'", nameof(pathMatch));}// 克隆一个IApplicationBuilder,共用之前的属性,这里其实创建了分支管道var branchBuilder = app.New();// 将创建出来的branchBuilder进行相关配置configuration(branchBuilder);// 构造出分支管道var branch = branchBuilder.Build();// 将构造出来的管道和匹配路径进行封装var options = new MapOptions{Branch = branch,PathMatch = pathMatch,};// 注册中间件return app.Use(next => new MapMiddleware(next, options).Invoke);
    }// MapMiddleware 的Invoke方法,及如何进入分支管道处理的
    public async Task Invoke(HttpContext context)
    {// 参数判断if (context == null){throw new ArgumentNullException(nameof(context));}PathString matchedPath;PathString remainingPath;// 判断是否匹配路径,如果匹配上就进入分支if (context.Request.Path.StartsWithSegments(_options.PathMatch, out matchedPath, out remainingPath)){// 更新请求地址var path = context.Request.Path;var pathBase = context.Request.PathBase;context.Request.PathBase = pathBase.Add(matchedPath);context.Request.Path = remainingPath;try{// 进入分支管道await _options.Branch(context);}finally{// 恢复原先请求地址,回到主管道之后,并没有进行主管道也下一个中间件的传递,所以主管道后续不在执行context.Request.PathBase = pathBase;context.Request.Path = path;}}else{// 匹配不到路径就继续主管道执行await _next(context);}
    }
    
  • MapWhen:其实和Map差不多,只是传入的匹配规则不一样,比较灵活:

    public static IApplicationBuilder MapWhen(this IApplicationBuilder app, Predicate predicate, Action<IApplicationBuilder> configuration)
    {if (app == null){throw new ArgumentNullException(nameof(app));}if (predicate == null){throw new ArgumentNullException(nameof(predicate));}if (configuration == null){throw new ArgumentNullException(nameof(configuration));}// 构建分支管道,和Map一致var branchBuilder = app.New();configuration(branchBuilder);var branch = branchBuilder.Build();// 封装匹配规则var options = new MapWhenOptions{Predicate = predicate,Branch = branch,};// 注册中间件return app.Use(next => new MapWhenMiddleware(next, options).Invoke);
    }
    // MapWhenMiddleware 的Invoke方法
    public async Task Invoke(HttpContext context)
    {// 参数校验if (context == null){throw new ArgumentNullException(nameof(context));}// 判断是否匹配规则,如果匹配就进入分支管道if (_options.Predicate(context)){await _options.Branch(context);}else{// 没有匹配就继续执行主管道await _next(context);}
    }

现在是不是清晰明了多了,不懵了吧;还没完呢,继续往下;

上面注册中间件的方式是不是有点不那么好看,当中间件多了时候,可读性很是头疼,维护性也得花点功夫,所以微软肯定想到这了,提供了类的方式进行中间件的封装(但是要按照约定来),从而可以像使用第三方中间件那样简单,如下:

使用及运行:

是不是自定义也没想象中那么难,其中注册封装的中间件时,在扩展方法中使用了app.UseMiddleware<T>()进行注册,这个上一节中提到过,就是那段在上一节中有点嫌早的代码,这里就拷过来了(偷个懒):

// 看着调用的方法
public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args)
{// 内部调用了以下方法return app.UseMiddleware(typeof(TMiddleware), args);
}
// 其实这里是对自定义中间件的注册,这里可以不用太深入了解
public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
{if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo())){// IMiddleware doesn't support passing args directly since it's// activated from the containerif (args.Length > 0){throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));}return UseMiddlewareInterface(app, middleware);}// 取得容器var applicationServices = app.ApplicationServices;// 反编译进行包装成注册中间件的样子(Func<ReuqestDelegate,RequestDelegate>),但可以看到本质使用IApplicationBuilder中Use方法return app.Use(next =>{// 获取指定类型中的方法列表var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);// 找出名字是Invoke或是InvokeAsync的方法var invokeMethods = methods.Where(m =>string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)|| string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)).ToArray();// 如果有多个方法 ,就抛出异常,这里保证方法的唯一if (invokeMethods.Length > 1){throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));}// 如果没有找到,也就抛出异常if (invokeMethods.Length == 0){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));}// 取得唯一的方法Invoke或是InvokeAsync方法var methodInfo = invokeMethods[0];// 判断类型是否返回Task,如果不是就抛出异常,要求返回Task的目的是为了后续包装RequestDelegateif (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType)){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));}// 判断方法的参数,参数的第一个参数必须是HttpContext类型var parameters = methodInfo.GetParameters();if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext)){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));}// 开始构造RequestDelegate对象var ctorArgs = new object[args.Length + 1];ctorArgs[0] = next;Array.Copy(args, 0, ctorArgs, 1, args.Length);// 这里找到参数匹配最多的构造函数进行实例创建var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);// 如果参数只有一个HttpContext 就包装成一个RequestDelegate返回if (parameters.Length == 1){return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);}// 如果参数有多个的情况就单独处理,这里不详细进去了var factory = Compile<object>(methodInfo, parameters);return context =>{var serviceProvider = context.RequestServices ?? applicationServices;if (serviceProvider == null){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));}return factory(instance, context, serviceProvider);};});
}

可以看出,框架将我们封装的中间件类进行了反射获取对应的方法和属性,然后封装成中间件(Func<RequestDelegate,RequestDelegate>)的样子,从而是得编码更加方便,中间件更容易分类管理了;通过以上代码注释也能看出在封装中间件的时候对应的约定,哈哈哈,是不是得重新看一遍代码(如果这样,目标达到了);对了,框架提供了IMiddleware了接口,实现中间件的时候可以实现,但是约定还是一个不能少;

总结

我去,不能熬了,再熬明天起不来跑步了;这篇内容有点多,之所以没分开,感觉关联性比较强,一口气看下来比较合适;下一节说说文件相关的点;

---------------------------------------------------

CSDN:Code综艺圈

知乎:Code综艺圈

掘金:Code综艺圈

博客园:Code综艺圈

bilibili:Code综艺圈

---------------------------------------------------

一个被程序搞丑的帅小伙,关注"Code综艺圈",识别关注跟我一起学~~~

撸文不易,莫要白瞟,三连走起~~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307596.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode42. 接雨水

一:题目 二:上码 // class Solution { // public: // /**超时 // 思路: // 1.我们按列来计算 这就是表明的是 我们求取接雨水 向上的高度就是雨水量 // 但是这里的话我们的需要对雨水的高度 来进行判定 // 2.那么如何判定…

Magicodes.IE之导入导出筛选器

总体设计Magicodes.IE是一个导入导出通用库&#xff0c;支持Dto导入导出以及动态导出&#xff0c;支持Excel、Word、Pdf、Csv和Html。在本篇教程&#xff0c;笔者将讲述如何使用Magicodes.IE的导入导出筛选器。在开始之前&#xff0c;我们需要先了解Magicodes.IE目前支持的筛选…

谈了千百遍的缓存数据的一致性问题

“灵魂拷问保证缓存和数据库的一致性很简单吗&#xff1f;有哪些方式能保证缓存和数据库的一致性呢&#xff1f;如果发生了缓存和数据库数据不一致的情况怎么办呢&#xff1f;在上篇文章我们介绍了缓存的定义分类以及优缺点等&#xff0c;如果还没看的同学可以移步这里听说你会…

BS作业 基于springboot + Thymeleaf +mybatis 实现的书城管理系统

一:项目背景 项目描述 一个基本功能较为完整的后台管理项目。项目主要功能有&#xff1a;登录验证&#xff0c;登录功能还加入了随机验证码的验证&#xff1b; 用户注册&#xff0c;注册中密码基于srping 安全框架提供的加密(自动加盐)的密码储存方式&#xff0c;对注册重名进…

Istio Pilot 源码分析(二)

张海东&#xff0c; ‍多点生活&#xff08;成都&#xff09;云原生开发工程师。本篇主要介绍 Pilot 源码中的 ServiceEntryStore 及其推送 xDS 的流程。本文为 Istio Pilot 源码分析系列的第二篇文章。Istio Pilot 源码分析&#xff08;一&#xff09;了解了 Pilot 源码的基本…

Pytorch中的 torch.Tensor() 和 torch.tensor() 的区别

直接在搜索引擎里进行搜索&#xff0c;可以看到官方文档中两者对应的页面&#xff1a; 分别点击进去&#xff0c;第一个链接解释了什么是 torch.Tensor&#xff1a; torch.Tensor 是一个包含单一数据类型元素的多维矩阵&#xff08;数组&#xff09;。 正因为 torch.Tensor 只包…

leetcote34. 在排序数组中查找元素的第一个和最后一个位置

一:题目 二&#xff1a;上码&#xff08;暴力二分&#xff09; // class Solution { // public: // /** // 思路:1.首先这是一个升序的 那么相同的一定是会相连的// */// vector<int> searchRange(vector<int>& nums, int target) {// …

Git 图形化操作之合并提交记录

Git 图形化操作之合并提交记录独立观察员 2020 年 9 月 24 日目录1、显示日志2、合并提交记录3、推送合并的提交前言&#xff1a;当我们使用 Git 时&#xff0c;有时会遇到刚提交推送完一次修改&#xff0c;发现漏了该某处&#xff0c;只好又提交推送一次&#xff0c;这样在提交…

Pytorch中的 torch.as_tensor() 和 torch.from_numpy() 的区别

之前我写过一篇文章&#xff0c;比较了 torch.Tensor() 和 torch.tensor() 的区别&#xff0c;而这两者都是深拷贝的方法&#xff0c;返回张量的同时&#xff0c;会在内存中创建一个额外的数据副本&#xff0c;与原数据不共享内存&#xff0c;所以不受原数据改变的影响。 这里…

chrome禁止三方cookie,网站登录不了怎么办

背景新版chrome(80)浏览器默认屏蔽所有三方cookie已经不是什么新闻了&#xff0c;具体原因这里不去深究&#xff0c;有大量相关文章介绍&#xff0c;由于目前许多网站都依赖三方cookie&#xff0c;因此该特性的推出还是造成了一些的影响&#xff0c;比如收集用户信息的广告商&a…

leetcode69. x 的平方根

一:题目 二:上码 class Solution { public:/**思路:1.因为我们的 ans的平方 < x 那么我们就可以用二分法来做 不断缩小左右范围来确定 ans**/int mySqrt(int x) {int left 0; int right x;int ans 0;while (left < right) {long mid (right-left)/2 left;if (mid*…

初识ABP vNext(11):聚合根、仓储、领域服务、应用服务、Blob储存

点击上方蓝字"小黑在哪里"关注我吧聚合根仓储领域服务BLOB储存应用服务单元测试模块引用前言在前两节中介绍了ABP模块开发的基本步骤&#xff0c;试着实现了一个简单的文件管理模块&#xff1b;功能很简单&#xff0c;就是基于本地文件系统来完成文件的读写操作&…

leetcode367. 有效的完全平方数

一:题目 二:上码 class Solution { public:/**完全平方数:若一个数能表示成某个整数的平方的形式&#xff0c;则称这个数为完全平方数思路:1.我们将num先折半,因为它是某个整数的平方&#xff0c;而这个数的范围肯定不会超过num的一半2.那么这就相当于在[left,num/2]中查找某个…

跟我一起学.NetCore之文件系统应用及核心浅析

前言在开发过程中&#xff0c;肯定避免不了读取文件操作&#xff0c;比如读取配置文件、上传和下载文件、Web中html、js、css、图片等静态资源的访问&#xff1b;在配置文件读取章节中有说到&#xff0c;针对不同配置源数据读取由对应的IConfigurationProvider进行读取&#xf…

深度学习入门笔记(1)——导论部分

此笔记来源于 Sebastian Raschka 的 Introduction to Deep Learning 系列课程。 首先介绍的是传统的编程范式&#xff0c;假设我们想实现垃圾邮件识别的功能&#xff0c;传统的方法就是由程序员来找出垃圾邮件的规则并对其进行编程&#xff0c;得到一个垃圾邮件识别的程序。 机…

深度学习入门笔记(2)—— 感知器

最经典的神经元模型&#xff0c;从左到右依次是&#xff1a;输入、权重、加权和、阈值、输出。加权和又叫做 Net Input&#xff0c;符号为 z&#xff0c;当 z 的值大于阈值时输出 1&#xff0c;小于阈值时输出 0。 实现与门和或门&#xff0c;权重为 1&#xff0c;阈值分别为 1…

创建一个对象时,在一个类当中 静态代码块 和普通代码块构造方法 的顺序?

一:前言须知 普通代码块&#xff0c;在创建对象实例的时候&#xff0c;会被调用&#xff0c;每创建一次&#xff0c;就调用一次静态代码块&#xff0c;在类加载的时候执行&#xff0c;并且只会执行一次类加载的时机: 创建对象实例的时候&#xff08;new&#xff09;创建子类实…

ASP.NET Core 基于声明的访问控制到底是什么鬼?

从ASP.NET 4.x到ASP.NET Core&#xff0c;内置身份验证已从基于角色的访问控制(RBAC)转变为基于声明的访问控制(CBAC)。我们常用的HttpContext.User属性ASP.NET 4.0时代是IPrincipal类型&#xff0c;ASP.NETCore现在强化为ClaimsPrincipal类型。本文就一起来看看这难缠的、晦涩…

回溯的问题合集(Leetcode题解-Python语言)

78. 子集 class Solution:def subsets(self, nums: List[int]) -> List[List[int]]:ans []cur []def dfs(i):if i len(nums):ans.append(cur.copy())return# 包括 nums[i]cur.append(nums[i])dfs(i1)# 不包括 nums[i]cur.pop()dfs(i1)dfs(0)return ans要找出所有子集&a…