<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          理解ASP.NET Core - 路由(Routing)

          共 18490字,需瀏覽 37分鐘

           ·

          2021-11-05 16:15


          Routing

          • Routing(路由):更準(zhǔn)確的應(yīng)該叫做Endpoint Routing,負(fù)責(zé)將HTTP請(qǐng)求按照匹配規(guī)則選擇對(duì)應(yīng)的終結(jié)點(diǎn)

          • Endpoint(終結(jié)點(diǎn)):負(fù)責(zé)當(dāng)HTTP請(qǐng)求到達(dá)時(shí),執(zhí)行代碼

          路由是通過(guò)UseRoutingUseEndpoints兩個(gè)中間件配合在一起來(lái)完成注冊(cè)的:
          public?class?Startup
          {
          ????public?void?ConfigureServices(IServiceCollection?services)
          ????{
          ????????//?添加Routing相關(guān)服務(wù)
          ????????//?注意,其已在?ConfigureWebDefaults?中添加,無(wú)需手動(dòng)添加,此處僅為演示
          ????????services.AddRouting();
          ????}
          ????
          ????public?void?Configure(IApplicationBuilder?app,?IWebHostEnvironment?env)
          ????{
          ????????app.UseRouting();
          ????
          ????????app.UseEndpoints(endpoints?=>
          ????????{
          ????????????endpoints.MapGet("/",?async?context?=>
          ????????????{
          ????????????????await?context.Response.WriteAsync("Hello?World!");
          ????????????});
          ????????});
          ????}
          }
          • UseRouting:用于向中間件管道添加路由匹配中間件(EndpointRoutingMiddleware)。該中間件會(huì)檢查應(yīng)用中定義的終結(jié)點(diǎn)列表,然后通過(guò)匹配 URL 和 HTTP 方法來(lái)選擇最佳的終結(jié)點(diǎn)。簡(jiǎn)單說(shuō),該中間件的作用是根據(jù)一定規(guī)則來(lái)選擇出終結(jié)點(diǎn)
          • UseEndpoints:用于向中間件管道添加終結(jié)點(diǎn)中間件(EndpointMiddleware)。可以向該中間件的終結(jié)點(diǎn)列表中添加終結(jié)點(diǎn),并配置這些終結(jié)點(diǎn)要執(zhí)行的委托,該中間件會(huì)負(fù)責(zé)運(yùn)行由EndpointRoutingMiddleware中間件選擇的終結(jié)點(diǎn)所關(guān)聯(lián)的委托。簡(jiǎn)單說(shuō),該中間件用來(lái)執(zhí)行所選擇的終結(jié)點(diǎn)委托


          UseRouting`與`UseEndpoints`必須同時(shí)使用,而且必須先調(diào)用`UseRouting`,再調(diào)用`UseEndpoints


          Endpoints

          先了解一下終結(jié)點(diǎn)的類(lèi)結(jié)構(gòu):

          public?class?Endpoint
          {
          ????public?Endpoint(RequestDelegate?requestDelegate,?EndpointMetadataCollection??metadata,?string??displayName);

          ????public?string??DisplayName?{?get;?}

          ????public?EndpointMetadataCollection?Metadata?{?get;?}

          ????public?RequestDelegate?RequestDelegate?{?get;?}

          ????public?override?string??ToString();
          }

          終結(jié)點(diǎn)有以下特點(diǎn):

          • 可執(zhí)行:含有RequestDelegate委托
          • 可擴(kuò)展:含有Metadata元數(shù)據(jù)集合
          • 可選擇:可選的包含路由信息
          • 可枚舉:通過(guò)DI容器,查找EndpointDataSource來(lái)展示終結(jié)點(diǎn)集合。

          在中間件管道中獲取路由選擇的終結(jié)點(diǎn)

          對(duì)于中間件還不熟悉的,可以先看一下中間件(Middleware)。

          在中間件管道中,我們可以通過(guò)HttpContext來(lái)檢索終結(jié)點(diǎn)等信息。需要注意的是,終結(jié)點(diǎn)對(duì)象在創(chuàng)建完畢后,是不可變的,無(wú)法修改。

          • 在調(diào)用UseRouting之前,你可以注冊(cè)一些用于修改路由操作的數(shù)據(jù),比如UseRewriterUseHttpMethodOverrideUsePathBase等。
          • 在調(diào)用UseRoutingUseEndpoints之間,可以注冊(cè)一些用于提前處理路由結(jié)果的中間件,如UseAuthenticationUseAuthorizationUseCors等。

          我們一起看下面的代碼:

          public?void?Configure(IApplicationBuilder?app,?IWebHostEnvironment?env)
          {
          ????app.Use(next?=>?context?=>
          ????{
          ????????//?在?UseRouting?調(diào)用前,始終為?null
          ????????Console.WriteLine($"1.?Endpoint:?{context.GetEndpoint()?.DisplayName????"null"}");
          ????????return?next(context);
          ????});

          ????//?EndpointRoutingMiddleware?調(diào)用?SetEndpoint?來(lái)設(shè)置終結(jié)點(diǎn)
          ????app.UseRouting();

          ????app.Use(next?=>?context?=>
          ????{
          ????????//?如果路由匹配到了終結(jié)點(diǎn),那么此處就不為?null,否則,還是?null
          ????????Console.WriteLine($"2.?Endpoint:?{context.GetEndpoint()?.DisplayName????"null"}");
          ????????return?next(context);
          ????});

          ????//?EndpointMiddleware?通過(guò)?GetEndpoint?方法獲取終結(jié)點(diǎn),
          ????//?然后執(zhí)行該終結(jié)點(diǎn)的?RequestDelegate?委托
          ????app.UseEndpoints(endpoints?=>
          ????{
          ????????endpoints.MapGet("/",?context?=>
          ????????{
          ????????????//?匹配到了終結(jié)點(diǎn),肯定不是?null
          ????????????Console.WriteLine($"3.?Endpoint:?{context.GetEndpoint()?.DisplayName????"null"}");
          ????????????return?Task.CompletedTask;
          ????????}).WithDisplayName("Custom?Display?Name");??//?自定義終結(jié)點(diǎn)名稱(chēng)
          ????});

          ????app.Use(next?=>?context?=>
          ????{
          ????????//?只有當(dāng)路由沒(méi)有匹配到終結(jié)點(diǎn)時(shí),才會(huì)執(zhí)行這里
          ????????Console.WriteLine($"4.?Endpoint:?{context.GetEndpoint()?.DisplayName????"null"}");
          ????????return?next(context);
          ????});
          }

          當(dāng)訪問(wèn)/時(shí),輸出為:

          1.?Endpoint:?null
          2.?Endpoint:?Custom?Display?Name
          3.?Endpoint:?Custom?Display?Name

          當(dāng)訪問(wèn)其他不匹配的URL時(shí),輸出為:

          1.?Endpoint:?null
          2.?Endpoint:?null
          4.?Endpoint:?null

          當(dāng)路由匹配到了終結(jié)點(diǎn)時(shí),EndpointMiddleware則是該路由的終端中間件;當(dāng)未匹配到終結(jié)點(diǎn)時(shí),會(huì)繼續(xù)執(zhí)行后面的中間件。

          終端中間件:與普通中間件不同的是,該中間件執(zhí)行后即返回,不會(huì)調(diào)用后面的中間件。

          配置終結(jié)點(diǎn)委托

          可以通過(guò)以下方法將委托關(guān)聯(lián)到終結(jié)點(diǎn)

          • MapGet
          • MapPost
          • MapPut
          • MapDelete
          • MapHealthChecks
          • 其他類(lèi)似“MapXXX”的方法
          public?void?Configure(IApplicationBuilder?app,?IWebHostEnvironment?env)
          {
          ????app.UseRouting();

          ????//?在執(zhí)行終結(jié)點(diǎn)前進(jìn)行授權(quán)
          ????app.UseAuthorization();

          ????app.UseEndpoints(endpoints?=>
          ????{
          ????????endpoints.MapGet("/",?async?context?=>?await?context.Response.WriteAsync("get"));
          ????????endpoints.MapPost("/",?async?context?=>?await?context.Response.WriteAsync("post"));
          ????????endpoints.MapPut("/",?async?context?=>?await?context.Response.WriteAsync("put"));
          ????????endpoints.MapDelete("/",?async?context?=>?await?context.Response.WriteAsync("delete"));
          ????????endpoints.MapHealthChecks("/healthChecks");
          ????????endpoints.MapControllers();
          ????});

          }

          路由模板

          規(guī)則:

          通過(guò){}來(lái)綁定路由參數(shù),如: {name}

          ?作為參數(shù)后綴,以指示該參數(shù)是可選的,如:{name?}

          通過(guò)=設(shè)置默認(rèn)值,如:{name=jjj} 表示name的默認(rèn)值是jjj

          通過(guò):添加內(nèi)聯(lián)約束,如:{id:int},后面追加:可以添加多個(gè)內(nèi)聯(lián)約束,如:{id:int:min(1)}

          多個(gè)路由參數(shù)間必須通過(guò)文本或分隔符分隔,例如 {a}{b} 就不符合規(guī)則,可以修改為類(lèi)似 {a}+-{b} 或 {a}/{b} 的形式

          先舉個(gè)例子,/book/{name}中的{name}為路由參數(shù),book

          為非路由參數(shù)文本。非路由參數(shù)的文本和分隔符/


          • 是不分區(qū)大小寫(xiě)的(官方中文文檔翻譯錯(cuò)了)


          • 要使用沒(méi)有被Url編碼的格式,如空格會(huì)被編碼為 %20,不應(yīng)使用 %20,而應(yīng)使用空格


          • 如果要匹配{},則使用{{}}進(jìn)行轉(zhuǎn)義


          catch-all參數(shù)

          路由模板中的星號(hào)*和雙星號(hào)**被稱(chēng)為catch-all參數(shù),該參數(shù)可以作為路由參數(shù)的前綴,如/Book/{*id}/Book/{**id},可以匹配以/Book開(kāi)頭的任意Url,如/Book/Book//Book/abc/Book/abc/def等。

          ***在一般使用上沒(méi)有什么區(qū)別,它們僅僅在使用LinkGenerator時(shí)會(huì)有不同,如id = abc/def,當(dāng)使用/Book/{*id}模板時(shí),會(huì)生成/Book/abc%2Fdef,當(dāng)使用/Book/{**id}模板時(shí),會(huì)生成/Book/abc/def

          復(fù)雜段

          復(fù)雜段通過(guò)非貪婪的方式從右到左進(jìn)行匹配,例如[Route("/a{b}cgo7utgvlrp")]就是一個(gè)復(fù)雜段。實(shí)際上,它的確很復(fù)雜,只有了解它的工作方式,才能正確的使用它。

          • 貪婪匹配(也稱(chēng)為“懶惰匹配”):匹配最大可能的字符串
          • 非貪婪匹配:匹配最小可能的字符串

          接下來(lái),就拿模板[Route("/a{b}cgo7utgvlrp")]來(lái)舉兩個(gè)例子:

          成功匹配的案例——當(dāng)Url為/abcd時(shí),匹配過(guò)程為(|用于輔助展示算法的解析方式):

          • 從右到左讀取模板,找到的第一個(gè)文本為c。接著,讀取Url/abcd,可解析為/ab|c|d
          • 此時(shí),Url中右側(cè)的所有內(nèi)容d均與路由參數(shù)go7utgvlrp匹配
          • 然后,繼續(xù)從右到左讀取模板,找到的下一個(gè)文本為a。接著,從剛才停下的地方繼續(xù)讀取Url/ab|c|d,解析為/a|b|c|d
          • 此時(shí),Url中右側(cè)的值b與路由參數(shù){b}匹配
          • 最后,沒(méi)有剩余的路由模板段或參數(shù),也沒(méi)有剩余的Url文本,因此匹配成功。

          匹配失敗的案例——當(dāng)Url為/aabcd時(shí),匹配過(guò)程為(|用于輔助展示算法的解析方式):

          • 從右到左讀取模板,找到的第一個(gè)文本為c。接著,讀取Url/aabcd,可解析為/aab|c|d
          • 此時(shí),Url中右側(cè)的所有內(nèi)容d均與路由參數(shù)go7utgvlrp匹配
          • 然后,繼續(xù)從右到左讀取模板,找到的下一個(gè)文本為a。接著,從剛才停下的地方繼續(xù)讀取Url/aab|c|d,解析為/a|a|b|c|d
          • 此時(shí),Url中右側(cè)的值b與路由參數(shù){b}匹配
          • 最后,沒(méi)有剩余的路由模板段或參數(shù),但還有剩余的Url文本,因此匹配不成功。

          使用復(fù)雜段,相比普通路由模板來(lái)說(shuō),會(huì)造成更加昂貴的性能影響

          路由約束

          通過(guò)路由約束,可以在路由匹配過(guò)程中,檢查URL是否是可接受的。另外,路由約束一般是用來(lái)消除路由歧義,而不是用來(lái)進(jìn)行輸入驗(yàn)證的。

          實(shí)現(xiàn)上,當(dāng)Http請(qǐng)求到達(dá)時(shí),路由參數(shù)和該參數(shù)的約束名會(huì)傳遞給IInlineConstraintResolver服務(wù),IInlineConstraintResolver服務(wù)會(huì)負(fù)責(zé)創(chuàng)建IRouteConstraint實(shí)例,以針對(duì)Url進(jìn)行處理。

          預(yù)定義的路由約束

          摘自官方文檔

          正則表達(dá)式路由約束

          通過(guò)regex(expression)來(lái)設(shè)置正則表達(dá)式約束,并且該正則表達(dá)式是:

          • RegexOptions.IgnoreCase:忽略大小寫(xiě)
          • RegexOptions.Compiled:將該正則表達(dá)式編譯為程序集。這會(huì)使得執(zhí)行速度更快,但會(huì)拖慢啟動(dòng)時(shí)間。
          • RegexOptions.CultureInvariant:忽略區(qū)域文化差異。

          另外,還需要注意對(duì)某些字符進(jìn)行轉(zhuǎn)義:

          • \替換為\\
          • {替換為{{}替換為}}
          • [替換為[[]替換為]]

          例如:

          • 指定 regex 約束的兩種方式:
          //?內(nèi)聯(lián)方式
          app.UseEndpoints(endpoints?=>
          {
          ????endpoints.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
          ????????context?=>?
          ????????{
          ????????????return?context.Response.WriteAsync("inline-constraint?match");
          ????????});
          ?});
          ?
          //?變量聲明方式
          app.UseEndpoints(endpoints?=>
          {
          ????endpoints.MapControllerRoute(
          ????????name:?"people",
          ????????pattern:?"People/{ssn}",
          ????????constraints:?new?{?ssn?=?"^\\d{3}-\\d{2}-\\d{4}$",?},
          ????????defaults:?new?{?controller?=?"People",?action?=?"List",?});
          });?

          不要書(shū)寫(xiě)過(guò)于復(fù)雜的正則表達(dá)式,否則,相比普通路由模板來(lái)說(shuō),會(huì)造成更加昂貴的性能影響

          自定義路由約束

          先說(shuō)一句,自定義路由約束很少會(huì)用到,在你決定要自定義路由約束之前,先想想是否有其他更好的替代方案,如使用模型綁定。

          通過(guò)實(shí)現(xiàn)IRouteConstraint接口來(lái)創(chuàng)建自定義路由約束,該接口僅有一個(gè)Match方法,用于驗(yàn)證路由參數(shù)是否滿(mǎn)足約束,返回true表示滿(mǎn)足約束,false則表示不滿(mǎn)足約束。

          以下示例要求路由參數(shù)中必須包含字符串“1”:

          public?class?MyRouteConstraint?:?IRouteConstraint
          {
          ????public?bool?Match(HttpContext?httpContext,?IRouter?route,?string?routeKey,?RouteValueDictionary?values,?RouteDirection?routeDirection)
          ????{
          ????????if?(values.TryGetValue(routeKey,?out?object?value))
          ????????{
          ????????????var?valueStr?=?Convert.ToString(value,?CultureInfo.InvariantCulture);

          ????????????return?valueStr?.Contains("1")????false;
          ????????}

          ????????return?false;
          ????}
          }

          然后進(jìn)行路由約束注冊(cè):

          public?void?ConfigureServices(IServiceCollection?services)
          {
          ????services.AddRouting(options?=>
          ????{
          ????????//?添加自定義路由約束,約束?Key?為?my
          ????????options.ConstraintMap["my"]?=?typeof(MyRouteConstraint);
          ????});
          }

          最后你就可以類(lèi)似如下進(jìn)行使用了:

          [HttpGet("{id:my}")]
          public?string?Get(string?id)
          {
          ????return?id;
          }

          路由模板優(yōu)先級(jí)

          考慮一下,有兩個(gè)路由模板:/Book/List/Book/{id},當(dāng)url為/Book/List時(shí),會(huì)選擇哪個(gè)呢?從結(jié)果我們可以得知,是模板/Book/List。它是根據(jù)以下規(guī)則來(lái)確定的:

          • 越具體的模板優(yōu)先級(jí)越高
          • 包含更多匹配段的模板更具體
          • 含有文本的段比參數(shù)段更具體
          • 具有約束的參數(shù)段比沒(méi)有約束的參數(shù)段更具體
          • 復(fù)雜段和具有約束的段同樣具體
          • catch-all參數(shù)段是最不具體的

          核心源碼解析

          AddRouting

          public?static?class?RoutingServiceCollectionExtensions
          {
          ????public?static?IServiceCollection?AddRouting(this?IServiceCollection?services)
          ????{
          ????????//?內(nèi)聯(lián)約束解析器,負(fù)責(zé)創(chuàng)建?IRouteConstraint?實(shí)例
          ????????services.TryAddTransient();
          ????????//?對(duì)象池
          ????????services.TryAddTransient();
          ????????services.TryAddSingleton>(s?=>
          ????????{
          ????????????var?provider?=?s.GetRequiredService();
          ????????????return?provider.Create(new?UriBuilderContextPooledObjectPolicy());
          ????????});

          ????????services.TryAdd(ServiceDescriptor.Transient(s?=>
          ????????{
          ????????????var?loggerFactory?=?s.GetRequiredService();
          ????????????var?objectPool?=?s.GetRequiredService>();
          ????????????var?constraintResolver?=?s.GetRequiredService();
          ????????????return?new?TreeRouteBuilder(loggerFactory,?objectPool,?constraintResolver);
          ????????}));

          ????????//?標(biāo)記已將所有路由服務(wù)注冊(cè)完畢
          ????????services.TryAddSingleton(typeof(RoutingMarkerService));

          ????????var?dataSources?=?new?ObservableCollection();
          ????????services.TryAddEnumerable(ServiceDescriptor.Transient,?ConfigureRouteOptions>(
          ????????????serviceProvider?=>?new?ConfigureRouteOptions(dataSources)));

          ????????//?EndpointDataSource,用于全局訪問(wèn)終結(jié)點(diǎn)列表
          ????????services.TryAddSingleton(s?=>
          ????????{
          ????????????return?new?CompositeEndpointDataSource(dataSources);
          ????????});

          ????????services.TryAddSingleton();
          ????????//?MatcherFactory,用于根據(jù)?EndpointDataSource?創(chuàng)建?Matcher
          ????????services.TryAddSingleton();
          ????????//?DfaMatcherBuilder,用于創(chuàng)建?DfaMatcher?實(shí)例
          ????????services.TryAddTransient();
          ????????services.TryAddSingleton();
          ????????services.TryAddTransient();
          ????????services.TryAddSingleton(services?=>
          ????????{
          ????????????return?new?EndpointMetadataComparer(services);
          ????????});

          ????????//?LinkGenerator相關(guān)服務(wù)
          ????????services.TryAddSingleton();
          ????????services.TryAddSingletonstring>,?EndpointNameAddressScheme>();
          ????????services.TryAddSingleton,?RouteValuesAddressScheme>();
          ????????services.TryAddSingleton();

          ????????//?終結(jié)點(diǎn)選擇、匹配策略相關(guān)服務(wù)
          ????????services.TryAddSingleton();
          ????????services.TryAddEnumerable(ServiceDescriptor.Singleton());
          ????????services.TryAddEnumerable(ServiceDescriptor.Singleton());

          ????????services.TryAddSingleton();
          ????????services.TryAddSingleton();
          ????????return?services;
          ????}

          ????public?static?IServiceCollection?AddRouting(
          ????????this?IServiceCollection?services,
          ????????Action?configureOptions
          )

          ????{
          ????????services.Configure(configureOptions);
          ????????services.AddRouting();

          ????????return?services;
          ????}
          }

          UseRouting

          public?static?class?EndpointRoutingApplicationBuilderExtensions
          {
          ????private?const?string?EndpointRouteBuilder?=?"__EndpointRouteBuilder";
          ????
          ????public?static?IApplicationBuilder?UseRouting(this?IApplicationBuilder?builder)
          ????{
          ????????VerifyRoutingServicesAreRegistered(builder);
          ????
          ????????var?endpointRouteBuilder?=?new?DefaultEndpointRouteBuilder(builder);
          ????????//?將?endpointRouteBuilder?放入共享字典中
          ????????builder.Properties[EndpointRouteBuilder]?=?endpointRouteBuilder;
          ????
          ????????//?將?endpointRouteBuilder?作為構(gòu)造函數(shù)參數(shù)傳入?EndpointRoutingMiddleware
          ????????return?builder.UseMiddleware(endpointRouteBuilder);
          ????}
          ????
          ????private?static?void?VerifyRoutingServicesAreRegistered(IApplicationBuilder?app)
          ????{
          ????????//?必須先執(zhí)行了?AddRouting
          ????????if?(app.ApplicationServices.GetService(typeof(RoutingMarkerService))?==?null)
          ????????{
          ????????????throw?new?InvalidOperationException(Resources.FormatUnableToFindServices(
          ????????????????nameof(IServiceCollection),
          ????????????????nameof(RoutingServiceCollectionExtensions.AddRouting),
          ????????????????"ConfigureServices(...)"));
          ????????}
          ????}
          }

          EndpointRoutingMiddleware

          終于到了路由匹配的邏輯了,才是我們應(yīng)該關(guān)注的,重點(diǎn)查看Invoke

          internal?sealed?class?EndpointRoutingMiddleware
          {
          ????private?const?string?DiagnosticsEndpointMatchedKey?=?"Microsoft.AspNetCore.Routing.EndpointMatched";

          ????private?readonly?MatcherFactory?_matcherFactory;
          ????private?readonly?ILogger?_logger;
          ????private?readonly?EndpointDataSource?_endpointDataSource;
          ????private?readonly?DiagnosticListener?_diagnosticListener;
          ????private?readonly?RequestDelegate?_next;

          ????private?Task??_initializationTask;

          ????public?EndpointRoutingMiddleware(
          ????????MatcherFactory?matcherFactory,
          ????????ILogger?logger,
          ????????IEndpointRouteBuilder?endpointRouteBuilder,
          ????????DiagnosticListener?diagnosticListener,
          ????????RequestDelegate?next
          )

          ????{
          ????????_matcherFactory?=?matcherFactory????throw?new?ArgumentNullException(nameof(matcherFactory));
          ????????_logger?=?logger????throw?new?ArgumentNullException(nameof(logger));
          ????????_diagnosticListener?=?diagnosticListener????throw?new?ArgumentNullException(nameof(diagnosticListener));
          ????????_next?=?next????throw?new?ArgumentNullException(nameof(next));

          ????????_endpointDataSource?=?new?CompositeEndpointDataSource(endpointRouteBuilder.DataSources);
          ????}

          ????public?Task?Invoke(HttpContext?httpContext)
          ????{
          ????????//?已經(jīng)選擇了終結(jié)點(diǎn),則跳過(guò)匹配
          ????????var?endpoint?=?httpContext.GetEndpoint();
          ????????if?(endpoint?!=?null)
          ????????{
          ????????????Log.MatchSkipped(_logger,?endpoint);
          ????????????return?_next(httpContext);
          ????????}

          ????????//?等待?_initializationTask?初始化完成,進(jìn)行匹配,并流轉(zhuǎn)到下一個(gè)中間件
          ????????var?matcherTask?=?InitializeAsync();
          ????????if?(!matcherTask.IsCompletedSuccessfully)
          ????????{
          ????????????return?AwaitMatcher(this,?httpContext,?matcherTask);
          ????????}
          ????????
          ????????//?_initializationTask在之前就已經(jīng)初始化完成了,直接進(jìn)行匹配任務(wù),并流轉(zhuǎn)到下一個(gè)中間件
          ????????var?matchTask?=?matcherTask.Result.MatchAsync(httpContext);
          ????????if?(!matchTask.IsCompletedSuccessfully)
          ????????{
          ????????????return?AwaitMatch(this,?httpContext,?matchTask);
          ????????}

          ????????//?流轉(zhuǎn)到下一個(gè)中間件
          ????????return?SetRoutingAndContinue(httpContext);

          ????????static?async?Task?AwaitMatcher(EndpointRoutingMiddleware?middleware,?HttpContext?httpContext,?Task?matcherTask)
          ????????{
          ????????????var?matcher?=?await?matcherTask;
          ????????????//?路由匹配,選擇終結(jié)點(diǎn)
          ????????????await?matcher.MatchAsync(httpContext);
          ????????????await?middleware.SetRoutingAndContinue(httpContext);
          ????????}

          ????????static?async?Task?AwaitMatch(EndpointRoutingMiddleware?middleware,?HttpContext?httpContext,?Task?matchTask)
          ????????{
          ????????????await?matchTask;
          ????????????await?middleware.SetRoutingAndContinue(httpContext);
          ????????}
          ????}

          ????[MethodImpl(MethodImplOptions.AggressiveInlining)]
          ????private?Task?SetRoutingAndContinue(HttpContext?httpContext)
          ????{
          ????????//?終結(jié)點(diǎn)仍然為空,則匹配失敗
          ????????var?endpoint?=?httpContext.GetEndpoint();
          ????????if?(endpoint?==?null)
          ????????{
          ????????????Log.MatchFailure(_logger);
          ????????}
          ????????else
          ????????{
          ????????????//?匹配成功則觸發(fā)事件
          ????????????if?(_diagnosticListener.IsEnabled()?&&?_diagnosticListener.IsEnabled(DiagnosticsEndpointMatchedKey))
          ????????????{
          ????????????????//?httpContext對(duì)象包含了相關(guān)信息
          ????????????????_diagnosticListener.Write(DiagnosticsEndpointMatchedKey,?httpContext);
          ????????????}

          ????????????Log.MatchSuccess(_logger,?endpoint);
          ????????}

          ????????//?流轉(zhuǎn)到下一個(gè)中間件
          ????????return?_next(httpContext);
          ????}

          ????private?Task?InitializeAsync()
          ????{
          ????????var?initializationTask?=?_initializationTask;
          ????????if?(initializationTask?!=?null)
          ????????{
          ????????????return?initializationTask;
          ????????}

          ????????//?此處我刪減了部分線程競(jìng)爭(zhēng)代碼,因?yàn)檫@不是我們討論的重點(diǎn)
          ????????//?此處主要目的是在該Middleware中,確保只初始化_initializationTask一次

          ????????var?matcher?=?_matcherFactory.CreateMatcher(_endpointDataSource);

          ????????using?(ExecutionContext.SuppressFlow())
          ????????{
          ????????????_initializationTask?=?Task.FromResult(matcher);
          ????????}
          ????}
          }

          上述代碼的核心就是將_endpointDataSource傳遞給_matcherFactory,創(chuàng)建matcher,然后進(jìn)行匹配matcher.MatchAsync(httpContext)。ASP.NET Core默認(rèn)使用的 matcher 類(lèi)型是DfaMatcher,DFA(Deterministic Finite Automaton)是一種被稱(chēng)為“確定有限狀態(tài)自動(dòng)機(jī)”的算法,可以從候選終結(jié)點(diǎn)列表中查找到匹配度最高的那個(gè)終結(jié)點(diǎn)。

          UseEndpoints

          public?static?class?EndpointRoutingApplicationBuilderExtensions
          {
          ????public?static?IApplicationBuilder?UseEndpoints(this?IApplicationBuilder?builder,?Action?configure)
          ????{
          ????????VerifyRoutingServicesAreRegistered(builder);

          ????????VerifyEndpointRoutingMiddlewareIsRegistered(builder,?out?var?endpointRouteBuilder);

          ????????configure(endpointRouteBuilder);

          ????????var?routeOptions?=?builder.ApplicationServices.GetRequiredService>();
          ????????foreach?(var?dataSource?in?endpointRouteBuilder.DataSources)
          ????????{
          ????????????routeOptions.Value.EndpointDataSources.Add(dataSource);
          ????????}

          ????????return?builder.UseMiddleware();
          ????}
          ????
          ????private?static?void?VerifyEndpointRoutingMiddlewareIsRegistered(IApplicationBuilder?app,?out?DefaultEndpointRouteBuilder?endpointRouteBuilder)
          ????{
          ????????//?將?endpointRouteBuilder?從共享字典中取出來(lái),如果沒(méi)有,則說(shuō)明之前沒(méi)有調(diào)用?UseRouting
          ????????if?(!app.Properties.TryGetValue(EndpointRouteBuilder,?out?var?obj))
          ????????{
          ????????????var?message?=
          ????????????????$"{nameof(EndpointRoutingMiddleware)}?matches?endpoints?setup?by?{nameof(EndpointMiddleware)}?and?so?must?be?added?to?the?request?"?+
          ????????????????$"execution?pipeline?before?{nameof(EndpointMiddleware)}.?"?+
          ????????????????$"Please?add?{nameof(EndpointRoutingMiddleware)}?by?calling?'{nameof(IApplicationBuilder)}.{nameof(UseRouting)}'?inside?the?call?"?+
          ????????????????$"to?'Configure(...)'?in?the?application?startup?code.";
          ????????????throw?new?InvalidOperationException(message);
          ????????}

          ????????endpointRouteBuilder?=?(DefaultEndpointRouteBuilder)obj!;

          ????????//?UseRouting?和?UseEndpoints?必須添加到同一個(gè)?IApplicationBuilder?實(shí)例上
          ????????if?(!object.ReferenceEquals(app,?endpointRouteBuilder.ApplicationBuilder))
          ????????{
          ????????????var?message?=
          ????????????????$"The?{nameof(EndpointRoutingMiddleware)}?and?{nameof(EndpointMiddleware)}?must?be?added?to?the?same?{nameof(IApplicationBuilder)}?instance.?"?+
          ????????????????$"To?use?Endpoint?Routing?with?'Map(...)',?make?sure?to?call?'{nameof(IApplicationBuilder)}.{nameof(UseRouting)}'?before?"?+
          ????????????????$"'{nameof(IApplicationBuilder)}.{nameof(UseEndpoints)}'?for?each?branch?of?the?middleware?pipeline.";
          ????????????throw?new?InvalidOperationException(message);
          ????????}
          ????}
          }

          EndpointMiddleware

          EndpointMiddleware中間件中包含了很多異常處理和日志記錄代碼,為了方便查看核心邏輯,我都刪除并進(jìn)行了簡(jiǎn)化:

          internal?sealed?class?EndpointMiddleware
          {
          ????internal?const?string?AuthorizationMiddlewareInvokedKey?=?"__AuthorizationMiddlewareWithEndpointInvoked";
          ????internal?const?string?CorsMiddlewareInvokedKey?=?"__CorsMiddlewareWithEndpointInvoked";

          ????private?readonly?ILogger?_logger;
          ????private?readonly?RequestDelegate?_next;
          ????private?readonly?RouteOptions?_routeOptions;

          ????public?EndpointMiddleware(
          ????????ILogger?logger,
          ????????RequestDelegate?next,
          ????????IOptions?routeOptions
          )

          ????{
          ????????_logger?=?logger????throw?new?ArgumentNullException(nameof(logger));
          ????????_next?=?next????throw?new?ArgumentNullException(nameof(next));
          ????????_routeOptions?=?routeOptions?.Value????throw?new?ArgumentNullException(nameof(routeOptions));
          ????}

          ????public?Task?Invoke(HttpContext?httpContext)
          ????{
          ????????var?endpoint?=?httpContext.GetEndpoint();
          ????????if?(endpoint?.RequestDelegate?!=?null)
          ????????{
          ????????????//?執(zhí)行該終結(jié)點(diǎn)的委托,并且視該中間件為終端中間件
          ????????????var?requestTask?=?endpoint.RequestDelegate(httpContext);
          ????????????if?(!requestTask.IsCompletedSuccessfully)
          ????????????{
          ????????????????return?requestTask;
          ????????????}

          ????????????return?Task.CompletedTask;
          ????????}
          ????????
          ????????//?若沒(méi)有終結(jié)點(diǎn),則繼續(xù)執(zhí)行下一個(gè)中間件
          ????????return?_next(httpContext);
          ????}
          }

          總結(jié)

          說(shuō)了那么多,最后給大家總結(jié)了三張UML類(lèi)圖:

          RoutePattern

          EndPoint

          Matcher

          另外,本文僅僅提到了路由的基本使用方式和原理,如果你想要進(jìn)行更加深入透徹的了解,推薦閱讀蔣金楠老師的ASP.NET Core 3框架揭秘的路由部分。

          轉(zhuǎn)自:xiaoxiaotank

          鏈接:cnblogs.com/xiaoxiaotank/p/15468491.html

          瀏覽 113
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操逼一级片 | 一级A级毛片 | 成人av影视在线观看国产高清 | 亚洲国产麻豆视频 | 樱桃视频黄 |