Asp.Net MVC Projelerinde Hata Yönetimi (Exception Handling)

Merhaba arkadaşlar,

Kısa bir süre önce; 3 yıldır çalıştığım firmamdan ayrılıp, yepyeni bir ekibin parçası oldum. Kariyerimde açılan bu yeni sayfanın heyecanı ile uzun süredir birlikte vakit geçirdiğim insanlardan ayrılmanın burukluğu karmakarışık bir şekilde kenarda dursun..

Ben bu yazımda; işin romantik olmayan boyutundan yola çıkmaktayım. Birkaç gündür, yabancısı olduğum bir konu için geliştirilen projeleri inceleyerek; o yabancısı olduğum konuyu, tüm ayrıntılarıyla anlamaya çalışıyorum. Beynimin bir kısmı bu yeni projeleri incelemeye odaklanmış olsa da, diğer kısmı iyi kod-kötü kod kıyaslamalarından bir türlü vazgeçmiyor.

Bu kıyaslama olayının bir ürünü olarak; bir yazılım projesinde Hata Yakalama (Exception Handling) işlemi neden önemlidir, ve bu işlem; nasıl yapılırsa kodlar daha temiz olur biraz bundan bahsetmek istedim.

Bildiğimiz üzere; bir yazılım projesinin en temel amacı, herhangi bir (yada birden fazla) soruna çözüm getirmektir. Bir sorunun çözümüne giden yolda, bir yazılımdan beklenen tek şey ise o yazılımın hatasız çalışmasıdır. Bir yazılımın çalışması esnasında hataya sebep olan durumlar, genelde dış etkenlerden (kullanıcıdan, işletim sisteminden, gelen veriden v.b..) kaynaklanmaktadır. Eğer biz, hataya sebep olabilecek durumları iyi bir şekilde yönetebilirsek geliştirdiğimiz yazılımın minimum hata ile çalışmasını sağlayabiliriz.

Yazılım dünyasında, bir yazılımın hatalı çalışmasına (yada tamamen durmasına) neden olacak durumlar Exception olarak tanımlanmaktadır.

Diğer programlama dillerinde olduğu gibi, C# dilinde de exception‘lar try-catch-finally blokları ile yakalanabilmektedir.

try 
{
// normalde çalışacak olan kodlar buraya yazılır
} 
catch {
// eğer herhangi bir istisna oluşursa buradaki kodlar çalışmaya başlar
}
finally {
// try yada catch içindeki kodların çalışması tamamlandığında buradaki kodlar çalıştırılır.
}

Bir Asp.Net MVC projesinde hata yönetimini try-catch bloklarıyla şu şekilde yapabilirsiniz;

    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            try
            {
                // anasayfa bilgilerini getir
            }
            catch (Exception exc)
            {
                // hatayı logla
                // kullanıcıya hata bilgisi göster
                // v.s...
            }
            return View(viewModel);
        }

        public ActionResult GetMember(int id)
        {
            try
            {
                // yapılacak işlemler...
            }
            catch (Exception exc)
            {
                // hatayı logla
                // kullanıcıya hata bilgisi göster
                // v.s...
            }

            return View(viewModel);
        }

        [HttpPost]
        public ActionResult Register(RegisterModel model)
        {
            try
            {
                // yapılacak işlemler...
            }
            catch (Exception exc)
            {
                // hatayı loglama
                // kullanıcıya hata bilgisi gösterme
                // v.s...
            }

            return View(viewModel);
        }
    }

Yukarıdaki kod örneği gözümüze makulmuş gibi görünebilir. Fakat çalıştığınız projede çok sayıda Controller ve Action var ise her yer try-catch bloklarıyla dolup taşıyor. Bu try-catch bloklarının çokluğu kodların okunabilirliğini azaltıyor. Bir de Exception tiplerine göre ayrı işlemler yapılacaksa o zaman iş daha da içinden çıkılmaz bir hâle geliyor.. Hata yakalama mekanizmasında herhangi bir değişik yapmak istendiğinizde, tüm try-catch bloklarının değiştirilmesi gerekiyor. Ayrıca try-catch blokları, uygulamanızın performansına da olumsuz bir etki yapacaktır.

Peki bu try-catch fazlalıklarından nasıl kurtulabiliriz? Cevap: HandleErrorAttribute..

HandleErrorAttribute sınıfı; FilterAttribute sınıfını ve IExceptionFilter arayüzünü miras almaktadır. Dolayısıyla; herhangi bir Action‘a yada Controller‘a (attribute olarak) eklenebilir. Ayrıca, HandleErrorAttribute sınıfını implemente ederek kendi hata yakalayıcılarınızı (Exception Handler) yazabilir ve bunları global filtrelere ekleyebilirsiniz. Şimdi bir exception handler sınıfı yazalım ve bunu yukarıdaki try-catch bloklarıyla dolu örnek kodumuzda uygulayalım.

    public class MyExceptionHandler : HandleErrorAttribute
    {
        public override void OnException(ExceptionContext filterContext)
        {
             // hatayı loglama
             // kullanıcıya hata bilgisi gösterme
             // v.s...
        }
    }

Kendi oluşturduğumuz MyExceptionHandler.cs sınıfını HandleErrorAttribute sınıfından miras aldırdıktan sonra OnException() metodunu ezerek (override) hata yakalandığında çalışacak kodları buraya alıyoruz. Daha sonra Controller sınıfını tekrar düzenliyoruz.

    [MyExceptionHandler]
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            // anasayfa bilgilerini getir

            return View(viewModel);
        }

        public ActionResult GetMember(int id)
        {
             // yapılacak işlemler...

            return View(viewModel);
        }

        [HttpPost]
        public ActionResult Register(RegisterModel model)
        {
            // yapılacak işlemler...

            return View(viewModel);
        }
    }

Gördüğünüz gibi kodlar biraz daha sadeleşti. Ayrıca hata yakalama mekanizmasında yapılmak istenen herhangi bir değişiklik sadece OnException() metodu içerisinde gerçekleştirerek yönetimi daha kolay bir hâle getirdik.

Az önce de belirttiğin gibi, Controller‘a koyduğumuz [MyExceptionHandler] etiketini sadece istediğiniz Action‘lar için de kullanabilmeniz mümkündür.

Diyelim ki yazdığımız handler etiketinin tüm Controller‘lar için geçerli olmasını istiyoruz. Tek tek tüm Controller‘lara [MyExceptionHandler] etiketini eklemek zorunda mıyız? Cevap: Tabi ki değiliz. 🙂

Global.asax.cs sınıfında Application_Start() metoduna gelerek uygulamanın global filtrelerine MyExceptionHandler sınıfını ekleyelim.

    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            GlobalFilters.Filters.Add(new MyExceptionHandler());

            ...

        }

        ...

    }

Artık Controller sınıflarından [MyExceptionHandler] etiketini kaldırabiliriz.

    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            // anasayfa bilgilerini getir

            return View(viewModel);
        }

        public ActionResult GetMemberName(int id)
        {
             // yapılacak işlemler...

            return View(viewModel);
        }

        [HttpPost]
        public ActionResult Register(RegisterModel model)
        {
            // yapılacak işlemler...

            return View(viewModel);
        }
    }

Artık tüm Controller‘larda oluşabilecek exception‘ları MyExceptionHandler sınıf içerisinde yakalabileceğiz. Böylece hem gereksiz kod tekrarından kurtulmuş oluyoruz, hem de hata yakalama mekanizmamızı tek bir merkezden yönetilebilir hale getiriyoruz. Bu da, değişiklik yapmak istediğimizde sadece tek bir yerin değiştirilmesi anlamına geliyor. Zaman olarak kâra geçiyoruz ve kod yazarken hata yapma oranımız daha da azalmış oluyor.

Şimdi, handler’ımız tüm Controller için aktif olmuş durumdadır. Fakat bu durum, tamamen MyExceptionHandler‘a bağımlı olduğumuz anlamına gelmiyor. Eğer istersek, hatayı yakaladığımız OnException() metodunu istediğimiz Controller içerisinde ezerek; Controller bazında başka işlemler de gerçekleştirilmesini sağlayabiliyoruz.

    public class ProductController : Controller
    {
        ...

        protected override void OnException(ExceptionContext filterContext)
        {
            // ProductController hataları için yapılacak başka işlemler..
        }
    }

Böyle bir durumda; ProductController içerisinde oluşabilecek hatalar ilk olarak MyExceptionHandler sınıfı içerisindeki OnException() metodunda yakalanacaktır. Daha sonra ProductController içerisindeki OnException() metodunda hata tekrar yakalanacaktır.

Yazının buraya kadar olan bölümünde, Controller içerisinde meydana gelebilecek hataların nasıl yakalanabileceği üzerinde durduk. Fakat, Asp.Net MVC uygulamaları her ne kadar Controller‘lar içerisinde çalışıyor olsa da Controller dışında da hata meydana. (örneğin kullanıcı olmayan bir sayfaya erişmek istediğinde oluşabilecek ‘sayfa bulunamadı’ hatası)

Bu gibi durumlarda oluşacak hataları da Global.asax.cs sınıfı içerisinde Application_Error() metodu sayesinde yakalayabilmekteyiz.

    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            GlobalFilters.Filters.Add(new MyExceptionHandler());

            ...
        }

        protected void Application_Error(object sender, EventArgs e)
        {
            var exception = Server.GetLastError();

            // hatayı loglama
            // kullanıcıya hata bilgisi gösterme
            // kullanıcıyı başka sayfaya yönlendirme
            // v.s...            
        }
    }

Application_Error() metodu, hatanın yakalanabileceği en son aşamadır.

Toparlayacak olursak; Controller içerisinde oluşan herhangi bir hata,

  • İlk olarak MyExceptionHandler sınıfında yakalanacaktır.

  • Eğer Controller içerisinde OnException() metodu override edilmişse; 2. olarak burada tekrar yakalanabilir.

Bu aşamalar geçildikten sonra da hata son olarak Application_Error() metodu içerisinde tekrar yakalanabilir.

Asp.Net MVC projelerimizde ki hata yönetim mekanizmasını bu sıraya göre kurgulayarak; temiz kodlanmış ve daha stabil çalışan uygulamalar geliştirebiliriz.

Ve sona geldik. Örnek kodlara buradan ulaşabilirsiniz.

Faydalı olması dileğiyle.. Mutlu kodlamalar 🙂

Last updated