在启动流程文章中提到,在WebHost类中,通过BuildApplication完成http请求处理管道的构建。在来看一下代码:

 。。。。。。
//这个调用的就是Startup.cs类中的Configure方法
configure(builder);
//生成中间件链式结构
return builder.Build();

在框架中,一个中间件处理逻辑是使用一个RequestDelegate委托类型来表示的,定义:delegate Task RequestDelegate(HttpContext context)

那是不是我们直接创建委托方法就可以了?答案是否定的,为了形成一个链式结构,中间定义跟注册都有一定的要求。

首先先介绍下如何定义一个中间件。定义中间件只需要定义一个类即可,但是这个类并不是随意写,里面的结构由一定的要求:

1,类必须包含一个构造方法,这个构造方法的第一个参数必须是一个RequestDelegate类型,这个参数表达的就是当前定义中间件的下一个中间件。

2,必须包含一个Invoke方法,方法的第一个参数必须是HttpContext类型,返回值类型必须是Task,Invoke方法中实现中间件逻辑

下面是一个中间件定义实例代码:

class MiddlewareSample
{
private RequestDelegate _next;
public MiddlewareSample(RequesetDelegate next)
{
_next=next;
}
public Task Invoke(HttpContext context)
{
//中间件逻辑代码
...... //调用下一个中间件,实现链式调用,当然可以根据业务场景去确定是否继续往下调用
_next(context);
}
}

  

  

再来看下,如何把定义好的中间件注册到管道中。IApplicationBuilder提供了UseMiddleware的扩展方法,通过这个方法就可以把一个中间件类型注册到管道中。那我们来看一下,它里面到底做了什么?

public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
{
var applicationServices = app.ApplicationServices;
return app.Use(next =>
{
var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
//通过反射获取Invoke方法信息
var invokeMethods = methods.Where(m => string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)).ToArray();
if (invokeMethods.Length > 1)
{
throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName));
} if (invokeMethods.Length == 0)
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName));
} var methodinfo = invokeMethods[0];
if (!typeof(Task).IsAssignableFrom(methodinfo.ReturnType))
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, nameof(Task)));
} var parameters = methodinfo.GetParameters();
//判断Invoke方法是否包含HttpContext参数,并且要求是第一个参数
if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, nameof(HttpContext)));
} var ctorArgs = new object[args.Length + 1];
ctorArgs[0] = next;
Array.Copy(args, 0, ctorArgs, 1, args.Length); //实例化中间件类型,并把next最为构造方法的第一个参数传递进去
var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
if (parameters.Length == 1)
{
//如果invoke方法只包含一个httpcontext参数,直接创建RequestDelegate
return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
}
//这里我把它理解成一个RequestDelegate代理委托
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);
};
});
}  

  

  

  在UseMiddleware方法中,其实是通过反射的方式,解析中间件定义的类型,最后创建了一个中间件工厂委托对象,工厂就是一个Func<RequestDelegate, RequestDelegate>委托类型,它接收一个RequestDelegate中间件,然后返回一个新的中间件,接收的这个中间件参数是新生成的这个中间件的下一个中间件。创建好工厂后,调用IApplicationBuilder.Use方法,把这个工厂加入到中间件工厂列表中,Use方法实现如下:

     public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
_components.Add(middleware);
return this;
}

  到此只是有了一个中间件工厂集合,最后通过调用builder.Build()方法中完成的中间件链式结构的生成。

     public RequestDelegate Build()
{
RequestDelegate app = context =>
{
context.Response.StatusCode = 404;
return TaskCache.CompletedTask;
}; foreach (var component in _components.Reverse())
{
app = component(app);
} return app;
}

  首先定义了一个返回404错误异常的一个RequestDelegate,这个是作为中间件的然后把中间件集合反转,依次调用中间件工厂来完成中间件的初始化。这里为什么要反转,我们来分析下。

  假如我们注册中间件顺序为1->2->3->4,那_components中工厂的顺序就是工厂1->工厂2->工厂3->工厂4,反转后就成了工厂4->工厂3->工厂2->工厂1,然后进入for循环,首先调用工厂4,用返回404错误中间件作为参数,最后返回一个中间件4,然后调用工厂3,把中间件4传递进去,再生成一个中间件,最后调用工厂1,这样就形成了 中间件1->中间件2->中间件3->中间件4->404错误中间件 这样的链式结构。当一个http请求过来后,就会按照这个顺序依次执行对应的中间件逻辑。

  另外框架还提供了分支特性,就是根据条件不同,可以选择不同的管道分支来处理,如下图:

  HttpAbstractions项目中提供了IApplicationBuilder.MapWhen扩展方法,方法如下:

public static IApplicationBuilder MapWhen(this IApplicationBuilder app, Predicate predicate, Action<IApplicationBuilder> configuration)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
} if (predicate == null)
{
throw new ArgumentNullException(nameof(predicate));
} if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
} // 创建了一个新的分支ApplicationBulider
var branchBuilder = app.New();
//configuration类似Startup.cs中的Configuration方法,目的是配置分支的处理管道
configuration(branchBuilder);
var branch = branchBuilder.Build(); // predicate就是分支进入的条件,branch就是创建好的分支管道
var options = new MapWhenOptions
{
Predicate = predicate,
Branch = branch,
};
//借助MapWhenMiddleware封装了分支处理管道,然后加入到当前的主管道中
return app.Use(next => new MapWhenMiddleware(next, options).Invoke);
}

  

  来看一下MapWhenMiddleware中间件的Invoke方法实现

public async Task Invoke(HttpContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
     //判断当前条件是否符合配置的条件
if (_options.Predicate(context))
{
//符合条件,直接进入分支的处理管道
await _options.Branch(context);
}
else
{
//不符合条件的话,直接进入主管道的下一个中间件
await _next(context);
}
}

  

  从上面的逻辑我们可以看出来,如果进入了分支管道,那主管道中的后续中间件都执行不了了,我们可以这么理解,一旦使用了MapWhen,创建好分支后,主管道后续注册中间件也可以看作一个分支,只有当其他分支管道都不符合条件的时候,再进入这个分支,如下图:

分支3并不是通过MapWhen注册的分支,这个只有在分支1,分支2都不符合条件的时候,才进入。

  

asp.net core mvc剖析:处理管道构建的更多相关文章

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

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

  2. asp.net core mvc剖析:路由

    在mvc框架中,任何一个动作请求都会被映射到具体控制器中的方法上,那框架是如何完成这样一个过程的,现在我们就来简单分析下流程. 我们紧跟上面的主题,任何一个请求都会交给处理管道进行处理,那mvc处理的 ...

  3. asp.net core mvc剖析:KestrelServer

    KestrelServer是基于Libuv开发的高性能web服务器,那我们现在就来看一下它是如何工作的.在上一篇文章中提到了Program的Main方法,在这个方法里Build了一个WebHost,我 ...

  4. asp.net core mvc剖析:mvc执行过程(一)

    前面介绍了路由的过程,我们再来看下MvcRouteHandler的代码: public Task RouteAsync(RouteContext context) { ...... //根据路由信息查 ...

  5. asp.net core mvc剖析:mvc动作选择

    一个http请求过来后,首先经过路由规则的匹配,找到最符合条件的的IRouter,然后调用IRouter.RouteAsync来设置RouteContext.Handler,最后把请求交给RouteC ...

  6. asp.net core mvc剖析:动作执行

    紧跟上一篇文章.通过路由和动作匹配后,最终会得到跟当前请求最匹配的一个ActionDescriptor,然后通过IActionInvoker执行动作. 我们先来看一下IActionInvoker如何得 ...

  7. net core mvc剖析:启动流程

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

  8. 剖析ASP.NET Core MVC(Part 1)- AddMvcCore(译)

    原文:https://www.stevejgordon.co.uk/asp-net-core-mvc-anatomy-addmvccore发布于:2017年3月环境:ASP.NET Core 1.1 ...

  9. ASP.NET Core MVC中构建Web API

    在ASP.NET CORE MVC中,Web API是其中一个功能子集,可以直接使用MVC的特性及路由等功能. 在成功构建 ASP.NET CORE MVC项目之后,选中解决方案,先填加一个API的文 ...

随机推荐

  1. ubuntu升级php版本

    如果安装的 PHP 版本过低的话,可以通过下面的指令来升级: sudo add-apt-repository ppa:ondrej/php5   sudo apt-get update   sudo  ...

  2. 【 bzoj4537】HNOI2016 最小公倍数

    首先将边按a的值分组,每$\sqrt{m}$一组. 对于每一组,将符合一组a的询问选出来,将这些询问和这一块之前的边(a一定小于这些询问)按b排序,然后交替插入,询问,对于一个询问,在当前块也有可能有 ...

  3. jQuery之事件移除

    当事件执行完了,想取消事件的效果可以通过一定的办法来处理.比如bind()方法,可以通过unbind()方法来移除事件的效果. 比如下面的一个案例: <script type="tex ...

  4. ServiceStack.Redis 使用链接池方法

    PooledRedisClientManager 1.RedisManage.cs public static class RedisManager { private static PooledRe ...

  5. Java虚拟机——进度1

    Java 虚拟机       一.Java虚拟机的基本结构 ①类加载子系统:从文件系统或者网络中加载Class信息,存放在方法区中. ②方法区中存放放进来的Class信息,也包括一些运行时常量池信息包 ...

  6. 解析xml的问题未解决

    工作上需要解析xml,目前的逻辑是:解析xml到对象中,然后对象再插入数据库.但这存在内存溢出的风险. 今天做的另外一件事是将循环用到工作上,暂时还没有测试,是否能保证程序的重启.有待观察 ##### ...

  7. 谈谈线程同步Lock和unLock

    Lock可以使用Condition进行线程之间的调度,它有更好的灵活性,而且在一个对象里面可以有多个Condition(即对象监视器),则线程可以注册在不同的Condition,从而可以 有选择性的调 ...

  8. Java经典案例之-“最大公约数和最小公倍数”

    /** * 描述:输入两个正整数m和n,求其最大公约数和最小公倍数.(最大公约数:最大公约数, * 也称最大公因数.最大公因子,指两个或多个整数共有约数中最大的一个.) * (最小公倍数:几个数共有的 ...

  9. Delphi流的操作

    一.流的概念 流简单说是建立在面向对象基础上的一种抽象的处理数据的工具,它定义了一些处理数据的基本操作,如读取数据,写入数据等,程序员只需掌握对流进行操作,而不用关心流的另一头数据的真正流向.其实,流 ...

  10. eclipse开发velocity实例(初学)

    开发环境         Eclipse Java EE IDE for Web Developers.(Version: Helios Service Release 1) jdk1.6.0_07 ...