FluentValidation ile Katmanlı Doğrulama Rehberi konusunu anlatan renkli 3D teknik illüstrasyon

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.

fluentvalidation-akis-diyagrami

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

Avantajlar

  • 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());
    }
}

fluentvalidation-katmanli-mimari-semasi

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ç

  1. FluentValidation ile temel kurallar
  2. Conditional ve child collection senaryoları
  3. 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.

By tanju.bozok

Senior .NET Engineer | Backend architecture, distributed systems & real-time communication