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).
SignUpUserCommandValidator — Cascade(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(); }}
SeedWork/Validations/ altında, Türkiye’ye özel domain kuralları için yeniden kullanılabilir validator’lar bulunur.
MustBeValidMobilePhone (extension)
MobilePhoneValidator
TCKimlikNoValidator
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.");}
MobilePhoneValidator.cs — PropertyValidator<T, TProperty> türeten daha düşük seviyeli sürüm. .SetValidator(new MobilePhoneValidator<...>()) ile kullanılır:
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}";}
TCKimlikNoValidator.cs — 11 hane + resmi T.C. kimlik checksum algoritmasını uygular:
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.
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 }); } }}
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).