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. Failed to execute goal org.mybatis.generator:mybatis-generator-maven-plugin:generate (default-cli) on project : <properties> resource does not exist

    使用mybatis-generator自动生成mapper.dao等文件时,报错如下: org.apache.maven.lifecycle.LifecycleExecutionException: ...

  2. malloc、free、new、delete

    一.C语言中不定大小多维数组的处理: 如果要给二维数组(m*n)分配空间,代码可以写成下面: char **a, i; // 先分配m个指针单元,注意是指针单元 // 所以每个单元的大小是sizeof ...

  3. MyBatis核心配置文件详解

    ------------------------siwuxie095                                     MyBatis 核心配置文件详解         1.核心 ...

  4. gridview 级联删除、dataset

    gridview编辑列(不使用控件绑定数据源)需要如下代码:<asp:GridView ID="GridView1" runat="server" Aut ...

  5. .net VS2008 时间加减,时间段,时间格式化到秒

    举个例子: DateTime time1 = DateTime.Now; DateTime time2 = time1.AddDays(1); time1是当前时间,time2比当前时间多一天.也就是 ...

  6. Silverlight实用窍门系列:57.Silverlight中的Binding使用(二)-数据验证

    本文将简单讲述Silverlight中的Binding数据时的数据验证. NotifyOnValidationError:是否在出现异常/错误信息的时候激发BindingValidationError ...

  7. 用Vue-cli生成vue+webpack的项目模板怎么设置为vue1.0版本?

    用Vue-cli生成vue+webpack的项目模板 $ npm install -g vue-cli $ vue init webpack my-project $ cd my-project $ ...

  8. [Selenium] CSS3 选择器

    在 CSS 中,选择器是一种模式,用于选择需要添加样式的元素. "CSS" 列指示该属性是在哪个 CSS 版本中定义的.(CSS1.CSS2 还是 CSS3.) 选择器 例子 例子 ...

  9. 二进制搭建kubernetes多master集群【一、使用TLS证书搭建etcd集群】

    上一篇我们介绍了kubernetes集群架构以及系统参数配置,参考:二进制搭建kubernetes多master集群[开篇.集群环境和功能介绍] 下面本文etcd集群才用三台centos7.5搭建完成 ...

  10. 2018.10.23 hdu4745Two Rabbits(区间dp)

    传送门 区间dp经典题目. 首先断环为链. 然后题目相当于就是在找最大的回文子序列. 注意两个位置重合的时候相当于范围是n,不重合时范围是n-1. 代码: #include<bits/stdc+ ...