ASP.NET Core üzerinde Form Helper ve Fluent Validation Kullanımı

Sinan BOZKUŞ
BilgeAdam Teknoloji
11 min readMay 9, 2020

ASP.NET Core üzerinde Form ve Validation işlemlerini kolaylaştırmak için Form Helper ve Fluent Validation kütüphanelerini inceleyeceğiz.

Neden Validation’a ihtiyaç duyuyoruz?

Hazırladığımız uygulamaların güvenliğinin ve veri doğruluğunun sağlanması için validation’a (doğrulama) ihtiyaç duyarız. Kullanıcılardan aldığımız doğrulanmamış veya eksik bir veri sistemin aksamasına sebep olabileceği gibi güvenlik zaafiyetlerine de sebep olabilir. Uygulamalarımızın bir çoğunun internete açık şekilde yayın yaptığını ve erişebilecek kitle içerisinde kötü niyetli kişilerin de olabileceğini hesaba katarsak uygulamalarımız için validation olmazsa olmazımızdır.

Yazı boyunca istemci tarafı (kullanıcı tarayıcısı) “client-side”, sunucu tarafı ise “server-side” olarak adlandırılacaktır. İş kuralları ve verilerin doğruluğunun sağlanması içinse “validation” ifadesi kullanılacaktır.

ASP.NET Core ile yazılan MVC tabanlı uygulamalarda genellikle web tarafındaki validation işlemleri için jQuery Validation veya alternatif client-side çalışan Javascript kütüphanelerini tercih ediyoruz. Bu kütüphaneler ile bu sorunun önüne kısmen geçsek de bu doğrulamalar Javascript ile yapıldığından tam güvenlik sağlayamazlar. (Mobil uygulamalar içinde durum farklı değildir.) Uygulamalarımıza direkt olarak dışarıdan (get-post vb.) talep gelebileceğinden veya internet tarayıcısı üzerinde kolaylıkla Javascript kapatılabileceğinden client-side olarak yapılan hiç bir doğrulama yöntemine güvenemeyiz.

Bu yazımda ASP.NET Core üzerinde Fluent Validation kullanımını inceleyeceğiz. Aynı zamanda hazırlayacağımız html formumuzdan alacağımız verileri sunucuya göndermek ve FluentValidation’a client-side desteği kazandırabilmek için de Form Helper kütüphanesinden faydalanacağız.

Örnek içerisinde kullanacağımız kütüphaneleri kısaca inceleyelim.

Fluent Validation

Neden Fluent Validation?

Server-side doğrulama işlemini ASP.NET Core ile birlikte gelen (built-in) “Data Annotations” lar veya kendi yazacağımız custom attributeler ile de yapabiliriz. Bununla birlikte GitHub üzerinde bulunan farklı validation kütüphanelerini de tercih edebiliriz. Ancak Fluent Validation, basit fluent yazım tarzı, merkezi validation yönetimi ve kompleks validationlar için sunduğu kolaylıkları nedeniyle en çok tercih edilen .NET validation kütüphanesi olarak öne çıkıyor. Nuget üzerinde 36 Milyonun üzerinde indirme sayısına ve GitHub üzerinde ise 5000+ yıldıza sahip. Fluent Validation ile server-side validationlar yapılabildiği gibi kısmi olarak (required, maxlength vb.) client-side validationlar da yapılabilmektedir. Client-side desteği için jQuery Validation ve jQuery Validation Unobtrusive kütüphanelerine ihtiyaç duymaktadır.

https://github.com/JeremySkinner/FluentValidation

Fluent Validation kütüphanesi Data Annotations alternatifi olmayıp birlikte uyum içerisinde çalışmaktadır. Örneğin propertyler için verilen DisplayName işlemi yine Data Annotations üzerinden yapılmaktadır. Tercih edilmemesi gereken bir durum olsa da validation işlemlerinin bir bölümü fluent validation ile diğer bir bölümü ise fluent validation ile de yapılabilir.

Form Helper

Neden Form Helper?

Form Helper, ASP.NET Core üzerinde javascript kodu yazmamıza gerek kalmadan ajax destekli formlar oluşturmamızı sağlayan bir kütüphane. Yine benzer şekilde arka planda istek yaparak server-side olarak yazılan tüm validationları javascript kodu yazmadan client-side’a taşıyabiliyor. İçerisinde gelen hazır methodlar ile Controller’lardan dönülen yanıt ile ekranda notification gösterme, form kaydedildiğinde başka bir url’e redirect (yönlendirme), birden fazla gönderime karşı submit butonunu pasife alma, json tipinde gönderim gibi bir çok özelliğe sahip. Kısacası ASP.NET Core MVC projelerimizde yapacağımız form bazlı işlemler için inanılmaz bir kolaylık sunuyor.

Örneğimizde Fluent Validation ile oluşturulan server-side kompleks validationları, Form Helper ile birlikte basit bir şekilde client-side’a taşıyacağız.

https://github.com/SinanBozkus/FormHelper

Yerli ve açık kaynak bir kütüphane olan FormHelper‘ı GitHub üzerinde destekleyebilir ve geliştirmesine katkıda bulunabilirsiniz.

Fluent Validation kütüphanesi server-side yani backend tarafta validation işlemlerini yapmaktadır. FluentValidation.AspNetCore kütüphanesi ASP.NET Core’a destek verse de required, maxlength gibi basit validationlar dışında tüm validationları server-side yapmaktadır. Varsayılan kullanımda bunların client-side’a yansıtılması için tüm sayfanın post olması ve ardından ilgili sayfaya redirect yapılarak tekrar üretilmesi gerekmektedir.

Örnek senaryomuz sisteme yeni bir öğrenci kayıt edilmesi ve devamında güncellenmesi şeklinde olacaktır. Aynı model için kayıt ve güncelleme esnasında farklı doğrulama kuralları çalıştırarak ve kompleks validationlar yaparak Fluent Validation’ın kullanışlı özelliklerinden de faydalanacağız. Fluent Validation tarafında oluşan validationları Form Helper kütüphanesi yardımıyla client-side’a aktaracağız. Kayıt işlemi sonrasında gösterilecek bildirim ve yönlendirme işlemleri için yine Form Helper ile birlikte gelen hazır methodlardan faydalanacağız.

Yeni bir ASP.NET Core projesi (MVC Template) oluşturarak Nuget Package Manager üzerinden Fluent Validation ve Form Helper kütüphanelerini kuralım.

Örnek projede ASP.NET Core 3.1 sürümü kullanılmıştır.

Startup.cs içerisine gelip FormHelper ve FluentValidation’ın çalışması için gerekli olan servisleri servisleri kayıt edelim. Configure Services methodunun içerisini düzenliyoruz.

Gerekli Namespaceler: (Uygulamamızın ilerleyen bölümlerinde de bu namespacelere ihtiyaç duyulacaktır.)

using FormHelper;
using FluentValidation.AspNetCore;

ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
.AddFluentValidation();

services.AddFormHelper(new FormHelperConfiguration
{
CheckTheFormFieldsMessage = "Form alanlarını kontrol ediniz."
});
}

Eklemiş olduğumuz CheckTheFormFieldsMessage parametresi ile FormHelper’ın varsayılan hata mesajını Türkçeleştirdik. FormHelper kütüphanesinde varsayılan dil İngilizce’dir. Diğer gelecek hata/uyarı mesajları uygulamamızın diline göre Fluent Validation tarafından üretilecektir. FormHelperConfiguration ile kütüphanenin global bir çok ayarını buradan düzenleyebiliriz.

CheckTheFormFieldsMessage -> Varsayılan form hata mesajı.
RedirectDelay -> Yönlendirme işlemlerinde beklenecek varsayılan süre.
ToastrDefaultPosition -> Bildirim/Uyarı mesajlarının ekranda görüneceği pozisyon.

Servis kayıt işlemini tamamladıktan sonra yine Startup.cs içerisinde bulunan Configure methoduna FormHelper’ın çalışması için gerekli kodları ekliyoruz. Configure methodu altında yaptığımız bu düzenleme FormHelper’a ait css, javascript gibi embeded dosyaların projeye dahil olmasını sağlıyor.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseFormHelper();
}

FormHelper ve Fluent Validation gibi kütüphaneler varsayılan olarak uygulamanızın diline göre hata mesajı gösterilirler. Geliştirme yaptığınız bilgisayarın veya uygulamanın üzerinde çalıştığı sunucunun dili İngilizce ise hata mesajları da İngilizce gelecektir. Bu gibi durumda Configure methodu içerisine uygulamanın varsayılan dilini Türkçe olarak çalışmaya zorlamasını sağlayacak kodları ekleyebiliriz.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseFormHelper();

var cultureInfo = new CultureInfo("tr-TR");
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
}

Kullandığımız kütüphaneler client-side çalışacağından bazı css ve javascript kütüphanelerine Bunları Views/Shared klasörü altında bulunan _Layout.cstml‘imize ekliyor ve devam ediyoruz. Tanımlamaları sayfanın head bölümünde yaparsak FormHelper’ı dinamik oluşturduğumuz formlarda da (modal içerisindeki vb.) kullanabiliriz.

<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<link rel="stylesheet" href="/formhelper/formhelper.min.css" />
<script src="/formhelper/formhelper.bundle.min.js"></script>

jQuery javascript dosyasını CDN aracılığı ile ekledik. FormHelper’a ait dosyaların ise proje içerisinden direkt olarak url’ini verdik. FormHelper’a ait css ve javascript dosyalarını projemize ayrıca eklememize gerek yok, yüklediğimiz nuget paketi ile embeded olarak gelmekte ve sanki projemizin içerisinde varmış gibi çalışmaktadır. Uygulama çalıştıktan sonra bu linklere istekte bulunursak ilgili dosyaların geldiğini görebiliriz.

Projemize FormHelper ve FluentValidation’ı entegre ettikten sonra artık uygulamamızı geliştirmeye başlayabiliriz.

Models klasörü içerisine Student.cs adında bir class oluşturuyor ve aşağıdaki propertyleri ekliyoruz.

public class Student
{
public int Id { get; set; }
[Display(Name = "Öğrenci Adı")]
public string StudentName { get; set; }
[Display(Name = "E-Posta")]
public string Email { get; set; }
[Display(Name = "Öğrenci No")]
public string StudentNumber { get; set; }
[Display(Name = "Puan")]
public int Credit { get; set; }
[Display(Name = "Mezun Olma Durumu")]
public bool Graduated { get; set; }

public bool IsNew => Id == default;
}

IsNew, modelimizi view ve controller içerisinde kullanırken yeni kayıt mı, yoksa güncelleme mi yapılacağının bilgisini iletecektir. Bunu yaparken Id değeri eğer integer default değerine yani sıfıra eşit ise bize true değilse false dönecektir. Profesyonel kullanımda bunu BaseViewModel içerisinde tanımlamak daha doğru olacaktır.

Oluşturduğumuz bu ViewModel için validation işlemini gerçekleştirecek bir “Validator” oluşturalım ve formumuzda kaydet butonuna basıldığında hangi kontrollerin yapılacağıyla ilgili validationları yazalım.

Uygulamamızın ana dizininde Validators adında bir klasör ve içeriside StudentValidator.cs adında bir class oluşturuyoruz. Bu class içerisinde Fluent Validation’dan faydalanarak doğrulama kurallarımızı yazacağız. Bu yüzden ilk iş namepacelerimize using FluentValidation; ekliyoruz.

Klasör ve Validator isimleri varsayılan kullanımlara göre verilmiştir. Farklı bir isimlendirme de kullanabilirsiniz.

Classımızın Fluent Validation’dan gelen AbstractValidator sınıfından miras alması gerekiyor. Bu class generic yapıda ve içerisine hangi class için validator hazırlamak istiyorsak onu göndermemiz gerekiyor. Bu yüzden classımız AbstractValidator<Student> den inheritance alacaktır.

Doğrulama senaryomuz şu şekilde olacak:

  • StudentName (öğrenci adı) alanı boş olamaz ve en az 4, en fazla 50 karakter olabilir.
  • Email (e-posta) alanı boş olabilir ancak doluysa e-posta formatına uymak zorunda.
  • StudentNumber (öğrenci no) alanı “A”, “B”, “C” harflerinden biriyle başlamalı ve en az 6 en fazla 8 karakter olmalıdır.
  • Credit (öğrenci notu/puanı) boş olabilir ancak doluysa 0 ile 100 arasında bir sayı olabilir.
  • Graduated (mezun olma durumu) seçili ise öğrenci puanı 70 üzerinde olmalıdır, aksi halde mezun olamaz.

Kurallarımızı yazabilmek için class içerisinde bir constructor oluşturuyor ve içerisine yukarıdaki senaryoya göre validationları tanımlıyoruz.

public class StudentValidator : AbstractValidator<Student>
{
public StudentValidator()
{
RuleFor(x => x.StudentName)
.NotEmpty()
.Length(4, 50);

RuleFor(x => x.Email)
.EmailAddress();

RuleFor(x => x.StudentNumber)
.NotEmpty()
.Length(6, 8)
.Custom((studentNumber, context) =>
{
var arr = new[] { "A", "B", "C" };

if (!arr.Contains(studentNumber.Substring(0, 1)))
{
context.AddFailure("Öğrenci numarası 'A', 'B' veya 'C' harfi ile başlamalıdır.");
}
});

RuleFor(x => x.Credit)
.InclusiveBetween(0, 100)
.GreaterThanOrEqualTo(70).When(x => x.Graduated == true);
}
}

Fluent Validation ile yazabileceğimiz kurallar neredeyse sınırsız. İşin tadını kaçırıp veritabanından query dahi çekebiliriz. Yine de veritabanı işlemlerini UI katmanına kadar indirmemekte fayda var :)

Şimdi oluşturduğumuz bu validator’ı Startup.cs üzerinde kayıt etmemiz gerekiyor. Startup.cs classımız içerisinde bulunan ConfigureServices methoduna aşağıdaki satırı ekliyoruz.

services.AddTransient<IValidator, StudentViewModelValidator>();

ConfigureServices methodumuzun son hali aşağıdaki gibi olmalıdır.

public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
.AddFluentValidation();

services.AddFormHelper(new FormHelperConfiguration
{
CheckTheFormFieldsMessage = "Form alanlarını kontrol ediniz.",
});

services.AddTransient<IValidator<Student>, StudentValidator>();
}

Örneğimizde Controller ismi olarak “HomeController”, Actionlar için ise “Form” ve “List” kullanılacaktır. Farklı bir isimde de oluşturabiliriz.

Modelimiz ve validation kurallarıyla ilgili tanımlamalarımız bittiğine göre uygulamamıza devam edebiliriz. Controller classımıza geliyoruz ve hemen içerisinde statik bir liste oluşturacağız. Örneğimizde veritabanı kullanmayacağımızdan kayıtlarımızı statik bir listede tutacağız.

public class HomeController : Controller
{
private static List<Student> _studentList = new List<Student>();

Controller’ımızın içerisinde “List” adında, öğrenci listemizi dönen bir action oluşturuyoruz. Bu action uygulamamızın ana sayfası olacak ve öğrencileri listeleyecek.

public IActionResult List()
{
return View(_studentList);
}

Oluşturduğumuz “List” action’ı için “/Views/Home/” dizini altında“List.cshtml” view’ini oluşturuyoruz. İçerisinde yeni öğrenci ekleme sayfasına gidebilmek için bir buton, mevcut öğrencileri listeleme ve düzenleyebilme fonksiyonları yer alacak.

Örnek projede arayüzleri oluşturmak için Bootstrap kullanacağız.

@model List<Student>

<div>
<a href="/home/form" class="btn btn-primary mb-3">Yeni Öğrenci</a>
</div>

<table class="table table-bordered">
<thead>
<tr>
<td>Öğrenci</td>
<td>E-Posta</td>
<td>Öğrenci No</td>
<td>Puan</td>
<td>Mezun Durumu</td>
<td></td>
</tr>
</thead>
<tbody>
@if (Model.Any())
{
foreach (var student in Model)
{
<tr>
<td>@student.StudentName</td>
<td>@student.Email</td>
<td>@student.StudentNumber</td>
<td>@student.Credit</td>
<td>@(student.Graduated ? "Evet" : "Hayır")</td>
<td>
<a class="btn btn-primary" href="/home/form/@student.Id">Düzenle</a>
</td>
</tr>
}
}
else
{
<tr>
<td colspan="6" class="text-center">
Henüz bir öğrenci kaydı yapılmamıştır.
</td>
</tr>
}
</tbody>
</table>

Uygulamamızın açılış sayfası “HomeController.cs” altındaki “List” action’ı olacağından “Startup.cs” e gidip ilgili düzenlemeyi routing (endpoint) tanımlarında yapıyoruz. Uygulamayı çalıştırdığımızda artık bizi öğrenci listesi karşılayacak.

app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=List}/{id?}");
});

Listeleme tarafıyla ilgili işlemlerimizi tamamladığımıza göre formumuza geçebiliriz. Controller’ımızın içerisinde “Form” adında bir action oluşturuyoruz. Yeni kayıt ve düzenleme işlemlerinde kullanılacak formumuzu bu action üzerinden sunacağız. Formumuz, eğer url’imize bir Id değeri geliyorsa güncelleme amacıyla, Id gelmiyorsa yeni bir kayıt oluşturmak amacıyla oluşacaktır.

public IActionResult Form(int? id)
{
if (id.HasValue == true)
{
var student = _studentList.Single(x => x.Id == id.Value);
return View(student);
}
else
{
return View(new Student());
}
}

Viewimizi oluşturmaya geçmeden önce _ViewImports.cshtml dosyamıza FormHelper’a ait tanımlamaları ekliyoruz. Bu sayede her view içerisinde tekrar tekrar tanımlmama yapmamıza gerek kalmayacak.

@using FormHelper
@addTagHelper *, FormHelper

Şimdi Form.cshtml viewimizi oluşturuyoruz. İçeriği aşağıdaki gibi olacaktır.

@model Student

<div class="container">
<div class="row justify-content-center">
<div class="col col-lg-8">
<div class="card">
<div class="card-header">
Öğrenci Bilgileri
</div>
<div class="card-body">
<form asp-formhelper="true" asp-controller="Home" asp-action="Save">

<input type="hidden" asp-for="Id" />

<div class="form-group">
<label asp-for="StudentName"></label>
<input asp-for="StudentName" type="text" class="form-control">
<span asp-validation-for="StudentName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Email"></label>
<input asp-for="Email" type="email" class="form-control">
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StudentNumber"></label>
<input asp-for="StudentNumber" type="text" class="form-control">
<span asp-validation-for="StudentNumber" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Credit"></label>
<input asp-for="Credit" type="text" class="form-control">
<span asp-validation-for="Credit" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Graduated"></label>
<input asp-for="Graduated" type="checkbox" />
<span asp-validation-for="Graduated" class="text-danger"></span>
</div>
<hr />
<div class="form-group text-right">
<button class="btn btn-secondary" type="reset">Temizle</button>
<button class="btn btn-primary" type="submit">Kaydet</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>

Burada hidden olarak belirttiğimiz Id değeri düzenleme işlemi sırasında mevcut öğrenci Id sini server-side’a göndermek için kullanılacaktır.

ASP.NET Core’da yapılmış bir standart bir formu FormHelper’a çevirmek için sadece asp-formhelper=”true” eklememiz yeterli oldu. Artık client-side yapılabilen tüm validationlar client-side tarafında yapılacak. Yapılamayanlar ve form kayıt işlemleri için arka planda FormHelper tarafından ajax işlemleri otomatik olarak halledilecektir. Yazdığımız bu parametre dışında FormHelper’ın bir çok parametresi daha bulunmaktadır.

asp-callback -> Form submit olduktan sonra sunucudan gelen yanıt burada belirtilen javascript methoduna düşer.

asp-beforeSubmit -> Form submit olmadan önce burada belirtmiş olduğunuz javascript methodu çalışır. Form gönderilmeden önce yapılması istenen ek kontroller ve düzenlemeler burada yapılabilir.

asp-dataType -> FormHelper varsayılan olarak formları FormData tipinde göndermektedir. Bu parametre ile JSON tipinde gönderim sağlayabiliriz.

asp-enableButtonAfterSuccess -> Varsayılan olarak Kaydet butonuna bastıktan sonra eğer bir validation hatası yoksa çift tıklamayı önlemek için Kaydet butonu pasife alınır.

asp-resetFormAfterSuccess -> Varsayılan olarak form başarıyla gönderildikten sonra form elemanları temizlenir.

asp-toastrPosition -> Varsayılan olarak FormHelper'dan gelen uyarılar (toast) sağ üst köşede görünür, bunu form bazında değiştirebiliriz.

View tarafında yapmamız gereken tüm işlemler tamamlandı. Şimdi Controller’ımıza gidip Save Action’ını oluşturup üzerine [FormValidator] attribute’ünü tanımlayacağız.

[FormValidator]
public IActionResult Save(Student student)
{
if (student.IsNew)
{
if(_studentList.Any(x => x.StudentNumber == student.StudentNumber))
{
return FormResult.CreateWarningResult("Aynı öğrenci numarasını kullanan başka bir öğrenci vardır.");
}

_studentList.Add(student);
return FormResult.CreateSuccessResult("Öğrenci eklendi.", Url.Action("List", "Home"));
}
else
{
var currentStudent = _studentList.Single(x => x.Id == student.Id);
currentStudent = student;
return FormResult.CreateSuccessResult("Öğrenci güncellendi.", Url.Action("List", "Home"));
}
}

Burada action’ımızın üzerinde bulunan [FormValidator] attribute’ü form’a gönderilen veriye bakacak ve validation işlemini arka planda yapacaktır. Eğer bir problem görürse yapılan istek method’a girmeden, action tarafında client’a geri dönüş yapacaktır.

Yapılan istek FluentValidation tarafında yaptığımız doğrulama işlemlerini geçer ve methodumuza girerse gelen kaydın Id değerine bakıp IsNew ile yeni bir kayıt mı yoksa mevcut kayıt mı güncellenmek isteniyor anlayacağız. Yeni bir kayıt ise method içerisinde öğrenci listesine bakıp aynı öğrenci numarasına ait başka kayıt olup olmadığını kontrol edeceğiz Eğer varsa yine FormHelper’dan faydalanarak client-side’a yeni kayıt yapamadığımıza dair bir uyarı mesajı göndereceğiz. Burada yapılan kontrolde de bir problem yoksa bu sefer client-side’a işlemin yapıldığını bir belirten bir mesaj gönderip, ardından listemize yönlenmesini sağlayacağız. Listemize yönlendirme işlemini FormResult.CreateSuccessResult kullanımındaki ikinci parametreye belirtmiş olduğumuz url sağlayacak.

Uygulamamızı çalıştırıyoruz. “Save” butonuna bastığımızda önce client-side validationlar çalışıyor. Herhangi bir validation hatası alınmazsa, ajax ile server-side’a gönderiliyor, burada bir hata yakalanırsa bunları client-side’a yansıtıyor.

Tek satır javascript kod yazmadık. Ajax form gönderim işlemlerinden, kompleks validationlara kadar her şeyi bir kaç satırda hallettik. Her şey mükemmel!

Hem FormHelper, hem de FluentValidation çok daha fazla özelliklere sahip. İlgili kütüphanelerin GitHub sayfaları üzerinden daha detaylı bilgi edinilebilir. Yaptığımız örnek projenin kaynak kodlarına github üzerinden ulaşabilirsiniz. Genel paylaşıma açık olduğu için GitHub üzerinde bulunan projenin dili İngilizce’dir.

https://github.com/sinanbozkus/fluent-validation-with-form-helper

Yazı Kaynak: http://www.sinanbozkus.com/form-helper-ve-fluent-validation-kullanarak-asp-net-core-validation-islemleri/

--

--