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.

Asp.Net Core Dependency Injection

Asp.Net Core basitçe nedir ne değildir bahsetmiştik daha önceki yazılarımızda. Bugünkü yazıda ise Asp.Net Core için Dependency Injection konusuna değinip framework içerisinde default gelen built-in DI Container'ını inceleyeceğiz.

.Net core için 3rd party DI container'lar kullanılabildiği gibi küçük basit uygulamalar yada microservice'ler için kullanılabilen microsoft'un geliştirmiş olduğu basit kullanıma sahip bir built-in default DI tool'u da framework ile sunulmuştur. Castle, Autofac yada Unity framework'leri kadar çeşitli özelliklere sahip olmasa da oldukça kullanması basit ve performanslı bir framework olarak karşımıza çıkmakta. Örnek bir proje üzerinden nedir, ne değildir, nasıl kullanılır anlatmaya başlayalım.

Creating a Sample Project

Aşağıdaki gibi Asp.Net Core'da Web Api olarak yazılmış bir projemiz olsun ve proje içerisinde Repository ve Service katmanları için kullandığımız interface'leri container'a register edelim ve kullanalım. İlk olarak domain objemizi aşağıdaki gibi yaratalım.

namespace AspCoreDIContainerSample.Domain
{
    public class City
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
    }
}

Sonrasında yukarıda oluşturduğumuz domain model için repsoitory inteface ve sınıfını oluşturalım. 

namespace AspCoreDIContainerSample.Repository
{
    public interface ICityRepository
    {
        IEnumerable<City> GetAll();
    }
}
namespace AspCoreDIContainerSample.Repository
{
    public class CityRepository : ICityRepository
    {
        public IEnumerable<City> GetAll()
        {
            // send back some hard-coded data
            return new List<City>
            {
                new City { Id = "3938ca15-cba2-44a5-bd53-b5d6a5e306f5", Name = "Ankara" },
                new City { Id = "cfd61cbf-a161-440f-9d1d-fdef32379b71", Name = "Samsun" }
            };
        }
    }
}

Yukarıda oluşturduğumuz ICityRepository interface'i aşağıda tanımlayacağımız CityService sınıfına constructer-injected parameter olarak vereceğiz. 

namespace AspCoreDIContainerSample.Service
{
    public interface ICityService
    {
        List<City> GetAllCities();
    }
}
namespace AspCoreDIContainerSample.Application
{
    public class CityService : ICityService
    {
        private readonly ICityRepository cityRepository;
 
        public CityService(ICityRepository cityRepository)
        {
            this.cityRepository = cityRepository;
        }
 
        public List<City> GetAllCities()
        {
            return this.cityRepository.GetAll().ToList();
        }
    }
}

Register All Used Components

Yukarıda kullandığımız repository ve service objelerini container'a implementasyonlarını belirterek register etmemiz gerekiyor. Bunu için aşağıdaki gibi proje solution'ında bulunan Startup.cs sınıfından faydalanacağız. Startup.cs proje ilk run edildiğinde çalışan kod satırlarının bulunduğu sınıftır. Bizde proje ilk run edildiğinde ilgili kullanılan bağımlılıkları register edeceğiz. Startup.cs de bulunan ConfigureServices adlı metot register işlemlerini yapmak için default gelen bir metottur. Aşağıdaki gibi tanımlamalarımızı yapalım.

    public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<ICityRepository, CityRepository>();
            services.AddScoped<ICityService, CityService>();
        }

 Yukarıda görüldüğü üzre ICityRepository ve ICityService interface'lerini implementasyonlarını belirterek AddScoped metodunu kullanarak register ettik. Peki ne bu AddScoped ?

 

Dependency Injection Lifetimes - Scoped, Singleton, Transient

Dependency injection lifetime container'dan istenen objenin ne zaman instance create edileceğini yada ne zaman yeniden create edilmesi gerektiğini sağlar. Container'da lifetime tanımı yapabilmemizi sağlayan 3 state vardır. Sırasıyla bu üçlüye bakacak olursak;

Scoped : Her scope için tek bir instance yaratılmasını sağlayan lifetime adı dır. Örneğin web projesi için projenize gelen her bir HttpRequest için ilgili instance'ı yaratıp container'da tutar ve o http lifecycle'ı boyunca hep aynı instance'ı kullanır.

Singleton : İsminden de anlaşılacağı üzre singleton yani uygulama ilk çalıştığında tek bir instance yaratır ve sonrasından uygulama stop olana kadar bu instance'ı kullanır.

Transient : Bu lifetime ise ilgili obje her istendiğinde yeni bir instance yaratır ve özellikle stateless servisler için best-practice olarak kullanılır.

Biz yukarıda kullandığımız interface'lerin lifetime'larını Scoped olarak tanımladık ancak ihtiyaca göre Singleton yada Transient olarak da tanımlayabilirdik.

  services.AddSingleton<ICityRepository, CityRepository>();
  services.AddSingleton<ICityService, CityService>();

 

Using Registered Components

Son adım olarak register ettiğimiz bu service'leri kullanmak kaldı. Aşağıdaki gibi CityController içerisinde container'da register edilen instance'ları kullanarak geriye cityList return edelim.

namespace AspCoreDIContainerSample.Api
{
    public class CityController : Controller
    {
        private readonly ICityService _cityService;
 
        public CityService(ICityService cityService)
        {
            this._cityService= cityService;
        }

        [HttpGet]
        public List<City> GetCityList()
        {
            return this.cityService.GetAllCities();
        }
    }
}

Asp.Net Core için Dependency Injection kullanım örneğimiz bu kadardı. Yukarıdaki end-point'e call yaparak geriye Samsun ve Ankara illerinin bulunduğu array'i return ettiğini göreceğiz.

Built-in dependency injection castle, ninject veya autofac kadar zenginliğe sahip bir IoC container değildir ancak oldukça basit kullanıma sahip hızlı ve implementasyonu kolay bir container dır. Eğer isterseniz bu container'ın asp.net core için bir kütüphanesi geliştirildiyse default built-in container ile replace de edebilirsiniz. Sonraki Asp.Net Core DI yazımızda 3rd party asp.net core DI container'larından birini nasıl built-in container yerine kullanabiliriz inceleyeceğiz.