ASP.NET Core 2.0 : 图说管道,唐僧扫塔的故事

本文通过一张GIF动图来继续聊一下ASP.NET Core的请求处理管道,从管道的配置、构建以及请求处理流程等方面做一下详细的研究。(ASP.NET Core系列目录)

一、概述

  上文说到,请求是经过 Server监听=>处理成httpContext=>Application处理生成Response。 这个Application的类型RequestDelegate本质是 public delegate Task RequestDelegate (HttpContext context); ,即接收HttpContext并返回Task, 它是由一个个中间件 Func<RequestDelegate, RequestDelegate> middleware 嵌套在一起构成的。它的构建是由ApplicationBuilder完成的,先来看一下这个ApplicationBuilder:

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;

    }

    public RequestDelegate Build()

    {

        RequestDelegate app = context =>

        {

            context.Response.StatusCode = 404;

            return Task.CompletedTask;

        }; 

        foreach (var component in _components.Reverse())20         {

            app = component(app);

        }

        return app;

    }

}

 ApplicationBuilder有个集合 IList<Func<RequestDelegate, RequestDelegate>> _components 和一个用于向这个集合中添加内容的  Use(Func<RequestDelegate, RequestDelegate> middleware) 方法,通过它们的类型可以看出来它们是用来添加和存储中间件的。现在说一下大概的流程:

  1. 调用startupFilters和_startup的Configure方法,调用其中定义的多个UseXXX(进一步调用ApplicationBuilder的Use方法)将一个个中间件middleware按照顺序写入上文的集合_components(记住这个_components)

  2. 定义了一个 context.Response.StatusCode = 404 的RequestDelegate。

  3. 将集合_components颠倒一下, 然后遍历其中的middleware,一个个的与新创建的404 RequestDelegate 连接在一起,组成一个新的RequestDelegate(即Application)返回。

  这个最终返回的RequestDelegate类型的Application就是对HttpContext处理的管道了,这个管道是多个中间件按照一定顺序连接在一起组成的,startupFilters先不说,以我们非常熟悉的Startup为例,它的Configure方法默认情况下已经依次进行了UseBrowserLink、UseDeveloperExceptionPage、UseStaticFiles、UseMvc了等方法,请求进入管道后,请求也会按照这个顺序来经过各个中间件处理,首先进入UseBrowserLink,然后UseBrowserLink会调用下一个中间件UseDeveloperExceptionPage,依次类推到达UseMVC后被处理生成Response开始逆向返回再依次反向经过这几个中间件,正常情况下,请求到达MVC中间件后被处理生成Response开始逆向返回,而不会到达最终的404,这个404是为了防止其他层未配置或未能处理的时候的一个保险操作。  

  胡扯两句:这个管道就像一座塔,话说唐僧路过金光寺去扫金光塔,从前门进入第一层开始扫,然后从前门的楼梯进入第二层、第三层、第四层,然后从第四层的后门扫下来直至后门出去,却不想妖怪没处理好, 被唐僧扫到了第五层(顶层)去,发现佛宝被奔波儿灞和霸波尔奔偷走了,大喊:悟空悟空,佛宝被妖怪偷走啦!(404...)

  下面就以这4个为例通过一个动图形象的描述一下整个过程:

  一个“中规中矩”的管道就是这样构建并运行的,通过上图可以看到各个中间件在Startup文件中的配置顺序与最终构成的管道中的顺序的关系,下面我们自己创建几个中间件体验一下,然后再看一下不“中规中矩”的长了杈子的管道。

二、自定义中间件

  先仿照系统现有的写一个

public class FloorOneMiddleware

    {

        private readonly RequestDelegate _next;

        public FloorOneMiddleware(RequestDelegate next)

        {

            _next = next;

        }

        public async Task InvokeAsync(HttpContext context)

        {

            Console.WriteLine("FloorOneMiddleware In");

            //Do Something

            //To FloorTwoMiddleware

            await _next(context);

            //Do Something

            Console.WriteLine("FloorOneMiddleware Out");

        }

    }

这是塔的第一层,进入第一层后的 //Do Something 表示在第一层需要做的工作, 然后通过 _next(context) 进入第二层,再下面的 //Do Something 是从第二层出来后的操作。同样第二层调用第三层也是一样。再仿写个UseFloorOne的扩展方法:

public static class FloorOneMiddlewareExtensions

    {

        public static IApplicationBuilder UseFloorOne(this IApplicationBuilder builder)

        {

            Console.WriteLine("Use FloorOneMiddleware");

            return builder.UseMiddleware<FloorOneMiddleware>();

        }

    }

这样在Startup的Configure方法中就也可以写 app.UseFloorOne(); 将这个中间件作为管道的一部分了。

  通过上面的例子仿照系统默认的中间件完成了一个简单的中间件的编写,这里也可以用简要的写法,直接在Startup的Configure方法中这样写:

app.Use(async (context,next) =>

{

    Console.WriteLine("FloorThreeMiddleware In");

    //Do Something

    //To FloorThreeMiddleware

    await next.Invoke();

    //Do Something

    Console.WriteLine("FloorThreeMiddleware Out");

});

同样可以实现上一种例子的工作,但还是建议按照那样的写法,在Startup这里体现的简洁并且可读性好的多。

复制一下第一种和第二种的例子,形成如下代码:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)

        {

            app.UseFloorOne();

            app.UseFloorTwo();

            app.Use(async (context,next) =>

            {

                Console.WriteLine("FloorThreeMiddleware In");

                //Do Something

                //To FloorThreeMiddleware

                await next.Invoke();

                //Do Something

                Console.WriteLine("FloorThreeMiddleware Out");

            });

            app.Use(async (context, next) =>

            {

                Console.WriteLine("FloorFourMiddleware In");

                //Do Something

                await next.Invoke();

                //Do Something

                Console.WriteLine("FloorFourMiddleware Out");

            });


            if (env.IsDevelopment())

            {

                app.UseBrowserLink();

                app.UseDeveloperExceptionPage();

            }

            else

            {

                app.UseExceptionHandler("/Home/Error");

            }


            app.UseStaticFiles();


            app.UseMvc(routes =>

            {

                routes.MapRoute(

                    name: "default",

                    template: "{controller=Home}/{action=Index}/{id?}");

            });

        }

运行一下看日志:

CoreMiddleware> Use FloorOneMiddleware

CoreMiddleware> Use FloorTwoMiddleware

CoreMiddleware> Hosting environment: Development

CoreMiddleware> Content root path: C:\Users\FlyLolo\Desktop\CoreMiddleware\CoreMiddleware

CoreMiddleware> Now listening on: http://localhost:10757

CoreMiddleware> Application started. Press Ctrl+C to shut down.

CoreMiddleware> info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]

CoreMiddleware>       Request starting HTTP/1.1 GET http://localhost:56440/  

CoreMiddleware> FloorOneMiddleware In

CoreMiddleware> FloorTwoMiddleware In

CoreMiddleware> FloorThreeMiddleware In

CoreMiddleware> FloorFourMiddleware In

CoreMiddleware> info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]

CoreMiddleware>       Executing action method CoreMiddleware.Controllers.HomeController.Index (CoreMiddleware) with arguments ((null)) - ModelState is Valid

CoreMiddleware> info: Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewResultExecutor[1]

CoreMiddleware>       Executing ViewResult, running view at path /Views/Home/Index.cshtml.

CoreMiddleware> info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]

CoreMiddleware>       Executed action CoreMiddleware.Controllers.HomeController.Index (CoreMiddleware) in 9896.6822ms

CoreMiddleware> FloorFourMiddleware Out

CoreMiddleware> FloorThreeMiddleware Out

CoreMiddleware> FloorTwoMiddleware Out

CoreMiddleware> FloorOneMiddleware Out

CoreMiddleware> info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]

CoreMiddleware>       Request finished in 10793.8944ms 200 text/html; charset=utf-8

可以看到,前两行的Use FloorOneMiddleware和Use FloorTwoMiddleware是将对应的中间件写入集合_components,而中间件本身并未执行,然后10至12行是依次经过我们自定义的例子的处理,第13-18就是在中间件MVC中的处理了,找到并调用对应的Controller和View,然后才是19-22的逆向返回, 最终Request finished返回状态200, 这个例子再次验证了请求在管道中的处理流程。

那么我们试一下404的情况, 把Configure方法中除了自定义的4个中间件外全部注释掉,再次运行

//上面没变化  省略

CoreMiddleware> FloorOneMiddleware In

CoreMiddleware> FloorTwoMiddleware In

CoreMiddleware> FloorThreeMiddleware In

CoreMiddleware> FloorFourMiddleware In

CoreMiddleware> FloorFourMiddleware Out

CoreMiddleware> FloorThreeMiddleware Out

CoreMiddleware> FloorTwoMiddleware Out

CoreMiddleware> FloorOneMiddleware Out

CoreMiddleware> info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]

CoreMiddleware>       Request finished in 218.7216ms 404


可以看到,MVC处理的部分没有了,因为该中间件已被注释,而最后一条可以看到系统返回了状态404。

 那么既然MVC可以正常处理请求没有进入404, 我们怎么做可以这样呢?是不是不调用下一个中间件就可以了? 试着把FloorFour改一下

app.Use(async (context, next) =>

{

    Console.WriteLine("FloorFourMiddleware  In");

    //await next.Invoke();

    await context.Response.WriteAsync("Danger!");

    Console.WriteLine("FloorFourMiddleware  Out");

});

再次运行,查看输出和上文的没有啥太大改变, 只是最后的404变为了200, 网页上的“404 找不到。。”也变成了我们要求输出的"Danger!", 达到了我们想要的效果。

但一般情况下我们不这样写,ASP.NET Core 提供了Use、Run和Map三种方法来配置管道。

三、Use、Run和Map

  Use上面已经用过就不说了,对于上面的问题, 一般用Run来处理,Run主要用来做为管道的末尾,例如上面的可以改成这样:

app.Run(async (context) =>{   
   
await context.Response.WriteAsync("Danger!"); });

 因为本身他就是作为管道末尾,也就省略了next参数,虽然用use也可以实现, 但还是建议用Run。

    Map:

   static IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, Action<IApplicationBuilder> configuration); pathMatch用于匹配请求的path, 例如“/Home”, 必须以“/”开头, 判断path是否是以pathMatch开头。

若是,则进入 Action<IApplicationBuilder> configuration) , 这个参数是不是长得很像startup的Configure方法? 这就像进入了我们配置的另一个管道,它是一个分支,如下图

640?wx_fmt=png

图2

做个例子:

app.UseFloorOne();

app.Map("/Manager", builder =>

{

    builder.Use(async (context, next) =>

    {

        await next.Invoke();

    });


    builder.Run(async (context) =>

    {

        await context.Response.WriteAsync("Manager.");

    });

});

app.UseFloorTwo();

 进入第一层后, 添加了一个Map, 作用是当我们请求 localhost:56440/Manager/index 这样的地址的时候(是不是有点像Area), 会进入这个Map创建的新分支, 结果也就是页面显示"Manager." 不会再进入下面的FloorTwo。若不是“/Manager”开头的, 这继续进入FloorTwo。虽然感觉这个Map灵活了我们的管道配置, 但这个只能匹配path开头的方法太局限了,不着急, 我们看一下MapWhen。

  Map When:

  MapWhen方法就是一个灵活版的Map,它将原来的PathMatch替换为一个 Func<HttpContext, bool> predicate ,这下就开放多了,它返回一个bool值,现在举个栗子随便改一下

app.MapWhen(context=> {return context.Request.Query.ContainsKey("XX");}, builder =>{    //...TODO...}

  当根据请求的参数是否包含“XX”的时候进入这个分支。

  从图2可知,一旦进入分支,是无法回到原分支的, 如果只是想在某种情况下进入某些中间件,但执行完后还可以继续后续的中间件怎么办呢?对比MapWhen,Use也有个UseWhen。

  UseWhen:

   它和MapWhen一样,当满足条件的时候进入一个分支,在这个分支完成之后再继续后续的中间件,当然前提是这个分支中没有Run等短路行为

app.UseWhen(context=> {return context.Request.Query.ContainsKey("XX");}, builder =>{    //...TODO...}

四、IStartupFilter

  我们只能指定一个Startup类作为启动类,那么还能在其他的地方定义管道么? 文章开始的时候说到,构建管道的时候,会调用startupFilters和_startup的Configure方法,调用其中定义的多个UseXXX方法来将中间件写入_components。自定义一个StartupFilter,实现IStartupFilter的Configure方法,用法和Startup的Configure类似,不过要记得最后调用 next(app) 。

public class TestStartupFilter : IStartupFilter

{

    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)

    {

        return app => { 

            app.Use(async (context, next1) =>

            {

                Console.WriteLine("filter.Use1.begin");

                await next1.Invoke();

                Console.WriteLine("filter.Use1.end");

            });

            next(app);

        };

    }

}

在复制一个,去startup的ConfigureServices注册一下:

public void ConfigureServices(IServiceCollection services)

{

    services.AddMvc();

    services.AddSingleton<IStartupFilter,TestStartupFilter>();

    services.AddSingleton<IStartupFilter, TestStartupFilter2>();

}

这样的配置就生效了,现在剖析一下他的生效机制。回顾一下WebHost的BuildApplication方法:

private RequestDelegate BuildApplication()

{

 //....省略

    var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();

    Action<IApplicationBuilder> configure = _startup.Configure;

    foreach (var filter in startupFilters.Reverse())

    {

        configure = filter.Configure(configure);

    }


    configure(builder);


    return builder.Build();

仔细看这段代码,其实这和构建管道的流程非常相似,对比着说一下:

  1. 首先通过GetService获取到注册的IStartupFilter集合startupFilters(类比_components)

  2. 然后获取Startup的Configure(类比404的RequestDelegate)

  3. 翻转startupFilters,foreach它并且与Startup的Configure链接在一起。

  4. 上文强调要记得最后调用 next(app),这个是不是和 next.Invoke() 类似。

  是不是感觉和图一的翻转拼接过程非常类似,是不是想到了拼接先后顺序的问题。对比着管道构建后中间件的执行顺序,体会一下后,这时应该可以想到各个IStartupFilter和Startup的Configure的执行顺序了吧。没错就是按照依赖注入的顺序:TestStartupFilter=>TestStartupFilter2=>Startup。

}

原文地址 https://www.cnblogs.com/FlyLolo/p/ASPNETCore2_8.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/321574.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P3521-[POI2011]ROT-Tree【线段树合并】

正题 题目链接:https://www.luogu.com.cn/problem/P3521 题目大意 一棵二叉树&#xff0c;叶子节点有权值&#xff0c;对于每个非叶子节点可以选择交换左右节点&#xff0c;求最后遍历出来的叶子节点权值逆序对最少。 解题思路 十分显然一个节点是否交换是不影响该节点子树之…

7、oracle下的序列

ORACLE下的序列 序列是一种数据库对象&#xff0c;用于生成一系列的整数&#xff0c;可以用来唯一的标记一条记录&#xff0c;在mysql种有字段自增的概念&#xff0c;但是oracle种是没有字段自增的&#xff0c;所以可以使用序列来作为主键的自动生成方式。 1.序列的使用 序列…

ASP.NET MVC应用迁移到ASP.NET Core及其异同简介

ASP.NET Core是微软新推出支持跨平台、高性能、开源的开发框架&#xff0c;相比起原有的ASP.NET来说&#xff0c;ASP.NET Core更适合开发现代应用程序&#xff0c;如跨平台、Dorker的支持、集成现代前端开发框架(如npm、bower、gulp等等)。另外相比ASP.NET它的性能更好&#xf…

Div1 小A抓小B tarjan双连通分量缩点+dfs

题目描述 小A和小B在一个无向图G上进行一个游戏。图G是连通的&#xff0c;有n个点&#xff0c;n条边&#xff0c;无重边&#xff0c;无自环&#xff0c;结点编号为1~n。游戏开始前小A在结点x&#xff0c;小B在结点y&#xff08;x≠y&#xff09;。游戏开始后&#xff0c;小A和小…

P3809-[模板]后缀排序(SA)

正题 题目链接:https://www.luogu.com.cn/problem/P3809 题目大意 长度为nnn的字符串&#xff0c;求它的字符数组(后缀排序后排名为iii的在哪个位置)。 解题思路 大概思路就是倍增排序&#xff0c;先排每个后缀的第一个字符&#xff0c;然后是两个&#xff0c;然后是四个&am…

8、oracle数据库下的索引

ORACLE下的索引 索引是oracle下的一类对象&#xff0c;主要用于提高查询的效率。 1.操作索引 1.1 创建、修改索引 -- 添加一般索引 create index i_test_tname on test(tname); -- 添加唯一索引&#xff0c;不能包含相同的值 create unique index i_test_tname on test(tnam…

使用 C# (.NET Core) 实现模板方法模式 (Template Method Pattern)

本文的概念内容来自深入浅出设计模式一书.项目需求有一家咖啡店, 供应咖啡和茶, 它们的工序如下:咖啡:茶:可以看到咖啡和茶的制作工序是差不多的, 都是有4步, 其中有两步它们两个是一样的, 另外两步虽然具体内容不一样, 但是都做做的同一类工作.现在问题也有了, 当前的设计两个…

P2396-yyylovesMathsVII【状压dp】

正题 题目链接:https://www.luogu.com.cn/problem/P2396 题目大意 nnn个数字&#xff0c;依次选择若干个数字使得没有任何一个前缀和等于厄运数字&#xff0c;厄运数字有mmm个。 解题思路 先预处理出disidis_idisi​表示集合iii的数字和。 然后对于disidis_idisi​不等于厄运…

9、oracle数据库下的视图和同义词

ORACLE下的视图和同义词 1.视图 视图就是一个查询的结果&#xff0c;可能包含一张表或者多张表的信息&#xff0c;创建视图其目的在于&#xff0c;便于查看表中的信息。视图只是在逻辑上存在。 1.1创建/修改视图 创建视图一定要有CREATE VIEW权限&#xff0c;基本语法为&am…

.NET Core Community 首个千星项目诞生:CAP

项目简介在我们构建 SOA 或者 微服务系统的过程中&#xff0c;我们通常需要使用事件来对各个服务进行集成&#xff0c;在这过程中简单的使用消息队列并不能保证数据的最终一致性&#xff0c; CAP 采用的是和当前数据库集成的本地消息表的方案来解决在分布式系统互相调用的各个环…

P2463-[SDOI2008]Sandy的卡片【SA,二分答案】

正题 题目链接:https://www.luogu.com.cn/problem/P2463 题目大意 nnn个长度不同的数字序列&#xff0c;序列的子串相同的定义是该子串相邻的两两差相同。 求公共子串的最长长度。 解题思路 做一个差分后问题就变为了求nnn个串的最长公共子串。 我们将所有的字符串接在一起…

10、oracle下PL/SQL编程基础

ORACLE下的PL/SQL编程基础 PL/SQL语言是程序化程序设计语言&#xff0c;块是PL/SQL编程中的基本结构&#xff0c;其优点在于支持SQL、支持面向对象编程、性能好、可移植性、与sql集成、安全性高等。 1.基本语法 1.1 基本语法结构 [set severoutput on] declare 变量&…

.NET Core 从 Github到 Nuget 持续集成、部署

一.前言Nuget 作为一个.NET研发人员&#xff0c;我想你都不会陌生&#xff0c;他为我们提供非常方便的程序包管理&#xff0c;不管是版本&#xff0c;还是包的依赖都能轻松应对&#xff0c;可以说是我们的好助手。而 Nuget 除了官方nuget.org以外&#xff0c;我们也可以用起提供…

P2336-[SCOI2012]喵星球上的点名【SA,树状数组】

正题 题目链接:https://www.luogu.com.cn/problem/P2336 题目大意 nnn个名字(每个名字两个串)&#xff0c;mmm次点名&#xff0c;如果一次点名里是一个名字两个串中的子串该人就要答到。 对于每次点名求多少个人答到&#xff0c;每个名字求答到多少次。 解题思路 先考虑第一…

11、oracle数据库下的事务和触发器

ORACLE下的事务和触发器 1.事务 事务是数据库的一种机制&#xff0c;当执行一系列操作时&#xff0c;事务可以保证这一系列操作都能完成&#xff0c;在此期间如果出现问题&#xff0c;则这一系列操作导致的结果均回退到原始状态。这样就保证了数据的一致性&#xff0c;事务在…

微软发布自己定制的 Linux 内核和发行版,面向物联网

微软首次发布了自己的定制 Linux 内核和发行版。在旧金山举行的新闻发布会上&#xff0c;微软宣布了针对物联网设备的解决方案 Azure Sphere。Azure Sphere 包含三个组件。其中之一是微软设计的 Sphere MCU&#xff0c;将免费提供给厂商&#xff0c;联发科将在今年晚些时候推出…

P3804-[模板]后缀自动机【SAM】

正题 题目链接:https://www.luogu.com.cn/problem/P3804 题目大意 长度为nnn的串&#xff0c;求一个出现次数不小于2的子串使得子串长度乘上出现次数最大。 解题思路 构建SAMSAMSAM的时候统计一下每个子串出现多少次即可。 codecodecode #include<cstdio> #include&l…

MEDIATR 一个低调的中介者类库

微软官方的开源项目eShopOnContainers中&#xff0c;用到了一个实现中介者模式的类库&#xff1a;MediatR。这个类库的作者叫Jimmy Bogard&#xff0c;在其gtihub主页上可以看到&#xff0c;注明的对象映射组件AutoMapper 就是他写的。其博客上的自我介绍是这么写的&#xff1a…

12、oracle数据库下的存储过程和函数

ORACLE下的存储过程和函数 存储过程和函数是一种操作块&#xff0c;用来流程化、整体化处理业务逻辑的数据库操作方式。我理解的是相当于java开发语言中方法的概念&#xff0c;存储过程和函数的区别在于函数可以有返回值&#xff0c;而过程没有返回值。 1.存储过程 -- 创建存…

Codeforces Gym 101173 CERC 16 D BZOJ 4790 Dancing Disks

Codeforces Gym 101173 CERC 16 D & BZOJ 4790 Dancing Disks 强烈安利这道构造题目&#xff0c;非常有意思。 这里用到的思想是归并排序&#xff01; 多路归并排序&#xff01; 我们这样想&#xff0c;假设6*6的网格中除了最后一个网格外&#xff0c;其他的凡是有元素…