TB
← Tüm yazılar

ASP.NET Core middleware zinciri

ASP.NET Core middleware pipeline'inin çalışma mantigi, siralama kurallari ve özel middleware yaziminda dikkat edilmesi gereken teknik detaylari ele aliyoruz.

ASP.NET Core'da gelen her HTTP istegi, middleware pipeline adi verilen bir zincirden gecer. Bu zincir, uygulamanin capraz kesen endiselerini (cross-cutting concerns) moduler parcalara ayirmanin temel mekanizmasidir. Authentication, routing, CORS, exception handling ve static file sunumu gibi islevlerin çoğu middleware olarak uygulanir. Pipeline'i doğru anlamak, performans sorunlarini çözmek ve güvenli uygulama insa etmek icin zorunludur.

Middleware'in çalışma modeli

Her middleware iki asamali bir delegedir: istek gelirken Invoke veya InvokeAsync çalışır; yanıt donerken stack geri acilir. Konsept su sekildir:

app.Use(async (context, next) =>
{
    // Istek girisi (inbound)
    await next(context);
    // Yanit cikisi (outbound)
});

next cagrilmazsa pipeline kesilir; sonraki middleware'ler ve endpoint calismaz. Bu, erken donus (short-circuit) senaryolarinda kullanılır: örneğin API key gecersizse 401 donup pipeline'i durdurmak.

RequestDelegate ve IMiddleware

Iki middleware yazim stili vardir. Conventional middleware, constructor'da bir sonraki RequestDelegate'i alir:

public class CorrelationIdMiddleware
{
    private readonly RequestDelegate _next;

    public CorrelationIdMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context)
    {
        var correlationId = context.Request.Headers["X-Correlation-Id"].FirstOrDefault()
            ?? Guid.NewGuid().ToString("N");
        context.Items["CorrelationId"] = correlationId;
        context.Response.Headers["X-Correlation-Id"] = correlationId;
        await _next(context);
    }
}

IMiddleware arayüzü ise DI ile tam entegre çalışır; middleware kendisi scoped veya transient olarak kayıt edilebilir:

public class TenantMiddleware : IMiddleware
{
    private readonly ITenantResolver _resolver;

    public TenantMiddleware(ITenantResolver resolver) => _resolver = resolver;

    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        var tenant = await _resolver.ResolveAsync(context.Request);
        context.Items["Tenant"] = tenant;
        await next(context);
    }
}

Scoped bağımlılık gerektiren middleware'lerde IMiddleware veya UseMiddleware<T> factory pattern tercih edilir.

Siralama: en kritik karar

Middleware sırası davranisi kokten degistirir. Resmi dokumantasyondaki oneri sırası kabaca su sekildedir:

  1. Exception handling (en dis katman)
  2. HSTS, HTTPS redirection
  3. Static files
  4. Routing
  5. CORS
  6. Authentication
  7. Authorization
  8. Endpoint mapping

CORS'un routing'den once ama endpoint'ten once olmasi gerektigi sik karsilasilan bir hatadir. Authentication, Authorization'dan once gelmelidir; aksi halde kullanıcı kimligi cozulmeden yetki kontrolu anlamsiz kalir.

Exception handler en dista olmazsa, ic middleware'deki hatalar yakalanmayabilir ve ham stack trace istemciye sizabilir.

Branching pipeline: Map ve UseWhen

Tüm istekler ayni zincirden gecmek zorunda degildir:

app.Map("/metrics", metricsApp =>
{
    metricsApp.UseMiddleware<MetricsAuthMiddleware>();
    metricsApp.Run(async ctx => await ctx.Response.WriteAsync("ok"));
});

app.UseWhen(
    ctx => ctx.Request.Path.StartsWithSegments("/api"),
    branch => branch.UseMiddleware<ApiVersionMiddleware>());

Map path prefix ile tam dallanma yapar; UseWhen kosullu middleware ekler. Admin ve public API'ler icin farklı güvenlik katmanları bu yolla uygulanir.

Endpoint middleware ve routing

.NET 6+ ile minimal hosting modelinde pipeline su şekilde kurulur:

var app = builder.Build();
app.UseExceptionHandler();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

MapControllers ve MapGet gibi cagrilar terminal middleware ekler. Terminal middleware next cagirmaz; istegi sonlandirir. Routing middleware'i endpoint secimini yapar; secilen endpoint delegate calistirilir.

Performans ve allocation

Her middleware ek yük getirir. Gereksiz async state machine, her istekte allocation demektir. Basit header ekleme gibi islemlerde senkron devam yeterli olabilir; I/O bekleyen islemlerde await next(context) kullanın.

  • Middleware icinde agir işlem yapmayin; arka plan kuyruguna delege edin.
  • HttpContext.Request.Body okumak stream'i tuketir; tekrar okuma icin enable buffering gerekir.
  • Response basladiktan sonra status code değiştirmek mümkün olmayabilir.

Güvenlik middleware ornekleri

Güvenlik header'lari icin özel middleware yaygindir:

public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
    context.Response.Headers["X-Content-Type-Options"] = "nosniff";
    context.Response.Headers["X-Frame-Options"] = "DENY";
    context.Response.Headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
    await next(context);
}

Rate limiting .NET 7+ ile kutuphane olarak geldi; yine de eski surumlerde custom middleware ile token bucket uygulanabilir. Middleware seviyesinde IP bazli sinirlama, endpoint'e ulasmadan yuku azaltir.

Diagnostic ve logging

Pipeline'da gecikme olcumek icin middleware idealdir:

var sw = Stopwatch.StartNew();
try
{
    await next(context);
}
finally
{
    sw.Stop();
    _logger.LogInformation(
        "{Method} {Path} => {Status} in {Elapsed}ms",
        context.Request.Method,
        context.Request.Path,
        context.Response.StatusCode,
        sw.ElapsedMilliseconds);
}

OpenTelemetry entegrasyonunda Activity baslatmak da middleware icinde yapilir; boylece tüm downstream span'ler ayni trace altinda toplanir.

Test stratejisi

Middleware birim testinde DefaultHttpContext ve mock RequestDelegate kullanılır:

var context = new DefaultHttpContext();
context.Request.Path = "/api/orders";
RequestDelegate next = _ => Task.CompletedTask;
var middleware = new CorrelationIdMiddleware(next);
await middleware.InvokeAsync(context);
Assert.True(context.Response.Headers.ContainsKey("X-Correlation-Id"));

Entegrasyon testlerinde WebApplicationFactory ile gerçek pipeline ayaga kaldirilir; middleware'in diger bilesenlerle etkilesimi dogrulanir.

Yaygın hatalar

  • Cift exception handler: Hem UseExceptionHandler hem custom catch blogu ayni hatayi iki kez loglar.
  • Body okuma: Model binding'den once body'yi tuketen middleware form POST'lari bozar.
  • Scoped servis singleton middleware'de: Captive dependency ve stale state üretir.
  • Yanlış CORS sırası: Preflight OPTIONS istegi endpoint'e ulasmadan reddedilir.

Özet

Middleware pipeline, ASP.NET Core'un omurgasidir. Inbound ve outbound fazlari, short-circuit davranisi ve siralama kurallari iyi anlasildiginda capraz kesen endiseler temiz ayrilir. Özel middleware yazarken DI yaşam dongusune, performansa ve güvenlik header'larinin doğru uygulanmasina dikkat etmek; Map ve UseWhen ile dallanma kullanmak büyük cozumlerde pipeline'i yönetilebilir tutar.

Middleware factory ve activation

UseMiddleware<T> extension'i, middleware'i DI container uzerinden activate eder. Constructor injection RequestDelegate dışında servis alabilir; ancak singleton middleware scoped servis enjekte etmemelidir. Object pool veya middleware instance caching davranisi surume gore farklilik gosterebilir; resmi dokumantasyonu kontrol edin.

Reverse proxy ve forwarded headers

Kubernetes ingress veya Azure Front Door arkasinda çalışan uygulamalarda UseForwardedHeaders middleware'i scheme ve client IP bilgisini duzeltir. Bu middleware routing ve rate limiting'den once gelmelidir; aksi halde HTTPS zorunlulugu ve IP bazli kurallar yanlış çalışır.

Response compression ve caching

Compression middleware outbound fazda devreye girer. Static file middleware ile birlikte kullanıldığında cache header'lari doğru ayarlanmazsa CDN ve tarayici cache tutarsizligi olusur. Vary header ve content-type filtreleri compression options ile yapilandirilir.

Kaynak ve sürüm notlari

ASP.NET Core her major surumde pipeline davranisinda ince ayarlar yapar; minimal hosting model .NET 6 ile varsayilan oldu. Endpoint routing önceki UseMvc yapisinin yerini aldi. Middleware order documentation her release'te güncellenir; upgrade sirasinda breaking change kontrol listesine pipeline siralamasi eklenmelidir.

Kestrel server limitleri ve request body size middleware'den once veya sonra uygulanabilir; IIS ve reverse proxy arkasinda max request body ayarlari katmanli dusunulmelidir.

HttpContext ve Items sozlugu

HttpContext.Items request boyunca yasayan key-value deposudur. Correlation ID, tenant bilgisi veya feature flag sonucu burada tasınır; downstream middleware ve endpoint handler'lar ayni veriye erisir. Items thread-safe degildir ancak tek request tek thread modelinde guvenle kullanılır. Async flow'da HttpContextAccessor ile arka plan islerine context taşıma dikkat gerektirir; fire-and-forget task'larda capture edilen context stale olabilir.

Request buffering ve body tekrar okuma

Audit veya imza dogrulama middleware'leri request body'yi okumak isteyebilir. Varsayilan olarak Kestrel stream'i forward-only'dir. context.Request.EnableBuffering() cagrildiktan sonra body okunup Position = 0 ile sifirlanmalidir; aksi halde model binder bos body görür. Büyük body'lerde memory limiti MultipartBodyLengthLimit ile ayarlanir.

Terminal middleware ve Run

app.Run delegate pipeline sonuna terminal middleware ekler; sonrasinda baska middleware eklenemez. Health check veya webhook endpoint'leri icin Map altinda Run kullanmak izole davranis sağlar. Metrics endpoint'i authentication ile korunmali; Prometheus scrape genelde internal ag uzerinden yapilir.

Global exception handler ve ProblemDetails

IExceptionHandler (.NET 8+) exception handling'i middleware'den ayri bir abstraction'a tasir. ProblemDetails ile RFC 7807 uyumlu JSON donmek API tutarliligini artirir. Development ortaminda DeveloperExceptionPage ayrı tutulur; production'da generic mesaj ve detayli log ayrimi güvenlik icin zorunludur.