C# Minimal API'lerinde Küresel Hata İşleme
[[academy-video-youtube({"vid": "B5NsgtdwOlg", "start_time": "0", "title": "C# Minimal API'lerde Küresel Hata Yönetimi", "creator": "Tim Corey", "length": "13m 30s"})]]
Bir web API'si, işlenmemiş bir istisna attığında, varsayılan olarak bir geliştiricinin yerel olarak hata ayıklamasına yardımcı olan ve bir yabancının çağrı yığınınızı haritalamasına yardımcı olan türde bir hata sayfası döndürecektir. Satır numaraları, tür adları ve kaynak dosyanın yolu, isteği yapan kimseye geri akar. Hatanın olabileceği her nokta için yakalama yapmak doğru yaklaşımdır, ancak bu yalnızca bir sonraki unutulmuş try/catch kadar işe yarar. Küresel bir işleyici, uç noktanın kaçırdığı şeyi yakalayan güvenlik ağıdır.
Tim Corey, "C# Minimal API'lerde Global Hata Yönetimi" adlı videosunda, kasten bozuk bir uç noktaya sahip küçük bir minimal API oluşturur, koruma olmadan geri dönen geliştirici hata sayfasını gösterir ve herhangi bir yakalanmamış istisnayı kesmek ve genel bir 500 ile yanıtlamak için app.UseExceptionHandler'yi bağlar. Ayrıca uç nokta düzeyinde yönetimin neden tercih edilen yol olduğunu pekiştirir: küresel işleyici yedeklemedir, strateji değil. Minimal bir API gönderen herkes, asla yığın izi sunucuyu terk etmediğinden emin olmak isterse, middleware kurulumunu ve etrafındaki tasarım gerekçesini aşağıda bulacaktır.
Kırık Bir Uç Nokta ile Minimal API İnşa Etme
[1:08 - 3:01] Tim, adında ErrorDemoApp olan yeni bir .NET 8 ASP.NET Core Web API projesiyle başlar. Proje şablonu seçenekleri, varsayılan ayarlara yakın kalır: HTTPS açık, OpenAPI açık, kimlik doğrulama yok, üst düzey ifadeler açık bırakılmış ve kontrol kutusu işaretlenmemiş, çünkü bu bir minimal API'dir. Program.cs'te üretilen dosya Swagger'ı saklar, ancak hava durumu tahmini örneği uç noktası ve kaydı silinir, böylece dosya yalnızca temel bilgileri gösterir.
Örneğin yerine, sadece başarısızlık amacı taşıyan /demo adresine tek bir uç nokta ekler.
app.MapGet("/demo", () =>
{
throw new Exception("This is a demo exception");
});app.MapGet("/demo", () =>
{
throw new Exception("This is a demo exception");
});Projeyi Ctrl+F5 ile (hata ayıklama olmadan başlat) çalıştırmak, başarısızlığın gerçek bir HTTP arayan için yüzeye çıkması gerektiği şekilde çalışır, böylece Visual Studio hata ayıklayıcıyı engellemekten alıkoyar. Swagger açılır, /demo uç noktası yalnızca mevcuttur ve bunu yürütmek 500 yanıt döndürür. Yanıt gövdesi istisna türünü, mesajı ve Program.cs satır 18'e referansı barındırır.
Varsayılan Hata Sayfası Neden Uygulama Detaylarını Sızdırır?
[3:01 - 5:00] Tarayıcıda doğrudan /demo'ya (Swagger sarayı olmadan) çarptığında, JSON yanıtı yerine geliştirici istisna sayfasını gösterir. Sayfa, istisna adını, mesajı, atanma gerçekleştiği dosya yolunu ve satır numarasını, ham istisna ayrıntılarını ve atanma üzerindeki yığın çerçevelerini gösterir. Yerel olarak çalışan bir geliştirici için bu altın değerindedir. Başka biri içinse, kod tabanının ücretsiz bir haritasıdır.
Tim, bu sayfanın geliştiricilere yardımcı olmak için var olduğunu belirtir ve bu sayfa son kullanıcılara asla ulaşmamalıdır. Bazan bu olduğunda küresel bir işleyicinin neden önemli olduğu sorusudur. Her noktayı istisna işlemi içine alan takımlar bile sonunda birini kaçırır ve maliyeti onları isteyene tüm yığın izini göndermek olur.
İlk Olarak Uç Noktada Hataları Yakalamak
[5:00 - 6:30] Küresel işleyiciyi kurmadan önce Tim, tercih edilen yolu örneğim uç noktasını try/catch içine sarmaktadır. İşleyici, atılan herhangi bir istisna için Results.BadRequest(ex.Message) döndürüyor:
app.MapGet("/demo", () =>
{
try
{
throw new Exception("This is a demo exception");
}
catch (Exception ex)
{
return Results.BadRequest(ex.Message);
}
});app.MapGet("/demo", () =>
{
try
{
throw new Exception("This is a demo exception");
}
catch (Exception ex)
{
return Results.BadRequest(ex.Message);
}
});Sonuç, yalnızca mesaj dizesi taşıyan bir 400 olur. Yığın izi, dosya yolu, satır numarası yoktur. Mesajın kendisinin açığa çıkarılıp çıkarılmaması uygulamaya bağlıdır; genel bir API için, mesaj bile takımın istemediğinden daha fazla sızabilir, bu durumda işleyici genel bir dizeyle değiştirilir. Yerel yakalama, uç noktanın neyi gördüğünü tam kontrol etmesine olanak tanır; hatta, hata modunun gerçekten bilinir olduğu durumda 500 yerine daha spesifik bir durum kodu döndürme seçeneği de dahil.
Bu deseni yapamayacağı şey, uç noktanın sarmayı unuttuğu şeyi yakalamaktır. Herhangi bir yeni kod yolu, daha derin bir katmandan tekrar fırlatma, ayrı bir iş parçacığında fırlatan herhangi bir Task, tümüyle uç noktanın try/catch'ini atlar. Bu boşluğu middleware doldurur.
UseExceptionHandler Middleware Bağlama
[6:30 - 10:00] app.UseHttpsRedirection() satırının hemen altında, işleyici app.UseExceptionHandler ile kaydedilir. Oluşturucu hareketini alan aşırı yükleme, altta yatan hattı açığa çıkarır ve işleyicinin yanıt şeklini açıkça ayarlamasına izin verir:
app.UseExceptionHandler(appError =>
{
appError.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/json";
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature is not null)
{
Console.WriteLine($"Error: {contextFeature.Error}");
}
await context.Response.WriteAsJsonAsync(new
{
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error"
});
});
});app.UseExceptionHandler(appError =>
{
appError.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/json";
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature is not null)
{
Console.WriteLine($"Error: {contextFeature.Error}");
}
await context.Response.WriteAsJsonAsync(new
{
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error"
});
});
});Bu bloktaki birkaç seçenek önemlidir. Durum kodunu 500'e zorlamak, arayanın yanıt numarasından bir şey çıkaramayacağını ifade eder; iç istisna türü ne olursa olsun, yüzey aynı görünür. İçeriği application/json türüne zorlamak, API'nin diğer yanıtlarına uyar ve müşterileri tek bir analizciye bağlar. IExceptionHandlerFeature, gerçek bir işleyicinin kaydedebilmesi için orijinal istisnayı açığa çıkarır; Tim, buradaki projenin aslında taşıyacağı herhangi bir kaydediciyi temsil etmesi için Console.WriteLine'yi kullanıyor.
Son WriteAsJsonAsync çağrısı, durum kodu ve genel mesajla anonim bir nesne döndürüyor. Gövde, başarısız olan şeyin ötesinde neyin başarısız olduğuna dair hiçbir şey söylemez, ki bu noktadır. Dahili tanılar kayıtta yer alır, yanıtın kendisinde değil.
Yapılan ve Yapılmayan Yolları Test Etme
[10:00 - 13:14] Try/catch hala yerindeyken, uç nokta yerel yolu çalıştırır: Swagger, 'Bu bir demo istisnasıdır' taşıyan bir 400 gösterir. Yakalama bloğu ilk önce yakaladığı için ara yazılım atmayı hiç görmez. Bu, varsayılan olarak Tim'in tasarladığı tasarım: yerel işleyiciler işlerini yapar ve küresel işleyici uyurdadır.
Try/catch'i çıkarıp tekrar çalıştırmak yedeği kullanmasını sağlar. Aynı istek artık 500 ve JSON gövdesi { "statusCode": 500, "message": "Internal Server Error" } ile döndürüyor. Yanıttaki hiçbir şey, istisnanın nereye çarptığını veya ne tür olduğunu göstermez. Ancak Visual Studio konsol penceresi, dosya yolu ve satır numarası dahil olmak üzere Console.WriteLine yedeği aracılığıyla kaydedilen orijinal istisna metnini gösteriyor. Tam teşhis, geliştiricilerin okuyabileceği yerde kalır; yanıt, sızma olasılığının olmadığı yerde kalır.
Bu desen, doğrulama middleware ile minimal API'ye, özel kimlik doğrulamaya veya başka herhangi bir hat bileşenine taşınır. İstisna işleyici, hattın erken bir aşamasında oturur ve daha sonraki bir aşamadan yukarı propagandayı yakalar.
Sonuç: Derinlikte Savunma
[13:14 - 13:30] Yerel işleyici ve küresel işleyici alternatifler değildir; katmanlardır. Yerel işleyici, hata modu bilindiğinde, uç noktaya anlamlı bir şekilde yanıt verme şansı verir. Küresel işleyici, yerel katmanın kaçırdığı her şeyin tutarlı, genel ve güvenli bir yanıt üretmesini sağlar. Controller tabanlı API'ler, küçük dilbilgisi uyarlamaları ile aynı fikri kullanır, ancak minimal API formu, ilk önce alışılmaya değer olanıdır çünkü yüzey alanı, bir dosyada tüm resmi görmelerini sağlamak için yeterince küçüktür.
Sonuç
[13:14 - 13:30] Minimal API'de küresel bir hata işleyici kurmak üç adımdır: UseExceptionHandler'yi boru hattının erken aşamasında kaydedin, işleyici içinde yanıt durumunu ve içerik türünü ayarlayın ve hiçbir uygulama detayı kaçmasın diye kasten genel bir gövde yazın. Bunu, en çok başarısız olma olasılığı olan kod yolları çevresindeki yerel try/catch bloklarıyla eşleştirin, böylece küresel işleyici strateji değil, bir güvenlik ağının parçasıdır.
Örnek İpucu: İşleyici gerçek bir günlükçüye çağrı yaparken, yapılandırılmış günlük hattı türü, yığını ve varsa iç istisnaları yakaladığı için tüm istisna nesnesini (yalnızca mesajı değil) geçirin. Serilog gibi bir günlükleyici, tüm bunları sorgulanabilir özellikler olarak koruyacaktır, bu da, bir üretim 500 için ateşlenen uyarının, kimsenin isteği yeniden çalıştırmasına gerek kalmadan yerel olarak yeniden oluşturulabilecek kadar bağlam taşıdığı anlamına gelir.
Tam videoyu, YouTube'daki Kanalında izleyin ve 10 Dakikalık Eğitim serisinde üretime hazır minimal API'ler oluşturma konusunda daha fazla fikir edinin.

