Asp.NET Core’da ki Exception’ları Otomatik Olarak Yönetin
Merhaba,
Asp.NET Core’da API geliştiriyorsanız eğer aşağıdaki yapılanmanın size oldukça tanıdık geldiğinden eminim:
public IActionResult Get(int id) { var product = _productService.GetById(id); if (product == null) return NotFound(); return Ok(product); }
Bu koda şöyle bir göz attığınızda yanlış bir şey görebiliyor musunuz? Pek değil dimi? Sadece id bazında bir product’ı sorgulayan ve ilgili nesne geldiği taktirde geriye döndüren aksi taktirde ‘NotFound’ ile hata döndüren bir action görmekteyiz. Bu gayet doğal bir kodlama gibi gözükebilir. Keza öyledir. Lakin, her bir action’da bu ve bunun gibi nesnelerin olmadığı durumları kontrol etmek ve bu kontrol neticesine göre kodumuzu inşa etmek esasında sürekli kendimizi tekrar etmemiz anlamına gelmeyecek mi? Ayrıca bu tarz bir durumda, nesnenin null geldiği anlarda loglama yapmak istersek, her operasyonda ayrı ayrı ama aynı kodları yazmamız gerekmeyecek mi?
Bununla birlikte, buradaki operasyonda iş katmanı olarak sorumluluk üstlenen servisin ilgili id’ye karşılık sorgulama neticesinde herhangi bir nesnenin gelmediği durumlarda buradaki problem kontrolünü farklı bir katman olan controller’a taşıması yersiz ve saçma bir işleyiş olmayacak mı? Nihayetinde iş mantığını yürüten katman, gelen id’ye karşılık bir nesne bulunmadığı taktirde bir exception fırlatarak mimariyi uyarması daha cazip olacaktır, öyle değil mi? 🙂
Öyleyse bu koddan nesne kontrolünü kaldırmayı ve aynı zamanda kendimizi tekrar ettirecek olan bu tarz durumları daha merkezi hale getirmeyi sorgulamalıyız… Peki bunu nasıl yapacağız? Cevap : Asp.NET Core mimarisinin damar yollarından biri olan, Action Filter’lardır…
Asp.NET Core’da ‘Filter’ Tam Olarak Nedir?
Asp.NET Core’da ‘Filter’lar, request pipeline’ının belirli aşamalarından önce veya sonra herhangi bir kodu çalıştırmanın yollarından biridir. C# dilinde bu filter’lar attribute olarak tasarlanmaktadır. Asp.NET Core’da birçok filter olmasına rağmen yukarıda belirtildiği gibi, atılan exception’a göre tetiklenecek olan filter ExceptionFilterAttribute
‘dır.
Şimdi gelin, hızlı bir şekilde basit bir örnek gerçekleştirelim.
‘Product’ entity’si:
public class Product { public int Id { get; set; } public string Name { get; set; } }
‘ProductService’ sınıfı ve ‘IProductService’ arayüzü:
public interface IProductService { Product GetById(int id); } public class ProductService : IProductService { List<Product> _products = new() { new() { Id = 1, Name = "Book" }, new() { Id = 2, Name = "Pencil" } }; public Product GetById(int id) { var product = _products.FirstOrDefault(p => p.Id == id); return product; } }
Şimdiye kadar ihtiyacımız olan tek şey, ‘ProductService’ içerisindeki ‘GetById’ metodunda ilgili id’ye karşılık herhangi bir nesnenin gelip gelmediğini kontrol etmek ve gelmediği taktirde hata fırlatmaktır. Bunun için fırlatılacak exception’ı custom olarak tasarlamakta fayda vardır.
public class DataNotFoundException : Exception { public DataNotFoundException(string type, object id) : base($"{type} türündeki {id} id değerine sahip olan obje bulunamadı! ") { } }
Ve artık bahsedilen kontrolü ‘GetById’ fonksiyonunda gerçekleştirebiliriz:
public Product GetById(int id) { var product = _products.FirstOrDefault(p => p.Id == id); if (product == null) throw new DataNotFoundException(nameof(Product), id); return product; }
Custom ExceptionFilterAttribute Oluşturma
Yukarıda tasarlandığı gibi id’ye karşılık herhangi bir nesne olmadığı taktirde hata fırlatılacaktır. Dolayısıyla bu hataya karşılık devreye girecek filtreyi custom olarak oluşturmamız gerekmektedir.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class ExceptionFilter : ExceptionFilterAttribute { public async override Task OnExceptionAsync(ExceptionContext context) { //Fırlatılan exception'ın status code'unu bilemediğimiz durumda //default olarak '500 Internal Server Error'ı belirliyoruz. var statusCode = HttpStatusCode.InternalServerError; //Fırlatılan exception DataNotFoundException ise //status code'u '404 Not Found' yapıyoruz. if (context.Exception is DataNotFoundException) statusCode = HttpStatusCode.NotFound; //Bu request'e karşılık verilecek response'a status code'u ve //result'u değiştirerek dönebiliriz. context.HttpContext.Response.ContentType = "application/json"; context.HttpContext.Response.StatusCode = (int)statusCode; context.Result = new JsonResult(new { error = new[] { context.Exception.Message }, statusCode = (int)statusCode, stackTrace = context.Exception.StackTrace }); } }
Görüldüğü üzere, hata esnasında devreye girecek action filter’ımız yukarıdaki gibidir. Tabi bu daha da özelleştirilebilir lakin bizler şimdilik bu kadarla yetineceğiz. Şimdi geriye kalan tek şey Asp.NET Core mimarisini oluşturulan bu filtre hakkında bilgilendirmektir. Bunun için iki farklı yöntem seçebiliriz.
- 1. Yöntem – Filter Olarak Global Ekleme
Bir filter’ı global olarak ekleyebilmek için ‘Startup.cs’ dosyası içerisindeki ‘AddControllers’ servisinde aşağıdaki gibi bildirmek yeterlidir.public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddControllers(options => options.Filters.Add(typeof(ExceptionFilter))); . . . } . . . }
Global olarak eklenen filter’lar türüne özgü tüm eylem durumlarında tetiklenmektedirler. Dolayısıyla bir filter’ı sadece özelleştirilmiş durumlarda kullanmak istiyorsanız 2. yöntem’i tercih etmeniz gerekmektedir.
- 2. Yöntem – Attribute Olarak Controller yahut Action Bazlı Ekleme
Oluşturulan filter özünde bir attribute olduğu için bu şekilde de kullanılabilmektedir.[HttpGet("{id}")] [ExceptionFilter] public IActionResult Get(int id) { var product = _productService.GetById(id); return Ok(product); }
Bu kullanım, yapısal olarak daha tercih edilebilir bir davranış sergilememizi sağlamakta ve gereksiz yerlerden ilgili filtrenin tetiklenmesini arındırmaktadır.
Test Edelim
İşte bu kadar… Şimdi tek yapmamız gereken bu API’ye istek göndererek test etmek.
/api/Products/1 ya da /api/Products/2 | /api/Products/3 |
---|---|
![]() |
![]() |
Görüldüğü üzere iş mantığında üretilecek olan datanın kontrol sorumluluğunu bir filter aracılığıyla merkezi hale getirip sonraki action’lar da ki ihtiyaca istinaden çıkabilecek kod israfını önlemiş olduk…
Ve tabi daha profesyonel bir yaklaşım olduğu da aşikar 😉
İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…
Not : Örnek projeyi indirebilmek için buraya tıklayınız.
Bu yöntemin diğerlerine göre daha iyi olduğu kesin. Fakat UI katmanında olması sıkıntı değil mi? Yani klasik tabirle business katmanında bu tip kontrollerin yapılması daha doğru sanki. Java Spring de bulunan Aspect Oriented Programmimg’in .NET’e uyarlanmış hali olsa güzel olabilir. Bu arada try catch kullanımı, exception fırlatma ve bunların yönetimi hakkında detaylı bir video yapsanız çok iyi olur hocam. Piyasada ki projelerin çoğunda neredeyse her metot try catch içinde yazılıyor. Doğrusu nedir sizden dinlemek isteriz.
Hocam,
burada if bloğunun içini böyle tutmamız doğrumu? Yoksa try, catch içindemi tutmalıyım. Yani tam olarak sorum şu, API – de her controllerin içinde mutlaka try catch bloğu kullanılmalımı yoksa bunu handle etmenin başka yollarıda varmı?
Merhaba,
Mutlaka kullanılmalı diye bir şey diyemeyiz! Hatta mümkün mertebe kullanılmamaya özen gösterilmelidir. ‘Handle etmenin başka yolu var mı?” diye sormuşsunuz. Güzel soru… Exception’ları handle etmenin try-catch dışında farklı bir yolu olarak bu makaleyi okumanızı tavsiye edebilirim.
Sevgiler.
çok güzel ve önemli paylaşım eline sağlık
harika gerçekten