理解ASP.NET Core - 路由(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í)行代碼
UseRouting和UseEndpoints兩個(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ù),比如UseRewriter、UseHttpMethodOverride、UsePathBase等。在調(diào)用 UseRouting和UseEndpoints之間,可以注冊(cè)一些用于提前處理路由結(jié)果的中間件,如UseAuthentication、UseAuthorization、UseCors等。
我們一起看下面的代碼:
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
