RRedis Handbook

UZMAN

ASP.NET Output Cache & Response Cache

HTTP response'ları Redis'te cache'leyerek backend yükünü dramatik azaltma.

Kod örneği görünümü Bu sayfadaki eşleşen örnekleri seçilen istemciye göre gösterir.
Request Output Cache UseOutputCache() Redis backend HIT → 200 OK Endpoint MISS Redis SET + TTL

Ne Zaman Output Cache Kullan / Kullanma

Kullan Kullanma Gerçek Hayat
Aynı response farklı kullanıcılara dönüyor (public) Her kullanıcıya farklı response (kişiselleştirilmiş) E-ticaret: Ürün listesi sayfası — tüm ziyaretçiler aynı listeyi görür
Response üretmek pahalı (DB query + serialization) Response çok küçük ve hızlı üretiliyor (<1ms) CMS: Blog post render — Markdown→HTML 50ms, cache'ten 0ms
Read-heavy endpoint (%90+ GET) Sık değişen veri (anlık stok, canlı skor) API: /api/categories — günde 1 kez değişir, saatte 100K hit
Multi-instance uygulama (shared cache gerekli) Tek pod + IMemoryCache yeterli SaaS: 10 pod arkasında — Output Cache Redis'te → hepsi aynı cache

Gerçek hayat senaryosu — Blog platformu: Her blog post'u render etmek 40-80ms (Markdown parse + syntax highlight + template). Output Cache 5dk TTL → ilk request sonrası tüm ziyaretçiler 0ms'de response alır. Post güncellenince IOutputCacheStore.EvictByTagAsync("post:123") ile invalidate.

IDistributedCache ile Response Caching

# ASP.NET otomatik key oluşturur:
GET "myapp:ResponseCache:/api/products?page=1"
TTL "myapp:ResponseCache:/api/products?page=1"
// === Output Caching (.NET 7+) — Redis backend ===
// NuGet: dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis

builder.Services.AddStackExchangeRedisOutputCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("Redis");
    options.InstanceName = "oc:";
});

builder.Services.AddOutputCache(options =>
{
    // Global policy
    options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromMinutes(5)));

    // Named policy
    options.AddPolicy("ProductList", builder =>
        builder.Expire(TimeSpan.FromMinutes(10))
               .Tag("products"));

    // Vary by query string
    options.AddPolicy("Paginated", builder =>
        builder.SetVaryByQuery("page", "size")
               .Expire(TimeSpan.FromMinutes(5)));
});

var app = builder.Build();
app.UseOutputCache();

// Endpoint'lere uygula
app.MapGet("/api/products", async (AppDbContext db) =>
{
    var products = await db.Products.AsNoTracking().ToListAsync();
    return Results.Ok(products);
})
.CacheOutput("ProductList");

// Tag-based invalidation
app.MapPost("/api/products", async (
    Product product,
    AppDbContext db,
    IOutputCacheStore cache) =>
{
    db.Products.Add(product);
    await db.SaveChangesAsync();

    // "products" tag'li tüm cache entry'leri temizle
    await cache.EvictByTagAsync("products", default);
    return Results.Created($"/api/products/{product.Id}", product);
});

// Controller'da kullanım
[OutputCache(Duration = 300, VaryByQueryKeys = new[] { "category" })]
[HttpGet]
public async Task<IActionResult> GetProducts([FromQuery] string? category)
{
    // ...
}
// === Response Caching (HTTP cache headers) ===
builder.Services.AddResponseCaching();
app.UseResponseCaching();

app.MapGet("/api/config", () =>
{
    return Results.Ok(new { version = "1.0" });
})
.WithMetadata(new ResponseCacheAttribute
{
    Duration = 600,
    Location = ResponseCacheLocation.Any,
    VaryByQueryKeys = new[] { "env" }
});
Özellik Output Cache Response Cache
Depolama Redis (server-side) HTTP headers (client/proxy)
Tag invalidation Evet Hayır
Programmatic eviction Evet Hayır
Authenticated Varsayılan hayır (config ile evet) Hayır
Ne zaman API response'lar Statik/public content