ASP.NET CORE + FluentValidation ile Form Verisi Doğrulama

Bu yazıda asp.net core web api projemizde fluentvalidation ile veri doğrulama işlemini gerçekleştireceğim, adım adım en basit haliyle anlatacağım ki siz de projenize entegre ederken sorun yaşamayın. Sadece validasyon kısmına odaklanacağım.

Annotation tabanlı validasyona göre çok daha esnek olan FluentValidation paketi .net dünyasında en çok kullanılan validasyon yöntemi.

En önemli faydalarından biri validasyon mantığını kurallar ile ayrı bir dosyada tanımlayabiliyorsunuz ve böylelikle kodunuz daha temiz ve yönetilebilir hale geliyor. İleride validasyon kuralı eklemek isterseniz ilgili dosyayı açarak ekleyebilirsiniz.

Hali hazırda bir .net 9 asp.net web api projesi başlattığınızı varsayacağım ve hemen paketlerimi ekleyerek başlayacağım.

FluentValidation paketini test etmek için ek olarak swagger’de kuracağım. Bildiğiniz gibi .net 9 ile birlikte default olarak entegre gelmiyor ve basit bir kaç adımla projemize dahil edebiliyoruz.

Öncelikle paketlerimizi yükleyerek başlayalım.

C#
dotnet add package FluentValidation.DependencyInjectionExtensions
dotnet add package Swashbuckle.AspNetCore

Ben CLI kullanacağım fakat siz isterseniz nuget paket yöneticisi ile de dahil edebilirsiniz aynı kapıya çıkar.

Paketlerimzi eklediğimize göre hemen DTO’ları tanımlayarak başlayalım.

Projemizde tek endpoint olacak kullanıcı kayıt endpointi ve nested(iç içe) DTO objelerini valide etmemiz gerekecek, RegisterDTO içerisinde AddressInfoDTO olacak.

📄 DTOs/RegisterDTO.cs

C#
namespace FluentValidationDemo.DTOs
{
    public class RegisterDTO
    {
        public string? FirstName { get; set; } = string.Empty;
        public string? LastName { get; set; } = string.Empty;
        public string? Email { get; set; } = string.Empty;
        public AddressInfoDTO? AddressInfo { get; set; }
    }
}

📄 DTOs/AddressInfoDTO.cs

C#
namespace FluentValidationDemo.DTOs
{
    public class AddressInfoDTO
    {
        public string? Phone { get; set; } = string.Empty;
        public string? Zip { get; set; } = string.Empty;
    }
}

Sırada validasyon sınıfımız var, projemiz içinde istediğmiiz yerde kullanabilmek için validasyonumuzu ayrı bir sınıf olarak kodluyoruz.

Öncelikle alt nesne olan AddressInfoValidator sınıfını kodluyoruz.

📄 Validators/AddressInfoValidator.cs

C#
using FluentValidation;
using FluentValidationDemo.DTOs;

namespace FluentValidationDemo.Validators
{
    public class AddressInfoValidator : AbstractValidator<AddressInfoDTO>
    {
        public AddressInfoValidator()
        {
            RuleFor(x => x.Phone)
                .NotEmpty().WithMessage("Telefon numarası zorunludur.")
                .Matches(@"^\+?\d{10,15}$").WithMessage("Telefon numarası formatı geçersiz.");

            RuleFor(x => x.Zip)
                .NotEmpty().WithMessage("Posta kodu zorunludur.")
                .Length(4, 10).WithMessage("Posta kodu 4 ile 10 karakter arasında olmalıdır.");
        }
    }
}

Sınıfımız AbstractValidator sınıfından türetilmiş olacak ve <AddressInfoDTO> valide ettiğimiz sınıfı belirtmiş olacağız.

Sırada ana validasyon sınıfımız var, aynı şekilde AbstractValidator<RegisterDTO> üzerinden türeterek kurallarımızı tanımlıyoruz.

📄 Validators/RegisterValidator.cs

C#
using FluentValidation;
using FluentValidationDemo.DTOs;

namespace FluentValidationDemo.Validators
{
    public class RegisterValidator : AbstractValidator<RegisterDTO>
    {
        public RegisterValidator()
        {
            RuleFor(x => x.FirstName)
                .NotEmpty().WithMessage("Ad alanı boş olamaz.")
                .MinimumLength(2).WithMessage("Ad en az 2 karakter olmalıdır.");

            RuleFor(x => x.LastName)
                .NotEmpty().WithMessage("Soyad alanı boş olamaz.");

            RuleFor(x => x.Email)
                .NotEmpty().WithMessage("Email zorunludur.")
                .EmailAddress().WithMessage("Email formatı geçersiz.");

            // Nested validator kullanımı:
            RuleFor(x => x.AddressInfo)
                .SetValidator(new AddressInfoValidator()!)
                .When(x => x.AddressInfo != null);
        }
    }
}

Controller’da kullanımı

controller’da kullanırken önce DI ile enjecte ettiğimiz sınıfı property olarak tanımlıyoruz.

C#
  private readonly IValidator<RegisterDTO> _validator;

  public RegisterController(IValidator<RegisterDTO> validator)
  {
      _validator = validator;
  }

Sonrasında bu sunufı controller içinde ilgili fonksiyonda manuel olarak kullanıyoruz.

C#
[HttpPost("signup")]
        public IActionResult Register([FromBody] RegisterDTO dto)
        {
            ValidationResult result = _validator.Validate(dto);

            if (!result.IsValid)
            {
                var errors = result.Errors
                    .Select(e => new { Field = e.PropertyName, Message = e.ErrorMessage })
                    .ToList();

                return BadRequest(new
                {
                    Success = false,
                    Errors = errors
                });
            }

            return Ok(new
            {
                Success = true,
                Message = "Kayıt başarılı.",
                Data = dto
            });
        }
    }

Controller’in son hali:

📄 Controllers/RegisterController.cs

C#
using FluentValidation;
using FluentValidation.Results;
using FluentValidationDemo.DTOs;
using Microsoft.AspNetCore.Mvc;

namespace FluentValidationDemo.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class RegisterController : ControllerBase
    {
        private readonly IValidator<RegisterDTO> _validator;

        public RegisterController(IValidator<RegisterDTO> validator)
        {
            _validator = validator;
        }

        [HttpPost("signup")]
        public IActionResult Register([FromBody] RegisterDTO dto)
        {
            ValidationResult result = _validator.Validate(dto);

            if (!result.IsValid)
            {
                var errors = result.Errors
                    .Select(e => new { Field = e.PropertyName, Message = e.ErrorMessage })
                    .ToList();

                return BadRequest(new
                {
                    Success = false,
                    Errors = errors
                });
            }

            return Ok(new
            {
                Success = true,
                Message = "Kayıt başarılı.",
                Data = dto
            });
        }
    }
}

Program.cs dosyamızda aşağıdaki satır ile tanımladığımız validasyon sınıfını DI ine enjecte ediyoruz ve tabi opsiyonel swagger ayarları var ben test etmek için swagger’da kurdum siz kurmak zorunda değilsiniz.

C#
// ✅ FluentValidation ayarları 
builder.Services.AddValidatorsFromAssemblyContaining<RegisterValidator>();

Program.cs son hali bu şekilde:

📄 Program.cs

C#

using FluentValidation;  
using ValidationProjecr.Validators; 

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();

builder.Services.AddOpenApi();
// Swagger.NET 9’da manuel açılmalı:
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// ✅ FluentValidation ayarları 
builder.Services.AddValidatorsFromAssemblyContaining<RegisterValidator>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
    app.UseSwagger();
    app.UseSwaggerUI(options =>
    {
        options.SwaggerEndpoint("/swagger/v1/swagger.json", "ECommerce API V1");
        options.RoutePrefix = string.Empty;
    });
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Bu videoda DI ile FluentValidation injecte ettik ve ilgili endpoint içeri,sinde çağıarark hataları kullanıcıya gösterdik.

Bir sonraki yazıda görüşmek üzere.

Örnek test sonucu ve api response

Form verisini doğruladık herşey çok güzel ama bitmedi, dto oluşmadan önce tip uyumsuzlukları varsa eğer DTO hiç oluşmaz ve biz validasyonu hiç gerçekleştiremeyiz. Bu nedenle DTO oluşmadan önce bir global validasyon middleware’e ihtiyacımız var, bize ulaşmdan önce valide edecek nesnelerimizi ve biz her adımda tek tek uğraşmayacağız.

Yorumlar

“ASP.NET CORE + FluentValidation ile Form Verisi Doğrulama” için bir yanıt

FluentValidation Global Validation Middleware ile Model Binding Hataları Yakalamak – Gökhan Çelebi | Web Yazılım için bir yanıt yazın Yanıtı iptal et

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir