asp.net core mvc 管道之中间件
程序员文章站
2023-03-28 10:00:57
asp.net core mvc 管道之中间件 http请求处理管道通过注册中间件来实现各种功能,松耦合并且很灵活 此文简单介绍asp.net core mvc中间件的注册以及运行过程 通过理解中间件,将asp.net core mvc分解,以便更好地学习 中间件写法 先看一个简单的中间件,next ......
asp.net core mvc 管道之中间件
- http请求处理管道通过注册中间件来实现各种功能,松耦合并且很灵活
- 此文简单介绍asp.net core mvc中间件的注册以及运行过程
- 通过理解中间件,将asp.net core mvc分解,以便更好地学习
中间件写法
- 先看一个简单的中间件,next是下一个委托方法,在本中间件的invoke方法里面需要执行它,否则处理就会终止,消息处理到此中间件就会返回了
- 因此,根据这个约定,一个中间生成一个委托方法,需要把所有的委托方法处理成嵌套的委托,即每个中间件里面执行下一个委托,这样处理过程就像管道一样连接起来,每个中间件就是管道处理的节点
- 至于为什么要这样写中间件,这是约定好的,还有注意点,下面将会讲到
public class middleware { private readonly requestdelegate _next; public routermiddleware(requestdelegate next) { _next = next; } public async task invoke(httpcontext httpcontext) { // do something await _next.invoke(httpcontext); // do something } }
中间件管道生成
- 以上中间件会通过方法生成一个委托,并添加到委托集合,中间生成委托的过程后面讲
namespace microsoft.aspnetcore.builder.internal { public class applicationbuilder : iapplicationbuilder { private readonly ilist<func<requestdelegate, requestdelegate>> _components = new list<func<requestdelegate, requestdelegate>>(); public iapplicationbuilder use(func<requestdelegate, requestdelegate> middleware) { _components.add(middleware); return this; } } }
- 最后的
applicationbuilder.build
方法会处理所有注册的中间件生成的委托集合,将所有中间件生成的委托集合,处理成嵌套的形式,最终得到一个委托,连成一段管道。 - 以下方法首先声明一个响应404的委托方法,把它当成所有中间件的最后一个,当然它不一定会被执行到,因为某个中间件可能不会调用它
- 然后将这个委托作为参数,传入并执行_components这个委托集合里面的每一个委托,_components就是所有注册的中间件生成的委托集合,
reverse
方法将集合反转,从最后注册的中间件对应的委托开始处理 - 所以呢中间件的注册是有顺序的,也就是
startup.cs
类里面的configure
方法,里面的每个use开头的方法都对应一个中间件注册,代码的顺序就是注册的顺序,也是执行的顺序,千万不能写错了。因为mvc处于处理流程的最后面,因此usemvc方法总是位于最后 - 在看
component
,是从_components
委托集合里面取出来的,执行后又得到一个requestdelegate
类型的委托,因此由中间件生成的委托的类型应该是func<requestdelegate, requestdelegate>
public requestdelegate build() { requestdelegate app = context => { context.response.statuscode = 404; return task.completedtask; }; foreach (var component in _components.reverse()) { app = component(app); } return app; }
中间件生成委托
- 以下是中间件注册方法,实际是调用
applicationbuilder.use
方法,将中间件生成的委托加入委托集合,完成中间件注册 -
app.use
方法参数,就是上面需要的类型func<requestdelegate, requestdelegate>
的委托,该委托的参数next
就是下一个中间件对应的委托,返回值就是中间件的invoke
方法对应的委托,该方法用到了next
- 源码位于microsoft.aspnetcore.builder.usemiddlewareextensions这个类
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) { // 省略部分代码 var applicationservices = app.applicationservices; return app.use(next => { // 省略部分代码 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); 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); }; }); }
中间件写法约定
- 看以上代码,第一种写法,首先如果中间继承自
imiddleware
接口,则调用usemiddlewareinterface
方法。使用了接口规范,那么你也不能乱写了,只需要注意在invoke
方法调用next
即可
private static iapplicationbuilder usemiddlewareinterface(iapplicationbuilder app, type middlewaretype) { return app.use(next => { return async context => { var middlewarefactory = (imiddlewarefactory)context.requestservices.getservice(typeof(imiddlewarefactory)); if (middlewarefactory == null) { // no middleware factory throw new invalidoperationexception(resources.formatexception_usemiddlewarenomiddlewarefactory(typeof(imiddlewarefactory))); } var middleware = middlewarefactory.create(middlewaretype); if (middleware == null) { // the factory returned null, it's a broken implementation throw new invalidoperationexception(resources.formatexception_usemiddlewareunabletocreatemiddleware(middlewarefactory.gettype(), middlewaretype)); } try { await middleware.invokeasync(context, next); } finally { middlewarefactory.release(middleware); } }; }); }
- 第二种是开头举的例子,不继承自接口
- 至少要有名为
invoke
或invokeasync
的一个方法
public static class usemiddlewareextensions { internal const string invokemethodname = "invoke"; internal const string invokeasyncmethodname = "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)); }
- 类的返回值是
task
var methodinfo = invokemethods[0]; 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))); }
- 方法的参数如果只有一个,则将
usemiddleware
方法传入的自定义参数args
加上下一个委托next
,得到新的参数数组,然后创建中间件实例,生成invoke
方法对应委托。此处注意,如果中间件的构造函数中有其它参数,但是未注册到applicationservices
的话,需要在usemiddleware
方法中传入
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); if (parameters.length == 1) { return (requestdelegate)methodinfo.createdelegate(typeof(requestdelegate), instance); }
- 方法的参数如果多于一个,则调用
compile
方法,生成一个委托,该委托从iserviceprovider
中获取需要的参数的实例,再调用invoke
方法,相比上面的情况,多了一步从iserviceprovider
获取实例,注入到invoke
而已。 -
compile
方法使用了linq表达式树,源码位于microsoft.aspnetcore.builder.usemiddlewareextensions,此处不作讲解,因为我也不太懂
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); };
总结
- 以上就是通过调试和阅读源码分析得到的结果,写出来之后阅读可能有偏差,但这是为了方便大家理解,感觉这个顺序介绍会好理解点,反正我是理解了,介绍顺序对我影响不大
- 通过动手记录的过程,把之前调试阅读的时候没发现或者没理解的点都找到弄明白了,整明白了中间件的注册过程以及需要注意的书写规范,收获显而易见,所以源码才是最好的文档,而且文档未必有这么详细。通过记录,可以把细节补全甚至弄明白,这一点至关重要,再次体会到其重要性
- 另外,千万不要在大晚上写技术博文啊,总结之类的东西,切记
最后,文章可能有更新,请阅读原文获得更好的体验哦
上一篇: C# 生成唯一订单号
下一篇: List接口以及数据结构存储特点
推荐阅读
-
Asp.net Core中如何使用中间件来管理websocket
-
解析ASP.NET Mvc开发之删除修改数据
-
asp.net core项目mvc权限控制:分配权限
-
asp.net core mvc权限控制:在视图中控制操作权限
-
ASP.NET Core中间件计算Http请求时间示例详解
-
ASP.NET MVC5+EF6+EasyUI后台管理系统 微信公众平台开发之消息管理
-
ASP.NET MVC IOC依赖注入之Autofac系列(一)
-
基于Asp.Net Core MVC和AdminLTE的响应式管理后台之侧边栏处理
-
asp.net core系列 68 Filter管道过滤器
-
在Asp.Net Core MVC 3.0 视图运行时编译