> ## Documentation Index
> Fetch the complete documentation index at: https://docs.diyanet.pro/llms.txt
> Use this file to discover all available pages before exploring further.

# Validation & Mapping

> FluentValidation custom kuralları, AutoMapper IMapFrom convention ve pagination modeli

## 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](/application/pipeline-behaviors)).

### Kayıt

`DependencyInjection.cs` tüm Application assembly'sini tarar:

```csharp theme={null}
builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
```

### AbstractValidator örneği

`SignUpUserCommandValidator` — `Cascade(CascadeMode.Stop)` ile boş telefon kontrolünden sonra format kontrolüne geçilir:

```csharp theme={null}
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.

<Tabs>
  <Tab title="MustBeValidMobilePhone (extension)">
    `MobilePhoneValidatorExtensions.cs` — en sık kullanılan kural. `NotEmpty` + `MobilePhoneUtility` doğrulamasını tek çağrıda paketler:

    ```csharp theme={null}
    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.");
    }
    ```
  </Tab>

  <Tab title="MobilePhoneValidator">
    `MobilePhoneValidator.cs` — `PropertyValidator<T, TProperty>` türeten daha düşük seviyeli sürüm. `.SetValidator(new MobilePhoneValidator<...>())` ile kullanılır:

    ```csharp theme={null}
    public class MobilePhoneValidator<T, TProperty> : PropertyValidator<T, TProperty>
    {
        public override string Name => "MobilePhoneValidator";

        public override bool IsValid(ValidationContext<T> context, TProperty value)
            => MobilePhoneUtility.Instance.IsValidNumber(value?.ToString());

        protected override string GetDefaultMessageTemplate(string errorCode)
            => "Telefon numarası doğru formatta değil! {PropertyValue}";
    }
    ```
  </Tab>

  <Tab title="TCKimlikNoValidator">
    `TCKimlikNoValidator.cs` — 11 hane + resmi T.C. kimlik checksum algoritmasını uygular:

    ```csharp theme={null}
    public class TCKimlikNoValidator<T, TProperty> : PropertyValidator<T, TProperty>
    {
        public override string Name => "TCKimlikNoValidator";

        public override bool IsValid(ValidationContext<T> context, TProperty value)
        {
            string tc = value?.ToString();
            if (tc == null || tc.Length != 11) return false;

            // ... C1..C9 hanelerinden Q1, Q2 checksum'ları hesaplanır ...
            Q1 = ((10 - ((((C1 + C3 + C5 + C7 + C9) * 3) + (C2 + C4 + C6 + C8)) % 10)) % 10);
            Q2 = ((10 - (((((C2 + C4 + C6 + C8) + Q1) * 3) + (C1 + C3 + C5 + C7 + C9)) % 10)) % 10);

            return ((BTCNO * 100) + (Q1 * 10) + Q2 == TcNo);
        }

        protected override string GetDefaultMessageTemplate(string errorCode)
            => "T.C. Kimlik numarası {PropertyValue} doğru formatta değil";
    }
    ```
  </Tab>
</Tabs>

`SeedWork/Validations/` altındaki diğer validator'lar: `YabanciTCKimlikNoValidator`, `HESValidator`, `BirthdateValidator`, `ImageValidator`, `SmsCodeValidator`, `GuidValidator`, `RenkValidator`, `BasvuruBelgeValidator`.

<Note>
  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.
</Note>

## AutoMapper

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

### Kayıt

```csharp theme={null}
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:

```csharp theme={null}
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:

```csharp theme={null}
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:

```csharp theme={null}
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));
    }
}
```

<Tip>
  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)`.
</Tip>

## Pagination

Sayfalı yanıtlar `IPaged<T>` / `Paged<T>` ile döner.

```csharp theme={null}
public interface IPaged<T>
{
    IEnumerable<T> Data { get; }
    double TotalItems { get; }
    int Page { get; }
    int PageSize { get; }
}
```

Handler içinde `CountAsync` + `ListAsync` ile doldurulur:

```csharp theme={null}
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);
```

<Warning>
  `Page` **0-tabanlıdır**. Backend specification ve `PaginationHelper` 0-based indeks bekler; frontend `useState(0)` ile uyumludur. `page=1` ilk sayfayı sessizce atlar.
</Warning>

## Sonraki adımlar

<CardGroup cols={2}>
  <Card title="Command / Query deseni" icon="code" href="/application/commands-queries">
    Validator ve mapping'lerin handler içinde kullanımı.
  </Card>

  <Card title="Pipeline Behaviors" icon="layer-group" href="/application/pipeline-behaviors">
    ValidationPipelineBehavior ve 422 yanıt üretimi.
  </Card>

  <Card title="Value Objects" icon="gem" href="/domain/value-objects">
    Phone, Email, TCKimlikNo invariant'larının domain tarafı.
  </Card>

  <Card title="Caching" icon="database" href="/caching/overview">
    IReadRepository ve specification tabanlı HybridCache.
  </Card>
</CardGroup>
