ASP.NET Core Middleware抽絲剝繭
一. 中間件的概念和數(shù)據(jù)結(jié)構(gòu)
ASP.NET Core Middleware是在ASP.NET Core處理管道中處理特定業(yè)務(wù)邏輯的組件。
ASP.NET Core處理管道由一系列請求委托組成,一環(huán)接一環(huán)的調(diào)用特定的中間件。

上圖示例:
處理管道包含四個中間件,每個中間件都包含后續(xù)中間件執(zhí)行動作的引用(next),同時每個中間件在交棒之前和交棒之后可以自行參與針對HttpContxt的業(yè)務(wù)處理。
通過上面的分析,中間件其實具備兩個特征:
入?yún)?/span>:下一個中間件的執(zhí)行委托 RequestDelegate?(public delegate Task RequestDelegate(HttpContext context);)輸出:特定中間件的業(yè)務(wù)處理動作:因為中間件是處理管道中預設(shè)的處理邏輯,所以這個動作其實也是一個委托 RequestDelegate
所以.NET Core用Func 數(shù)據(jù)結(jié)構(gòu)表示中間件是合理的。
二. Middleware的定義方式
ASP.NETCore 提供了很多內(nèi)置的中間件,幫助我們完成基礎(chǔ)通用的業(yè)務(wù)邏輯。
有兩種自定義中間件的方式:
1. Factory-based Middleware
基于工廠模式的中間件有如下優(yōu)點:
在每個客戶端請求時激活實例 (injection of scoped services) 實現(xiàn)IMiddleware接口: 強類型
//??該接口只有一個固定的函數(shù)
public?Task?InvokeAsync(HttpContext?context,RequestDelegate?next);
??public?class?FactoryActivatedMiddleware?:?IMiddleware
????{
????????private?readonly?ILogger?_logger;
????????public?FactoryActivatedMiddleware(ILoggerFactory?logger)???//?
????????{
????????????_logger?=?logger.CreateLogger();
????????}
????????public?async?Task?InvokeAsync(HttpContext?context,?RequestDelegate?next)
????????{
????????????//?TODO??logic?handler
????????????_logger.LogInformation("測試");
????????????await?next(context);
????????????//?TODO??logic?handler
????????}
????}
使用工廠模式的中間件,構(gòu)造函數(shù)參數(shù)由依賴注入(DI)填充;?
在[使用UseMiddleware()注冊中間件]時不允許顯式傳參。

源碼在https://github.com/dotnet/aspnetcore/blob/v5.0.1/src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs第56行。
2. Conventional-based Middleware
顧名思義,基于約定的中間件類有一些特定約定:
具有RequestDelegate類型參數(shù)的公共構(gòu)造函數(shù) 名稱為Invoke或InvokeAsync的公共方法, 此方法必須 ①返回Task ? ② 方法第一個參數(shù)是HttpContext
public?class?ConventionalMiddleware
{
????private?readonly?RequestDelegate?_next;
????public?ConventionalMiddleware(RequestDelegate?next)
????{
????????_next?=?next;
????}
????public?async?Task?InvokeAsync(HttpContext?context,?AppDbContext?db)
????{
????????var?keyValue?=?context.Request.Query["key"];
????????if?(!string.IsNullOrWhiteSpace(keyValue))
????????{
????????????db.Add(new?Request()
????????????????{
????????????????????DT?=?DateTime.UtcNow,?
????????????????????MiddlewareActivation?=?"ConventionalMiddleware",?
????????????????????Value?=?keyValue
????????????????});
????????????await?db.SaveChangesAsync();
????????}
????????await?_next(context);
????}
}
構(gòu)造函數(shù)和Invoke/InvokeAsync的其他參數(shù)由依賴注入(DI)填充;
基于約定的中間件,在[使用UseMiddleware()注冊中間件]時允許顯式傳參。
三. 注冊中間件的算法分析
app.UseMiddleware內(nèi)部使用app.Use(Func注冊中間件,
返回值還是IApplicationBuilder, 故具備鏈式注冊的能力。
算法類似于基礎(chǔ)鏈表, 只是指針指向的不是節(jié)點,而是后續(xù)節(jié)點的字段。

//--------節(jié)選自?Microsoft.AspNetCore.Builder.Internal.ApplicationBuilder--------
private?readonly?IList>?_components?=?new?List>();
?
public?IApplicationBuilder?Use(Func?middleware)
{
????this._components.Add(middleware);
????return?this;
}
?
public?RequestDelegate?Build()
{
????????RequestDelegate?app?=?context?=>
????????????{
????????????????//?If?we?reach?the?end?of?the?pipeline,?but?we?have?an?endpoint,?then?something?unexpected?has?happened.
????????????????//?This?could?happen?if?user?code?sets?an?endpoint,?but?they?forgot?to?add?the?UseEndpoint?middleware.
????????????????var?endpoint?=?context.GetEndpoint();
????????????????var?endpointRequestDelegate?=?endpoint?.RequestDelegate;
????????????????if?(endpointRequestDelegate?!=?null)
????????????????{
????????????????????var?message?=
????????????????????????$"The?request?reached?the?end?of?the?pipeline?without?executing?the?endpoint:?'{endpoint!.DisplayName}'.?"?+
????????????????????????$"Please?register?the?EndpointMiddleware?using?'{nameof(IApplicationBuilder)}.UseEndpoints(...)'?if?using?"?+
????????????????????????$"routing.";
????????????????????throw?new?InvalidOperationException(message);
????????????????}
????????????????context.Response.StatusCode?=?StatusCodes.Status404NotFound;
????????????????return?Task.CompletedTask;
????????????};
????????????foreach?(var?component?in?_components.Reverse())
????????????{
????????????????app?=?component(app);
????????????}
????????????return?app;
}?
通過以上代碼我們可以看出:
注冊中間件的過程實際上,是給一個Type為List
> 的集合依次添加元素的過程; 中間件的數(shù)據(jù)結(jié)構(gòu):(input)后置中間件的執(zhí)行函數(shù)指針(以委托RequestDelegate表示),(output)當前中間件的執(zhí)行函數(shù)指針(以委托RequestDelegate表示);
通過
build方法將集合打通為鏈表,鏈表就是我們常說的[處理管道]。
build方法是在web托管服務(wù)
GenericWebHostService開始啟動的時候被調(diào)用。源碼在https://github.com/dotnet/aspnetcore/blob/master/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs 105 行。
附:非標準中間件的用法
短路中間件、 分叉中間件、條件中間件
Use方法是一個注冊中間件的簡便寫法 Run方法是一個約定,一些中間件使用Run方法來完成管道的結(jié)尾
Map擴展方法:Path滿足指定條件,將會執(zhí)行分叉管道
MapWhen方法:HttpContext滿足條件,將會執(zhí)行分叉管道,相比Map有更靈活的匹配功能
UseWhen方法:HttpContext滿足條件則插入中間件


關(guān)注并星標我們
更多干貨及最佳實踐分享
