前幾天,公眾號後臺有朋友在問Core的中間件,所以專門抽時間整理了這樣一篇文章。 一、前言 中間件(Middleware)最初是一個機械上的概念,說的是兩個不同的運動結構中間的連接件。後來這個概念延伸到軟體行業,大家把應用操作系統和電腦硬體之間過渡的軟體或系統稱之為中間件,比方驅動程式,就是一個典型 ...
前幾天,公眾號後臺有朋友在問Core的中間件,所以專門抽時間整理了這樣一篇文章。
一、前言
中間件(Middleware)最初是一個機械上的概念,說的是兩個不同的運動結構中間的連接件。後來這個概念延伸到軟體行業,大家把應用操作系統和電腦硬體之間過渡的軟體或系統稱之為中間件,比方驅動程式,就是一個典型的中間件。再後來,這個概念就泛開了,任何用來連接兩個不同系統的東西,都被叫做中間件。
所以,中間件只是一個名詞,不用太在意,實際代碼跟他這個詞,也沒太大關係。
中間件技術,早在.Net framework時期就有,只不過,那時候它不是Microsoft官方的東西,是一個叫OWIN的三方框架下的實現。到了.Net core,Microsoft才把中間件完整加到框架里來。
感覺上,應該是Core參考了OWIN的中間件技術(猜的,未求證)。在實現方式上,兩個框架下的中間件沒什麼區別。
下麵,我們用一個實際的例子,來理清這個概念。
為了防止不提供原網址的轉載,特在這裡加上原文鏈接:https://www.cnblogs.com/tiger-wang/p/13038419.html
二、開發環境&基礎工程
這個Demo的開發環境是:Mac + VS Code + Dotnet Core 3.1.2。
$ dotnet --info
.NET Core SDK (reflecting any global.json):
Version: 3.1.201
Commit: b1768b4ae7
Runtime Environment:
OS Name: Mac OS X
OS Version: 10.15
OS Platform: Darwin
RID: osx.10.15-x64
Base Path: /usr/local/share/dotnet/sdk/3.1.201/
Host (useful for support):
Version: 3.1.3
Commit: 4a9f85e9f8
.NET Core SDKs installed:
3.1.201 [/usr/local/share/dotnet/sdk]
.NET Core runtimes installed:
Microsoft.AspNetCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
首先,在這個環境下建立工程:
- 創建Solution
% dotnet new sln -o demo
The template "Solution File" was created successfully.
- 這次,我們用Webapi創建工程
% cd demo
% dotnet new webapi -o demo
The template "ASP.NET Core Web API" was created successfully.
Processing post-creation actions...
Running 'dotnet restore' on demo/demo.csproj...
Restore completed in 179.13 ms for demo/demo.csproj.
Restore succeeded.
- 把工程加到Solution中
% dotnet sln add demo/demo.csproj
基礎工程搭建完成。
三、創建第一個中間件
我們先看下Demo項目的Startup.cs
文件:
namespace demo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
這是Startup
預設生成後的樣子(註意,不同的操作系統下生成的代碼會略有不同,但本質上沒區別)。
其中,Configure
是中間件的運行定義,ConfigureServices
是中間件的參數設置註入。
我們在Configure
方法里,加入一個簡單的中間件:
app.UseAuthorization();
/////////////////////下麵是加入的代碼
app.Use(async (context, next) =>
{
// your code
await next.Invoke();
// your code
});
/////////////////////////
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
在這個代碼中,app.Use
是引入中間件的方式,而真正的中間件,是async (context, next)
,這是一個delegate
方法。
中間件方法的兩個參數,context
是上下文HttpContext
,next
指向下一個中間件。
其中,next
參數很重要。中間件採用管道的形式執行。多個中間件,通過next
進行調用。
四、中間件的短路
在前一節,我們看到了中間件的標準形式。
有時候,我們希望中間件執行完成後就退出執行,而不進入下一個中間件。這時候,我們可以把await next.Invoke()
從代碼中去掉。變成下麵的形式:
app.Use(async (context, next) =>
{
// your code
});
對於這種形式,Microsoft給出了另一個方式的寫法:
app.Run(async context =>
{
// your code
});
這兩種形式,效果完全一樣。
這種形式,我們稱之為短路,就是說在這個中間件執行後,程式即返回數據給客戶端,而不執行下麵的中間件。
五、中間件的映射
有時候,我們需要把一個中間件映射到一個Endpoint
,用以對外提供簡單的API處理。這種時間,我們需要用到映射:
app.Map("/apiname", apiname => {
app.Use(async (context, next) =>
{
// your code
await next.Invoke();
});
});
此外,映射支持嵌套:
app.Map("/router", router => {
router.Map("/api1name", api1Name => {
app.Use(async (context, next) =>
{
// your code
await next.Invoke();
});
});
router.Map("/api2name", api2Name => {
app.Use(async (context, next) =>
{
// your code
await next.Invoke();
});
});
})
對於這兩個嵌套的映射,我們訪問的Endpoint
分別是/router/api1name
和/router/api2name
。
以上部分,就是中間件的基本內容。
但是,這兒有個問題:為什麼我們從各處文章里看到的中間件,好像都不是這麼寫的?
嗯,答案其實很簡單,我們看到的方式,也都是中間件。只不過,那些代碼,是這個中間件的最基本樣式的變形。
下麵,我們就來說說變形。
六、變形1: 獨立成一個類
大多數情況下,我們希望中間件能獨立成一個類,方便控制,也方便程式編寫。
這時候,我們可以這樣做:先寫一個類:
public class TestMiddleware
{
private readonly RequestDelegate _next;
public TestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
// Your code
await _next.Invoke(context);
}
}
這個類里context
和next
和上面簡單形式下的兩個參數,類型和意義是完全一樣的。
下麵,我們把這個類引入到Configure
中:
app.UseMiddleware<TestMiddleware>();
註意,引入Middleware類,需要用app.UseMiddleware
而不是app.Use
。
app.Use
引入的是方法,而app.UseMiddleware
引入的是類。就這點區別。
如果再想高大上一點,可以做個Extensions:
public static class TestMiddlewareExtensions
{
public static IApplicationBuilder UseTestMiddleware(this IApplicationBuilder app)
{
return app.UseMiddleware<TestMiddleware>();
}
}
然後,在Configure
中,就可以換成:
app.UseTestMiddleware();
看著高大上了有沒有?
七、變形2: 簡單引入參數
有時候,我們需要給在中間件初始化時,給它傳遞一些參數。
看類:
public class TestMiddleware
{
private readonly RequestDelegate _next;
private static object _parameter
public TestMiddleware(RequestDelegate next, object parameter)
{
_next = next;
_parameter = parameter;
}
public async Task Invoke(HttpContext context)
{
// Your code
await _next.Invoke(context);
}
}
那相應的,我們在Configure
中引入時,需要寫成:
app.UseMiddleware<TestMiddleware>(new object());
同理,如果我們用Extensions時:
public static class TestMiddlewareExtensions
{
public static IApplicationBuilder UseTestMiddleware(this IApplicationBuilder app, object parameter)
{
return app.UseMiddleware<TestMiddleware>(parameter);
}
}
同時,引入變為:
app.UseTestMiddleware(new object());
八、變形3: 依賴註入參數
跟前一節一樣,我們需要引入參數。這一節,我們用另一種更優雅的方式:依賴註入參數。
先創建一個interface: