iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 3
2
Modern Web

我與 ASP.NET Core 的 30天系列 第 3

[Day03] Middleware- 我與 ASP.NET Core 3 的 30天

ASP.NET Core Middleware簡介

ASP.NET Core 是依據 OWIN規格所實作的網站架構

OWIN 是一個怎麼樣的規格,為什麼要改用OWIN的規格呢?
簡單來說,OWIN就是為了解除應用程式與特定伺服器間的依賴與耦合所設計出來的架構。

OWIN採取四層的分層架構,分別是:
Host/Server/Middleware/Application

透過OWIN的設計,可以更有彈性的去依照不同需求及場景來抽換不同的模組

如果對OWIN有興趣,更詳細的內容可以參考黑大的文章

在 ASP.NET Core 裡,也用了Middleware組成的Pipeline來取代原本Http Moudles以及HTTP Handlers來處理所有HTTP 的 Request及Response

每個元件(Middleware)可以選擇

  • 是否要將Request傳送到下一個Middleware
  • 下一個元件的前後執行工作(直接Response回用戶端)

https://ithelp.ithome.com.tw/upload/images/20200917/201293891fGZ5uB9hs.png
圖片來源:官方文件

定義Middleware

要定義Middleware需要再 Startup.cs 中的 Configure()方法中去做設定

執行順序:

  • Request 過程是由上往下
  • Response 過程是由下往上

下列範例設定要求處理管線(pipeline):

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    
    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

假設今天將 app.UserRouting() 放在 app.UseStaticFiles()前面,就會造成路由先被設定,而無法讀到靜態檔案。

所以正確的定義Middleware是非常重要的

順序的設定對於安全性、執行效能以及功能性都有非常密切的關係

在Middleware中使用了 IApplicationBuilder擴充方法串接 Requests/Responses,有以下三種擴充方法

  • Run
  • Use
  • Map

Run

Run 是最後一個Middleware,不會收到 next 參數,後面的Middleware也不會再執行,一般來說不會直接使用。
通常都使用 app.UseEndpoints() 來建立自己所需要的項目
自行寫Run 的範例

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.Run(async context =>{
        await context.Response.WriteAsync("Hello, World!");
    });
}

USE

Use 可以用自訂的程式碼邏輯來設定一層Middleware
可以透過呼叫 next(); 來決定是否進入下一個 Middleware

public void Configure(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("use 1 start \r\n");
        await next.Invoke();
        await context.Response.WriteAsync("use 1 end \r\n");
    });

    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("use 2 start \r\n");
        await next.Invoke();
        await context.Response.WriteAsync("use 2 end \r\n");
    });
    
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Run \r\n");
    });
}

執行後可以看到下方結果
https://ithelp.ithome.com.tw/upload/images/20200917/20129389PtcGv7b5kD.png

不過如果有很多自定義的Middlewares 需要使用的話 全部都塞在Configure裡會顯得比較亂。
這時候就需要使用到擴充方法來擴充我們的Middlewares了。
(不懂如何使用擴充方法可以看簡單的介紹)
首先我們在專案底下建立一個Middlewares的資料夾,並建立CustomMiddleware.cs

https://ithelp.ithome.com.tw/upload/images/20200917/20129389cPkGtuVGvL.png

CustomMiddleware.cs 內容

namespace api1.Middleware
{
    public static class CustomMiddlewareExtensions
    {
        public static void UseCustom(this IApplicationBuilder app)
        {
            app.UseMiddleware<CustomMiddleware>();
        }
    }

    public class CustomMiddleware
    {
        private readonly RequestDelegate _next;

        // "Scoped" SERVICE SHOULDN'T DO CONSTRUCTOR DI!!
        public CustomMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            await context.Response.WriteAsync("use 1 start \r\n");
            await _next(context);
            await context.Response.WriteAsync("use 1 end \r\n");
        }
    }
}

之後在Configure裡就可以使用自定義的Middleware擴充方法了 (記得要先using namespace)

public void Configure(IApplicationBuilder app)
{
    app.UseCustom();
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Run \r\n");
    });
}

Map

Map 擴充方法則是用來分支管線的慣例, Map 會依據指定要求路徑的相符項目將要求管線分支
如果要求路徑以指定路徑為開頭,則會執行分支。

Map可以針對自訂的路由設定獨立的管線(pipeline)

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.Map("/map1", Map1);
    app.Map("/map2", Map2);
}

private static void Map1(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map 1");
    });
}

private static void Map2(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map 2");
    });
}

可以從以下兩張圖片看到 Map的效果
https://ithelp.ithome.com.tw/upload/images/20200917/20129389Hl6eTet8so.png

https://ithelp.ithome.com.tw/upload/images/20200917/20129389ELYUmugPVa.png

Map支援巢狀項目,同時也能一次比對多個線段
巢狀

app.Map("/map1", lv1 =>
{
    lv1.Map("/map2",lv2 =>
    {
        //Do somting
    });
});

多線段

app.Map("/map1/seg1", Map1);

Middleware順序

下圖顯示ASP.NET Core MVC 和 RazorPage 應用程式的完整要求處理管線,可以查看一般應用程式常用官方提供的Middleware,以及新增自定義Middleware的位置該如何排列。
https://ithelp.ithome.com.tw/upload/images/20200917/20129389CIhrwSrpcq.png
圖片來源:官方文件

上圖中的Endpoint Middleware為相對應的應用程式類型執行Pipline。
https://ithelp.ithome.com.tw/upload/images/20200917/20129389Yma7VLXo7q.png
圖片來源:官方文件

Endpoint(端點)使用介紹

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapRazorPages();
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");

});

UseEndpoints內部的操作順序不會影響路由的行為,只有一個例外。 MapControllerRoute和MapAreaRoute會根據調用的順序自動將順序值分配給其Endpoint。

透過Middleware的建構,可以讓我們對於每個請求或回應做更客製化的處理。

目前官方也提供了許多內建的Middlewares,在準備自製Middleware前,不妨可以先查看官方是否已經有提供相對應功能的Middlewre了。

這邊附上ASP.NET Core內建的Middleware列表

相關連結
官方文件


上一篇
[Day02] 自我介紹!我叫做 ASP.NET Core 3
下一篇
[Day04] 依賴注入 (Dependency Injection) - 我與 ASP.NET Core 3 的 30天
系列文
我與 ASP.NET Core 的 30天31

尚未有邦友留言

立即登入留言