☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
ASP.NET Core 中級

Middleware 管線

Middleware 是什麼?

想像 ASP.NET Core 的請求處理就像一根水管——水(請求)從一端流入,經過一連串的過濾器(Middleware),最後流出另一端(回應)。

請求 Request →
    [中介軟體 1: 日誌記錄]
        → [中介軟體 2: 身份驗證]
            → [中介軟體 3: 授權]
                → [中介軟體 4: 路由]
                    → Controller Action
                ← [中介軟體 4]
            ← [中介軟體 3]
        ← [中介軟體 2]
    ← [中介軟體 1]
← 回應 Response

每個 Middleware 可以:

  1. 在請求進入時做某些事(例如記錄日誌)
  2. 決定是否傳給下一個 Middleware
  3. 在回應出去時做某些事(例如加 Header)

app.Use、app.Map、app.Run

var app = builder.Build();

// app.Use → 處理請求,然後傳給下一個 Middleware
app.Use(async (context, next) =>
{
    Console.WriteLine("1. 請求進入");   // 請求進入時執行
    await next();                       // 呼叫下一個 Middleware
    Console.WriteLine("4. 回應離開");   // 回應離開時執行
});

// 第二個 Middleware
app.Use(async (context, next) =>
{
    Console.WriteLine("2. 第二層進入");  // 進入第二個 Middleware
    await next();                       // 繼續傳遞
    Console.WriteLine("3. 第二層離開");  // 離開第二個 Middleware
});

// app.Run → 終端 Middleware,不會再傳給下一個
app.Run(async context =>
{
    Console.WriteLine("到達終點!");     // 請求到達終點
    await context.Response.WriteAsync("Hello!"); // 寫入回應
});

// 輸出順序:1 → 2 → 到達終點 → 3 → 4
// app.Map → 根據路徑分支
app.Map("/api", apiApp =>
{
    apiApp.Run(async context =>
    {
        await context.Response.WriteAsync("API 端點"); // /api 路徑的處理
    });
});

app.Map("/health", healthApp =>
{
    healthApp.Run(async context =>
    {
        await context.Response.WriteAsync("OK");      // /health 健康檢查
    });
});

自訂 Middleware 類別

// Middleware/RequestTimingMiddleware.cs
public class RequestTimingMiddleware
{
    private readonly RequestDelegate _next;       // 下一個 Middleware 的委派

    // 建構子注入 RequestDelegate
    public RequestTimingMiddleware(RequestDelegate next)
    {
        _next = next;                             // 保存下一個 Middleware
    }

    // 每個請求都會呼叫 InvokeAsync
    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = System.Diagnostics.Stopwatch.StartNew(); // 開始計時

        await _next(context);                     // 呼叫下一個 Middleware

        stopwatch.Stop();                         // 停止計時
        var elapsed = stopwatch.ElapsedMilliseconds; // 取得經過時間

        // 加入自訂 Header 顯示處理時間
        context.Response.Headers["X-Response-Time"] = $"{elapsed}ms";
        Console.WriteLine($"請求 {context.Request.Path} 花了 {elapsed}ms"); // 記錄
    }
}

// 擴充方法讓註冊更方便
public static class RequestTimingMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestTiming(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestTimingMiddleware>(); // 註冊 Middleware
    }
}
// Program.cs - 使用自訂 Middleware
var app = builder.Build();
app.UseRequestTiming();                  // 使用計時 Middleware
app.UseStaticFiles();                    // 靜態檔案
app.UseRouting();                        // 路由
app.UseAuthorization();                  // 授權

順序很重要!

Middleware 的註冊順序就是執行順序

// Program.cs - 正確的 Middleware 順序
var app = builder.Build();

// 1. 例外處理(最外層,捕捉所有錯誤)
if (!app.Environment.IsDevelopment())
    app.UseExceptionHandler("/Home/Error");  // 錯誤處理

// 2. HTTPS 重新導向
app.UseHttpsRedirection();               // HTTP → HTTPS

// 3. 靜態檔案(不需驗證即可存取)
app.UseStaticFiles();                     // wwwroot 底下的檔案

// 4. 路由(決定要走哪個 Endpoint)
app.UseRouting();                         // 路由比對

// 5. CORS(跨域請求)
app.UseCors();                            // 跨來源資源共用

// 6. 身份驗證(你是誰?)
app.UseAuthentication();                  // 驗證身份

// 7. 授權(你能做什麼?)
app.UseAuthorization();                   // 檢查權限

// 8. Endpoint 執行
app.MapControllerRoute(                   // 對應到 Controller
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

內建常用 Middleware

Middleware 功能 順序建議
UseExceptionHandler 全域例外處理 最先
UseHttpsRedirection HTTP 轉 HTTPS 靠前
UseStaticFiles 提供靜態檔案 驗證前
UseRouting 路由比對 中間
UseCors 跨域設定 驗證前
UseAuthentication 身份驗證 授權前
UseAuthorization 授權檢查 驗證後

🤔 我這樣寫為什麼會錯?

❌ 錯誤 1:把 UseAuthorization 放在 UseRouting 前面

// ❌ 順序錯誤!授權在路由前面
app.UseAuthorization();                   // 授權(但還不知道 Endpoint!)
app.UseRouting();                         // 路由
// ✅ 正確順序
app.UseRouting();                         // 先比對路由
app.UseAuthorization();                   // 再檢查授權

為什麼? UseAuthorization 需要知道目標 Endpoint 才能判斷授權策略,放在 UseRouting 前面就無法取得 Endpoint 資訊。

❌ 錯誤 2:忘記呼叫 next() 導致請求中斷

// ❌ 忘記呼叫 next(),請求到這裡就停了
app.Use(async (context, next) =>
{
    Console.WriteLine("記錄日誌");        // 記錄後...什麼都沒做
    // 忘記 await next()!                 // 請求無法繼續!
});
// ✅ 記得呼叫 next()
app.Use(async (context, next) =>
{
    Console.WriteLine("記錄日誌");        // 記錄日誌
    await next();                         // 傳給下一個 Middleware
});

為什麼? app.Use 中如果不呼叫 next(),請求就不會傳到後面的 Middleware,用戶會收到空白回應。

❌ 錯誤 3:UseStaticFiles 放在 UseAuthentication 後面

// ❌ 靜態檔案(CSS/JS/圖片)也要驗證身份
app.UseAuthentication();                  // 先驗證
app.UseStaticFiles();                     // 靜態檔案也被驗證擋住了!
// ✅ 靜態檔案放在驗證前面
app.UseStaticFiles();                     // 靜態檔案不需驗證
app.UseAuthentication();                  // 只驗證動態請求

為什麼? CSS、JavaScript、圖片等靜態資源不需要驗證身份,放在 UseAuthentication 後面會導致未登入用戶連頁面樣式都載入不了。

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊