跟我一起学.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执行逻辑,并没有传递功能
// 本质也是使用方法Use
app.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()进行注册,这个上一节中提到过,就是那段在上一节中有点嫌早的代码,这里就拷过来了(偷个懒):
// 看着调用的方法
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 container
if (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的目的是为了后续包装RequestDelegate
if (!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综艺圈",识别关注跟我一起学~~~
撸文不易,莫要白瞟,三连走起~~~~
跟我一起学.NetCore之中间件(Middleware)应用和自定义的更多相关文章
- 跟我一起学.NetCore之中间件(Middleware)简介和解析请求管道构建
前言 中间件(Middleware)对于Asp.NetCore项目来说,不能说重要,而是不能缺少,因为Asp.NetCore的请求管道就是通过一系列的中间件组成的:在服务器接收到请求之后,请求会经过请 ...
- 跟我一起学.NetCore之静态文件处理的那些事
前言 如今前后端分离开发模式如火如荼,开发职责更加分明(当然前后端一起搞的模式也没有完全褪去):而对于每个公司产品实施来说,部署模式会稍有差别,有的会单独将前端文件部署为一个站点,有的会将前端文件和后 ...
- 跟我一起学.NetCore之WebApi接口裸奔有风险(Jwt)
前言 撸码需谨慎,裸奔有风险.经常在一些技术交流群中了解到,还有很多小伙伴的项目中Api接口没有做任何安全机制验证,直接就裸奔了,对于一些临时项目或是个人小项目还好,其余的话,建议小伙伴们酌情考虑都加 ...
- 跟我一起学.NetCore之MVC过滤器,这篇看完走路可以仰着头走
前言 MVC过滤器在之前Asp.Net的时候就已经广泛使用啦,不管是面试还是工作,总有一个考点或是需求涉及到,可以毫不疑问的说,这个技术点是非常重要的: 在之前参与的面试中,得知很多小伙伴只知道有一两 ...
- Django中间件(Middleware)处理请求
关注公众号"轻松学编程"了解更多. 1.面向切面编程 切点(钩子) 切点允许我们动态的在原有逻辑中插入一部分代码 在不修改原有代码的情况下,动态注入一部分代码 默认情况,不中断传播 ...
- ASP.NET Core 开发-中间件(Middleware)
ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下一个组件的请求,并能之前和下一组 ...
- ASP.NET Core中间件(Middleware)实现WCF SOAP服务端解析
ASP.NET Core中间件(Middleware)进阶学习实现SOAP 解析. 本篇将介绍实现ASP.NET Core SOAP服务端解析,而不是ASP.NET Core整个WCF host. 因 ...
- laravel中间件-----------middleware
middleware中间件 是访问到达服务器后在被对应的路由处理之前所经过的一层过滤层,故称中间件. 中间件是存放在app\http\middleware中,需要定一个 handle 处理方法,在ha ...
- 二、中间件(middleware)
1. 中间件(middleware) Django中的中间件主要实现一些附加功能,在request被用户handler处理前,以及用户handler处理后生存的response进行处理.因此 ...
随机推荐
- Python基础教程,流程控制语句详解
1.程序结构 计算机在解决问题时,分别是顺序执行所有语句.选择执行部分语句.循环执行部分语句,分别是:顺序结构.选择结构.循环结构.如下图: 很多人学习python,不知道从何学起.很多人学习pyth ...
- SpringBoot+Mybatis关于开启驼峰映射的设置
mybatis自定义的SQL语句中,如select语句,如果数据库表的字段为驼峰命名,即如img_address这样的形式,那么select语句执行的结果会变成null. 解决办法是在配置文件中加上开 ...
- 12、Java 正则表达式
简介 用来描述或者匹配一系列符合某个语句规则的字符串 正则表达式定义了字符串的模式. 正则表达式可以用来搜索.编辑或处理文本. 正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别. 一.正则 ...
- 解决CocoaPods could not find compatible versions for pod "React/Core"
react-native框架中,在ios文件夹下执行pod install命令时出现的问题. 下面时完整的异常信息: [!] CocoaPods could not find compatible v ...
- MySQL“被动”性能优化汇总!
年少不知优化苦,遇坑方知优化难. --村口王大爷 本文内容导图如下: 我之前有很多文章都在讲性能优化的问题,比如下面这些: <switch 的性能提升了 3 倍,我只用了这一招!> < ...
- Flutter build apk 如何访问网络
将下列配置放到路径:your_project\android\app\src下的 main 文件夹下的 AndroidManifest.xml 和 profile 文件夹下的 AndroidManif ...
- 面试官:怎么做JDK8的垃圾收集器的调优?
面试官:怎么做JDK8的垃圾收集器的调优? 看着面试官真诚的眼神,心中暗想看起来年纪轻轻却提出如此直击灵魂的问题.擦了擦额头上汗,我稍微调整了一下紧张的情绪,对面试官说: 在JDK8中有Serial收 ...
- 生成对抗网络GAN介绍
GAN原理 生成对抗网络GAN由生成器和判别器两部分组成: 判别器是常规的神经网络分类器,一半时间判别器接收来自训练数据中的真实图像,另一半时间收到来自生成器中的虚假图像.训练判别器使得对于真实图像, ...
- POW共识机制原理及优缺点
PoW共识机制 POW工作量证明(英文全称为Proof of Work)在比特币之前就已经出现,中本聪在设计区块链的共识机制的时候就是借鉴了POW工作量证明.常见的是利用HASH运算的复杂度进行CPU ...
- scanf函数与getchar函数
#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<string.h>#include<stdlib.h&g ...