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

🔍 Filters 與 Action 篩選器

📌 什麼是 Filter?

Filter(篩選器)就像機場安檢——在你的 Action 方法執行前後,自動幫你做一些額外的工作。

想像搭飛機的流程:

  1. Authorization Filter = 護照檢查(你有沒有資格搭這班飛機?)
  2. Resource Filter = 行李寄放櫃檯(可以快取,不用每次都重新打包)
  3. Action Filter = 安檢門(檢查你帶了什麼,出來時再檢查一次)
  4. Exception Filter = 緊急處理中心(出事了!在這裡統一處理)
  5. Result Filter = 登機門檢查(最後確認一切沒問題)

📋 Filter 的五種類型

請求進來
    │
    ▼
┌─────────────────────┐
│ Authorization Filter │ ← 第 1 關:有沒有權限?
└──────────┬──────────┘
           ▼
┌─────────────────────┐
│   Resource Filter    │ ← 第 2 關:快取?資源處理?
│   (OnExecuting)      │
└──────────┬──────────┘
           ▼
┌─────────────────────┐
│   Action Filter      │ ← 第 3 關:Action 前檢查
│   (OnExecuting)      │
└──────────┬──────────┘
           ▼
┌─────────────────────┐
│   Action 方法執行     │ ← 你寫的程式碼在這裡執行
└──────────┬──────────┘
           ▼
┌─────────────────────┐
│   Action Filter      │ ← 第 3 關:Action 後檢查
│   (OnExecuted)       │
└──────────┬──────────┘
           ▼
┌─────────────────────┐
│   Result Filter      │ ← 第 5 關:結果處理
└──────────┬──────────┘
           ▼
      回傳結果

如果中間有 Exception Filter,任何一關出錯都會被它接住。


⚙️ IActionFilter 與 IAsyncActionFilter

// 同步版本:IActionFilter
public class LogActionFilter : IActionFilter // 實作 IActionFilter 介面
{
    private readonly ILogger<LogActionFilter> _logger; // 注入日誌服務

    public LogActionFilter(ILogger<LogActionFilter> logger) // 建構函式
    {
        _logger = logger; // 儲存日誌服務
    }

    // Action 執行「前」呼叫(像安檢門前的金屬探測器)
    public void OnActionExecuting(ActionExecutingContext context)
    {
        var actionName = context.ActionDescriptor.DisplayName; // 取得 Action 名稱
        _logger.LogInformation($"開始執行:{actionName}"); // 記錄開始時間
    }

    // Action 執行「後」呼叫(像離開安檢門時的檢查)
    public void OnActionExecuted(ActionExecutedContext context)
    {
        var actionName = context.ActionDescriptor.DisplayName; // 取得 Action 名稱
        if (context.Exception != null) // 如果有例外發生
        {
            _logger.LogError($"執行 {actionName} 時發生錯誤"); // 記錄錯誤
        }
        else
        {
            _logger.LogInformation($"完成執行:{actionName}"); // 記錄完成
        }
    }
}

// 非同步版本:IAsyncActionFilter(推薦使用)
public class AsyncLogFilter : IAsyncActionFilter // 實作非同步介面
{
    private readonly ILogger<AsyncLogFilter> _logger; // 注入日誌服務

    public AsyncLogFilter(ILogger<AsyncLogFilter> logger) // 建構函式
    {
        _logger = logger; // 儲存日誌服務
    }

    public async Task OnActionExecutionAsync(
        ActionExecutingContext context,   // 執行前的上下文
        ActionExecutionDelegate next)     // 代表下一步(執行 Action)
    {
        // === 在 Action 執行「前」做的事 ===
        _logger.LogInformation("Action 即將執行"); // 記錄日誌
        var stopwatch = System.Diagnostics.Stopwatch.StartNew(); // 開始計時

        var resultContext = await next(); // 執行 Action(像按下開始鍵)

        // === 在 Action 執行「後」做的事 ===
        stopwatch.Stop(); // 停止計時
        _logger.LogInformation($"Action 執行完成,花費 {stopwatch.ElapsedMilliseconds} ms");
    }
}

🛠️ 實用範例:效能計時 Filter

// 計時 Filter:測量每個 API 端點的執行時間(像比賽用的計時器)
public class PerformanceFilter : IAsyncActionFilter // 非同步 Action Filter
{
    private readonly ILogger<PerformanceFilter> _logger; // 日誌服務

    public PerformanceFilter(ILogger<PerformanceFilter> logger) // 建構函式注入
    {
        _logger = logger; // 儲存日誌服務
    }

    public async Task OnActionExecutionAsync(
        ActionExecutingContext context, // 執行前上下文
        ActionExecutionDelegate next)  // 下一步
    {
        var actionName = context.ActionDescriptor.DisplayName; // 取得 Action 名稱
        var stopwatch = System.Diagnostics.Stopwatch.StartNew(); // 按下碼表

        var result = await next(); // 執行 Action

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

        if (elapsed > 500) // 如果超過 500 毫秒(太慢了!)
        {
            _logger.LogWarning($"⚠️ 慢速 Action:{actionName} 花了 {elapsed}ms"); // 警告
        }
        else
        {
            _logger.LogInformation($"✅ {actionName} 完成,{elapsed}ms"); // 正常記錄
        }
    }
}

🎯 Filter 的套用層級

// 層級 1:全域 Filter(所有 Controller 的所有 Action 都會經過)
// 在 Program.cs 中註冊
builder.Services.AddControllers(options =>
{
    options.Filters.Add<PerformanceFilter>(); // 全域套用效能計時 Filter
    options.Filters.Add(new RequireHttpsAttribute()); // 全域要求 HTTPS
});

// 層級 2:Controller 級 Filter(該 Controller 的所有 Action)
[ServiceFilter(typeof(LogActionFilter))] // 整個 Controller 套用日誌 Filter
public class ProductsController : Controller // 商品控制器
{
    // 這裡的所有 Action 都會被 LogActionFilter 過濾
    public IActionResult Index() // 清單頁面
    {
        return View(); // 回傳視圖
    }

    public IActionResult Details(int id) // 詳情頁面
    {
        return View(); // 回傳視圖
    }
}

// 層級 3:Action 級 Filter(只影響單一 Action)
public class OrdersController : Controller // 訂單控制器
{
    [ServiceFilter(typeof(PerformanceFilter))] // 只有這個 Action 套用計時 Filter
    public IActionResult GetReport() // 取得報表(可能很慢,需要計時)
    {
        return View(); // 回傳視圖
    }

    public IActionResult Index() // 這個 Action 不會被 PerformanceFilter 過濾
    {
        return View(); // 回傳視圖
    }
}

🔧 [ServiceFilter] 與 [TypeFilter]

// [ServiceFilter]:Filter 從 DI 容器取得(需要先在 Program.cs 註冊)
// 步驟 1:在 Program.cs 註冊 Filter
builder.Services.AddScoped<LogActionFilter>(); // 註冊到 DI 容器

// 步驟 2:用 [ServiceFilter] 使用
[ServiceFilter(typeof(LogActionFilter))] // 從 DI 容器取得 Filter 實例
public class MyController : Controller { } // 控制器

// [TypeFilter]:不需要先在 DI 中註冊,還能傳遞額外參數
[TypeFilter(typeof(CustomFilter), Arguments = new object[] { "特殊參數" })]
public class AnotherController : Controller { } // 可以傳參數的 Filter

// CustomFilter 可以接收建構函式參數
public class CustomFilter : IActionFilter // 自訂 Filter
{
    private readonly string _prefix; // 前綴參數
    private readonly ILogger<CustomFilter> _logger; // 注入的日誌服務

    public CustomFilter(string prefix, ILogger<CustomFilter> logger)
    {
        _prefix = prefix;   // 從 [TypeFilter] 的 Arguments 傳入
        _logger = logger;   // 從 DI 容器自動注入
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        _logger.LogInformation($"[{_prefix}] Action 開始"); // 使用前綴記錄
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        _logger.LogInformation($"[{_prefix}] Action 完成"); // 使用前綴記錄
    }
}

🤔 我這樣寫為什麼會錯?

❌ 錯誤 1:搞錯 Filter 的執行順序

// ❌ 錯誤理解:以為 Filter 只有 "先進先出"
// 實際上 Filter 的執行順序像洋蔥——一層一層包裹,執行時先進後出

// 全域 Filter A (OnExecuting) → 進入
//   Controller Filter B (OnExecuting) → 進入
//     Action Filter C (OnExecuting) → 進入
//       === Action 執行 ===
//     Action Filter C (OnExecuted) → 離開
//   Controller Filter B (OnExecuted) → 離開
// 全域 Filter A (OnExecuted) → 離開
// ✅ 正確理解:用 Order 屬性控制順序(數字越小越先執行)
builder.Services.AddControllers(options =>
{
    options.Filters.Add<FilterA>(1); // Order=1,最先執行 OnExecuting,最後執行 OnExecuted
    options.Filters.Add<FilterB>(2); // Order=2,第二個執行
    options.Filters.Add<FilterC>(3); // Order=3,最後執行 OnExecuting,最先執行 OnExecuted
});

解釋: Filter 的執行順序像俄羅斯套娃——進去時從外到內(OnExecuting),出來時從內到外(OnExecuted)。如果你以為 A 先執行完才輪到 B,就會搞混執行順序。

❌ 錯誤 2:在 Filter 中產生副作用

// ❌ 錯誤寫法:在 Filter 中修改資料庫(Filter 應該做的是「檢查」而非「修改」)
public class BadFilter : IActionFilter // 不好的 Filter
{
    private readonly MyDbContext _db; // 資料庫上下文

    public BadFilter(MyDbContext db) { _db = db; } // 注入資料庫

    public void OnActionExecuting(ActionExecutingContext context)
    {
        // 在 Filter 裡直接修改資料庫——這不是 Filter 該做的事!
        _db.Logs.Add(new Log { Message = "有人來了" }); // 插入日誌
        _db.SaveChanges(); // 儲存變更(如果這裡失敗會怎樣?)
    }

    public void OnActionExecuted(ActionExecutedContext context) { }
}
// ✅ 正確寫法:Filter 只做輕量的檢查和記錄
public class GoodFilter : IActionFilter // 好的 Filter
{
    private readonly ILogger<GoodFilter> _logger; // 只用日誌服務

    public GoodFilter(ILogger<GoodFilter> logger) { _logger = logger; } // 注入日誌

    public void OnActionExecuting(ActionExecutingContext context)
    {
        _logger.LogInformation("請求進入"); // 只記錄日誌,不修改資料庫
    }

    public void OnActionExecuted(ActionExecutedContext context) { }
}

解釋: Filter 就像機場安檢——安檢員的工作是「檢查」而不是「幫你重新打包行李」。如果在 Filter 裡做太多事(例如修改資料庫),不但違反單一職責原則,還可能因為 Filter 的執行順序導致難以預測的問題。

💡 大家的想法 · 0

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