Bắt đầu từ đâu
Tôi gặp phải vấn đề này khi di chuyển dự án sang Asp.Net Core. Trong ứng dụng web chứa cả MVC và WebAPI, tôi cần thêm bộ lọc xác thực giao diện IActionFilter cho phần WebAPI. Cách tiếp cận thông thường là gắn nhãn bộ lọc vào các controller cần thiết, nhưng cách nâng cao hơn là đăng ký bộ lọc toàn cục để tránh phải thêm thủ công. Cách đăng ký như sau:
services.AddMvc(options =>
{
options.Filters.Add(typeof(AccessControlFilter));
});
Tuy nhiên, điều này ảnh hưởng đến cả controller MVC. Dù có thể phân biệt trong bộ lọc, nhưng việc thêm một bộ lọc vô ích cho MVC khiến tôi không hài lòng. Tôi tìm cách khác để thực hiện điều này và phát hiện ra ModelConvention.
Hiểu về ApplicationModel
Theo tài liệu chính thức, ApplicationModel mô tả các thành phần của ứng dụng MVC. Bạn có thể đọc và thay đổi mô hình này để điều chỉnh hành vi của các phần tử MVC. Mặc định, MVC sử dụng các quy ước để xác định các lớp controller, phương thức action, tham số và định tuyến. Bạn có thể tùy chỉnh bằng cách tạo các quy ước riêng và áp dụng toàn cục hoặc dưới dạng thuộc tính.
Các lớp liên quan đến ApplicationModel nằm trong không gian tên Microsoft.AspNetCore.Mvc.ApplicationModels. Các mô hình này được xây dựng thông qua IApplicationModelProvider, mặc định là DefaultApplicationModelProvider. Bạn có thể chỉnh sửa mô hình để thay đổi hành vi thông qua ModelConvention.
ModelConvention
ModelConvention định nghĩa điểm nhập để thao tác mô hình, có thể coi như một giao thức. Qua đó, bạn có thể thay đổi mô hình, các Convention phổ biến bao gồm:
- IApplicationModelConvention
- IControllerModelConvention
- IActionModelConvention
- IParameterModelConvention
- IPageRouteModelConvention
Các giao diện này cung cấp phương thức chung Apply, ví dụ với IControllerModelConvention:
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
public interface IControllerModelConvention
{
void Apply(ControllerModel controller);
}
}
Phương thức Apply cho phép tùy chỉnh ControllerModel, bạn có thể thay đổi tên controller, cập nhật bộ lọc, thay đổi định tuyến... khác với bộ lọc thông thường, ModelConvention chỉ chạy một lần khi ứng dụng khởi động, trước cả khi controller được kích hoạt.
Để đáp ứng yêu cầu ban đầu, ta tạo Convention như sau:
public class ApiControllerAuthorizeConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
if (controller.Filters.Any(x => x is ApiControllerAttribute) && !controller.Filters.Any(x => x is AccessControlFilter))
{
controller.Filters.Add(new AccessControlAttribute());
}
}
}
Để đăng ký Convention, sử dụng thuộc tính Conventions trong MvcOptions:
services.AddMvc(options =>
{
options.Conventions.Add(new ApiControllerAuthorizeConvention());
})
Nếu bộ lọc AccessControlFilter cần phụ thuộc dịch vụ, ta dùng ServiceFilter:
public class GlobalApiFilter : IActionFilter
{
private IUserService _userService;
public GlobalApiFilter(IUserService service)
{
_userService = service;
}
public void OnActionExecuting(ActionExecutingContext context)
{
// Mô phỏng thao tác
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
Đăng ký như sau:
controller.Filters.Add(new ServiceFilterAttribute(typeof(GlobalApiFilter)));
Và thêm vào container DI:
services.AddScoped<GlobalApiFilter>();
Kết luận
Tôi đã sử dụng ModelConvention để phân tách bộ lọc toàn cục, dù không trực tiếp như options.Filters.Add(xxx) nhưng đây là cách hiệu quả nhất hiện tại. Tôi nghi ngờ options.Filters.Add(xxx) cũng hoạt động tương tự, chỉ là không biết chính xác.