Skip to main content

FluentValidation

Validator’lar AbstractValidator<TRequest> türünden yazılır ve assembly taramasıyla otomatik kaydedilir. ValidationPipelineBehavior bunları handler’dan önce çalıştırır (bkz. Pipeline Behaviors).

Kayıt

DependencyInjection.cs tüm Application assembly’sini tarar:
builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());

AbstractValidator örneği

SignUpUserCommandValidatorCascade(CascadeMode.Stop) ile boş telefon kontrolünden sonra format kontrolüne geçilir:
public class SignUpUserCommandValidator : AbstractValidator<SignUpUserCommand>
{
    public SignUpUserCommandValidator()
    {
        RuleFor(x => x.FullName)
            .NotEmpty().WithMessage("Ad soyad boş olamaz")
            .MaximumLength(100).WithMessage("Ad soyad en fazla 100 karakter olabilir");

        RuleFor(x => x.Phone)
            .Cascade(CascadeMode.Stop)
            .NotEmpty().WithMessage("Telefon numarası gerekli")
            .MustBeValidMobilePhone();
    }
}

Custom validator’lar

SeedWork/Validations/ altında, Türkiye’ye özel domain kuralları için yeniden kullanılabilir validator’lar bulunur.
MobilePhoneValidatorExtensions.cs — en sık kullanılan kural. NotEmpty + MobilePhoneUtility doğrulamasını tek çağrıda paketler:
public static IRuleBuilderOptions<T, string> MustBeValidMobilePhone<T>(
    this IRuleBuilder<T, string> ruleBuilder)
{
    return ruleBuilder
        .NotEmpty()
        .Must(phone => MobilePhoneUtility.Instance.IsValidNumber(phone))
        .WithMessage("Lütfen Türkiye'de kullanılan geçerli bir mobil telefon numarası giriniz.");
}
SeedWork/Validations/ altındaki diğer validator’lar: YabanciTCKimlikNoValidator, HESValidator, BirthdateValidator, ImageValidator, SmsCodeValidator, GuidValidator, RenkValidator, BasvuruBelgeValidator.
Format/checksum gibi tekrar eden kurallar value object seviyesinde de (Domain) doğrulanır — örn. Phone, TCKimlikNo value object’leri. Validator katmanı, request’i daha controller seviyesinde reddedip aggregate’i hiç kurmadan hızlı geri bildirim verir; value object ise invariant’ın son savunma hattıdır.

AutoMapper

Mapping’ler IMapFrom<T> convention’ı ile tanımlanır ve reflection ile otomatik toplanır.

Kayıt

builder.Services.AddAutoMapper(cfg => cfg.AddMaps(Assembly.GetExecutingAssembly()));

IMapFrom convention

SeedWork/Mappings/IMapFrom.cs — default interface metodu basit CreateMap üretir; özel kural gerekirse DTO Mapping(Profile) metodunu override eder:
public interface IMapFrom<T>
{
    void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType());
}

MappingProfile reflection taraması

MappingProfile.cs assembly’deki IMapFrom<> uygulayan tüm tipleri bulur ve Mapping metodlarını çağırır:
public class MappingProfile : Profile
{
    public MappingProfile() => ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());

    private void ApplyMappingsFromAssembly(Assembly assembly)
    {
        var types = assembly.GetExportedTypes()
            .Where(t => t.GetInterfaces().Any(i =>
                i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
            .ToList();

        foreach (var type in types)
        {
            var instance   = Activator.CreateInstance(type);
            var methodInfo = type.GetMethod("Mapping");
            methodInfo?.Invoke(instance, new object[] { this });
        }
    }
}

Örnek — UserDto mapping

VerifyOtp/UserDto.cs — value object’leri (Phone, Email) düz string’e, Status enumeration’ını .Name’e map eder:
public class UserDto : IMapFrom<User>
{
    public Guid Id { get; set; }
    public string FullName { get; set; }
    public string Phone { get; set; }
    public string Email { get; set; }
    public bool IsPhoneNumberVerified { get; set; }
    public bool IsEmailVerified { get; set; }
    public string Status { get; set; }

    public void Mapping(Profile profile)
    {
        profile.CreateMap<User, UserDto>()
            .ForMember(d => d.Phone,  o => o.MapFrom(s => s.Phone != null ? s.Phone.Value : null))
            .ForMember(d => d.Email,  o => o.MapFrom(s => s.Email != null ? s.Email.Value : null))
            .ForMember(d => d.Status, o => o.MapFrom(s => s.Status.Name));
    }
}
Liste DTO’larında ağır alanları (örn. bytea görsel) SELECT etmeyin. AdminBagisBasvuruPlanDto görseli yüklemek yerine sadece HasImage flag’ini map eder: o.MapFrom(s => s.Image != null).

Pagination

Sayfalı yanıtlar IPaged<T> / Paged<T> ile döner.
public interface IPaged<T>
{
    IEnumerable<T> Data { get; }
    double TotalItems { get; }
    int Page { get; }
    int PageSize { get; }
}
Handler içinde CountAsync + ListAsync ile doldurulur:
var total = await _repository.CountAsync(spec, cancellationToken);
var items = await _repository.ListAsync(spec, cancellationToken);
var data  = _mapper.Map<List<AdminBagisBasvuruPlanDto>>(items);
var paged = new Paged<AdminBagisBasvuruPlanDto>(filter.Page, filter.PageSize, total, data);
Page 0-tabanlıdır. Backend specification ve PaginationHelper 0-based indeks bekler; frontend useState(0) ile uyumludur. page=1 ilk sayfayı sessizce atlar.

Sonraki adımlar

Command / Query deseni

Validator ve mapping’lerin handler içinde kullanımı.

Pipeline Behaviors

ValidationPipelineBehavior ve 422 yanıt üretimi.

Value Objects

Phone, Email, TCKimlikNo invariant’larının domain tarafı.

Caching

IReadRepository ve specification tabanlı HybridCache.