asp.net core mvc 管道之中间件
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);
};
总结
- 以上就是通过调试和阅读源码分析得到的结果,写出来之后阅读可能有偏差,但这是为了方便大家理解,感觉这个顺序介绍会好理解点,反正我是理解了,介绍顺序对我影响不大
- 通过动手记录的过程,把之前调试阅读的时候没发现或者没理解的点都找到弄明白了,整明白了中间件的注册过程以及需要注意的书写规范,收获显而易见,所以源码才是最好的文档,而且文档未必有这么详细。通过记录,可以把细节补全甚至弄明白,这一点至关重要,再次体会到其重要性
- 另外,千万不要在大晚上写技术博文啊,总结之类的东西,切记
最后,文章可能有更新,请阅读原文获得更好的体验哦 https://www.cnblogs.com/xxred/p/9576622.html
asp.net core mvc 管道之中间件的更多相关文章
- asp.net core mvc 中间件之WebpackDevMiddleware
asp.net core mvc 中间件之WebpackDevMiddleware WebpackDevMiddleware中间件主要用于开发SPA应用,启用Webpack,增强网页开发体验.好吧,你 ...
- ASP.NET Core MVC 之路由(Routing)
ASP.NET Core MVC 路由是建立在ASP.NET Core 路由的,一项强大的URL映射组件,它可以构建具有理解和搜索网址的应用程序.这使得我们可以自定义应用程序的URL命名形式,使得它 ...
- ASP.NET Core MVC 2.x 全面教程_ASP.NET Core MVC 03. 服务注册和管道
ASP.NET Core MVC 2.x 全面教程_ASP.NET Core MVC 03. 服务注册和管道 语雀: https://www.yuque.com/yuejiangliu/dotnet/ ...
- asp.net core mvc 中间件之路由
asp.net core mvc 中间件之路由 路由中间件 首先看路由中间件的源码 先用httpContext实例化一个路由上下文,然后把中间件接收到的路由添加到路由上下文的路由集合 然后把路由上下文 ...
- ASP.NET Core MVC 中设置全局异常处理方式
在asp.net core mvc中,如果有未处理的异常发生后,会返回http500错误,对于最终用户来说,显然不是特别友好.那如何对于这些未处理的异常显示统一的错误提示页面呢? 在asp.net c ...
- asp.net core mvc剖析:启动流程
asp.net core mvc是微软开源的跨平台的mvc框架,首先它跟原有的MVC相比,最大的不同就是跨平台,然后又增加了一些非常实用的新功能,比如taghelper,viewcomponent,D ...
- ASP.NET Core MVC 源码学习:Routing 路由
前言 最近打算抽时间看一下 ASP.NET Core MVC 的源码,特此把自己学习到的内容记录下来,也算是做个笔记吧. 路由作为 MVC 的基本部分,所以在学习 MVC 的其他源码之前还是先学习一下 ...
- ASP.NET Core MVC 源码学习:MVC 启动流程详解
前言 在 上一篇 文章中,我们学习了 ASP.NET Core MVC 的路由模块,那么在本篇文章中,主要是对 ASP.NET Core MVC 启动流程的一个学习. ASP.NET Core 是新一 ...
- ASP.NET Core 入门教程 3、ASP.NET Core MVC路由入门
一.前言 1.本文主要内容 ASP.NET Core MVC路由工作原理概述 ASP.NET Core MVC带路径参数的路由示例 ASP.NET Core MVC固定前/后缀的路由示例 ASP.NE ...
随机推荐
- Alberta family's QR code is world's largest corn maze
BY DARREN WEIR SEP 10, 2012 IN ODD NEWS Link:http://www.digitaljournal.com/article/332512 Laco ...
- 139. Word Break (String; DP)
Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separa ...
- Majority Element(ARRAY-BINARY SEARCH)
QUESTION Given an array of size n, find the majority element. The majority element is the element th ...
- Frog and Portal(思维好题)
Frog and Portal https://hihocoder.com/problemset/problem/1873 时间限制:1000ms 单点时限:1000ms 内存限制:512MB 描述 ...
- session总结
1.session是服务器端内存中的一块存储空间. 2.不同的浏览器窗口对应着不同的Session对象,两者的关系由Session ID来进行维护. 3.session的生命周期是以最后一次请求到达服 ...
- ecplise导入工程出现乱码的解决方案
eclipse之所以会出现乱码问题是因为eclipse编辑器选择的编码规则是可变的.一般默认都是UTF-8或者GBK,当从外部导入的一个工程时,如果该工程的编码方式与eclipse中设置的编码方式不同 ...
- hreeJS加载Obj资源后如何实现内存释放?
问题: 我利用ThreeJS做了一个在同一个场景下动态加载Obj的页面,具体功能是:点击按钮A:加载A模型,点击按钮B:加载B模型...现在的问题是,前面已经加载过的模型,无法实现释放,内存一直在累加 ...
- AE教程:学会这个,你做的Logo就可以单独出道了
一.确定所需要做的动效 1.制作logo背景形状动效 2.制作U的动效 3.制作I的动效 4.制作消失动效 二.制作logo背景形状动效1.”合成 - 新建合成“ 新建一个1000*800的合成 2. ...
- oracle 新建数据库 ,新建用户
net manager 数据库名----电脑名localhost 1521 , 服务名 orcl (oracle 版本不一样, 不同版本不一样,,) 然后测试.. sys 账号登录 新建用 ...
- s111 stark组件
内容回顾: 1. 类当做key 2. django中的model所在app名称.以及小写类名. def index(request): # print(m1.UserInfo,m1.UserInfo. ...