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)
- Chạy đầu tiên trong pipeline
- Xác định xem người dùng có được ủy quyền cho request không
- Có thể short-circuit (ngắt mạch) pipeline nếu không được ủy quyền
- Không có phương thức "after"
2. Resource Filters (Bộ lọc tài nguyên)
- Chạy sau authorization
OnResourceExecuting: Chạy trước model binding và phần còn lại của pipelineOnResourceExecuted: Chạy sau khi pipeline hoàn thành- Hữu ích cho caching và short-circuiting pipeline
3. Action Filters (Bộ lọc action)
- Chạy ngay trước và sau khi thực thi phương thức action
- Có thể sửa đổi action arguments (đối số) và results (kết quả)
- Không được hỗ trợ trong Razor Pages
- Bao gồm cả các giai đoạn "executing" và "executed"
4. Endpoint Filters (Bộ lọc endpoint) (ASP.NET Core 7.0 trở lên)
- Tương tự như action filters
- Hoạt động với cả actions và các endpoint dựa trên route handler
- Không được hỗ trợ trong Razor Pages
5. Exception Filters (Bộ lọc ngoại lệ)
- Chạy khi có các ngoại lệ chưa được xử lý xảy ra trong quá trình thực thi action
- Không bắt ngoại lệ từ middleware, routing hoặc model binding
- Triển khai
IExceptionFilterhoặcIAsyncExceptionFilter
6. Result Filters (Bộ lọc kết quả)
- Chạy ngay trước và sau khi thực thi action result
- Chỉ thực thi khi action thành công
- Hữu ích cho việc bao quanh thực thi view hoặc formatter
- Bao gồm
IResultFilter,IAsyncResultFilter,IAlwaysRunResultFiltervà các biến thể async
Các mẫu triển khai
Synchronous Filters (Bộ lọc đồng bộ)
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ộ)
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:
- Global - Tất cả controllers và actions
- Controller - Toàn bộ lớp controller
- 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
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:
[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:
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ó:
// Đă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:
[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):
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
ActionFilterAttribute- Lớp cơ sở cho action filtersExceptionFilterAttribute- Lớp cơ sở cho exception filtersResultFilterAttribute- Lớp cơ sở cho result filtersFormatFilterAttributeServiceFilterAttributeTypeFilterAttribute
Ví dụ Exception Filter
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
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:
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
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
- Authorization: Sử dụng authorization policies thay vì custom authorization filters
- 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
- Logging: Không sử dụng filters chỉ để logging; sử dụng framework logging tích hợp sẵn
- 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
- Filters không hoạt động trực tiếp với Razor components
- Action filters không áp dụng cho Razor Pages (sử dụng
IPageFilterthay thế) - Filter attributes không thể sử dụng constructor dependency injection trực tiếp
- Exception filters không bắt ngoại lệ từ middleware, routing hoặc model binding