Asp.NET Core 8 – Global Error Handling
Merhaba,
Geliştirdiğimiz uygulamalarımızı her ne kadar hassas ve güvenilir bir şekilde tasarlıyor olsak da, genellikle yazılımların çalışma sürecinin doğal bir parçası olan hata durumları ister istemez meydana gelecektirler ve özellikle bu durumlar web uygulamaları için neredeyse kaçınılmazdır diyebiliriz. Ancak, iyi bir yazılım geliştirme sürecinde, düzgün test stratejileri ve hataya dayanıklı bir kod yazma yaklaşımı ile olası hataların sayısı azaltılabilecek ve uygulamaların daha sağlam ve sağlıklı olması sağlanabilecektir. Bunların yanında yine de hatalar gerçeğin birer parçası olduğunu hissettirecek ve elbet dış dünyadan gelen inputlardan yahut işin kalbi olan veritabanlarından, belki de hiçbir zaman bilemeyeceğimiz türlü faktörlerden kaynaklı er ya da geç kendilerini gösterecektirler. İşte bu tarz durumlarda -biz elimizden geleni yaptık, taktir böyleymiş- demeyecek ve hataları kullanıcılara yansıtmaksızın yöneteceğiz. Hata durumlarına karşın farklı davranışları devreye sokup, kullanıcı açısından olması gereken manipülasyonları gerçekleştireceğiz ve biryandan da, yeri gelecek trace ederek yeri gelecek loglama da bulunarak yazılım sürecinin risk yönetimine dair önemli olan bu parçaları eşliğinde süreci en ideal boyuta taşımaya odaklanacağız.
Bizler bu ideal boyut için önceden klavyeye aldığımız Asp.NET Core’da ki Exception’ları Otomatik Olarak Yönetin başlıklı yazımızda uygulayabileceğimiz yöntemlerden en elverişli olanını incelemiş ve tecrübe etmiştik.
Ya da alternatif olarak aşağıdaki gibi bir middleware eşliğinde de süreçte alınabilecek tüm hatalara karşın bir tutum sergileyebiliriz.
public class ExceptionHandlingMiddleware(ILogger<ExceptionHandlingMiddleware> logger, RequestDelegate next) { public async Task InvokeAsync(HttpContext context) { try { await next(context); } catch (Exception exception) { string errorMessage = $"Bir hata oluştu. Hata mesajı : {exception.Message}"; logger.LogError(exception, errorMessage); context.Response.StatusCode = StatusCodes.Status500InternalServerError; await context.Response.WriteAsJsonAsync(new { Title = "Server Error", Status = context.Response.StatusCode, Message = errorMessage }); } } }
Tabi bu middleware’in aşağıdaki gibi yapılandırılması kaydıyla;
var builder = WebApplication.CreateBuilder(args); builder.Services.AddLogging(); var app = builder.Build(); app.UseMiddleware<Global.Error.Handling.Example.Traditional_Method.ExceptionHandlingMiddleware>(); app.MapGet("/", () => { throw new Exception("Laylaylom galiba sana göre sevmeler..."); }); app.Run();
Görüldüğü üzere Asp.NET Core olası hata durumlarına karşın refleks gösterebilmemiz için bizlere birkaç seçenek sunmaktadır. Bu seçeneklere ek olarak Asp.NET Core 8 ile birlikte hata durumlarını yönetebilmek için IExceptionHandler
interface’ini sunmaktadır .
public class ExceptionHandler(ILogger<ExceptionHandler> logger) : IExceptionHandler { public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken) { string errorMessage = $"Bir hata oluştu. Hata mesajı : {exception.Message}"; logger.LogError(exception, errorMessage); httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; await httpContext.Response.WriteAsJsonAsync(new { Title = "Server Error", Status = httpContext.Response.StatusCode, Message = errorMessage }); return true; } }
Bu interface, yukarıdaki kod bloğunda da görüldüğü üzere TryHandleAsync
metodunu uygulattırmakta ve olası hataya karşın bir handle söz konusuysa geriye true değilse false olmak üzere boolean değer döndürmektedir.
Oluşturulan bu exception handler sınıfını Asp.NET Core request pipeline’ına dahil edebilmek için aşağıdaki gibi bir yapılandırma yeterlidir.
var builder = WebApplication.CreateBuilder(args); builder.Services.AddLogging(); builder.Services.AddExceptionHandler<Global.Error.Handling.Example.New_Method.ExceptionHandler>(); builder.Services.AddProblemDetails(); var app = builder.Build(); app.UseExceptionHandler(); app.MapGet("/", () => { throw new Exception("Laylaylom galiba sana göre sevmeler..."); }); app.Run();
Görüldüğü üzere; 5. satırda olduğu gibi AddExceptionHandler
metodu ile dependecy olarak uygulamaya ilgili servisi ekliyor, 6. satırda da olası hatanın detaylarına dair bir yanıt oluşturabilmek için AddProblemDetails
servisini dahil ediyoruz. Ve son olarak da 10. satırda olduğu gibi UseExceptionHandler
middleware’ini çağırarak ExceptionHandlerMiddleware
‘i devreye sokuyoruz.
Uygulamayı bu vaziyette derleyip, çalıştırdığımızda olası hata durumlarında exception handler sınıfının çalıştığını aşağıdaki gibi gözlemleyebiliriz;
Ayrıca birden fazla exception handler sınıfını da uygulamaya dahil edebilir ve olası hata durumuna karşın kaydedilme sırasına göre kontrol sağlayabilirsiniz. Şöyle ki;
public class DivideByZeroExceptionHandler(ILogger<DivideByZeroExceptionHandler> logger) : IExceptionHandler { public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken) { if (exception is not DivideByZeroException) return false; string errorMessage = $"Bir hata oluştu. Hata mesajı : {exception.Message}"; logger.LogError(exception, errorMessage); httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; await httpContext.Response.WriteAsJsonAsync(new { Title = exception.GetType().ToString(), Status = httpContext.Response.StatusCode, Message = errorMessage }); return true; } }
public class NullReferenceExceptionHandler(ILogger<NullReferenceExceptionHandler> logger) : IExceptionHandler { public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken) { if (exception is not NullReferenceException) return false; string errorMessage = $"Bir hata oluştu. Hata mesajı : {exception.Message}"; logger.LogError(exception, errorMessage); httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; await httpContext.Response.WriteAsJsonAsync(new { Title = exception.GetType().ToString(), Status = httpContext.Response.StatusCode, Message = errorMessage }); return true; } }
Burada oluşturduğumuz exception handler sınıflarına dikkat ederseniz, davranışsal olarak alınan hata kontrol edilmekte ve uygun olmadığı taktirde false değeri döndürülerek exception handle’ın o sınıfta olmayacağı ifade edilmektedir. Böylece bir sonraki handler sınıfı devreye girecek ve bu süreç true dönecek olan handler sınıfına kadar devam edecektir! İşte bu yüzden davranışsal olarak en genel hata türüne hitap edecek olan handler sınıfının aşağıdaki gibi en son tanımlanması gerekecektir.
builder.Services.AddExceptionHandler<Global.Error.Handling.Example.New_Method.DivideByZeroExceptionHandler>(); builder.Services.AddExceptionHandler<Global.Error.Handling.Example.New_Method.NullReferenceExceptionHandler>(); builder.Services.AddExceptionHandler<Global.Error.Handling.Example.New_Method.ExceptionHandler>();
Yani burada bir hata alındığı taktirde önce DivideByZeroExceptionHandler
kontrol edilecek eğer false dönerse ardından NullReferenceExceptionHandler
kontrol edilecektir. Eğer bu da false dönerse son olarak da ExceptionHandler
kontrol edilecektir. Süreçte bunlardan herhangi biri true döndüğü taktirde diğerleri kontrol edilmeyecektir. Yahut hepsi false dönerse kullanıcıya herhangi bir result döndürülmeyecektir.
Asp.NET Core 8 ile gelmiş olan IExceptionHandler
ile middleware yaklaşımına nazaran hata durumlarına karşın daha efektif ve esnek davranışlar sergileyebildiğimiz kanaatindeyim. Bunların dışında tabi ki de middleware’ler ile de harika sonuçlar ortaya koyduğumuz aşikar. Haliyle ben deniz, bundan sonraki projelerimde bu yeni yaklaşımdan istifade edecek amma velakin middleware’leri de yabana atmamaya özen göstereceğim 🙂
İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…
Not : Örnek çalışmaya aşağıdaki github adresinden erişebilirsiniz.
https://github.com/gncyyldz/Global.Error.Handling.Example
İçerik için çok teşekkürler. Peki MVC kullanarak bu detayı nasıl hata sayfasına yönlendirebiliriz?
adam adam