在asp.net core中,中介軟體中介軟體是一種裝配到應用管道以處理請求和響應的軟體。
每個元件:
請求委託用於生成請求管道。 請求委託處理每個 HTTP 請求。
ASP.NET Core 請求管道包含一系列請求委託,依次呼叫。每個委託均可在下一個委託前後執行操作。 應儘早在管道中呼叫例外處理委託,這樣它們就能捕獲在管道的後期階段發生的異常。 如下圖所示:
在asp.net core中已經內建了挺多的中介軟體,包括身份驗證,授權等等,詳細的可以看官方檔案內建中介軟體列表。
接下來主要講一下如何編寫我們自己的中介軟體,在前面的文章中我們也用到了自己寫的中介軟體,用的是最簡單的app.Use的方式。
Use 擴充套件可以使用兩個過載:
優先使用後面的過載,因為它省去了使用其他過載時所需的兩個內部每請求分配。
app.Use(async (context, next) =>
{
// 下游中介軟體執行前
await next.Invoke(); //往下執行中介軟體
// 下游中介軟體執行後
});
上面寫法就是一個最簡單的沒有任何操作的中介軟體。
在呼叫await next.Invoke()前我們寫的操作就是在下游中介軟體執行之前做的事情,對應的,在之後寫的操作則是在下游中介軟體響應後做的事情。
舉個例子,當我們要在下游中介軟體執行之前,做一些引數的賦值,如我想在Headers中新增一個頭部,
app.Use(async (context, next) =>
{
context.Request.Headers.Add("TestMiddlewareAdd", "Abc");
await next.Invoke();
});
在新增之後,下游就可以獲取Headers中TestMiddlewareAdd的值。
我們來實操一下,建立一個WebApi專案,然後在Program中MapControllers()之前新增上述中介軟體。
可以看到,Headers中已經加上了我們之前加的內容。
對應的,如果寫在await next.Invoke()後面,則是不生效的,這個可以自行測試。那麼在await next.Invoke()後面我們可以做一些什麼操作呢?比如記錄請求響應完成後的內容,或對相應內容做進一步的處理等等,根據我們的實際需要去寫。
除了app.Use(),在asp.net core中還有幾種中介軟體的編寫方式。
app.Map();
app.MpaWhen();
app.Run();
app.UseMiddleware();
Map擴充套件用作約定來建立管道分支。 Map 基於給定請求路徑的匹配項來建立請求管道分支。 如果請求路徑以給定路徑開頭,則執行分支。
MapWhen基於給定謂詞的結果建立請求管道分支。 Func<HttpContext, bool> 型別的任何謂詞均可用於將請求對映到管道的新分支。
Run 委託不會收到 next 引數。 第一個 Run 委託始終為終端,用於終止管道。 Run 是一種約定。 某些中介軟體元件可能會公開在管道末尾執行的 Run[Middleware] 方法:
UseMiddleware是我們最常用的封裝中介軟體的方式,中介軟體類是基於約定編寫的。其約定如下:
建構函式和 Invoke/InvokeAsync 的其他引數由依賴關係注入 (DI) 填充。
接下來我們來實操基於約定編寫一個Middleware類
public class AMiddleware
{
private readonly RequestDelegate _next;
public AMiddleware(RequestDelegate next)
=> _next = next;
public async Task InvokeAsync(HttpContext context, ILogger<AMiddleware> logger)
{
logger.LogInformation("AMiddleware Invoke");
await _next(context);
}
}
在Program使用UseMiddleware把中介軟體加入管道
app.UseAuthorization();
app.Use(async (context, next) =>
{
context.Request.Headers.Add("TestMiddlewareAdd", "Abc");
await next.Invoke();
});
app.UseMiddleware<AMiddleware>();
app.MapControllers();
app.Run();
啟動專案發出請求。可以看到下圖結果:
需要注意的是,這裡的Middleware會自動註冊為一個單例,所以在構造器注入時,無法注入Scope生命週期的服務。
如果注入,啟動會直接報錯
public class AMiddleware
{
private readonly RequestDelegate _next;
private readonly TestMiddlewareDi _testMiddlewareDi;
public AMiddleware(RequestDelegate next, TestMiddlewareDi testMiddlewareDi)
{
_next = next;
_testMiddlewareDi = testMiddlewareDi;
}
public async Task InvokeAsync(HttpContext context, ILogger<AMiddleware> logger)
{
logger.LogInformation("AMiddleware Invoke");
logger.LogInformation($"AMiddleware _testMiddlewareDi: {_testMiddlewareDi.Id}");
await _next(context);
}
}
builder.Services.AddScoped<TestMiddlewareDi>();
當我們需要注入Scope生命週期的服務時,直接在InvokeAsync方法中新增注入。
public class AMiddleware
{
private readonly RequestDelegate _next;
public AMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, ILogger<AMiddleware> logger, TestMiddlewareDi testMiddleware)
{
logger.LogInformation("AMiddleware Invoke");
logger.LogInformation($"AMiddleware _testMiddlewareDi: {testMiddleware.Id}");
await _next(context);
}
}
除了基於約定實現中介軟體,asp.net core還有一個基於工廠的中介軟體啟用擴充套件。
IMiddlewareFactory/IMiddleware 是中介軟體啟用的擴充套件點,具有以下優勢:
UseMiddleware 擴充套件方法檢查中介軟體的已註冊型別是否實現 IMiddleware。 如果是,則使用在容器中註冊的 IMiddlewareFactory 範例來解析 IMiddleware 實現,而不使用基於約定的中介軟體啟用邏輯。 中介軟體在應用的服務容器中註冊為作用域或瞬態服務。
接下來我們來實現一個IMiddleware
public class FactoryMiddleware : IMiddleware
{
private readonly ILogger _logger;
private readonly TestMiddlewareDi _testMiddleware;
public FactoryMiddleware(ILogger<FactoryMiddleware> logger, TestMiddlewareDi testMiddleware)
{
_logger = logger;
_testMiddleware = testMiddleware;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
_logger.LogInformation("FactoryMiddleware Invoke");
_logger.LogInformation($"FactoryMiddleware _testMiddlewareDi: {_testMiddleware.Id}");
await next(context);
}
}
app.UseAuthorization();
app.Use(async (context, next) =>
{
context.Request.Headers.Add("TestMiddlewareAdd", "Abc");
await next.Invoke();
});
app.UseMiddleware<AMiddleware>();
app.UseMiddleware<FactoryMiddleware>();
app.MapControllers();
app.Run();
需要注意的是,這種方式必須把中介軟體註冊到依賴注入容器中,否則會出現以下錯誤:
註冊注入之後,我們再次啟動服務,並測試請求。
builder.Services.AddScoped<FactoryMiddleware>();
基於約定的中介軟體無法通過建構函式注入Scope生命週期的服務,只能通過Invoke方法的引數進行注入。
基於工廠的中介軟體只能通過建構函式新增注入,Invoke無法注入(因為是基於IMiddleware介面的實現)。
基於約定的中介軟體無需手動註冊進依賴注入容器。
基於工廠的中介軟體必須註冊進依賴注入容器,且生命週期註冊為作用域或瞬態服務。
基於約定的中介軟體生命週期為單例
基於工廠的中介軟體生命週期為作用域
中間既然是一種管道的模式,那麼必然和順序有關係,管道前面的中介軟體先執行,後面的中介軟體後執行。
那麼這個順序會帶來哪種影響呢?
這裡盜官方檔案圖,下圖顯示了 ASP.NET Core MVC 和 Razor Pages 應用的完整請求處理管道。
這裡UseCors 和 UseStaticFiles 順序是最容易看出影響的。
若是UseStaticFiles在UseCors之前呼叫,則檢索靜態檔案時,不會檢查是否跨站點呼叫。所有靜態檔案可以直接檢索。
若是相反,則在跨站檢索靜態檔案時,則會優先檢查站點是否跨域,若是跨域則無法檢索靜態檔案。
由此我們可以想到,當我們需要做一些前置校驗的中介軟體時,可以把中介軟體順序放在前面,校驗不通過直接終止後續請求,可以提高應用的響應效率。
歡迎進群催更。