探究ASP.NET Core Middleware实现方法
概念
asp.net core middleware是在应用程序处理管道pipeline中用于处理请求和操作响应的组件。
每个组件:
- 在pipeline中判断是否将请求传递给下一个组件
- 在处理管道的下个组件执行之前和之后执行一些工作, httpcontxt对象能跨域请求、响应的执行周期
特性和行为
asp.net core处理管道由一系列请求委托组成,一环接一环的被调用, 下面给出自己绘制的middleware pipeline流程图:
从上图可以看出,请求自进入处理管道,经历了四个中间件,每个中间件都包含后续紧邻中间件 执行委托(next)的引用,同时每个中间件在交棒之前和交棒之后可以自行决定参与一些http请求和响应的逻辑处理。
每个中间件还可以决定不将请求转发给下一个委托,这称为请求管道的短路(短路是有必要的,某些专有中间件比如 staticfilemiddleware 可以在完成功能之后,避免请求被转发到其他动态处理过程)。
源码实现
观察一个标准的中间件代码的写法和用法:
using system.threading.tasks; using alyio.aspnetcore.apimessages; using gridsum.webdissector.common; using microsoft.aspnetcore.http; namespace gridsum.webdissector { sealed class authorizationmiddleware { private readonly requestdelegate _next; // 下一个中间件执行委托的引用 public authorizationmiddleware(requestdelegate next) { _next = next; } public task invoke(httpcontext context) // 贯穿始终的httpcontext对象 { if (context.request.path.value.startswith("/api/")) { return _next(context); } if (context.user.identity.isauthenticated && context.user().disallowbrowsewebsite) { throw new forbiddenmessage("you are not allow to browse the website."); } return _next(context); } } } public static iapplicationbuilder userauthorization(this iapplicationbuilder app) { return app.usemiddleware<authorizationmiddleware>(); } // 启用该中间件,也就是注册该中间件 app.userauthorization();
标准的中间件使用方式是如此简单明了,带着几个问题探究一下源码实现
(1).中间件传参是怎样完成的: app.usemiddleware<authorization>(authoption); 我们传参的时候,为什么能自动注入中间件构造函数非第1个参数
(2).编写中间件的时候,为什么必须要定义特定的 invoke/invokeasync 函数?
(3).设定中间件的顺序很重要,中间件的嵌套顺序是怎么确定的 ?
思考以上标准中间件的行为: 输入下一个中间件的执行委托next, 定义当前中间件的执行委托invoke/invokeasync;
每个中间件完成了 func<requestdelegate,requestdelegate>这样的行为;
通过参数next与下一个中间件的执行委托invoke/invokeasync 建立"链式"关系。
public delegate task requestdelegate(httpcontext context);
//-----------------节选自 microsoft.aspnetcore.builder.usemiddlewareextensions------------------ /// <summary> /// adds a middleware type to the application's request pipeline. /// </summary> /// <typeparam name="tmiddleware">the middleware type.</typeparam> /// <param name="app">the <see cref="iapplicationbuilder"/> instance.</param> /// <param name="args">the arguments to pass to the middleware type instance's constructor.</param> /// <returns>the <see cref="iapplicationbuilder"/> instance.</returns> public static iapplicationbuilder usemiddleware<tmiddleware>(this iapplicationbuilder app, params object[] args) { return app.usemiddleware(typeof(tmiddleware), args); } /// <summary> /// adds a middleware type to the application's request pipeline. /// </summary> /// <param name="app">the <see cref="iapplicationbuilder"/> instance.</param> /// <param name="middleware">the middleware type.</param> /// <param name="args">the arguments to pass to the middleware type instance's constructor.</param> /// <returns>the <see cref="iapplicationbuilder"/> instance.</returns> 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; 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)); } var methodinfo = invokemethods[0]; if (!typeof(task).isassignablefrom(methodinfo.returntype)) { throw new invalidoperationexception(resources.formatexception_usemiddlewarenontaskreturntype(invokemethodname, invokeasyncmethodname, nameof(task))); } var parameters = methodinfo.getparameters(); if (parameters.length == 0 || parameters[0].parametertype != typeof(httpcontext)) { throw new invalidoperationexception(resources.formatexception_usemiddlewarenoparameters(invokemethodname, invokeasyncmethodname, nameof(httpcontext))); } 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); } // 当前执行委托除了可指定httpcontext参数以外, 还可以注入更多的依赖参数 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); }; }); } //-------------------节选自 microsoft.aspnetcore.builder.internal.applicationbuilder------------------- private readonly ilist<func<requestdelegate, requestdelegate>> _components = new list<func<requestdelegate, requestdelegate>>(); publiciapplicationbuilder use(func<requestdelegate,requestdelegate> middleware) { this._components.add(middleware); return this; } public requestdelegate build() { requestdelegate app = context => { context.response.statuscode = 404; return task.completedtask; }; foreach (var component in _components.reverse()) { app = component(app); } return app; }
通过以上代码我们可以看出:
- 注册中间件的过程实际上,是给一个 type= list<func<requestdelegate, requestdelegate>> 的容器依次添加元素的过程;
- 容器中每个元素对应每个中间件的行为委托func<requestdelegate, requestdelegate>, 这个行为委托包含2个关键行为:输入下一个中间件的执行委托next:requestdelegate, 完成当前中间件的invoke函数: requestdelegate;
- 通过build方法完成前后中间件的链式传值关系
分析源码:回答上面的问题:
- 使用反射构造中间件的时候,第一个参数array[0] 是下一个中间件的执行委托
- 当前中间件执行委托 函数名称被限制为: invoke/invokeasync, 函数支持传入除httpcontext之外的参数
- 按照代码顺序添加进入 _components容器, 通过后一个中间件的执行委托 -----(指向)----> 前一个中间件的输入执行委托建立链式关系。
附:非标准中间件的用法
短路中间件、 分叉中间件、条件中间件
整个处理管道的形成,存在一些管道分叉或者临时插入中间件的行为,一些重要方法可供使用
- use方法是一个注册中间件的简便写法
- run方法是一个约定,一些中间件使用run方法来完成管道的结尾
- map扩展方法:请求满足指定路径,将会执行分叉管道,强调满足 path
- mapwhen方法:httpcontext满足条件,将会执行分叉管道:
- usewhen方法:httpcontext满足条件 则插入中间件
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。