ASP.NET Core Middleware ve Action Filter

ASP.NET Core request pipeline'ına müdahale etmek için middleware yazımı ve action filter'ların (authorization, exception, logging) nasıl oluşturulacağı uygulamalı anlatılır.

ASP.NET Core Middleware ve Action Filter

ASP.NET Core Pipeline'ını Özelleştirmek: Middleware ve Action Filter'lar

ASP.NET Core'un en güçlü yanlarından biri, HTTP isteklerinin işlenişine müdahale edebilme esnekliğidir. Bu iki temel mekanizma vardır: Middleware (tüm istekler için genel) ve Action Filters (Controller/Action özelinde). Bunları doğru kullanmak, kod tekrarını önler, cross-cutting concern'leri (loglama, validasyon, hata yönetimi) temiz bir şekilde ayrıştırır.


1. Middleware: Pipeline'ın İlk Kapısı

Middleware, istek (request) ve yanıt (response) akışında her istek için çalışan ardışık (pipeline) bileşenlerdir. Her middleware, bir sonraki middleware'i çağırabilir (next) veya pipeline'ı kısa devre yaparak doğrudan yanıt dönebilir.

Basit Bir Middleware Örneği (Loglama):

csharp

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

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

    public async Task InvokeAsync(HttpContext context)
    {
        // İSTEK GELİRKEN - ÖN İŞLEM
        _logger.LogInformation($"Request: {context.Request.Method} {context.Request.Path}");

        // Bir sonraki middleware'i çağır (pipeline'da ilerle)
        await _next(context);

        // YANIT DÖNERKEN - SON İŞLEM
        _logger.LogInformation($"Response: {context.Response.StatusCode}");
    }
}

// Program.cs / Startup.cs'ye ekleme:
app.UseMiddleware<RequestLoggingMiddleware>();
// veya extension metot ile:
public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder builder)
    => builder.UseMiddleware<RequestLoggingMiddleware>();

Middleware Sıralaması Çok Önemlidir!

  • UseExceptionHandler() en üstte olmalı (tüm hataları yakalamak için).

  • UseAuthentication() kimlik doğrulama için.

  • UseAuthorization() yetkilendirme için.

  • UseEndpoints() en sonda.

Kısa Devre (Short-Circuit) Örneği:
Diyelim ki /health endpoint'ine gelen isteklerin pipeline'ın geri kalanına gitmesini istemiyorsunuz.

csharp

public async Task InvokeAsync(HttpContext context)
{
    if (context.Request.Path.StartsWithSegments("/health"))
    {
        context.Response.StatusCode = 200;
        await context.Response.WriteAsync("Healthy");
        return; // next çağrılmaz, pipeline burada biter!
    }
    await _next(context);
}

2. Action Filter'lar: Controller/Action Özelinde Müdahale

Action Filter'lar, middleware'den farklı olarak MVC/WebAPI katmanında çalışır ve yalnızca belirli Controller veya Action'lara uygulanabilir. 5 tür filter vardır:

  • Authorization Filter (En önce çalışır, yetkilendirme)

  • Resource Filter (Model binding öncesi/sonrası, cache için)

  • Action Filter (Action metodu öncesi/sonrası)

  • Exception Filter (Action içinde fırlayan hataları yakalar)

  • Result Filter (ActionResult oluşturulduktan sonra, yanıt yazılmadan önce)

Örnek: Performans Loglama Action Filter (IAsyncActionFilter)

csharp

public class PerformanceLoggingFilter : IAsyncActionFilter
{
    private readonly ILogger<PerformanceLoggingFilter> _logger;

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

    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var stopwatch = Stopwatch.StartNew();

        // Action öncesi
        _logger.LogInformation($"Action {context.ActionDescriptor.DisplayName} başlıyor.");

        // Bir sonraki filter veya action'ı çalıştır
        var executedContext = await next();

        // Action sonrası
        stopwatch.Stop();
        _logger.LogInformation($"Action tamamlandı. Süre: {stopwatch.ElapsedMilliseconds}ms");
    }
}

// Controller/action üzerinde kullanım:
[ServiceFilter(typeof(PerformanceLoggingFilter))]
public IActionResult Get(int id) { ... }

// Veya global olarak (tüm action'lar için):
builder.Services.AddControllers(options =>
{
    options.Filters.Add<PerformanceLoggingFilter>();
});

Exception Filter ile Hata Yakalama:

csharp

public class CustomExceptionFilter : IExceptionFilter
{
    private readonly ILogger<CustomExceptionFilter> _logger;

    public void OnException(ExceptionContext context)
    {
        _logger.LogError(context.Exception, "Action hatası!");

        // Özel bir hata yanıtı dön:
        context.Result = new ObjectResult(new { error = "Bir hata oluştu." })
        {
            StatusCode = 500
        };
        context.ExceptionHandled = true; // Exception'ı bastır
    }
}

3. Middleware vs Action Filter: Ne Zaman Hangisi?

Özellik Middleware Action Filter
Kapsam Tüm istekler (statik dosyalar dahil) Sadece MVC/WebAPI action'ları
Erişim HttpContext üzerinden ham veri Model binding sonrası zengin veri (Controller, ModelState, ActionArguments)
Örnek Kullanım Authentication, CORS, Statik Dosya, Özel Header Loglama, Validasyon, Cache, İş mantığı öncesi kontrol
Sıralama Pipeline sırasına göre Filter türüne göre (Authorization > Resource > Action > Exception > Result)

Altın Kural: Eğer işleminiz Controller/Model bilgisine ihtiyaç duymuyorsa (ör. gelen token'ı doğrulama, CORS), Middleware kullanın. Eğer Action parametrelerine, ModelState'e veya ActionResult'a müdahale edecekseniz, Action Filter kullanın.


4. Factory-based Filters (Dependency Injection ile) ve ServiceFilter/TypeFilter

Filter'ları DI üzerinden kaydedip kullanmanın iki yolu:

  • [ServiceFilter] : Filter'ı DI container'a kaydetmenizi gerektirir (ölümcül bağımlılıkları çözümler).

  • [TypeFilter] : Filter'ı DI'a kaydetmeye gerek yoktur, container'dan çözümler ama her seferinde yeni bir örnek oluşturur.

csharp

// DI'a kaydet
builder.Services.AddScoped<PerformanceLoggingFilter>();

// Controller'da kullan
[ServiceFilter(typeof(PerformanceLoggingFilter))]
public IActionResult Get() { ... }

Not: Filter'lar varsayılan olarak Scoped yaşam döngüsüne sahiptir. İçinde DbContext kullanacaksanız, Scoped olması doğrudur. Ancak Singleton bir filter istiyorsanız (önerilmez), yaşam döngüsüne dikkat edin.


5. Performans ve Dikkat Edilmesi Gereken Noktalar

  • Async Kullanımı: Mümkünse IAsyncActionFilter veya IAsyncResultFilter kullanarak thread havuzunu bloklamayın.

  • Middleware'de HttpContext.Response.Body ile oynamak tehlikelidir: Yanıt yazılmaya başlandıktan sonra (response body) değiştirilemez. Bu nedenle yanıta müdahale edecekseniz (ör. loglama için), bunu next çağrıldıktan önce yapın veya bir Stream wrapper ile sarın.

  • Filter Sıralamasını Kontrol Edin: [ServiceFilter] ve [TypeFilter] sıralamasını Order parametresi ile belirleyebilirsiniz.

  • Exception Filter vs Middleware: Exception filter sadece MVC katmanındaki hataları yakalar. Middleware ise tüm pipeline'daki hataları yakalar. Bu nedenle global hata yönetimi için UseExceptionHandler middleware'i tercih edilir; sadece belirli Action'lara özel hata yönetimi için Exception Filter kullanılır.


Sonuç:

ASP.NET Core pipeline'ında Middleware genel, her isteği kapsayan işlemler için; Action Filter ise MVC/WebAPI katmanında daha kontrollü, veriye erişim gerektiren işlemler için idealdir. İkisini birlikte kullanarak uygulamanızı modüler, test edilebilir ve bakımı kolay hale getirebilirsiniz. Unutmayın: Pipeline sırası, performans ve güvenlik için hayati önem taşır.

Tüm yazılar