Validation¶
Validation, MediatR’da request’lerin doğruluğunu kontrol etmek için kullanılan bir mekanizmadır. FluentValidation kütüphanesi ile entegre çalışır.
Validation Özellikleri¶
- Request Validation: Command ve Query’lerin doğruluğunu kontrol eder
- Custom Validation: Özel validation kuralları tanımlanabilir
- Cross-Cutting: Validation logic’i merkezi olarak yönetilir
- Error Handling: Validation hataları özel exception’lar ile yönetilir
Validation Örnekleri¶
Command Validation¶
public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
{
public CreateProductCommandValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.MaximumLength(100);
RuleFor(x => x.Price)
.GreaterThan(0);
RuleFor(x => x.Description)
.MaximumLength(500);
}
}
public class CreateProductCommand : IRequest<int>
{
public string Name { get; set; }
public decimal Price { get; set; }
public string Description { get; set; }
}
Query Validation¶
public class GetProductsQueryValidator : AbstractValidator<GetProductsQuery>
{
public GetProductsQueryValidator()
{
RuleFor(x => x.PageNumber)
.GreaterThan(0);
RuleFor(x => x.PageSize)
.GreaterThan(0)
.LessThanOrEqualTo(100);
RuleFor(x => x.SortBy)
.Must(BeAValidSortField)
.When(x => !string.IsNullOrEmpty(x.SortBy));
}
private bool BeAValidSortField(string sortBy)
{
var validFields = new[] { "name", "price", "createdDate" };
return validFields.Contains(sortBy.ToLower());
}
}
public class GetProductsQuery : IRequest<PagedList<ProductDto>>
{
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
public string SortBy { get; set; }
public bool SortDescending { get; set; }
}
Custom Validation¶
public class UniqueProductNameValidator : AbstractValidator<CreateProductCommand>
{
private readonly IProductRepository _repository;
public UniqueProductNameValidator(IProductRepository repository)
{
_repository = repository;
RuleFor(x => x.Name)
.MustAsync(BeUniqueName)
.WithMessage("Product name must be unique");
}
private async Task<bool> BeUniqueName(string name, CancellationToken cancellationToken)
{
return !await _repository.ExistsAsync(x => x.Name == name);
}
}
Validation Best Practices¶
-
Validation Rules - Her request için ayrı validator sınıfı - Validation kuralları açık ve anlaşılır olmalı - Custom validation kuralları kullanılmalı
-
Error Messages - Hata mesajları açıklayıcı olmalı - Çoklu dil desteği olmalı - Hata kodları kullanılmalı
-
Performance - Validation kuralları optimize edilmeli - Gereksiz validation’lardan kaçınılmalı - Async validation kullanılmalı
-
Testing - Validator’lar unit test edilmeli - Edge case’ler test edilmeli - Custom validation’lar test edilmeli
Validation Pipeline¶
Validation pipeline üzerinden geçer:
- Rule Definition
- Rule Execution
- Error Collection
- Exception Throwing
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, 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)
{
var context = new ValidationContext<TRequest>(request);
var failures = _validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Any())
{
throw new ValidationException(failures);
}
return await next();
}
}
Validation Registration¶
services.AddMediatR(cfg => {
cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
cfg.AddBehavior(typeof(ValidationBehavior<,>));
});
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());