Table Of Content
- Tại sao nên sử dụng FluentValidation kết hợp MediatR Pipeline?
- Lợi ích cụ thể
- Cài đặt các gói cần thiết
- Đăng ký dịch vụ trong Program.cs
- Ví dụ về Command và Validator
- Command
- Validator
- Tạo lớp ValidationBehavior
- Gửi Request và xử lý lỗi
- Xử lý lỗi ValidationException toàn cục
- Kết quả trả về ví dụ
- Tổng kết
Khi phát triển các ứng dụng ASP.NET Core hiện đại, đặc biệt là những ứng dụng áp dụng kiến trúc CQRS và sử dụng MediatR để xử lý command/query, việc đảm bảo dữ liệu đầu vào được kiểm tra một cách nhất quán và có tổ chức là vô cùng quan trọng. Thay vì thực hiện kiểm tra thủ công trong từng handler, giải pháp sử dụng FluentValidation kết hợp MediatR Pipeline Behavior đang trở thành một phương pháp chuẩn hóa giúp mã nguồn sạch hơn, dễ bảo trì hơn và ít lỗi hơn.
Tại sao nên sử dụng FluentValidation kết hợp MediatR Pipeline?
Việc viết logic kiểm tra dữ liệu trong handler thường gây ra nhiều vấn đề:
- Tăng độ phức tạp của handler.
- Dễ lặp lại logic ở nhiều nơi.
- Khó bảo trì và dễ sai sót.
Sử dụng FluentValidation tách biệt rõ phần validation ra khỏi phần xử lý nghiệp vụ. Kết hợp thêm MediatR pipeline behavior sẽ giúp tự động gọi validator tương ứng trước khi MediatR thực thi handler tương ứng.
Lợi ích cụ thể
- Handler sạch hơn, chỉ chứa logic nghiệp vụ.
- Dễ viết unit test cho validation logic.
- Validator có thể tái sử dụng ở nhiều nơi.
- Tự động hóa kiểm tra dữ liệu cho mọi request.
- Chuẩn hóa lỗi trả về phía client và dễ tích hợp với các hệ thống khác.
Cài đặt các gói cần thiết
Sử dụng NuGet để cài đặt các thư viện liên quan:
Install-Package FluentValidation.AspNetCore
Install-Package FluentValidation.DependencyInjectionExtensions
Install-Package MediatR
Install-Package MediatR.Extensions.Microsoft.DependencyInjection
Đăng ký dịch vụ trong Program.cs
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<CreateUserCommand>());
builder.Services.AddValidatorsFromAssemblyContaining<CreateUserCommandValidator>();
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
Ví dụ về Command và Validator
Command:
public record CreateUserCommand(string Name, string Email) : IRequest<Guid>;
Validator:
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
public CreateUserCommandValidator()
{
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Tên không được để trống")
.MinimumLength(2).WithMessage("Tên phải có ít nhất 2 ký tự");
RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email không được để trống")
.EmailAddress().WithMessage("Email không hợp lệ");
}
}
Bạn có thể mở rộng validator bằng cách sử dụng When
, Unless
, hoặc thêm các kiểm tra bất đồng bộ như kiểm tra email đã tồn tại trong hệ thống.
Tạo lớp ValidationBehavior
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
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 cancellationToken)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
if (failures.Any())
{
throw new ValidationException(failures);
}
}
return await next();
}
}
Middleware này sẽ đảm nhiệm việc gọi tất cả các validator tương ứng của request trước khi handler được thực thi.
Gửi Request và xử lý lỗi
Giả sử bạn gửi một request như sau:
var result = await _mediator.Send(new CreateUserCommand("", "invalid-email"));
Nếu dữ liệu không hợp lệ, hệ thống sẽ ném ra ValidationException
và handler sẽ không được gọi.
Xử lý lỗi ValidationException toàn cục
Để xử lý các lỗi validation và phản hồi chúng một cách chuẩn hóa, bạn có thể dùng middleware:
app.Use(async (context, next) =>
{
try
{
await next();
}
catch (ValidationException ex)
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
var errorResponse = new
{
errors = ex.Errors
.GroupBy(e => e.PropertyName)
.ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray())
};
await context.Response.WriteAsJsonAsync(errorResponse);
}
});
Kết quả trả về ví dụ:
{
"errors": {
"Email": ["Email không hợp lệ"],
"Name": ["Tên không được để trống"]
}
}
Tổng kết
Sử dụng FluentValidation kết hợp với MediatR Pipeline Behavior là một cách làm chuẩn và hiệu quả để đảm bảo dữ liệu đầu vào luôn được kiểm tra kỹ lưỡng. Việc tách biệt validation khỏi handler giúp mã nguồn gọn gàng, dễ đọc và dễ mở rộng. Đây là một phần quan trọng trong việc xây dựng hệ thống có kiến trúc rõ ràng, khả năng kiểm soát cao và dễ kiểm thử.
No Comment! Be the first one.