2.请求的处理(图的后两个泳道)
请求的处理大部分功能在中间件EndpointRoutingMiddleware,他有个重要的属性_endpointDataSource保存了上文中初始化阶段生成的MvcEndpointDataSource,而中间件EndpointMiddleware的功能比较简单,主要是在EndpointRoutingMiddleware筛选出endpoint之后,调用该endpoint的endpoint.RequestDelegate(httpContext)进行请求处理。
⑦ InitializeAsync()方法主要是用于调用InitializeCoreAsync()创建一个matcher,而通过这个方法的代码可以看出它只是在第一次请求的时候执行一次。
private Task<Matcher> InitializeAsync() { var initializationTask = _initializationTask; if (initializationTask != null) { return initializationTask; } return InitializeCoreAsync(); }
⑧ MvcEndpointDataSource一个重要的方法UpdateEndpoints(),作用是读取所有action,并将这个action列表与它的ConventionalEndpointInfos列表(见⑤)进行匹配,最终生成一个新的列表。如下图,我们默认情况下只配置了一个"{controller=Home}/{action=Index}/{id?}"这样的路由,默认的HomeController有三个action,添加了一个名为FlyLoloController的controller并添加了一个带属性路由的action,最终生成了7个Endpoint,这有点像路由与action的“乘积”。当然,这里只是用默认程序举了个简单的例子,实际项目中可能会有更多的路由模板注册、会有更多的Controller和Action以及属性路由等。
图二
具体代码如下:
1 private void UpdateEndpoints() 2 { 3 lock (_lock) 4 { 5 var endpoints = new List<Endpoint>(); 6 StringBuilder patternStringBuilder = null; 7 8 foreach (var action in _actions.ActionDescriptors.Items) 9 { 10 if (action.AttributeRouteInfo == null) 11 { 12 // In traditional conventional routing setup, the routes defined by a user have a static order 13 // defined by how they are added into the list. We would like to maintain the same order when building 14 // up the endpoints too. 15 // 16 // Start with an order of '1' for conventional routes as attribute routes have a default order of '0'. 17 // This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world. 18 var conventionalRouteOrder = 1; 19 20 // Check each of the conventional patterns to see if the action would be reachable 21 // If the action and pattern are compatible then create an endpoint with the 22 // area/controller/action parameter parts replaced with literals 23 // 24 // e.g. {controller}/{action} with HomeController.Index and HomeController.Login 25 // would result in endpoints: 26 // - Home/Index 27 // - Home/Login 28 foreach (var endpointInfo in ConventionalEndpointInfos) 29 { 30 // An 'endpointInfo' is applicable if: 31 // 1. it has a parameter (or default value) for 'required' non-null route value 32 // 2. it does not have a parameter (or default value) for 'required' null route value 33 var isApplicable = true; 34 foreach (var routeKey in action.RouteValues.Keys) 35 { 36 if (!MatchRouteValue(action, endpointInfo, routeKey)) 37 { 38 isApplicable = false; 39 break; 40 } 41 } 42 43 if (!isApplicable) 44 { 45 continue; 46 } 47 48 conventionalRouteOrder = CreateEndpoints( 49 endpoints, 50 ref patternStringBuilder, 51 action, 52 conventionalRouteOrder, 53 endpointInfo.ParsedPattern, 54 endpointInfo.MergedDefaults, 55 endpointInfo.Defaults, 56 endpointInfo.Name, 57 endpointInfo.DataTokens, 58 endpointInfo.ParameterPolicies, 59 suppressLinkGeneration: false, 60 suppressPathMatching: false); 61 } 62 } 63 else 64 { 65 var attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template); 66 67 CreateEndpoints( 68 endpoints, 69 ref patternStringBuilder, 70 action, 71 action.AttributeRouteInfo.Order, 72 attributeRoutePattern, 73 attributeRoutePattern.Defaults, 74 nonInlineDefaults: null, 75 action.AttributeRouteInfo.Name, 76 dataTokens: null, 77 allParameterPolicies: null, 78 action.AttributeRouteInfo.SuppressLinkGeneration, 79 action.AttributeRouteInfo.SuppressPathMatching); 80 } 81 } 82 83 // See comments in DefaultActionDescriptorCollectionProvider. These steps are done 84 // in a specific order to ensure callers always see a consistent state. 85 86 // Step 1 - capture old token 87 var oldCancellationTokenSource = _cancellationTokenSource; 88 89 // Step 2 - update endpoints 90 _endpoints = endpoints; 91 92 // Step 3 - create new change token 93 _cancellationTokenSource = new CancellationTokenSource(); 94 _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token); 95 96 // Step 4 - trigger old token 97 oldCancellationTokenSource?.Cancel(); 98 } 99 }
View Code
本质就是计算出一个个可能被请求的请求终结点,也就是Endpoint。由此可见,如上一篇文章那样想自定义一个handler来处理特殊模板的方式(如 routes.MapRoute("flylolo/{code}/{name}", MyRouteHandler.Handler);)将被忽略掉,因其无法生成 Endpoint,且此种方式完全可以自定义一个中间件来实现,没必要混在路由中。
⑨ 就是用上面生成的Matcher,携带Endpoint列表与请求URL做匹配,并将匹配到的Endpoint赋值给feature.Endpoint。
⑩ 获取feature.Endpoint,若存在则调用其RequestDelegate处理请求httpContext。
三、新版与旧版的异同点总结
简要从应用系统启动和请求处理两个阶段对比说一下两个版本的区别:
1.启动阶段:
这个阶段大部分都差不多,都是通过Startup的app.UseMvc()方法配置一个路由表,一个Route的集合Routes(IList<IRouter>),然后将其简单转换一下
<=2.1: 将Routes转换为RouteCollection
2.2+ : 将Routes转换为List<MvcEndpointInfo>
二者区别不大,虽然名字不同,但本质上还是差不多,都仍可理解为Route的集合的包装。
2.请求处理阶段:
<=2.1: 1. 将请求的URL与RouteCollection中记录的路由模板进行匹配。
2. 找到匹配的Route之后,再根据这个请求的URL判断是否存在对应的Controlled和Action。
3. 若以上均通过,则调用Route的Handler对HttpContext进行处理。
2.2+ : 1. 第一次处理请求时,首先根据启动阶段所配置的路由集合List<MvcEndpointInfo>和_actions.ActionDescriptors.Items(所有的action的信息)做匹配,生成一个列表,这个列表存储了所有可能被匹配的URL模板,如图二,这个列表同样是List<MvcEndpointInfo>,记录了所有可能的URL模式,实际上是列出了一个个可以被访问的详细地址,已经算是最终地址了,即终结点,或许就是为什么叫Endpoint路由的原因。
2.请求的Url和这个生成的表做匹配,找到对应的MvcEndpointInfo。
3. 调用被匹配的MvcEndpointInfo的RequestDelegate方法对请求进行处理。
二者区别就是对于_actions.ActionDescriptors.Items(所有的action的信息)的匹配上,原版是先根据路由模板匹配后,再根据ActionDescriptors判断是否存在对应的Controller和action,而新版是先利用了action信息与路由模板匹配,然后再用请求的URL进行匹配,由于这样的工作只在第一次请求的时候执行,所以虽然没有做执行效率上的测试,但感觉应该是比之前快的。