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);
}
};
});
}
  • 第二种是开头举的例子,不继承自接口

    • 至少要有名为InvokeInvokeAsync的一个方法
    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 管道之中间件的更多相关文章

  1. asp.net core mvc 中间件之WebpackDevMiddleware

    asp.net core mvc 中间件之WebpackDevMiddleware WebpackDevMiddleware中间件主要用于开发SPA应用,启用Webpack,增强网页开发体验.好吧,你 ...

  2. ASP.NET Core MVC 之路由(Routing)

     ASP.NET Core MVC 路由是建立在ASP.NET Core 路由的,一项强大的URL映射组件,它可以构建具有理解和搜索网址的应用程序.这使得我们可以自定义应用程序的URL命名形式,使得它 ...

  3. 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/ ...

  4. asp.net core mvc 中间件之路由

    asp.net core mvc 中间件之路由 路由中间件 首先看路由中间件的源码 先用httpContext实例化一个路由上下文,然后把中间件接收到的路由添加到路由上下文的路由集合 然后把路由上下文 ...

  5. ASP.NET Core MVC 中设置全局异常处理方式

    在asp.net core mvc中,如果有未处理的异常发生后,会返回http500错误,对于最终用户来说,显然不是特别友好.那如何对于这些未处理的异常显示统一的错误提示页面呢? 在asp.net c ...

  6. asp.net core mvc剖析:启动流程

    asp.net core mvc是微软开源的跨平台的mvc框架,首先它跟原有的MVC相比,最大的不同就是跨平台,然后又增加了一些非常实用的新功能,比如taghelper,viewcomponent,D ...

  7. ASP.NET Core MVC 源码学习:Routing 路由

    前言 最近打算抽时间看一下 ASP.NET Core MVC 的源码,特此把自己学习到的内容记录下来,也算是做个笔记吧. 路由作为 MVC 的基本部分,所以在学习 MVC 的其他源码之前还是先学习一下 ...

  8. ASP.NET Core MVC 源码学习:MVC 启动流程详解

    前言 在 上一篇 文章中,我们学习了 ASP.NET Core MVC 的路由模块,那么在本篇文章中,主要是对 ASP.NET Core MVC 启动流程的一个学习. ASP.NET Core 是新一 ...

  9. ASP.NET Core 入门教程 3、ASP.NET Core MVC路由入门

    一.前言 1.本文主要内容 ASP.NET Core MVC路由工作原理概述 ASP.NET Core MVC带路径参数的路由示例 ASP.NET Core MVC固定前/后缀的路由示例 ASP.NE ...

随机推荐

  1. c++ stl常用

    #include<iostream>#include<string>#include<vector>#include<list>#include< ...

  2. js 放在公共头部 子页面引用js 失效问题

    先回忆一下二者的区别(对于此篇文章而言,二者在用法上没有区别) jsp:include是先编译一下included.jsp文件,然后再包含 先编译,后包含 @ include是先把文件包含就来,然后统 ...

  3. .net调用web邮箱发送邮件(转载)

    public static void SendEmail() { System.Net.Mail.SmtpClient client = new System.Net.Mail.SmtpClient( ...

  4. Ubuntu下多个版本OpenCV管理(Multiple Opencv version)

    背景: 最近,在Nvidia的GPU嵌入式开发板Jetson TX1(简称TX1)上移植深度学习目标检测算法YOLO.在TX1上安装了官方提供的opencv版本——OpenCV4Tegra(OpenC ...

  5. C# Socket网络编程精华篇

    我们在讲解Socket编程前,先看几个和Socket编程紧密相关的概念: TCP/IP层次模型 当然这里我们只讨论重要的四层 01,应用层(Application):应用层是个很广泛的概念,有一些基本 ...

  6. Java中==、equals、hashcode的区别与重写equals以及hashcode方法实例

    1.重写equals方法实例   部分代码参考http://blog.csdn.net/wangloveall/article/details/7899948 重写equals方法的目的是判断两个对象 ...

  7. URL统一资源定位符的组成

    URL:Uniform Resource Locator统一资源定位符 用于定位网络上我们需要访问的资源 组成:协议名称+域名+路径+资源的名称.如:https://img13.360img.com/ ...

  8. 七大排序的个人总结(二) 归并排序(Merge

    七大排序的个人总结(二)   归并排序(Merge  归并排序(Merge Sort): 归并排序是一个相当“稳定”的算法对于其它排序算法,比如希尔排序,快速排序和堆排序而言,这些算法有所谓的最好与最 ...

  9. lnmp vhost 虚拟目录配置

    以前常用Windows 很熟悉,lnmp 配置虚拟目录也很简单. 安装完lnmp环境之后,在nginx的配置文件夹下,我采用的方法是复制default.conf 然后重命名为vhost_a.conf ...

  10. jquery 元素筛选 13.6.20

    <ul> <li>list item 1</li> <li>list item 2</li> <li class="thir ...