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
WithMessage()ile özel hata metni verin.WithSeverity(Severity.Error|Warning|Info)ile öncelik belirtin.Cascade(CascadeMode.Stop)ile ilk hata sonrası kural zincirini durdurun.
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
WithName("Alan Adı")ile alan adını ekranda dostça gösterin.- Kaynak dosyaları (resx) ile çok dilliliği sağlayın.
- Global mesajlar için LanguageManager özelleştirilebilir.
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
- Validator’ınızın ctor’ına servis/repository enjekte ederek MustAsync içinde kullanabilirsiniz.
- Tekrarlanan kuralları
Includeile paylaşın:
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
- Validator’ları stateless yazın; ctor’da yalnızca bağımlılıkları alın.
- Yoğun trafikte MustAsync içinde IO yapan kuralları önbellekleme ile azaltın.
- Çok sayıda kural içeren modellerde
RuleSetile senaryo bazlı doğrulama çalıştırın. - Hata mesajlarını kullanıcı dostu ve kısa tutun; alan adlarını
WithNameile özelleştirin. - Sunucu tarafında doğrulamanın yanı sıra (gerekirse) istemci tarafı doğrulamayı da düşünün.
TL;DR
- FluentValidation, kuralları modelden ayırarak okunabilir ve test edilebilir doğrulama sağlar.
RuleFor,RuleForEach,SetValidatortemel 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
C# Dependency Injection Temelleri
C#’ta Dependency Injection kavramını öğrenin. Bağımlılıkların yönetimi, gevşek bağlılık ve test edilebilirlik örneklerle anlatılıyor.
C# Sınıf (Class), Object, Property ve Metotlar
C#’ta class, object, property ve metot kavramlarını öğrenin. Nesne yönelimli programlamanın temel yapı taşları örneklerle açıklanıyor.