ASP.NET Core中,請求與回應會經過一連串的Middleware(中介軟體),每個Middleware都有各自的職責,例如用於驗證、授權或是錯誤處理等。當請求或回應流經Middleware時,Middleware會處理並選擇是否將請求傳遞到下一個Middleware,或者是將其短路。

管線

Middleware的組成方式就像管線一樣,會一段接著一段。

可以把每個Middleware都想像成一個函式,在每個Middleware中都可以呼叫函式連到下一個Middleware,或是選擇不連到下一個函式直接短路。

除了單一管線之外,管線也可以有分支,可以依照不同的路徑執行不同條管線。而管線也不可能一直延伸下去,最終都會遇到盡頭。

串接

namespace Foo;

public class Program
{
    public static void Main(string[] args)
    {
        // 回傳一個類別為WebApplicationBuilder的物件,名稱為builder
        var builder = WebApplication.CreateBuilder(args);

        // 建置WebApplication,回傳WebApplication的物件
        var app = builder.Build();

        app.MapGet("/", () => "Hello World!");

        // 執行應用程式
        app.Run();
    }
}

在範例程式碼中,我們對WebApplicationBuilder的物件(builder)呼叫Build()方法後,會回傳一個WebApplication的物件(app),WebApplication類別提供Use()方法讓我們可以把Middleware加入到管線中。ASP.NET Core提供了許多現成的Middleware擴充函式(Extensions),我們可以找到需要的功能直接加入到管線中。

namespace Foo;

public class Program
{
    public static void Main(string[] args)
    {
        // 回傳一個類別為WebApplicationBuilder的物件,名稱為builder
        var builder = WebApplication.CreateBuilder(args);

        // 建置WebApplication,回傳WebApplication的物件
        var app = builder.Build();

        // 各種內建的Middleware Extension
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();
        app.UseAuthentication();
        app.UseAuthorization();
        app.MapRazorPages();
        app.MapControllers();

        // 執行應用程式
        app.Run();
    }
}

除了現成的Middleware可以使用外,我們也可以使用Use()方法自訂Middleware,事實上ASP.NET Core提供的現成Middleware也是使用相同的方法,只是被包裝為Extensions讓使用者可以方便使用。

namespace Foo;

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);
        var app = builder.Build();

        app.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("middleware 1 in \r\n");
            await next.Invoke();
            await context.Response.WriteAsync("middleware 1 out \r\n");
        });
        app.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("middleware 2 in \r\n");
            await next.Invoke();
            await context.Response.WriteAsync("middleware 2 out \r\n");
        });
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("terminal middleware \r\n");
        });
        app.Run();
    }
}
middleware 1 in
middleware 2 in
terminal middleware
middleware 2 out
middleware 1 out

從輸出的結果可以觀察到Middleware的執行順序是由最前方的Middleware依序執行,直到terminal middleware(終端中介軟體)也就是Run()被執行為止。Middleware的執行順序是非常重要的,若Middleware之間有先後順序的話必須非常注意Middleware擺放順序是否正確。

分支

除了單一管線的Middleware之外,Middleware還可以將不同的路徑對應到不同的Middleware管線,讓Middleware形成分支。WebApplication類別提供的Map()方法就可以指定哪個路徑要對應到哪一組Middleware管線,當收到指定路徑的請求時會執行對應管線中的Middleware。

namespace Foo;

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);
        var app = builder.Build();

        app.Map("/map1", HandleMapTest1);
        app.Map("/map2", HandleMapTest2);
        app.Run();

        static void HandleMapTest1(IApplicationBuilder app)
        {
            app.Use(async (context, next) =>
            {
                await context.Response.WriteAsync("map1 middleware 1 in \r\n");
                await next.Invoke();
                await context.Response.WriteAsync("map1 middleware 1 out \r\n");
            });
        }
        static void HandleMapTest2(IApplicationBuilder app)
        {
            app.Use(async (context, next) =>
            {
                await context.Response.WriteAsync("map2 middleware 1 in \r\n");
                await next.Invoke();
                await context.Response.WriteAsync("map2 middleware 1 out \r\n");
            });
        }
    }
}

盡頭

只要是管線必定會有盡頭,一般的管線盡頭有兩種,一種是WebApplication類別提供的Run()方法,作為終端Middleware,它的委派不具有next參數,所以無法使用next.Invoke()呼叫下一個Middleware。另一種盡頭是在一般的Middleware中不呼叫next.Invoke(),同樣也使得管線無法繼續下去,我們將這種盡頭稱之為「短路」。

參考資料