Giriş
Veri doğrulama beklenen input contract’ını korur; authentication, authorization veya domain invariant’larının yerine geçmez. Özellikle katmanlı mimarilerde hem API katmanında hem de iş katmanında validasyon kuralları net olmalı.
Bu örnekte FluentValidation, okunabilir API’leri ve güçlü kural tanımlarıyla kuralları ayrı sınıflarda ifade eder.

1.FluentValidation Kurulumu ve Entegrasyonu
NuGet Paketi
dotnet add package FluentValidation.DependencyInjectionExtensions --version 12.1.1
Hizmet Kaydı (Program.cs)
builder.Services.AddControllers();
builder.Services.AddValidatorsFromAssemblyContaining<CreateUserDtoValidator>();
2.Temel Kavramlar (DTO, Validator)
DTO (Data Transfer Object)
public class CreateUserDto
{
public string? FullName { get; set; }
public string? Email { get; set; }
public int Age { get; set; }
}
AbstractValidator<T>
public class CreateUserDtoValidator : AbstractValidator<CreateUserDto>
{
public CreateUserDtoValidator()
{
RuleFor(x => x.FullName)
.Cascade(CascadeMode.Stop)
.NotEmpty()
.WithMessage("Ad soyad boş olamaz.")
.Length(3, 100);
RuleFor(x => x.Email)
.Cascade(CascadeMode.Stop)
.NotEmpty()
.EmailAddress();
RuleFor(x => x.Age)
.InclusiveBetween(18, 99)
.WithMessage("Yaş 18–99 arasında olmalı.");
}
}
3.Katmanlı Mimari Entegrasyonu
| Katman | Yapılandırma |
|---|---|
| API Layer | IValidator<T> DI kaydı ve controller’da ValidateAsync sonucu |
| Application Layer | DTO validator’leri (AbstractValidator) |
| Domain Layer | Entity/aggregate invariant’ları domain’de korunur; request validation aynı sorumluluk değildir |
[ApiController]
[Route("api/users/validation")]
public class UsersController : ControllerBase
{
private readonly IValidator<CreateUserDto> _validator;
public UsersController(IValidator<CreateUserDto> validator) =>
_validator = validator;
[HttpPost]
public async Task<IActionResult> Create(
[FromBody] CreateUserDto dto,
CancellationToken ct
)
{
var result = await _validator.ValidateAsync(dto, ct);
if (!result.IsValid)
return BadRequest(
new ValidationProblemDetails(result.ToDictionary())
);
return Ok(new { Validated = true }); // Validation demo; kullanıcı kaydetmez.
}
}
4. Avantajlar & Dezavantajlar
- Fluent API: Zincirlenebilir ve okunabilir
- Modüler Validator’ler: Her DTO’ya özel sınıf
- Conditional Kurallar:
.When(),.Unless() - Child Collection:
RuleForEach()
Dezavantajlar
- Başlangıç Maliyeti: Paket, DI ayarları
- Performans Yükü: Çok karmaşık validator’lerde
- Localizasyon: Mesajlar için ekstra konfigürasyon
5. FluentValidation: conditional ve collection kuralları
Conditional Kurallar
public class PremiumUserDto
{
public decimal Discount { get; set; }
public bool IsPremiumUser { get; set; }
}
public class PremiumUserDtoValidator : AbstractValidator<PremiumUserDto>
{
public PremiumUserDtoValidator() =>
RuleFor(x => x.Discount)
.GreaterThan(0)
.When(x => x.IsPremiumUser);
}
Child Collection
public class OrderDto
{
public List<OrderItemDto>? Items { get; set; } = new();
}
public class OrderItemDto
{
public int ProductId { get; set; }
public int Quantity { get; set; }
}
public class OrderItemDtoValidator : AbstractValidator<OrderItemDto>
{
public OrderItemDtoValidator()
{
RuleFor(x => x.ProductId).GreaterThan(0);
RuleFor(x => x.Quantity).GreaterThan(0);
}
}
public class OrderDtoValidator : AbstractValidator<OrderDto>
{
public OrderDtoValidator()
{
RuleFor(x => x.Items)
.Cascade(CascadeMode.Stop)
.NotNull()
.NotEmpty();
RuleForEach(x => x.Items)
.NotNull()
.SetValidator(new OrderItemDtoValidator());
}
}

6.Gerçek Projelerde Dikkat Edilmesi Gerekenler
- Global Exception Filter ile tutarlı hata formatı
- TestValidate / TestValidateAsync ile geçerli, geçersiz ve sınır değer girdilerini test et
- Performans: Sadece ihtiyaç duyulan validator’leri kaydedin
- Localization: WithName yalnız property display name’i değiştirir; tam mesaj/culture için LanguageManager, WithMessage veya IStringLocalizer kullanın
Dahili Bağlantılar
- Validasyon sonrası Result Pattern ile tutarlı dönüş sağlayın.
- AutoMapper ile DTO dönüşümlerini otomatikleştirin.
7.İleri Okuma ve Kaynaklar
8.Sonuç
- FluentValidation ile temel kurallar
- Conditional ve child collection senaryoları
- Katmanlı mimaride temiz entegrasyon
Bu rehberle hem basit hem de ileri validasyon senaryolarını güvenle uygulayabilirsiniz.
Örnek FluentValidation 12.1.1 ve .NET 8+ içindir. DependencyInjectionExtensions core paketi de getirir. FluentValidation ve Microsoft.AspNetCore.Mvc using’lerini ekleyin; registration builder.Build öncesine gelir, controller route’ları için MapControllers gerekir. Endpoint validation sonucunu döndürür, kullanıcı oluşturmaz.
Resmî ASP.NET entegrasyonu yeni projelerde manual validation’ı açık bir seçenek olarak gösterir. Legacy FluentValidation.AspNetCore auto-validation artık önerilmiyor: MVC pipeline senkrondur ve async rule çalıştıramaz. ValidateAsync token’ı async kurala iletir; cancellation’ı beklenen validation hatasına çevirmeyin.
NotEmpty boş/whitespace metni reddeder; EmailAddress mailbox sahipliğini veya mesaj teslimini kanıtlamaz. 18–99 aralığı bu örneğin iş kuralıdır. Premium conditional kesiti sadece premium kullanıcı için pozitif Discount arar; diğer iş kurallarını varsaymaz. Child validator tek başına null/empty collection’ı reddetmez; Items ve null item kontrolleri bu yüzden ayrıdır.
AssertConfigurationIsValid AutoMapper API’sidir. FluentValidation için Test Extensions ile davranışı doğrulayın. Database uniqueness kontrolü ile insert arasındaki race condition’ı validator çözmez; DDD Validation yazısındaki domain/database sınırlarını da koruyun.
