Caner Tosuner

Leave your code better than you found it

Asp.Net Core Response Caching

Daha önceki asp.net core yazılarında kütüphane ile birlikte default olarak tıpkı bir feature gibi hazır gelen ve biz developer'lar için sadece bu feature enable/disable etmek gibi ufak birkaç konfigurasyonla implementasyon tamamlayabileceğimiz bir çok özelliğin olduğundan bahsetmiştik.

Response Caching de bu feature'lardan bir tanesidir ve Aspect Oriented yaklaşımına uygun olarak geliştirilmiş bir ResponseCaching Middleware'i framework ile birlikte default gelmektedir. Asp.net core projelerinde çok küçük birkaç extension-method call ederek response caching özelliğini projemize kazandırabiliriz. Default olarak memory-cache yapsada istendiğimiz herhangi bir third party cache-server da kullanabiliriz.

Örnek bir proje ile devam edelim, ilk olarak Visual Studio'da ResponseCachingSample adında bir empty api projesi oluşturalım.

Sonrasında Startup.cs içerisinde yer alan ConfigureServices metodu içerisinde projemiz serviclerine responseCaching Middleware'ini ekleyelim.

public void ConfigureServices(IServiceCollection services)
{
     //add responseCaching service
    services.AddResponseCaching();

    services.AddMvc();
}

ResponseCaching için geçerli 3 options bulunmakta. Bunlar;

  1. SizeLimit : Maximum size of the response cache. Default olarak 100 MB dır.
  2. UseCaseSensitivePaths : Cache de bulunan path'ler case sensitive path olup olmamasını belirleyen option.
  3. MaximumBodySize : Cache'lenecek response body'ler için geçerli maximum size. Default olarak 64 MB dır.

Dilersek bu özellikleri kullanarak da responseCache'i aşağıdaki gibi service'lere ekleyebiliriz.

public void ConfigureServices(IServiceCollection services)
{
    services.AddResponseCaching(options =>
     {
          options.UseCaseSensitivePaths = true;
          options.MaximumBodySize = 1024;
     });
    services.AddMvc();
}

Service olarak eklediğimiz bu özelliği uygulamamızda kullanabilmek içinde yine Startup.cs içerisinde yer alan Configure metodu içerisinde UseResponseCaching extension metodunu call etmemiz gerekmekte.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    app.UseResponseCaching();   
}

Gerekli konfigurasyonları yaptıktan sonra artık controller metotlarında [ResponseCache] attribute'ünü kullanarak metodun döndüğü response'u cache'e atabiliriz.

ResponseCache attribute'üne ait parametrelere bakacak olursak;

  • Duration : Saniye cinsinden response'un ne kadar süre cache'de tutulacağını belirttiğimiz property.
  • Location : Response'un nerede cache'leneceğini belirttiğimiz parametre. Any, Client, or None. Default olarak Any set edilmiştir.
  • NoStore : Cache data sı store edilip edilmeyeceği bilgisinin sete dildiği parametre.
  • CacheProfileName : Adından da anlaşıalcağı üzre cache profil ismi
  • VaryByHeader : Response header da bulunan Vary key'ine ait value değerini temsil eder.
  • VaryByQueryKeys : Query string parametresine göre hangi response'un cache'leneceği belirtmek için kullanılır. Örnek olarak ; VaryByQueryKeys = new string[] { "clientName" } query string de bulunan farklı "clientName" parametrelerine göre cache'lenecektir.

ResponseCache attribute'ünü aşağıdaki gibi ValuesController içerisinde bulunan Get metodu için kullanalım.

[Route("api/[controller]")] 
public class ValuesController : Controller
{
    [HttpGet]
    [ResponseCache(Duration = 30)]
    public IEnumerable<string> Get()
    {
        var time= "The response time is : " + DateTime.Now.ToString();

        return new string[] { "CachedItems", time};
    }
}

Yukarıda responseCache attribute'ünü kullanarak Get metodunun return ettiği response'u 30 sn exprie süresi olacak şekilde cache'e atılacağını belirttik. Uygulama çalıştıktan sonra Get metodundan başarılı dönen ilk response CacheMiddleware'ine düşecek ve 30 saniye boyunca response'u cache'de tutacak. Bu 30 sn içerisinde gelen bütün request'lere ait response'lar hiç Get metoduna düşmeden doğrudan middleware tarafından yönetilip cache'den return edilecektir.

Tabiki şunuda unutmamak gerek; Middleware sadece Http200 result'ları için response'u cache'lemekte. 

Caching doğru kullanıldığı taktirde büyük çapta projeler için oldukça hayat kurtaran özelliktir. Özellikle response'un çok sık değişmeyip request'in çok fazla geldiği endpoint'ler için kullanmak core uygulamanızı ve onun bulunduğu storage'ı sürekli meşgul etmemek adına projelerde oldukça yaygın kullanılmaktadır.

Asp.Net Core In-Memory Cache

Daha önceki Asp.Net Core yazılarında as.net core'a giriş yapıp sonrasında asp.net core framework ile birlikte gelen built-in container'ını incelemiştik.

Asp.Net Core Windows, Linux, MacOS üzerinde çalışan moden web uygulamaları geliştirmemizi sağlayan modüler bir framework'dür. Modüler olmasının dezavantajı olarak da klasik Asp.net kütüphanesine kıyasla içerisinde default olarak gelen bir çok özellik bulunmamaktadır. Bunlardan biride default olarak içerisinde bir Cache object bulunmuyor ancak bir kaç küçük geliştirmeyle uygulamanıza hem in-memory hemde distributed caching özelliklerini kazandırabiliyoruz.. 

Bu yazımızda da asp.net core uygulamamıza in-memory cache özelliğini nasıl kazandırabiliriz basit bir örnek ile  inceleyeceğiz. 

Enable In-Memory Cache

In-memory cache özelliği asp.net core içerisinde bir service olarak bulunmaktadır. Bu servis default kapalı gelir yapmamız gereken startup.cs içerisinde bulunan ConfigureServices metodunda aşağıdaki gibi cache servisini açmak.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddMemoryCache();
}

Core projelerinde in-memory cache kullanmamızı sağlayan arayüzün adı IMemoryCache. Bu interface'e ait metotları vs. kullanarak cache set,get,remove gibi işlemleri yapabiliriz.

public interface IMemoryCache : IDisposable
{
    bool TryGetValue(object key, out object value);
    ICacheEntry CreateEntry(object key);
    void Remove(object key);
}

Using IMemoryCache to Cache

ConfigureServices metodu içerisinde servisi aktifleştirdikten sonra IMemoryCache interface'ini kullanmak istediğimiz katmana ait constructor'da inject etmemiz gerekmekte.
Bizde geriye product list return ettiğimiz bir controller tanımlayarak IMemoryCache interface'ini aşağıdaki gibi const. inj. parameter olarak verelim.

[Route("api/[controller]")]
public class ProductController : Controller
{
    private readonly IMemoryCache _memoryCache;
    public ProductController(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;
    }

    // GET api/values
    [HttpGet]
    public IEnumerable<Product> Get()
    {

    }
}


public class Product
{
    public int Quantity { get; set; }
    public string Name { get; set; }
}

Şimdi ise get metodunun içerisini dolduralım. Set metodu parametre olarak 1:key, 2:value, 3:cacheOptions . Cache options olarak AbsoluteExpiration;cache expire süresi ve Priority; memory şiştiğinde cache objelerini hangi priority'de silecek bunun bilgisinin bulunduğu ayarları set edeceğiz. 

[HttpGet]
public IEnumerable<Product> Get()
{
    const string cacheKey = "productListKey";

    if (!_memoryCache.TryGetValue(cacheKey, out List<Product> response))
    {
        response = new List<Product> { new Product { Name = "test 1 ", Quantity = 20 }, new Product { Name = "test 2", Quantity = 45 } };

        var cacheExpirationOptions =
            new MemoryCacheEntryOptions
            {
                AbsoluteExpiration = DateTime.Now.AddMinutes(30),
                Priority = CacheItemPriority.Normal
            };
        _memoryCache.Set(cacheKey, response, cacheExpirationOptions);
    }
    return response;
}

Gelen ilk request için cache'de o key'e ait bir obje olmadığından ilk response source'a gidip(bir repository yada service layer olabilir) dönen değer alınıp 30 dkka expire süresi set edilerek oluşturacaktır. Artık ondan sonraki bütün request'ler 30 dkka süresince source'a gitmeden response'u cache'de bulup Get işlemi yapıp return edecektir. Expire süresi dolduğunda ise ilgili key ve obje cache'den silinecektir.

Set, Get yapabildiğimiz gibi Remove işlemide yapabiliriz. Bunun için cacheKey değerini parametre olarak Remove metoduna verip call yapmak yeterli.

 _memoryCache.Remove(cacheKey);

CacheItemPriority enum'ı içerisinde Low, Normal, High, NeverRemove değerleri mevcut. CachedObject Priority değerine göre memory de yer açmak için sırayla silinir. Memory'den otomatik silme işlemi yapıldığında bunun bilgisini bize iletmesini sağlayan bir callback handler metodunu aşağıdaki gibi options'a register edebiliriz ve silme işlemi yapılırken bu metot tetiklenerek bize haber verir.

 cacheExpirationOptions.RegisterPostEvictionCallback
     (ProductGetALLCacheItemChangedHandler, this);
 _memoryCache.Set(cacheKey, response, cacheExpirationOptions);

Cache nerede ve nasıl uygulanması gerektiğine karar verildiğinde server-side bir uygulama için olmazsa özelliklerden biri haline gelmiştir. Asp.net core'da da yazının başında bahsettiğimiz gibi memory ve distributed cache işlemleri yapmamızı sağlayan service'ler bulunmaktadır. Bu yazımızda basitçe memory cache özelliğini projemize nasıl kazandırabiliriz konusuna değindik. Sonraki yazılarda redis kullanarak distributed cache yapısını uygulamamıza nasıl entegre edebiliriz inceleyeceğiz.