1. <dd id="erndk"></dd>
                1. ASP.NET Core 2.2 : 十六.扒一扒新的Endpoint路由方案

                  FlyLolo 2019/9/4 11:31:09

                  ASP.NET Core 從2.2版本開始,采用了一個新的名為Endpoint的路由方案,與原來的方案在使用上差別不大,但從內部運行方式上來說,差別還是很大的。上一篇詳細介紹了原版路由方案的運行機制,本文仍然通過一幅圖來了解一下新版的運行機制,最后再總結一下二者的異同點。(ASP.NET Core

                  ASP.NET Core 從2.2版本開始,采用了一個新的名為Endpoint的路由方案,與原來的方案在使用上差別不大,但從內部運行方式上來說,差別還是很大的。上一篇詳細介紹了原版路由方案的運行機制,本文仍然通過一幅圖來了解一下新版的運行機制,最后再總結一下二者的異同點。(ASP.NET Core 系列目錄

                  一、概述

                         此方案從2.2版本開始,被稱作終結點路由(下文以“新版”稱呼),它是默認開啟的,若想采用原來的方案(<=2.1,下文以原版稱呼),可以在AddMvc的時候進行設置

                  services.AddMvc(option=>option.EnableEndpointRouting = false).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

                  EnableEndpointRouting 默認為true,也就是啟用新的Endpoint方案,設置為false則采用舊版(<=2.1)的路由方案。

                          在配置方法上來說,系統仍然采用在Startup中的use.Mvc()中配置,而實際上內部的處理中間件已由原來的RouterMiddleware改為EndpointMiddleware和EndpointRoutingMiddleware兩個中間件處理,下面依舊通過一幅圖來詳細看一下:

                   二、流程及解析

                                                                                              圖一

                  為了方便查看,依然對幾個“重點對象”做了顏色標識(點擊圖片可以看大圖):

                        1. 路由的初始化配置(圖的前兩個泳道) 

                    ①  一切依然是從Startup開始,而且和舊版一樣,是通過UseMvc方法進行配置,傳入routes.MapRoute(...)這樣的一個或多個配置, 不做贅述。
                    下面著重說一下后面的流程,看一下MvcApplicationBuilderExtensions中的UseMvc方法:
                   1 public static IApplicationBuilder UseMvc(
                      this IApplicationBuilder app,
                      Action<IRouteBuilder> configureRoutes)
                  {
                  //此處各種驗證,略。。
                      var options = app.ApplicationServices.GetRequiredService<IOptions<MvcOptions>>();
                      if (options.Value.EnableEndpointRouting)
                      {
                          var mvcEndpointDataSource = app.ApplicationServices
                              .GetRequiredService<IEnumerable<EndpointDataSource>>()
                              .OfType<MvcEndpointDataSource>()
                              .First();
                          var parameterPolicyFactory = app.ApplicationServices
                              .GetRequiredService<ParameterPolicyFactory>();
                  
                          var endpointRouteBuilder = new EndpointRouteBuilder(app);
                  
                          configureRoutes(endpointRouteBuilder);
                  
                          foreach (var router in endpointRouteBuilder.Routes)
                          {
                              // Only accept Microsoft.AspNetCore.Routing.Route when converting to endpoint
                              // Sub-types could have additional customization that we can't knowingly convert
                              if (router is Route route && router.GetType() == typeof(Route))
                              {
                                  var endpointInfo = new MvcEndpointInfo(
                                      route.Name,
                                      route.RouteTemplate,
                                      route.Defaults,
                                      route.Constraints.ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value),
                                      route.DataTokens,
                                      parameterPolicyFactory);
                               mvcEndpointDataSource.ConventionalEndpointInfos.Add(endpointInfo);
                              }
                              else
                              {
                                  throw new InvalidOperationException($"Cannot use '{router.GetType().FullName}' with Endpoint Routing.");
                              }
                          }
                          if (!app.Properties.TryGetValue(EndpointRoutingRegisteredKey, out _))
                          {
                              // Matching middleware has not been registered yet
                              // For back-compat register middleware so an endpoint is matched and then immediately used
                              app.UseEndpointRouting();
                          }
                          return app.UseEndpoint();
                      }
                      else
                      {
                         //舊版路由方案
                      }
                  }

                              ② 第6行,這里會獲取并判斷設置的EnableEndpointRouting的值,若為false,則采用舊版路由,詳見上一篇文章;該值默認為true,即采用新版路由。
                              ③ 對應第9行,MvcEndpointDataSource在新版路由中是個非常非常重要的角色,在啟動初始化階段,它完成了路由表存儲和轉換,此處先用顏色重點標記一下,大家記住它,在后面的流程中詳細介紹。
                              ④ 對應第16行,同舊版的RouteBuilder一樣,這里會new一個 endpointRouteBuilder,二者都是一個IRouteBuilder,所以也同樣調用configureRoutes(endpointRouteBuilder)方法(也就是startup中的配置)獲取了一個Route的集合(IList<IRouter>)賦值給endpointRouteBuilder.Routes,這里有個特別該注意的地方if (router is Route route && router.GetType() == typeof(Route)) ,也就是這里只接受route類型,終結點路由系統不支持基于 IRouter的可擴展性,包括從 Route繼承。
                              ⑤ 對應第20行,這里對剛獲取到的endpointRouteBuilder.Routes進行遍歷,轉換成了一個MvcEndpointInfo的集和,賦值給mvcEndpointDataSource.ConventionalEndpointInfos。
                              ⑥ 之后就是向管道塞中間件了,這里的處理中間件由原來的RouterMiddleware改為EndpointMiddleware和EndpointRoutingMiddleware。

                         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()
                          {
                              lock (_lock)
                              {
                                  var endpoints = new List<Endpoint>();
                                  StringBuilder patternStringBuilder = null;
                  
                                  foreach (var action in _actions.ActionDescriptors.Items)
                                  {
                                      if (action.AttributeRouteInfo == null)
                                      {
                                          // In traditional conventional routing setup, the routes defined by a user have a static order
                                          // defined by how they are added into the list. We would like to maintain the same order when building
                                          // up the endpoints too.
                                          //
                                          // Start with an order of '1' for conventional routes as attribute routes have a default order of '0'.
                                          // This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world.
                                          var conventionalRouteOrder = 1;
                  
                                          // Check each of the conventional patterns to see if the action would be reachable
                                          // If the action and pattern are compatible then create an endpoint with the
                                          // area/controller/action parameter parts replaced with literals
                                          //
                                          // e.g. {controller}/{action} with HomeController.Index and HomeController.Login
                                          // would result in endpoints:
                                          // - Home/Index
                                          // - Home/Login
                                          foreach (var endpointInfo in ConventionalEndpointInfos)
                                          {
                                              // An 'endpointInfo' is applicable if:
                                              // 1. it has a parameter (or default value) for 'required' non-null route value
                                              // 2. it does not have a parameter (or default value) for 'required' null route value
                                              var isApplicable = true;
                                              foreach (var routeKey in action.RouteValues.Keys)
                                              {
                                                  if (!MatchRouteValue(action, endpointInfo, routeKey))
                                                  {
                                                      isApplicable = false;
                                                      break;
                                                  }
                                              }
                  
                                              if (!isApplicable)
                                              {
                                                  continue;
                                              }
                  
                                              conventionalRouteOrder = CreateEndpoints(
                                                  endpoints,
                                                  ref patternStringBuilder,
                                                  action,
                                                  conventionalRouteOrder,
                                                  endpointInfo.ParsedPattern,
                                                  endpointInfo.MergedDefaults,
                                                  endpointInfo.Defaults,
                                                  endpointInfo.Name,
                                                  endpointInfo.DataTokens,
                                                  endpointInfo.ParameterPolicies,
                                                  suppressLinkGeneration: false,
                                                  suppressPathMatching: false);
                                          }
                                      }
                                      else
                                      {
                                          var attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template);
                  
                                          CreateEndpoints(
                                              endpoints,
                                              ref patternStringBuilder,
                                              action,
                                              action.AttributeRouteInfo.Order,
                                              attributeRoutePattern,
                                              attributeRoutePattern.Defaults,
                                              nonInlineDefaults: null,
                                              action.AttributeRouteInfo.Name,
                                              dataTokens: null,
                                              allParameterPolicies: null,
                                              action.AttributeRouteInfo.SuppressLinkGeneration,
                                              action.AttributeRouteInfo.SuppressPathMatching);
                                      }
                                  }
                  
                                  // See comments in DefaultActionDescriptorCollectionProvider. These steps are done
                                  // in a specific order to ensure callers always see a consistent state.
                  
                                  // Step 1 - capture old token
                                  var oldCancellationTokenSource = _cancellationTokenSource;
                  
                                  // Step 2 - update endpoints
                                  _endpoints = endpoints;
                  
                                  // Step 3 - create new change token
                                  _cancellationTokenSource = new CancellationTokenSource();
                                  _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token);
                  
                                  // Step 4 - trigger old token
                                  oldCancellationTokenSource?.Cancel();
                              }
                          }
                  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進行匹配,由于這樣的工作只在第一次請求的時候執行,所以雖然沒有做執行效率上的測試,但感覺應該是比之前快的。

                  隨時隨地學軟件編程-關注百度小程序和微信小程序
                  關于找一找教程網

                  本站文章僅代表作者觀點,不代表本站立場,所有文章非營利性免費分享。
                  本站提供了軟件編程、網站開發技術、服務器運維、人工智能等等IT技術文章,希望廣大程序員努力學習,讓我們用科技改變世界。
                  [ASP.NET Core 2.2 : 十六.扒一扒新的Endpoint路由方案]http://www.yachtsalesaustralia.com/tech/detail-90212.html

                  贊(0)
                  關注微信小程序
                  程序員編程王-隨時隨地學編程

                  掃描二維碼或查找【程序員編程王】

                  可以隨時隨地學編程啦!

                  技術文章導航 更多>
                  国产在线拍揄自揄视频菠萝

                        1. <dd id="erndk"></dd>