Nguon: Microsoft Learn · .NET 8.0

Filters trong ASP.NET Core

Nguồn: Filters in ASP.NET Core

Filters (bộ lọc) trong ASP.NET Core cho phép code chạy trước hoặc sau các giai đoạn cụ thể trong pipeline (đường ống) xử lý request. Chúng xử lý các mối quan tâm xuyên suốt (cross-cutting concerns) như authorization (xác thực quyền), caching (lưu bộ nhớ đệm), xử lý lỗi và logging (ghi log).

Cách Filters hoạt động

Filters chạy trong pipeline gọi action của ASP.NET Core sau khi framework chọn action nào để thực thi. Chúng xử lý các requests thông qua các giai đoạn được xác định trước khi response được gửi đến client.

Các loại Filter và thứ tự thực thi

Filters thực thi theo trình tự này:

1. Authorization Filters (Bộ lọc xác thực quyền)

2. Resource Filters (Bộ lọc tài nguyên)

3. Action Filters (Bộ lọc action)

4. Endpoint Filters (Bộ lọc endpoint) (ASP.NET Core 7.0 trở lên)

5. Exception Filters (Bộ lọc ngoại lệ)

6. Result Filters (Bộ lọc kết quả)

Các mẫu triển khai

Synchronous Filters (Bộ lọc đồng bộ)

csharp
public class SampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Làm gì đó trước khi action thực thi.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Làm gì đó sau khi action thực thi.
    }
}

Asynchronous Filters (Bộ lọc bất đồng bộ)

csharp
public class SampleAsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // Làm gì đó trước khi action thực thi.
        await next();
        // Làm gì đó sau khi action thực thi.
    }
}

Quan trọng: Hãy triển khai phương thức đồng bộ HOẶC bất đồng bộ, không phải cả hai. Runtime kiểm tra async trước.

Phạm vi và thứ tự thực thi của Filter

Filters có thể được áp dụng ở ba phạm vi:

  1. Global - Tất cả controllers và actions
  2. Controller - Toàn bộ lớp controller
  3. Action - Phương thức action riêng lẻ

Thứ tự thực thi mặc định theo dạng lồng nhau: global → controller → action, sau đó đảo ngược cho các phương thức "after".

Đăng ký Global Filter

csharp
builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<GlobalSampleActionFilter>();
});

Ghi đè thứ tự thực thi

Sử dụng interface IOrderedFilter với thuộc tính Order:

csharp
[SampleActionFilter(Order = int.MinValue)]
public class ControllerFiltersController : Controller
{
    // Các giá trị Order thấp hơn chạy code "before" của chúng trước
    // và code "after" của chúng sau cùng
}

Short-Circuiting (Ngắt mạch) Pipeline

Đặt thuộc tính Result trong ResourceExecutingContext để short-circuit:

csharp
public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.Result = new ContentResult
        {
            Content = "Short-circuited response"
        };
    }

    public void OnResourceExecuted(ResourceExecutedContext context) { }
}

Dependency Injection (Tiêm phụ thuộc) trong Filters

ServiceFilterAttribute

Đăng ký filter trong DI, sau đó lấy nó:

csharp
// Đăng ký
builder.Services.AddScoped<LoggingResponseHeaderFilterService>();

// Sử dụng
[ServiceFilter<LoggingResponseHeaderFilterService>]
public IActionResult WithServiceFilter() => Content("...");

TypeFilterAttribute

Kích hoạt kiểu mà không cần đăng ký DI trực tiếp:

csharp
[TypeFilter(typeof(LoggingResponseHeaderFilter),
    Arguments = new object[] { "Filter-Header", "Filter Value" })]
public IActionResult WithTypeFilter() => Content("...");

IFilterFactory

Attribute tùy chỉnh triển khai filter factory (nhà máy bộ lọc):

csharp
public class ResponseHeaderFilterFactory : Attribute, IFilterFactory
{
    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) =>
        new InternalResponseHeaderFilter();

    private class InternalResponseHeaderFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context) =>
            context.HttpContext.Response.Headers.Add("Header", "Value");

        public void OnActionExecuted(ActionExecutedContext context) { }
    }
}

Các Filter Attribute tích hợp sẵn

Ví dụ Exception Filter

csharp
public class SampleExceptionFilter : IExceptionFilter
{
    private readonly IHostEnvironment _hostEnvironment;

    public SampleExceptionFilter(IHostEnvironment hostEnvironment) =>
        _hostEnvironment = hostEnvironment;

    public void OnException(ExceptionContext context)
    {
        if (!_hostEnvironment.IsDevelopment())
            return;

        context.Result = new ContentResult
        {
            Content = context.Exception.ToString()
        };
    }
}

Ví dụ Result Filter

csharp
public class ResponseHeaderAttribute : ActionFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public ResponseHeaderAttribute(string name, string value) =>
        (_name, _value) = (name, value);

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add(_name, _value);
        base.OnResultExecuting(context);
    }
}

Always-Run Result Filters

Sử dụng IAlwaysRunResultFilter cho các filters cần chạy ngay cả khi các filters khác short-circuit:

csharp
public class UnprocessableResultFilter : IAlwaysRunResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is StatusCodeResult statusCodeResult
            && statusCodeResult.StatusCode == StatusCodes.Status415UnsupportedMediaType)
        {
            context.Result = new ObjectResult("Unprocessable")
            {
                StatusCode = StatusCodes.Status422UnprocessableEntity
            };
        }
    }

    public void OnResultExecuted(ResultExecutedContext context) { }
}

Sử dụng Middleware như Filters

csharp
public class FilterMiddlewarePipeline
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            context.Response.Headers.Add("Pipeline", "Middleware");
            await next();
        });
    }
}

[MiddlewareFilter<FilterMiddlewarePipeline>]
public class FilterMiddlewareController : Controller
{
    public IActionResult Index() => Content("...");
}

Thực hành tốt nhất

  1. Authorization: Sử dụng authorization policies thay vì custom authorization filters
  2. Xử lý ngoại lệ: Ưu tiên middleware cho xử lý ngoại lệ toàn cục; sử dụng exception filters chỉ khi xử lý lỗi khác nhau theo action
  3. Logging: Không sử dụng filters chỉ để logging; sử dụng framework logging tích hợp sẵn
  4. Thread Safety: Các instance filter được truyền cho Add() là singletons và không thread-safe; sử dụng types thay vì để có các instance thread-safe

Giới hạn