Makale Özeti

Bu yazımızda ASP.NET MVC 3 sürümü ile gelen ve doğrulama(validation) işlemlerinde sıklıkla kullanılabilecek CompareAttribute nitelik sınıfı ile IValidatableObject arayüz(interface) nesnesini inceliyoruz.

Makale

ASP.NET MVC 3 özellikle doğrulama işlemleriyle ilgili gerek istemci gerekse sunucu tarafında birçok yenilikleri getirmektedir. Bu yeniliklerden ikisi CompareAttribute sınıfı ile IValidatableObject arayüz nesnesidir. Model doğrulama işlemlerinde sıklıkla kullanacağımız bu iki nesneden CompareAttribute nesnesi bir entity nesnesi içerisindeki belirli bir özelliğin başka bir değerle kıyaslanması ve doğrulama yapılmasını sağlar. WebForm'lardaki Validation kontrollerinden tanıdığımız CompareValidator'a benzer bir işlevi entity bazında kullanmamızı sağlar. IValidatableObject ise sunucu tarafında özel doğrulama metotları yazarak sunucu tarafındaki işlerimizi kolaylaştırmaktadır.

ASP.NET 3.5 SP1 ile duyurulan Dynamic Data Web Site'larda kullanılan DataAnnotions sınıflarını, bir önceki MVC 2.0 sürümde Model katmanında entity objelerinde attribute bazlı olarak kullanabiliyorduk. .NET Framework 4.0'da daha da geliştirilen bu yapılara, MVC 3.0 ile de bazı ek sınıflar eklendi. Bu sınıflardan bir tanesi de CompareAttribute'dur. System.Web.Mvc isim alanı(namespace) içinde yer alan bu sınıf karşılaştırma(compare) işlemi için kullanılacağı için tek bir entity property'sine bağlı kalmayacaktır. Karşılaştırma yapılacak iki farklı property'den birine uygulanan CompareAttribute, parametre olacak değerine bakacağı diğer property adını da alır.

Karşılaştırma işlemini çok sıklıkla yaptığımız kullanıcı kayıt işleminlerindeki şifre doğrulama örneğini bu attribute nesnesinin kullanımında inceleyelim. Öncelikle içerisinde User adında bir tablo bulunduran veritabanımızı, MVC 3.0 projemizin Models klasörüne ADO.NET Entity Data Model(.edmx) dosyası olarak ekleyelim. Ben entity sınıfını kolay takip etmek ve kod karmaşasını azaltmak için .edmx dosyamın açık olduğu designer pencesine sağ tıklayıp Add Code Generation Item seçeneğine tıklıyorum. Açılan pencereden ADO.NET Self-Tracking Entity Generator dosya tipini seçiyorum(.tt uzantılı template dosyanıza uygun bir isim verebilirsiniz). POCO için gerekli eklentiler kurulu ise bu adımda POCO objeleri de oluşturabilirsiniz. Model katmanında şimdilik işimiz bitti. (User tablosunda bulunan kolonlar: Id, Username, Password, Email ve IsActive. Nesne çizimini yazının ilerleyen kısımlarındaki resimden görebilirsiniz)

Controllers klasörüne UserController.cs adında bir dosya ekliyorum. Index ve Create talepleri için ouşturduğum Action metotları aşağıdaki gibi.

Controllers/UserController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Demo_Validation.Models;
 
namespace CompareAttribute.Controllers
{
    public class UserController : Controller
    {
        NorthwindEntities northwind = new NorthwindEntities();
 
        public ActionResult Index()
        {
            return View(northwind.User);
        }
 
        public ActionResult Create()
        {
            return View(new User());
        }
 
        [HttpPost]
        public ActionResult Create(User user)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    northwind.User.AddObject(user);
                    northwind.SaveChanges();
                    return RedirectToAction("Index");
                }
                else
                {
                    ViewBag.ExceptionMessage = "Kullanıcı ekleme işleminde hata!!!";
                    return View(user);
                }
            }
            catch (Exception ex)
            {
                ViewBag.ExceptionMessage = "Kullanıcı ekleme işleminde hata!!!";
                return View(user);
            }
        }
    }
}

Action metotları içerisindeyken fareye sağ tıklayıp Add View seçeneğinden Index ve Create işlemleri için View nesnelerimizi ekliyoruz. View ekleme penceresinde Create a strongly-typed view seçeneğini aktif yapalım ve altta çıkan listeden User entity objesini seçili bırakalım. Index sayfası için Scaffold template'i List, Create sayfası içinde Create olarak belirleyebiliriz. Create sayfasını çalıştırıp test ettiğimizde herhangi bir validation işleminin yapılmadığını göreceğiz. Gelelim asıl konumuza; CompareAttribute nesnesini User sınıfına nasıl uygulayacağımıza.

Models klasörü altında NorthwindModel.tt(bu dosya adı sizde farklı olabilir) dosyasının altındaki User.cs dosyasını aşağıdaki gibi değiştiriyorum. Kodlara göz atmadan önce User entity nesnesinin orjinal halini de aşağıdaki nesne çiziminden görebilirsiniz.

Models/User.cs

...
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace CompareAttribute.Models
{
    [DataContract(IsReference = true)]
    public partial class UserIObjectWithChangeTrackerINotifyPropertyChanged
    {
        #region Primitive Properties
    
        [DataMember]
        public int Id
        {
            get { ...; }
            set
            {
                ...
            }
        }

        private int _id;
    
        [DataMember]
        [Required//Username boş bırakılamaz
        public string Username
        {
            get { return _username; }
            set
            {
                if (_username != value)
                {
                    _username = value;
                    OnPropertyChanged("Username");
                }
            }
        }

        private string _username;
    
        [DataMember]
        [Required//Şifre boş bırakılamaz
        public string Password
        {
            get { return _password; }
            set
            {
                if (_password != value)
                {
                    _password = value;
                    OnPropertyChanged("Password");
                }
            }
        }

        private string _password;

        [DataMember]
        [Required//Şifre tekrar alanı boş bırakılamaz [Compare("Password", ErrorMessage = "Şifrelerin aynı olması gerekiyor")] //Password alanı ile karşılaştırma public string PasswordRepeat
        {
            get { return _passwordRepeat; }
            set
            {
                if (_passwordRepeat != value)

                {
                    _passwordRepeat = value;
                }
            }
        }

        private string _passwordRepeat;
    
        [DataMember]
        public string Email
        {
            get { ... }
            set
            {
                ...
            }
        }

        private string _email;
    
        [DataMember]
        public Nullable<bool> IsActive
        {
            get { ... }
            set
            {
                ...
            }
        }
        private Nullable<bool> _isActive;

        #endregion
    
        //ChangeTracking ile ilgili metotlar...
    
   }
}

User sınıfında yaptığım değişiklikleri gözden geçirecek olursak; öncelikli olarak Username ve Password property'lerini System.ComponentModel.DataAnnotations isim alanı altındaki Required(RequiredAttribute) sınıfıyla işaretliyoruz. Required attribute sınıfı istemci ve sunucu tarafında ilgili property'lerin boş bırakılması durumunu değerlendirecektir. Konumuzda asıl ilişkili olan kısım ise Compare(CompareAttribute) sınıfıdır. Yukarıdaki entity çiziminden de göreceğiniz üzere tablomuzda bir tane şifre alanımız var. Yani View nesnemiz otomatik üretilirken eğer şifre tekrarı için de ayrı bir özelliğin algılanmasını istiyorsak sınıf içerisine bir şifre özelliği daha eklememiz gerekecek. PasswordRepeat adını verdiğim bu özellik, Compare attribute'unu asıl uygulayacağımız özellik olacak. Compare attribute'unun alacağı en önemli parametre çalışma zamanı esnasında üzerinde bulunduğu özelliğin hangi özellik ile karşılaştırılacağını belirtleyen othetProperty parametresidir. En yalın haliyle [Compare("Password")] şekliyle kullanabileceğimiz bu niteliğe istersek doğrulama ekranında verilecek hata mesajını da ekleyebiliriz. Kod örneğimizde hata mesajı da eklenmiştir.

Entity nesnemizde gerekli eklemeyi tamamladık. Sıra geldi arayüz tarafında yapılması gerekenlere. Aslında view nesnenizi entity modelinizi değiştirdikten sonra otomatik üretecek olursanız, zaten PasswordRepeat özelliğiyle ilgili kontrol ve doğrulama kısımları da oluşacaktır. Ama biz varolan model üzerinde bu değişikliği nasıl yapacağımızı inceliyoruz. Bu durumda User/Create.cshtml isimli view sayfamıza aşağıdaki kodları ekliyoruz.

User/Create.cshtml

<div class="editor-label">
    @Html.LabelFor(model => model.PasswordRepeat)
</div>

<div class="editor-field">
    @Html.EditorFor(model => model.PasswordRepeat)
    @Html.ValidationMessageFor(model => model.PasswordRepeat)
</div>

Ekran çıktısında sonucu gözlemleyebiliriz.

IValidatableObject ile Sunucu Tarafında Model Validation

ASP.NET MVC 3 sürümünde, validation ile ilgili gelen bir diğer yenilikte IValidatableObject interface nesnesiyle sunucu taraflı doğrulama işlemlerin kolaylaştırılmasıdır. ASP.NET MVC ile uygulama geliştirirken DataAnnotations sınıfları bazen doğrulama ile ilgili ihtiyaçlarımızı karşılamayabiliyor. Ya da bazen birden fazla property'nin yer aldığı karmaşık doğrulama işlemlerine de ihtiyaç duyabiliyoruz. Bu tip işlemlerde genellikle istemci tarafında JavaScript kodu yazarak doğrulama yapma işlemleri yapmak tercih edilse de, sunucu tarafında gerekli doğrulamaların yapılması hem kaçınılmaz, hem de en güvenli yoldur. İşte IValidatableObject interface'i sunucu tarafındaki bu görevi yerine getirmemizi kolaylaştırıyor.

ASP.NET MVC'nin M'si olan Model katmanımızdaki nesnelerimize uygulamayacağımız bu interface beraberinde Validate adında bir metot getirmektedir. Geriye IEnumerable<ValidationResult> tipinden bir liste döndüren metot, içerisinde birden fazla doğrulama kontrolü yapılıp bulunan her hata için farklı mesajlar döndürülmesini sağlar. Önceki örnekte kullandığımı User entity nesnesine IValidatableObject interface'ini implemente ederek Validate metodunun içeriğini aşağıdaki gibi dolduruyoruz.

public partial class UserIValidatableObject
{
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        List<ValidationResult> results = new List<ValidationResult>();
            
        if (Username.Length < 6)
            results.Add(new ValidationResult("Kullanıcı adı en az 6 karakterden oluşmalı!"));

        if (Password.Length < 4)
            results.Add(new ValidationResult("Şifreniz en az 4 karakterden oluşmalı!"));
            
        if ((Password.StartsWith("19") && Password.Length == 4))
            results.Add(new ValidationResult("Şifreniz doğum tarihi gibi kolayca tahmin edilebilir bir değer olamaz!"));

        return results;
    }
}

Views/Users/Create adresini çalıştırıp sayfası test ettiğimizde eğer istemci tarafındaki doğrulamaları başarıyla geçersek, sunucu tarafındaki Validate metodumuzun çalışacağını göreceğiz. Buradan ASP.NET MVC'nin öncelikli olarak istemci tarafında DataAnnotations attribute nesnelerini baz alarak doğrulama yaptığını, eğer bu doğrulama işlemi başarılı şekilde yapılırsa sunucuya giderek Validate metodunu çalıştırdığını görüyoruz. Yani önce property validation, sonra object validation ;) Controller'daki Create action metodunda zaten ModelState.IsValid kontrolü yaptığımız için Validate metodu geriye içerisi dolu bir liste nesnesi döndürürse model nesnesi doğrulanamaz ve işlem iptal edilerek istemcide hata mesajı görüntülenir.

Bu yazımızda ASP.NET MVC 3 ile gelen iki önemli validation yeniliğini inceleme fırsatı bulduk. Bir başka yazıda görüşmek dileğiyle.


Uğur UMUTLUOĞLU
Microsoft MVP (ASP.NET)
www.umutluoglu.com
twitter.com/umutluoglu