.Net 6.0 Üzerinde Validation Factory Yaratmak Part 2

Selamlar,

Bugün bir önceki makalede kaldığımız yerden, yani bir sınıfın propertylerini işaretleme amaçlı kullanılan attributelerin tanımlamasından devam edeceğiz. Hadi gelin tüm işaretleyicileri sıra ile tanımlayalım.

Attributes/DateData:  Aşağıda tanımlanan DateData attribute’ü, tanımlandığı property’deki tarih alanını kontrol eder. Tek property’si, yıl değerinin kontrol edildiği “minYear”‘dır. Girilen tarih alanının, bundan büyük olması beklenir.

Attributes/EmailData: Mail alanlarının, EmailValidateType enum propertysi ile, farklı bussineslar için kontrol edilmesini sağlayan bir işaretleyicidir.

Attributes/EncryptData : İşaretlendiği property alanının, çift yönlü şifrelenmesini sağlayan bir işaretleyicidir.

Attributes/HashData: İşaretlendiği property alanının tek yönlü, yani geri dönülemez olarak şifrelenmesini sağlayan, bir işaretleyicidir.

Attribute/StringData: İşaretlendiği property alanının, 3 farklı bussines için kontrol edilmesini sağlayan bir işaretleyicidir. Diğer Attributelardan farklı olarak 2 property’si vardır.

Validators/ValidatorFactory: Amaç, sınıf üzerindeki herbir property Attribute’una göre, uygun validator’ın çekilmesidir.  Singleton Design Pattern’ine göre, tüm validatorlar bir seferlik yaratılıp, static bir IValidator Dictionary içine konulurlar. Daha sonra, istenen validatorın model üzerindeki Attribute ismi, Reflection ile çekilip ilgili static dictionary içinden alınır.

Not: Neden Reflection ile Type’dan ilgili Record Oluşturulmamıştır ? Çünkü Recordlar Generic tipde IValidator<T> tipinde bir”Validator”‘dan oluşmaktadır. Normalde Classlar Reflection ile “Type”‘dan generate edilebilirler ama her record’un oluşum tipinin aynı olmamasından dolayı bu yapılamamaktadır. Bu nedenle Record bazında farklılaşmaya gidilmiş ve ortak “Validate()” methodunun çağrılması, yeterli olmamıştır.

Validators/ValidateClassProperties: Burada amaç, sınıfa ait tüm propertylerin gezilerek, varsa tanımlı attributeların yakalanıp ilgili  validatorların property değerleri üzerinde çalıştırılması ve yakalanan hataların topluca liste halinde geri dönülmesidir.

1-) Geriye dönülecek toplu ErrorList ve aynı property’e birden fazla attribute tanımlanması durumunda, birinde hata çıkması durumunda diğerlerine devam edilmemesi amaçlı “isError” değişkeni aşağıdaki gibi tanımlanmıştır. “isError==true” durumunda “break;” ile ilgili döngüden çıkılacaktır.

2-) Aşağıda görüldüğü gibi sınıfı ait tüm PropertyInfolar, Reflection kullanılarak ilgili Record üzerinden çekilmiş, ve daha sonrasında herbiri için tek tek gezilmiştir.

3-) Aşağıda görüldüğü gibi herbir property üzerinde var olan attributelar bir döngü içinde gezilmiş, “type” değişkenine herbir attribute’ün Type’ı, reflection ile alınmış ve ilgili attribute’a ait validator, yukarıda tanımladığımız GetValidator() methodu ile, “validator” değişkenine atanmıştır.

  • Örneğin User’a ait “Email” propertysine, aşağıda görüldüğü gibi 2 Attribute [EmailData ve EncryptData] tanımlanmıştır.

4-) Aşağıda görüldüğü gibi “propValue” değişkenine, ilgili property değeri atanmıştır. Bu örnekte Attributelere ait property alanı, ayrıca bir döngü içine sokulmamış ve en fazla 2 tane olması planlanıp “attributeValue” ve “attributeValue2” değişkenleri bu değerler için oluşturulmuştur. Aşağıda bakılan “if” koşulu ile, ilgili attribute’un bir propertysinin olup olup olmadığına bakılmıştır.

  • Örneğin “UserName” property’sinin => “StringData” attribute’ünün => “max, min” adında 2 propertysi bulunmaktadır. İlgili Validation, bu değerlere göre yapılmaktadır.

5-) Aşağıda görüldüğü gibi, Attributelare ait property değerleri varsa çekilmiş ve “attributeValue, attributeValue2” değişkenlerine atanmıştır.

6-) Aşağıda görüldüğü gibi çekilen Validator’a göre, herbir property değeri kontrol edilmekte ve bir hata var ise birden fazla attribute olması durumunda devam edilmemekte ve “break” komutu ile bir sonraki property’e geçilmemektedir. Ayrıca bulunan hatalar da, “errorListe”‘e doldurulmaktadır. “Validate()” methodu => “IValidate” interface’inden türetilmektedir. Tüm validatorlar da, IValidator’dan türetildiği için, Strategy Design Pattern’ine uyularak, ortak Validate methodu her record’dan çağrılabilmektedir.

7-) “return errorList” : Son olarak, tüm propertyler ilgili validatorlar ile kontrol edildikten sonra, bulunan hatalar topluca geri dönülmüştür.

Validators/ValidateClassProperties.cs:

Decrypt İşlemleri:

Encrypt edilmiş, yani çift yönlü şifrelenmiş datanın okunması durumunda, Decrypt işleminin yapılması gerekmektedir. Minimum kod, maximum iş prensibi ile propertylerden kaçınmak için, Encoding işlemi aşağıdaki gibi güncellenmiştir.

Validators/EncryptValidator.cs: Aşağıda görüldüğü gibi Decrypt işlemi için ayrıca bir property kullanılmamış ve, property alanın’ın Encrypt olup olmadığına bakılmıştır.

İlgili property’nin Encrypted olduğunun anlaşılabilmesi için, başına custom => “encß” değeri konulmuştur.

  • Yani “IsEncryted()” methodu ile, ilgili property value’su kontrol edilmiş, Encrypted yani şifreli ise, “DecryptText()” yok eğere değil ise, “EncryptText()” methodu çağrılmıştır.
  • EncryptValidator recordu ve diğer tüm Validatorlar artık sadece IValidator<T> interfaceinden değil, “Validator” record’undan da türetilmiştir. Böylece tüm validator Recordlar, “IsEncryted()” ve ayrıca “IsHashed()” methodlarını da kullanabilmektedirler.

Validators/HashtValidator.cs: Aşağıda görüldüğü gibi, ilgili text alanın Hashed olup olmadığına, başına sonradan konan “hasß“, karakteri ile bakılmaktadır.

EKLENEN : “if (!IsEncryted(stringValue) && !IsHashed(stringValue))“=> Eğer text içerik, Encrypt ya da Hashed değil ise, bu durumda value Hashli hale getirilmektedir.

Validators/Validator.cs: Aşağıda görüldüğü gibi istenir ise, gelen text value’nun 2 yönlü Encrypted, ya da tek yönlü Hased olup olmadığı cevabı “IsEncryted(), IsHashed()” methodları ile bulunmaktadır.

Not: Aşağıda, text’in hashli olup olmadığının anlaşılması için, HashValidator’da güncellenmiş ve geriye dönen tek yönlü şifreli text’in başına, “hasß” string değeri konulmuştur.

Tüm validatorlara, aşağıda görülen kontrol eklenmiştir. Amaç, şifreli Encrypted ya da Hashed içeriklerin, kontrol edilmemesidir. Örnek kod parçası DateValidator için, aşağıdaki gibi gösterilmektedir. Diğer tüm validatorlarda da yapılan değişiklik, aşağıda eklenen kontrolden başka birşey değildir.

Validates/DateValidator:

Controllers/HomeController/Index[GET]: Aşağıda örnek amaçlı, bir servisden dönmesi gereken User kaydı, dummy  olarak elle girilmiştir. DBtarafında encrypted olarak tutulan Email, Gsm ve hashed olarak tutulan Password alanı, “GetValidateResult()” methodundan geçirilerek, Encrypte alanların Decrypt olması sağlanmıştır. Ayrıca Encrypted olmayan alanların, attribute atanmış olan propertylerinin kontrolü sağlanmıştır.

Not: Dikkat edilir ise Encrypt alanlar, “encß” karakteri ile hashed alanlarda  “hasß” karakteri ile başlamaktadır.

Models/ResultModel: Client’a dönülen ViewModel, aşağıdaki gibidir. Geriye, üzerinde işlem yapılan User model ve validate işleminden sonra alınan tüm error Listesi dönülmektedir.

Aşağıda görüldüğü gibi UserName ve BirthDate alanlarında bulunan hatalar listelenirken, encrypted Email ve Gsm Decrypt edilerek ekrana basılmıştır. Password, zaten geri dönülemez Hashli olduğu için, üzerinde işlem yapılmadan olduğu gibi ekrana basılmıştır. Email hatalı olmasına rağmen, başta Encrypted bir alan olmasından dolayı, ayrıca validation’a sokulmamıştır.

Not: Get işlemi sırasında istenir ise Validation yapılmayıp, sadece Encrypt ya da Hashleme de yapılabilir. Bir sonraki adımda, bu konu da detaylıca anlatılacaktır.

İsteğe bağlı olarak Get İşleminde Validation Yapılmaması:

Hatırlarsanız tüm Validatorları => Validator record’undan türettik. Buraya, “IsGetMethod()” şeklinde bir method eklenip, Request tipine bakılabilir. Ve tüm “IsEncryted()” şeklinde kontrollerin yapıldığı yere “IsGetMethod()” kontrolü de eklenip, Request tipi == “GET” ise Validate işleminin yapılmaması sağlanabilir. Hadi gelin isterseniz, kodlara bakalım..

Öncelikle .Net Core 6.0 projesinde program.cs altına, aşağıdaki kod eklenir. Amaç, Validator sınıfı içinden, HttpContex’e erişimin izin verilmesidir.

        1-) program.cs:

        2-) Validator.cs: Aşağıda görüldüğü gibi, _httpContext değişekenine, Current HttpContext setlenmiştir. Daha sonra “IsGetMethod()” eklenerek, gelen request methodunun, “GET”‘mi yoksa “POST”‘mu olduğu kontrol edilmiştir.

        3-) StringValidator, DateValidator, EmailValidator:  “GET” işleminde istenir ise, validation yapan tüm validatorlara, “!IsGetMethod()” kontrolü eklenerek, hata yakalama işlemi iptal edilebilir.

Views/Home/Index.cshtml: Aşağıdaki View sayfası, model olarak ResultModel beklemektedir. Gönderilen ErrorList bir döngü şeklinde yukarıda görüldüğü gibi ekrana basılırken, geriye dönülen User model, hemen sayfanın aşağısında decrypt işlemleri yapıldıktan sonra basılmaktadır. Form’un Post olması durumunda => “Home/Index Post” sayfasına yönlendirme işlemi yapılmıştır.

Controllers/HomeController/Index[POST]: DB’ye kaydedilmek üzere, Client tarafından girilmesi gereken User data, örnek amaçlı dummy şekilde manuel olarak aşağıdaki gibi doldurulmuştur. Ve model üzerindeki tüm validationlar, güvenlik için yapılan Encryption ve Hash işlemler, aşağıdaki tek bir komut satırı ile yapılmaktadır.

Çalıştırılan tek satır => errorList = ValidateClassProperties.GetValidateResult(user)

  • UserName => “[StringData(max = 10,min =3)]” StringValidator’ı kullanılmış ve aşağıda görüldüğü gibi 2 tane hata bulunmuştur. En az 1 harf büyük olmalı ve içinde “@” işareti olmamalıdır.
  • Birthdate => “[DateData(minYear =1900)]” DateValidator kullanılmıştır. Aşağıda görüldüğü gibi, doğum yılı 1900’den küçük olmaz şeklinde tek hata bulunmuştur.
    • Email =>”[EmailData(type = EmailValidateType.Syntax)], [EncryptData]” Email ve Encrypt Validatorları kullanılmıştır. Önce üstteki sonra alttaki validator, sıra ile çalıştırılmıştır. Öncelikle geçerli bir mail mi diye bakılmış ve olmadığı görülmüştür. Bu nedenle 2. Validator’a geçilmemiş, ve geçerli olmayan email, Encrypt edilmemiştir.
    • Not:Attributelar her zaman, yukarıdan aşağı sıra ile çalıştırılırlar.”
  • Gsm([EncryptData]) alanı güvenlik amaçlı Encrypt edilmiş, Password([HashData]) alanı da Hashlenmiştir.

Geldik bir makalenin daha sonuna. Bu iki makalede, girilen datanın herbir propertysi, tek tek gezilerek, eğer herhangi bir attribute ile işaretlenmiş ise, kendisine özgü bir validator’ın kontrolünden geçirilip hatalı durumlar bir liste altında toplanmıştır. Daha sonra raporlanırken, güvenlik amacı ile şifrelenmesi gereken yerler, Encrypted ya da Hashed hale getirilmiştir. İstendiği takdirde, sadece “POST” methodunda validation yapılırken, “GET” methodunda, sadece Encrypted alanların Decryption’ı sağlanabilmektedir.

İlgili sınıfın => propertylerinin, propertylerin => attributelerinin ve en son attributelerin de=> parametrelerinin okunması işlemi, reflection ile yapılmaktadır. Başta kod okunaklığının bozulduğu düşünülse de, kod geliştirme esnasında ilgili kontrollerin tek bir satır ile yapılabilmesi, kesinlikle göz ardı edilmemesi gereken bir yöntemdir. İlgili attribute’a göre validator üretme işi, Singleton olarak static bir Dictionary içinden alınarak yapılsa da, Generic tipde olmayan bir Validator Record ile çalışılsa idi, ilgili recordun Type’ından reflection ile yeni bir validator oluşturulabilirdi. Bu da, yeni bir validator geldiği zaman, ilgili static listenin içine konmasına gerek kalmadan kod geliştimeyi sağlardı. Daha sonrasında, tüm validatorların aynı Interfaceden türetilmesi ve tipe bakılmaksızın “GetValidator()” methodunun çağrılması, Strategy Design Pattern kullanılarak yapılmaktadır.

Biri işi yapmanın birçok yöntemi olsa da, her zaman daha iyisi, daha okunaklısı ve daha temizini arama yolundan hiç vazgeçmeyin. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.

Source Code: https://github.com/borakasmer/ValidationFactory

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

2 Cevaplar

  1. Furkan dedi ki:

    Bora abi umarım örneklerde kendi numaranı paylaşmamışsındır paylaştıysan da bu yorum ile umarım kaldırırsın.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.