Yükleniyor...

C# FluentValidation Kullanımı

C#’ta FluentValidation kullanarak veri doğrulama kurallarını öğrenin. Temiz ve sürdürülebilir validation senaryoları.

FluentValidation, C#’ta doğrulama (validation) kurallarını akıcı (fluent) bir API ile tanımlamanızı sağlayan popüler bir kütüphanedir. Model sınıflarından bağımsız, tekrar kullanılabilir ve test edilebilir kurallar yazmanıza imkân verir. Bu makalede kurulumdan ileri özelliklere, ASP.NET Core entegrasyonundan birim testlerine kadar pratik bir rehber bulacaksınız.


Kurulum

Temel paket ve ASP.NET Core entegrasyonu (opsiyonel) için:


dotnet add package FluentValidation
dotnet add package FluentValidation.AspNetCore   # Web projeleri için

Masaüstü (WPF/WinForms) veya servis projelerinde yalnızca FluentValidation yeterlidir.


Temel Kullanım: Basit Bir Doğrulayıcı

Bir model için doğrulayıcı sınıfı, AbstractValidator<T>’den türetilir ve kurallar RuleFor ile yazılır.


using FluentValidation;

public class UserDto
{
    public string? Name { get; set; }
    public string? Email { get; set; }
    public int Age { get; set; }
}

public class UserDtoValidator : AbstractValidator<UserDto>
{
    public UserDtoValidator()
    {
        RuleFor(x => x.Name)
            .NotEmpty().WithMessage("Ad boş olamaz")
            .Length(2, 50).WithMessage("Ad 2-50 karakter olmalı");

        RuleFor(x => x.Email)
            .NotEmpty().WithMessage("E-posta zorunlu")
            .EmailAddress().WithMessage("Geçerli bir e-posta giriniz");

        RuleFor(x => x.Age)
            .InclusiveBetween(18, 120).WithMessage("Yaş 18-120 aralığında olmalı");
    }
}

Doğrulama çağrısı: var result = new UserDtoValidator().Validate(model); Hataları: result.Errors ile okuyabilirsiniz.


Mesajlar, Şiddet (Severity) ve Cascade Modu


RuleFor(x => x.Name)
    .Cascade(CascadeMode.Stop)
    .NotEmpty().WithMessage("Ad zorunlu").WithSeverity(Severity.Error)
    .MinimumLength(2).WithMessage("Ad en az 2 karakter olmalı");

Koşullu Kurallar: When / Unless

Belirli bir koşul sağlandığında kural çalıştırmak için kullanılır.


RuleFor(x => x.Email)
  .NotEmpty().EmailAddress();

RuleFor(x => x.Age)
  .GreaterThan(0);

RuleFor(x => x)
  .Must(x => x.Email!.EndsWith("@company.com"))
  .When(x => x.Age >= 18)
  .WithMessage("18+ kullanıcılar kurumsal e-posta kullanmalı");

İlişkili Nesneler ve Koleksiyonlar

Alt model doğrulaması için SetValidator; koleksiyon elemanları için RuleForEach kullanılır.


public class AddressDto { public string? City { get; set; } public string? Zip { get; set; } }

public class AddressValidator : AbstractValidator<AddressDto>
{
    public AddressValidator()
    {
        RuleFor(x => x.City).NotEmpty();
        RuleFor(x => x.Zip).NotEmpty().Length(5, 10);
    }
}

public class CustomerDto
{
    public string? Name { get; set; }
    public AddressDto? Address { get; set; }
    public List<string> Phones { get; set; } = new();
}

public class CustomerValidator : AbstractValidator<CustomerDto>
{
    public CustomerValidator()
    {
        RuleFor(x => x.Name).NotEmpty();

        RuleFor(x => x.Address)
            .NotNull().WithMessage("Adres gerekli")
            .SetValidator(new AddressValidator());

        RuleForEach(x => x.Phones)
            .NotEmpty().Matches(@"^\+?\d{7,15}$")
            .WithMessage("Telefon numarası geçersiz");
    }
}

Özel Kurallar (Custom), Asenkron Kurallar

Must/MustAsync ile karmaşık veya harici servise bağlı doğrulamalar yazılabilir.


RuleFor(x => x.Email)
  .MustAsync(async (email, ct) =>
  {
      // Örn: benzersizlik kontrolü (pseudo-repo)
      bool exists = await _userRepository.ExistsByEmailAsync(email, ct);
      return !exists;
  })
  .WithMessage("E-posta zaten kayıtlı");

RuleSet ile Senaryo Bazlı Doğrulama

Farklı kullanım senaryoları için kuralları gruplamak mümkündür (ör. Create, Update).


public class ProductDto { public string? Name { get; set; } public decimal Price { get; set; } }

public class ProductValidator : AbstractValidator<ProductDto>
{
    public ProductValidator()
    {
        RuleSet("Create", () =>
        {
            RuleFor(x => x.Name).NotEmpty();
            RuleFor(x => x.Price).GreaterThan(0);
        });

        RuleSet("Update", () =>
        {
            RuleFor(x => x.Price).GreaterThanOrEqualTo(0);
        });
    }
}

// Kullanım:
// validator.Validate(model, options: o => o.IncludeRuleSets("Create"));

Hata Mesajlarını Özelleştirme ve Yerelleştirme


RuleFor(x => x.Email)
  .WithName("E-Posta")
  .NotEmpty().WithMessage("{PropertyName} boş olamaz")
  .EmailAddress().WithMessage("{PropertyName} geçerli değil");

ASP.NET Core Entegrasyonu

Otomatik model doğrulaması ve ModelState entegrasyonu için servis kaydı yapın.


// Program.cs (Minimal API / ASP.NET Core 7+)
using FluentValidation;
using FluentValidation.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddFluentValidationAutoValidation();  // MVC model binding ile otomatik çalışır
builder.Services.AddValidatorsFromAssemblyContaining<UserDtoValidator>(); // Tüm validator'ları tara

var app = builder.Build();
app.MapControllers();
app.Run();

Controller’da manuel çağrıya gerek kalmaz; model binding sonrası FluentValidation devreye girer ve hataları ModelState’e yazar.


İleri Konular: Bağımlılıklar, Enjeksiyon ve Reusable Kurallar


public class NameRules : AbstractValidator<UserDto>
{
    public NameRules()
        => RuleFor(x => x.Name).NotEmpty().MaximumLength(50);
}

public class UserDtoValidator2 : AbstractValidator<UserDto>
{
    public UserDtoValidator2()
    {
        Include(new NameRules());
        RuleFor(x => x.Email).EmailAddress();
    }
}

Test Edilebilirlik: FluentValidation.TestHelper

Doğrulayıcıları hızlıca test etmek için yardımcı uzantıları kullanın.


dotnet add package FluentValidation.TestHelper

using FluentValidation.TestHelper;
using Xunit;

public class UserDtoValidatorTests
{
    [Fact]
    public void Name_Bos_Olamaz()
    {
        var v = new UserDtoValidator();
        var result = v.TestValidate(new UserDto { Name = "" });
        result.ShouldHaveValidationErrorFor(x => x.Name);
    }

    [Fact]
    public void Email_Gecerli_Olmali()
    {
        var v = new UserDtoValidator();
        var result = v.TestValidate(new UserDto { Email = "x" });
        result.ShouldHaveValidationErrorFor(x => x.Email);
    }
}

Gerçek Hayat Örneği: Kayıt Formu + Adres & Telefonlar

Aşağıdaki örnek; kullanıcı kaydı, iç içe adres doğrulaması ve telefon listesi doğrulamayı bir arada gösterir.


public class RegisterRequest
{
    public string? FullName { get; set; }
    public string? Email { get; set; }
    public AddressDto? Address { get; set; }
    public List<string> Phones { get; set; } = new();
}

public class RegisterValidator : AbstractValidator<RegisterRequest>
{
    public RegisterValidator(IEmailBlacklistService blacklist)
    {
        RuleFor(x => x.FullName)
            .NotEmpty().WithMessage("Ad Soyad zorunludur")
            .MaximumLength(80);

        RuleFor(x => x.Email)
            .NotEmpty().EmailAddress()
            .MustAsync(async (mail, ct) => !await blacklist.IsBlacklistedAsync(mail, ct))
            .WithMessage("E-posta kabul edilmiyor");

        RuleFor(x => x.Address)
            .NotNull().SetValidator(new AddressValidator());

        RuleForEach(x => x.Phones)
            .NotEmpty()
            .Matches(@"^\+?\d{7,15}$").WithMessage("Telefon formatı geçersiz");

        // Koşullu: Adres şehir 'TR' ise posta kodu 5 haneli olmalı
        When(x => x.Address?.Country == "TR", () =>
        {
            RuleFor(x => x.Address!.Zip)
                .Matches(@"^\d{5}$").WithMessage("TR için posta kodu 5 haneli olmalı");
        });
    }
}

Performans ve En İyi Uygulamalar


TL;DR

  • FluentValidation, kuralları modelden ayırarak okunabilir ve test edilebilir doğrulama sağlar.
  • RuleFor, RuleForEach, SetValidator temel yapı taşlarıdır.
  • When/Unless ile koşullu; RuleSet ile senaryo bazlı doğrulama yapın.
  • Must/MustAsync özel/harici servis kontrolleri için idealdir.
  • ASP.NET Core’da AddFluentValidationAutoValidation() ile otomatik entegrasyon sağlayın.
  • TestHelper ile doğrulayıcılarınızı birim testleriyle güvenceye alın.

İlişkili Makaleler