Yükleniyor

Neden Try-Catch Bloklarından Kurtulmalısınız .NET'te Merkezi Hata Yönetimi

Blog Kategorileri

.Net Core Mimariler
Tasarım Desenleri
ORM Araçları
API Geliştirme
Web Geliştirme
Veritabanları
25 Mart 2026 Çarşamba
Neden Try-Catch Bloklarından Kurtulmalısınız .NET'te Merkezi Hata Yönetimi

Merhaba,

Yazılım geliştirme süreçlerinde sürdürülebilirlik, kodun yalnızca çalışmasından çok daha fazlasını ifade eder. Özellikle backend uygulamarları için, projenin ilk aşamalarında hata yönetimi yaygın olarak tercih edilen try-catch blokları, başlangıçta pratik ve güvenli bir çözüm sunar. Ancak uygulama ölçeklendikçe, bu yaklaşım kontrolsüz bir şekilde yayılmaya başlar ve zamanla iş mantığının önüne geçen bir gürültüye dönüşür. Artan kod karmaşıklığıyla birlikte, asıl değeri üreten kodu ayırt etmek zorlaşır ve bakım maliyetleri yükselir.

Modern .NET uygulamalarında ise daha sürdürülebilir ve profesyonel yaklaşım, hata yönetimini metod seviyesinde ele almak yerine uygulama genelinde merkezi bir mimariyle yönetmektir Bu yaklaşım, kod tabanını sadeleştirmenin yanı sıra, tüm istemciler için tutarlı ve öngörülebilir bir hata yanıt standardı sunar. Bu yazıda, .NET ekosisteminin sunduğu güncel araçlar ve yaklaşımlar doğrultusunda, merkezi hata yönetimi yapısının nasıl kurgulanabileceğini adım adım inceleyeceğiz.

BaseResult Pattern Nedir?

Kod örneklerine geçmeden önce, kullandığımız BaseResult yapısına bir parantez açalım. API geliştiren ekiplerin en büyük sorunlarından biri, her endpoint'in farklı formatlarda veri dönmesidir. Bir hata oluştuğunda bazen sadece düz metin, bazen bir obje, bazen de boş bir sayfa dönebilir.

BaseResult Pattern, tüm API yanıtlarını standart bir zarf içine almamızı sağlar. Bu yapı sayesinde istemci, gelen cevabın başarılı olup olmadığını her zaman aynı property üzerinden kontrol edebilir. Projemizde kullandığımız standart yapı:

    public class BaseResult<T>
    {
        public T? Payload { get; set; }
        public List<Error>? Errors { get; set; }

        [JsonIgnore]
        public bool IsSuccess => Errors == null || !Errors.Any();

        [JsonIgnore]
        public bool IsFailure => !IsSuccess;

        public static BaseResult<T> Success(T payload)
        {
            return new BaseResult<T> { Payload = payload };
        }
        public static BaseResult<T> Fail(string message)
        {
            return new BaseResult<T> { Errors = [new Error { ErrorMessage = message }] };
        }

    }
    public class Error
    {
        public string ErrorMessage { get; set; }
        public string? PropertyName { get; set; }
    }

Hata nerede olursa olsun, kullanıcıya her zaman Errors listesi olan tutarlı bir JSON paketi döneriz.

Neden Try-Catch Her Yerde Kullanılmamalı?

Hata yönetimini metod bazlı yapmak yerine merkezi bir yapıya taşımanın üç temel nedeni vardır:

  1. Okunabilirlik: İş kurallarınızı içeren temiz kod, hata yakalama bloklarının gürültüsü arasında kaybolur. Kodun asıl amacını anlamak zorlaşır.

  2. Kod Tekrarı: Her metotta benzer loglama ve hata formatlama kodlarını yazmak, Spaghetti Code davetiyesidir.

  3. Bakım Zorluğu: Hata yönetim stratejinizi veya kullanıcıya dönen JSON formatını değiştirmek istediğinizde, yüzlerce metodu tek tek güncellemeniz gerekir.

.NET ekosistemi, bu sorunları profesyonelce çözmek için bize güçlü araçlar sunuyor. Gelin, bu yöntemleri basitten moderne doğru inceleyelim.

Custom Middleware ile Merkezi Hata Yönetimi

ASP.NET Core mimarisinin temelini oluşturan request-response pipeline, gelen her isteğin belirli bileşenlerden sırasıyla geçerek işlenmesini sağlar. Bu yapı sayesinde, uygulamaya giren ve çıkan her isteği merkezi bir noktadan kontrol etme imkânı elde ederiz.

Custom middleware kullanarak hata yönetimini bu pipeline’ın erken aşamalarına konumlandırdığımızda, uygulama içerisinde fırlatılan ve herhangi bir yerde ele alınmamış (unhandled) tüm exception’ları tek bir noktada yakalayabiliriz. Bu, yalnızca controller veya servis katmanında oluşan hataları değil; aynı zamanda routing, authentication, authorization gibi daha alt seviyelerde meydana gelen hataları da kapsayan geniş bir kontrol alanı sağlar.

Middleware tabanlı yaklaşımın en büyük avantajlarından biri, uygulamanın tamamını kapsayan bir güvenlik ağı oluşturmasıdır. Herhangi bir katmanda unutulmuş bir try-catch bloğu veya öngörülmemiş bir hata senaryosu olsa bile, bu hatalar kullanıcıya kontrolsüz bir şekilde yansımaz. Bunun yerine, tüm hatalar standart bir formatta ele alınarak loglanır ve istemciye tutarlı bir yanıt döndürülür.

Ayrıca bu yaklaşım, hata yönetimi ile iş mantığını kesin bir şekilde birbirinden ayırır. Controller veya servis katmanındaki kodlar yalnızca kendi sorumluluklarına odaklanırken, hata yakalama, loglama ve response üretme gibi sorumluluklar tamamen middleware katmanına taşınmış olur. Bu da kodun hem okunabilirliğini hem de bakımını önemli ölçüde iyileştirir.

public class ExceptionHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ExceptionHandlingMiddleware> _logger;

    public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context); // İsteği bir sonraki aşamaya iletir
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Beklenmeyen bir hata oluştu: {Message}", ex.Message);
            await HandleExceptionAsync(context, ex);
        }
    }

    private static Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

        // BaseResult gibi standart bir yapı kullanarak tutarlı response dönüyoruz
        var response = BaseResult<object>.Fail("Sistem kaynaklı bir hata oluştu. Lütfen teknik ekiple iletişime geçiniz.");
        return context.Response.WriteAsJsonAsync(response);
    }
}

Bu middleware'i Program.cs içerisinde çağırmak yeterlidir:

app.UseMiddleware<ExceptionHandlingMiddleware>();

Exception Filters

Hata yönetimini daha spesifik bir kapsamda, özellikle MVC/Web API katmanı ile sınırlı tutmak istediğiniz senaryolarda Exception Filter’lar oldukça etkili bir çözüm sunar. IExceptionFilter arayüzü sayesinde, yalnızca controller ve action seviyesinde fırlatılan exception’ları yakalayabilir ve bu katmana özel bir yönetim stratejisi geliştirebilirsiniz.

Middleware yaklaşımından en önemli farkı, HTTP pipeline’ın daha üst seviyesinde değil, doğrudan MVC execution sürecinin içinde konumlanmasıdır. Bu sayede filter’lar; route verileri, action parametreleri, model binding sonuçları gibi controller context’ine ait detaylı bilgilere erişebilir. Özellikle hatayı oluştuğu bağlamla birlikte değerlendirmek istediğiniz durumlarda bu ciddi bir avantaj sağlar.

Exception Filter’lar, genellikle iş kurallarına daha yakın hata senaryolarını ele almak için tercih edilir. Örneğin; belirli bir exception tipine göre farklı HTTP status kodları döndürmek, validation hatalarını özelleştirmek veya domain’e özel hata mesajları üretmek gibi ihtiyaçlarda oldukça esnek bir yapı sunar.

Ancak burada kritik bir sınır olduğunu unutmamak gerekir: Exception Filter’lar yalnızca MVC pipeline’ı içerisinde çalışır. Yani middleware katmanına hiç ulaşamayan bir hata (örneğin authentication aşamasında oluşan bir exception) bu filter’lar tarafından yakalanamaz. Bu nedenle, Exception Filter’lar genellikle middleware ile birlikte, daha katmanlı bir hata yönetimi stratejisinin parçası olarak konumlandırılır.

public class CustomExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        // Loglama işlemleri burada yapılabilir
        var result = new ObjectResult(BaseResult<object>.Fail("İşlem sırasında bir hata meydana geldi."));
        context.Result = result;
        context.ExceptionHandled = true; // Hatanın ele alındığını işaretliyoruz
    }
}

Global kullanım:

builder.Services.AddControllers(options =>
{
    options.Filters.Add<CustomExceptionFilter>();
});

Kritik Not: Exception Filter'lar sadece MVC/Web API içindeki hataları yakalar. Middleware ise daha derindedir; kimlik doğrulama (auth) aşamasında veya routing sırasında oluşan hataları da yakalayabilir.

.NET 8 ile Gelen IExceptionHandler

.NET 8 ile birlikte gelen IExceptionHandler arayüzü, merkezi hata yönetimini daha standart, esnek ve framework ile tam uyumlu hale getiren modern bir yaklaşım sunar. Geleneksel olarak custom middleware ile çözülen birçok senaryo, artık framework’ün sağladığı yerleşik mekanizmalar üzerinden daha sade ve sürdürülebilir bir şekilde ele alınabilir.

Bu yaklaşımın en önemli avantajı, hata yönetiminin doğrudan ASP.NET Core’un kendi exception handling pipeline’ına entegre edilmesidir. Böylece hataları yakalamak için manuel middleware kurgulamak yerine, framework’ün sunduğu altyapıyı genişleterek ilerleriz. Bu durum hem gereksiz kod tekrarını azaltır hem de uygulamanın genel mimarisiyle daha tutarlı bir yapı oluşturur.

IExceptionHandler aynı zamanda modüler bir kullanım sunar. Farklı exception tipleri için ayrı handler’lar tanımlayarak, her bir hata senaryosunu kendi bağlamında ele almak mümkündür. Örneğin; validation hataları, iş kuralı ihlalleri veya sistemsel hatalar için ayrı handler’lar tanımlanabilir ve bu handler’lar belirli bir sıra içerisinde çalışacak şekilde yapılandırılabilir. Özellikle büyük ölçekli uygulamalarda hata yönetimini daha okunabilir ve sürdürülebilir hale getirir.

Framework’ün optimize edilmiş pipeline’ı içerisinde çalıştığı için performans açısından da avantaj sağlar ve gereksiz middleware katmanlarının önüne geçer. Ayrıca ProblemDetails gibi standartlarla doğal entegrasyon sunar. Ancak burada önemli bir noktaya dikkat edilmelidir: ProblemDetails her ne kadar standart bir hata formatı sağlasa da, production ortamında exception detaylarının doğrudan istemciye yansıtılması güvenlik riski oluşturabilir. Bu nedenle hata detayları loglanmalı, istemciye ise her zaman sade ve güvenli mesajlar döndürülmelidir.

public class GlobalExceptionHandler : IExceptionHandler
{
    private readonly ILogger<GlobalExceptionHandler> _logger;

    public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
    {
        _logger = logger;
    }

    public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
    {
        _logger.LogError(exception, "Hata yakalandı: {Message}", exception.Message);

        var response = BaseResult<object>.Fail("Sunucu tarafında bir hata oluştu.");
        
        httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
        await httpContext.Response.WriteAsJsonAsync(response, cancellationToken);

        return true; // İşlem tamamlandı, pipeline burada kesilir.
    }
}

Program.cs tarafında ise kurulum oldukça sadedir:

builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails(); // RFC 7807 standardı için önerilir

var app = builder.Build();
app.UseExceptionHandler(); // Middleware'i aktif eder

Karşılaştırma Tablosu

Yaklaşım Etki Alanı Temel Avantajı Tercih Edilme Sebebi
Custom Middleware Tüm HTTP Pipeline Esneklik ve tam kontrol sağlar. Auth ve Routing hatalarını da kapsamak için.
Exception Filters MVC / API Katmanı Action context verilerine erişir. Sadece Controller seviyesinde özel mantık kurmak için.
IExceptionHandler Tüm Uygulama Modern ve optimize edilmiş standarttır. .NET 8+ projelerinde güncel mimariyi uygulamak için.

 

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace CentralizedErrorHandling.CustomMiddleware.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TestController : ControllerBase
    {
        [HttpGet]
        public IActionResult Test()
        {
            int x = 5;
            int y = 0;
            int z = x / y;
            return Ok();
        }
    }
}

Tüm bu yöntemleri somutlaştırmak adına basit bir DivideByZero (sıfıra bölme) senaryosu üzerinden ilerledik. Visual Studio içerisinde int test = 5 / 0; şeklinde statik bir tanımlama yaparsanız, derleyici bunu fark eder ve build hatası alırsınız. Ancak bölme işleminin dinamik hesaplamalar sonucunda sıfıra denk geldiği durumlarda hata, uygulama çalışırken ortaya çıkar.

Aşağıda, merkezi hata yönetimi uygulanmadığında ve uygulandığında sistemin sergilediği davranışı gösteren görselleri inceleyebilirsiniz. Elbette bu özel senaryo için sadece controller seviyesinde bir try-catch bloğu kullanmak yeterli olabilir; fakat buradaki asıl amacımız, farklı yöntemleri deneyimleyerek merkezi bir hata yönetimi yaklaşımı kazanmak ve projenin her katmanında tutarlı bir yapı inşa etmek.

Görsel Karşılaştırma: Neyi Değiştiriyoruz?

Hiçbir kontrolümüz olmadığında karmaşık ve güvenli olmayan bir hata mesajıyla karşılaşırken; merkezi yönetimle birlikte anlaşılan, şık bir JSON objesi döneriz.

[Görsel: Hata Yönetimi Olmayan Durum] -> Tarayıcıda veya Postman'de ham hata mesajı ve 500 kodu.

Hata Yönetimi Olmayan Durum --> Tarayıcıda veya Postman'de ham hata mesajı ve 500 kodu.


Merkezi Hata Yönetimi Uygulanmış Durum --> BaseResult formatında Fail dönen, temiz bir JSON yanıtı.


Önemli Güvenlik Hatırlatması

Geliştirme aşamasında Development hata detaylarını görmek faydalıdır ancak Production ortamında asla exception mesajını veya stack trace bilgilerini kullanıcıya dönmemelisiniz. Bu, saldırganlara sistem mimariniz hakkında ipucu veren ciddi bir güvenlik açığıdır. Hatalar loglanmalı, kullanıcıya ise her zaman sabit bir mesaj dönmelidir.

Sonuç 

Hata yönetimi, sadece uygulamanın çökmesini engellemek değildir; aynı zamanda uygulamanın profesyonelliğini yansıtan bir aynadır.  Merkezi hata yönetimi, projelerinizi bu profesyonelliğe ulaştıracak en önemli adımlardan biridir.

Bir sonraki yazımda görüşmek üzere! 👋