Pipeline
Behaviors
Cross-cutting concerns — logging, validation, caching qua IPipelineBehavior
Pipeline Behavior là gì?
Pipeline Behavior trong MediatR hoạt động giống middleware trong ASP.NET Core, nhưng ở tầng application logic. Mỗi behavior bọc quanh handler, có thể chạy code trước và sau khi handler thực thi — tạo thành mô hình onion (hành tây).
Mô hình Onion
IPipelineBehavior Interface
Mọi behavior đều implement IPipelineBehavior<TRequest, TResponse>:
public interface IPipelineBehavior<TRequest, TResponse> where TRequest : notnull { Task<TResponse> Handle( TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken); } // RequestHandlerDelegate là delegate gọi behavior/handler tiếp theo // Gọi next() = đi sâu thêm một lớp trong onion // Không gọi next() = short-circuit, handler không bao giờ chạy
Logging Behavior
Behavior phổ biến nhất — log request, response, và thời gian thực thi:
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull { private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger; public LoggingBehavior( ILogger<LoggingBehavior<TRequest, TResponse>> logger) => _logger = logger; public async Task<TResponse> Handle( TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct) { var requestName = typeof(TRequest).Name; _logger.LogInformation( "[MediatR] Handling {RequestName}: {@Request}", requestName, request); var sw = Stopwatch.StartNew(); var response = await next(); // Gọi behavior/handler tiếp theo sw.Stop(); _logger.LogInformation( "[MediatR] Handled {RequestName} in {ElapsedMs}ms", requestName, sw.ElapsedMilliseconds); return response; } }
Validation Behavior
Kết hợp với FluentValidation — tự động validate mọi request trước khi handler chạy:
dotnet add package FluentValidation.DependencyInjectionExtensions
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull { private readonly IEnumerable<IValidator<TRequest>> _validators; public ValidationBehavior( IEnumerable<IValidator<TRequest>> validators) => _validators = validators; public async Task<TResponse> Handle( TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct) { if (!_validators.Any()) return await next(); var context = new ValidationContext<TRequest>(request); var failures = (await Task.WhenAll( _validators.Select(v => v.ValidateAsync(context, ct)))) .SelectMany(r => r.Errors) .Where(f => f != null) .ToList(); if (failures.Count > 0) throw new ValidationException(failures); return await next(); // Chỉ chạy handler nếu valid } }
Ví dụ Validator
public class CreateProductValidator : AbstractValidator<CreateProductCommand> { public CreateProductValidator() { RuleFor(x => x.Name) .NotEmpty().WithMessage("Tên sản phẩm không được trống") .MaximumLength(200).WithMessage("Tên tối đa 200 ký tự"); RuleFor(x => x.Price) .GreaterThan(0).WithMessage("Giá phải lớn hơn 0"); RuleFor(x => x.Category) .NotEmpty().WithMessage("Danh mục không được trống"); } }
IEnumerable<IValidator<TRequest>> thay vì IValidator<TRequest>.
Nếu request chưa có validator, collection sẽ rỗng — behavior sẽ skip và gọi next() luôn, không
crash.
Performance Behavior
Cảnh báo khi một request chạy quá lâu — hữu ích để phát hiện bottleneck:
public class PerformanceBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull { private readonly ILogger<PerformanceBehavior<TRequest, TResponse>> _logger; private const int ThresholdMs = 500; public PerformanceBehavior( ILogger<PerformanceBehavior<TRequest, TResponse>> logger) => _logger = logger; public async Task<TResponse> Handle( TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct) { var sw = Stopwatch.StartNew(); var response = await next(); sw.Stop(); if (sw.ElapsedMilliseconds > ThresholdMs) { _logger.LogWarning( "[PERF] {RequestName} took {ElapsedMs}ms! Request: {@Request}", typeof(TRequest).Name, sw.ElapsedMilliseconds, request); } return response; } }
Authorization Behavior
Kiểm tra quyền trước khi handler chạy. Dùng marker interface hoặc attribute để đánh dấu request nào cần authorize:
// Marker interface public interface IRequireAuthorization { string[] RequiredRoles { get; } } // Request cần authorize public record DeleteProductCommand(int Id) : IRequest, IRequireAuthorization { public string[] RequiredRoles => ["Admin"]; } // Behavior public class AuthorizationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull { private readonly IHttpContextAccessor _httpContext; public AuthorizationBehavior(IHttpContextAccessor httpContext) => _httpContext = httpContext; public async Task<TResponse> Handle( TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct) { if (request is IRequireAuthorization authRequest) { var user = _httpContext.HttpContext?.User; if (user?.Identity?.IsAuthenticated != true) throw new UnauthorizedAccessException(); var hasRole = authRequest.RequiredRoles .Any(role => user.IsInRole(role)); if (!hasRole) throw new ForbiddenAccessException(); } return await next(); } }
Đăng ký Behaviors
Đăng ký behaviors trong Program.cs. Thứ tự đăng ký = thứ tự thực thi (ngoài vào
trong):
builder.Services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(typeof(Program).Assembly); // Thứ tự: Logging → Validation → Authorization → Handler cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>)); cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>)); cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(AuthorizationBehavior<,>)); });
AddOpenBehavior() thay vì AddBehavior(), thứ tự
không được đảm bảo. Dùng AddBehavior() khi cần kiểm soát thứ tự chính xác.
Pre & Post Processors
MediatR cung cấp hai interface đặc biệt cho logic chỉ chạy trước hoặc chỉ chạy sau handler:
// Chạy TRƯỚC handler public class LogRequestPreProcessor<TRequest> : IRequestPreProcessor<TRequest> where TRequest : notnull { private readonly ILogger<LogRequestPreProcessor<TRequest>> _logger; public LogRequestPreProcessor( ILogger<LogRequestPreProcessor<TRequest>> logger) => _logger = logger; public Task Process(TRequest request, CancellationToken ct) { _logger.LogInformation( "[PRE] {RequestName}: {@Request}", typeof(TRequest).Name, request); return Task.CompletedTask; } } // Chạy SAU handler public class LogResponsePostProcessor<TRequest, TResponse> : IRequestPostProcessor<TRequest, TResponse> where TRequest : notnull { private readonly ILogger<LogResponsePostProcessor<TRequest, TResponse>> _logger; public LogResponsePostProcessor( ILogger<LogResponsePostProcessor<TRequest, TResponse>> logger) => _logger = logger; public Task Process( TRequest request, TResponse response, CancellationToken ct) { _logger.LogInformation( "[POST] {RequestName} → {@Response}", typeof(TRequest).Name, response); return Task.CompletedTask; } }
IRequestPreProcessor chạy trước tất cả behaviors. IRequestPostProcessor
chạy sau tất cả behaviors (sau handler trả response). Chúng tự động được MediatR đăng ký —
không cần AddBehavior().