Entity Framework Core Nedir ? Generic Repository Pattern Kullanarak Asp.Net Core Web Api Projesi Geliştirme

.Net Core'un duyurulmasıyla birlikte microsoft .Net Framework çatısı altında geliştirmekte olduğu bütün ürünlerin -core versiyonlarını geliştirmeye devam ediyor ve Entity Framework Core da bunlardan bir tanesi. En son 2.1 versiyonu ile birlikte benchmark testlerinde en hızlı orm olarak karşımıza çıktı. Bizde bu yazımızda entity framework 2.1 kullanarak Generic Repository Pattern ile birlikte bir Asp.Net Core 2.1 WebApi uygulaması geliştireceğiz.

Proje Oluşturulması

İlk olarak vs'da EfCoreWithWebApiSample adında versiyon olarak Asp.Net Core 2.1 seçerek bir Web Api Application oluşturalım.

Not: Geliştirmeye başlamadan önce makinanızda .Net Core sdk 2.1.3 rc1 ve host edebilmemizi sağlayan .Net hosting 2.1.0 rc1 kurulumlarının olması gerekmekte.

DbContext-Entity Tanımlaması

Api projemizde bir ProductDbContext'i ile product database'inde bulunan ürünler için CRUD işlemlerini içeren api end-point'leri yer alacaktır. Bunun için ilk olarak projemizde ProductDbContext'ini ve Product entity sınıfını oluşturalım.

    public class ProductDbContext : DbContext
    {
        public ProductDbContext(DbContextOptions<ProductDbContext> options) : base(options)   {  }

        public DbSet<Product> Product { get; set; }
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
        }
    }

    public class Product
    {
        [Key]
        public Guid Id { get; set; }
        public string Name { get; set; }
    }

Yukarıda görüldüğü üzre context ve entity tanımlamalarını yaptık şimdi ise ProductDbContext'i Startup.cs içerisinde service olarak ekleme işlemini yapalım. Bunun için projemizde yer alan appsettings.json dosyasına connstring'i aşağıdaki gibi tanımlayalım ve sonrasında Startup.cs'de yer alan ConfigureServices metodu içerisinde context'i servislere ekleyelim.

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ProductDbConnString": "Server=.;Initial Catalog=productdb;Persist Security Info=False;User ID=productuser;Password=qwerty135-;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
}
   public void ConfigureServices(IServiceCollection services)
   {
       services.AddDbContext<ProductDbContext>(options =>
           options.UseSqlServer(Configuration.GetSection("ProductDbConnString")));

       services.AddMvc();
   }

Generic Repository Oluşturulması

Repository katmanı doğrudan context'i constructor injection yöntemi ile alarak database CRUD işlemlerini yapmamızı sağlayacak olan katman. Bunun için ilk olarak IGenericRepository adında bir interface tanımlayalım.

   public interface IGenericRepository<T> where T : class, IEntity
    {
      Guid Save(T entity);
      T Get(Guid id);
      void Update(T entity);
      void Delete(Guid id);
      IQueryable<T> All();
      IQueryable<T> Find(Expression<Func<T, bool>> predicate);
   }

Bu interface'e ait abstract GenericRepository sınıfını aşağıdaki gibi IGenericRepository interface'inden implement ederek metotlarını oluşturalım.

public abstract class GenericRepository<T> : IGenericRepository<T> where T : class, IEntity
{
    private readonly ProductDbContext _dbContext;
    private readonly DbSet<T> _dbSet;

    protected GenericRepository(ProductDbContext dbContext)
    {
        this._dbContext = dbContext;
        this._dbSet = _dbContext.Set<T>();
    }

    public Guid Save(T entity)
    {
        entity.Id = Guid.NewGuid();
        _dbSet.Add(entity);

        return entity.Id;
    }

    public T Get(Guid id)
    {
        return _dbSet.Find(id);
    }

    public void Update(T entity)
    {
        _dbSet.Attach(entity);
        _dbContext.Entry(entity).State = EntityState.Modified;
    }

    public void Delete(Guid id)
    {
        var entity = Get(id);
        _dbSet.Remove(entity);
    }

    public IQueryable<T> All()
    {
        return _dbSet.AsNoTracking();
    }

    public IQueryable<T> Find(Expression<Func<T, bool>> predicate)
    {
        return _dbSet.Where(predicate);
    }
}

Generic Repository için gerekli olan base sınıf ve interface'i yukarıdaki gibi tanımladık. Şimdi sırada Product entity'si için kullanacağımız ProductRepository ve onun interface'i var.

public interface IProductRepository : IGenericRepository<Product>
{ }

public class ProductRepository : GenericRepository<Product>, IProductRepository
{
    public ProductRepository(ProductDbContext dbContext) : base(dbContext)
    {
    }
}

Service Layer Oluşturulması

Service layer controller ile repository arasında kullanacağımız katman olacak ve uygula için business'ların bulunduğu katmanda diyebiliriz. Bunun için aşağıdaki gibi IProductService ve onun implementasyonu ile birlikte request-response dto sınıflarını oluşturalım.

public interface IProductService
{
    GetAllProductResponse GetAllProducts();
    void AddProduct(AddProductRequest reqModel);
}

public class ProductService : IProductService
{
    private readonly IProductRepository _productRepository;
    public ProductService(IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }

    public GetAllProductResponse GetAllProducts()
    {
        var result = _productRepository.All();
        var mappedList = new List<ProductDto>();

        foreach (var item in result)
        {
            mappedList.Add(new ProductDto { Id = item.Id, Name = item.Name });
        }

        return new GetAllProductResponse
        {
            ProductList = mappedList
        };
    }

    public void AddProduct(AddProductRequest reqModel)
    {
        _productRepository.Save(new Product { Name = reqModel.Name });
    }
}

Controller'a geçmeden şu ana kadar oluşturduğumuz dependency'leri inject edelim. Bunun için Startup.cs içerisinde yer alan ConfigureServices metodu içerisinde aşağıdaki tanımlamaları yapalım.

   services.AddScoped<IProductRepository, ProductRepository>();
   services.AddScoped<IProductService, ProductService>();

Api Controller Oluşturulması

Son adım olarak ise service'de yer alan bu iki metot için end-ponit'leri oluşturmak var. Bunun için projede yer alan Controller klasörü içerisine ProductController adında bir Controller ekleyelim ve aşağıdaki 2 end-point'i tanımlayalım.

[Route("api/[controller]")]
[ApiController]
public class ProductController : ControllerBase
{
    private readonly IProductService _productService;
    public ProductController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet]
    public ActionResult<GetAllProductResponse> GetAll()
    {
        var response = _productService.GetAllProducts();
        return Ok(response);
    }

    [HttpPost]
    public ActionResult Post([FromBody] AddProductRequest reqModel)
    {
        _productService.AddProduct(reqModel);
        return Ok();
    }
}

Geliştirmelerimiz bu kadardı. Entity Framework Core ve Asp.Net Core Web Api kullanarak uçtan uca bir ProductApi oluşturduk ve data access layer için Generic Repository Pattern'den faydalandık. 

Yukarıda da bahsettiğim gibi Entity Framework Core benchmark testlerinde en performanslı orm olarak karşımıza çıkmakta ve microsoft'un core çatısı altında en çok önem verdiği ürünlerin başında gelmekte. Sizlerde bu yazımızda olduğu gibi hızlı bir şekilde uçtan uca bir api projesi geliştirebilirsiniz.

Asp.Net Core Projelerine Swagger Ekleme

Swagger biz developer'lar için oldukça önemli bir tool'dur. Server side geliştirmeler için entegre olacak client'lar veya bu endpoint'leri test etmek isteyen test ekipleri yada farklı developer takımları için döküman ve request response örnekleri hazırlamak olmazsa olmazlardan dır. .Net framework'de olduğu gibi .Net Core projelerinde de swagger kütüphanesi tam da bu gibi ihtiyaçlar için bulunmaktadır.

Swagger

Swagger'n tanımına bakacak olursak;

"Swagger is a simple yet powerful representation of your RESTful API. With the largest ecosystem of API tooling on the planet, thousands of developers are supporting Swagger in almost every modern programming language and deployment environment. With a Swagger-enabled API, you get interactive documentation, client SDK generation and discoverability."

-swagger.io

Swagger yazılım dünyası tarafından oldukça büyük çapta kabul görmüş yaygın olarak kullanılan bir dynamic döküman oluşturma tool'u dur. .Net Core projeleri içinde implementasyonu oldukça basittir. 

Imp

Geliştirmekte olduğumuz .net core web api projesine nuget üzerinden Swashbuckle.AspNetCore  paketini indirip kuralım. Kurulum işlemi tamamlandıktan sonra projemizin Startup.cs sınıfı içerisinde yer alan ConfigureServices metodunda swagger'ı service olarak ekleyelim. 

public void ConfigureServices(IServiceCollection services)
{
	services.AddSwaggerGen(c =>
	{
		c.SwaggerDoc("v1", new Info { Title = "WebApplication1", Version = "v1" });
	});

        services.AddMvc();
}

Sonrasında eklediğimiz bu service'i yine Startup.cs içerisinde yer alan Configure metodunda kullanacağımızı belirtelim. 

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
	if (!env.IsProduction())
	{
		app.UseDeveloperExceptionPage();

		app.UseSwagger();

		app.UseSwaggerUI(c =>
		{
		c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication1");
		});
	}
}

Projenizi run ettiğinizde browser üzerinden Swagger Ui sayfasına localhost:40383/swagger şeklinde ulaşabilirsiniz ve sayfa default olarak aşağıdaki gibidir.

Yukarıda da görüldüğü gibi projemizde controller'lar içerisinde tanımlı end-point'ler, Http Request türleri, aldıkları parametreler vs gibi bilgiler yer almaktadır.

Örnek olarak POST /api/Values metodunu deneyelim. Metot isminin üzerine tıkladığımızda altta bir view expand olur ve burada request olarak göndereceğimiz parametreleri yazıp response'u alabiliriz. 

Yukarıdaki ekran görüntüsünde kısaca Values metodu string bir parametre alıyor ve geriye string bir response dönüyor. Request parametresini yazdıktan sonra Try it out butonuna tıkladığımızda aşağıdaki gibi bir ekranla karşılaşıyoruz.

 Http200 kodu ile response'u almış oluyoruz.

Özetle

Biz yazılımcılar için çile haline gelen request response örnek kodları açıklama döküman vs gibi konuları swagger ile gayet basit ve kullanışlı bir hale getirebiliriz. Swagger ile ilgili daha bir çok configuration bulunmakta. VS üzerinden XML dosya generate ederek kodlarınızın üzerinde bulunan yorumlardan yola çıkarak api dökümanı oluşturma gibi bir çok özelliği bulunmakta. Ayrıntılı bilgi için Swagger.io Swashbuckle ile ilgili güncel ve daha ayrıntılı bilgileri bu linkten takip edebilirsiniz.

Asp.Net Core Distributed Cache Nedir ? Redis İle Kullanımı

Daha önceki yazılarımızda Asp.Net Core da in-memory cache nedir nasıl kullanılır konularına değinmiştik. Bu yazımızda ise asp.net core projelerinde distributed cache nasıl uygulanır inceleyeceğiz.

Distributed Cache

Distributed cache projelerimizde daha performanslı ve ölçeklenebilir (scalability) modüller geliştirebilmemize olanak sağlar. In-Memory cache'de o an uygulamanın çalışmakta olduğu server'ı cache storage olarak kullandığımızdan birden fazla server'da çalışan uygulamalar için bu cache'in dağıtık olarak bütün sunucularda bulunan uygulamalara paylaştırılması ve yönetilmesi gerekmektedir. Distributed cache'de veriler merkezi olarak store edilir ve böylelikle sunuculardan herhangi biri down olduğunda bile diğer sunucularda bulunan uygulamalar cache'de bulunan data'yı kullanabilmektedirler. Ayrıca cache yapısını bozmadan istediğiniz kadar yeni app-server eklediğinizde veya stop ettiğinizde cache bundan etkilenmeyecektir. 

1-) İlk adım olarak Makinamızda redis-server kurulu olması gerekmektedir. Kurulum ile ilgili şu yazıdan yararlanabilirsiniz ve localhost:6379'dan bağlanacağımız varsayalım.

Örnek proje

Redis kurduktan sonra örnek proje üzerinden ilerleyelim. İlk olarak vs'da bir asp.net core web api uygulaması oluşturalım. 

Asp.Net Core uygulamalarında distributed-cache özelliğini kullanabilmek için Microsoft.Extensions.Caching nasmespace'i altında bulunan ve nuget'ten de kurabildiğimiz IDistributedCache interface'i bulunmaktadır ve bu interface içerisinde cache CRUD işlemlerini senkron-asenkron şekilde yapabilmemizi sağlayan metotlar bulunmaktadır.

  public interface IDistributedCache
  {
    byte[] Get(string key);
    Task<byte[]> GetAsync(string key, CancellationToken token = default (CancellationToken));
    void Set(string key, byte[] value, DistributedCacheEntryOptions options);
    Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default (CancellationToken));
    void Refresh(string key);
    Task RefreshAsync(string key, CancellationToken token = default (CancellationToken));
    void Remove(string key);
    Task RemoveAsync(string key, CancellationToken token = default (CancellationToken));
  }

2-) İkinci adım olarak Startup.cs içerisinde bulunan ConfigureServices metodu içerisinde uygulamamız servislerine redis-distributed-cache'i configure edip eklememiz gerekmekte.

 public void ConfigureServices(IServiceCollection services)
 {
     services.AddDistributedRedisCache(option =>
     {
         option.Configuration = "localhost:6379";
     });
 }

3-) Üçüncü ve son adım ise IDistributedCache interface'ini kullanarak cache crud işlemlerini yapmak. Bunun için asp.net core web api projelerinde default olarak gelen ValuesController'ı kullanalım.

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly IDistributedCache _distributedCache;

    public ValuesController(IDistributedCache distributedCache)
    {
        _distributedCache = distributedCache;
    }

    // GET api/values
    [HttpGet]
    public async Task<string> Get()
    {
        const string cacheKey = "values";

        var cachedItem = await _distributedCache.GetStringAsync(cacheKey);
        if (!string.IsNullOrEmpty(cachedItem))
        {
            return cachedItem; 
        }
        else
        {
            const string str = "value1 value2 value3 value4 etc.";
            await _distributedCache.SetStringAsync(cacheKey, str);
            return str;
        }
    }
}

Yukarıda görüldüğü üzre Get metoduna gelen istekte ilk olarak ilgili key'e ait cache'de bir değer var mı kontrol edilir varsa cache'de bulunan değer return edilir yoksa ilgili storage'a (database etc.) gidilir return value alınır önce cache'e atılır sonrasında return edilir.

Asp.Net Core uygulamalarında distributed-cache konfigurasyonu ve kullanımı bu kadar basitti diyebiliriz. Redis gibi couchbase veya sql server kullanarak da asp.net core uygulamalarında distributed cache özelliğini kazandırabilirsiniz ve IDistributedCache interface'ini kullanarak basitçe cache crud işlemlerini yönetebilirsiniz.

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.

.Net Core Register All Dependencies at Once (Scrutor)

.Net Core projelerinde dependency injection uygularken eğer built-in container'ı kullanıyorsanız ve onlarca Service,Repository veyahut manager vb. interface/implementasyon'u varsa bunları container'a register etmek biraz uğraştırıyor. Sebebi ise castleWindsor veya Autofac gibi DI Tool'larından alışkın olduğumuz register-all-at-once gibi bir özelliği şuan için bulunmamakta. Yani aşağıdaki gibi her bir isterface ve onun implementasyonu için ayrı ayrı registration tanımlanması gerekmekte.

services.AddSingleton<IUserService, UserService>();
services.AddSingleton<ICustomerService, CustomerService>();
services.AddSingleton<IProductService, ProductService>();
services.AddSingleton<IEmployeeService, EmployeeService>();
services.AddTransient<IUserManager, UserManager>();
services.AddTransient<ICustomerManager, CustomerManager>();
services.AddTransient<IProductManager, ProductManager>();
services.AddTransient<IEmployeeManager, EmployeeManager>();
...

Ancak kısa süre önce github'da paylaşılan ve nuget üzerinden de install edilebilen bir kütüphane yayınlandı.

Scrutor

Scrutor "Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection" şeklinde tanımlanmakta ve bu kütüphane ile aynısı olmasada CastleWindsor ve Autofac de olduğu gibi registration işlemleri biraz olsun basitleşiyor. Scrutor'u projenize install ettikten sonra registration tanımlarken belirttiğiniz interface'in bulunduğu bütün assembly'leri scan edip bağımlılıkları inject etmekte.

Yukarıda built-in container kullanarak register ettiğimiz bağımlılıkları scrutor kullanarak aşağıdaki gibi de yapabiliriz.

services.Scan(scan => scan  
  .FromAssemblyOf<IAssemblyMarker>()
  
   // ISingletonLifetime adında bir interface tanımlayıp container'a single instance olarak register olmasını istediğimiz dependency'leri bu interface'den inherit ederek doğrudan register edebilmekteyiz.
  .AddClasses(classes => classes.AssignableTo<ISingletonLifetime>())
  .AsImplementedInterfaces()
  .WithSingletonLifetime()
  
   // ITransientLifetime adında bir interface tanımlayıp container'a transientLifeTime'ına sahip olmasını istediğimiz dependency'leri bu interface'den inherit ederek doğrudan register edebilmekteyiz.
  .AddClasses(classes => classes.AssignableTo<ITransientLifetime>())
  .AsImplementedInterfaces()
  .WithTransientLifetime());
  
  public interface ISingletonLifetime
  { }
  public interface ITransientLifetime
  { }
  
  public interface ICustomerService : ISingletonLifetime
  { 
     //todo
  }
  
  public interface ICustomerManager : ITransientLifetime
  { 
    //todo
  }

Scrutor startUp anında assembly'i scan edip ITransientLifetime ve ISingletonLifetime interface'lerini ve onların implementedInterface'lerini bularak implementasyonlarını doğrudan container'a register eder.

Scrutor doğru kullanıldığı takdirde oldukça başarılı bir kütüphanedir ve bizleri dependecny injection konusunda belli başlı bazı zorluklardan kurtarabilmektedir.

Asp.Net Core JSON Web Token Kullanımı

Bu yazımızda asp.net core uygulamalarında token based authentication nedir, nasıl sağlanır bir örnek üzerinden inceleyeceğiz. 

ProductApi adında bir service projenizin olduğunu düşünün ve bu service üzerinde product tablonuz için CRUD işlemlerini yapan belli endpoint'ler sağladığınızı varsayalım. Herhangi bir güvenlik kontrolü bulunmayan ProductApi'nize call yapmak isteyen bir kişi geliştirme yaparken doğrudan erişebiliyor. Peki ama çok basit bir şekilde düşünecek olursak service url'lerini bulan herhangi bir kişi servisinizi manipüle etmek adına CRUD metotlarınıza doğrudan call yapabilir yada bazı metotları call edebilir bazılarını edemez vs. gibi riskler barındırmaktadır.

Bu gibi durumlara çözüm olarak token-based authentication yöntemleri geliştirilmiştir.

Token based authentication'ın genel konsepti oldukça basit; kullanıcıdan bir username ve password vs. gibi bir bilgi alıp bu bilgiyi server'a göndermek ve eğer valid bir username ve password ise karşılığında bir token dönüp o kullanıcının artık token expire oluncaya dek bütün api işlemlerini o token üzerinden yapması beklenir. 

JSON Web Token Nedir Nasıl Kullanılır;

Base64 olarak oluşturulmuş 3 ayrı bölümden oluşur;

HEADER.PAYLOAD.SIGNATURE

Header bölümünde; hangi token türünün ve şifreleme algoritmasının kullanıldığı bilgisi yer alır.

Payload; uygulama bazlı bilgilerin yer aldığı(claim,userId vs.) yani uygulamaya özel bölümdür.

Signature ise adından da anlaşıldığı gibi server tarafından üretilen signature'ın bulunduğu bölümdür.

 

Şimdi ise bir asp.net core projesinde JWT nasıl entegre edilir ve kullanılır bunu inceleyelim. 

İlk olarak aşağıdaki gibi Startup.cs bulunan ConfigureServices metodu içerisinde uygulama boyunca geçerli olan JWT Authentication middleware konfigurasyonlarını yapalım.

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(jwtBearerOptions =>
    {
        jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters()
        {
            ValidateActor = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = Configuration["Issuer"],
            ValidAudience = Configuration["Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["SigningKey"]))
        };
    });
    services.AddMvc();
}

Middleware tanımlamasını yukarıdaki gibi yaptıktan sonra bunu builder'a eklememiz gerekmekte. Bunun içinde yine Startup.cs de Configure metodu içerisinde aşağıdaki tanımlamayı yapalım.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    app.UseAuthentication();
    app.UseMvc();
}

Sırada JWT generate edecek olan endpoint'i oluşturma var. Bunun için TokenController adında bir controller oluşturalım ve içerisine kullanıcıyı validate ederken kullanılacak olan bilgilerin bulunduğu request modeli alıp geriye tokenResponse dönen bir endpoint oluşturalım.

[AllowAnonymous]
[HttpPost]
[Route("token")]
public IActionResult Post([FromBody]LoginRequest request)
{
    if (ModelState.IsValid)
    {
        var user = _userService.Get(request.UserName, request.Password); 
        if (user == null)
        {
            return Unauthorized();
        }

        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, request.Username),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
        };

        var token = new JwtSecurityToken
        (
            issuer: _configuration["Issuer"], //appsettings.json içerisinde bulunan issuer değeri
            audience: _configuration["Audience"],//appsettings.json içerisinde bulunan audince değeri
            claims: claims,
            expires: DateTime.UtcNow.AddDays(30), // 30 gün geçerli olacak
            notBefore: DateTime.UtcNow,
            signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SigningKey"])),//appsettings.json içerisinde bulunan signingkey değeri
                    SecurityAlgorithms.HmacSha256)
        );
        return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
    }
    return BadRequest();
}

public class LoginRequest
{
	public string UserName {get;set;}
	public string Password {get;set;}
}

JWT based authentication yapısı projemiz için hazır. Sırada bunu test etmek var. Bunun için asp.net core projesi oluşturulurken default gelen ValuesController.cs içerisindeki Get metodunu kullanarak testimizi yapalım. Controller seviyesinde [Authorize] atrtribute'ü kullanarak authentication zorunlu olduğunu belirtebiliriz.

[Authorize]
[Route("api/[controller]")] 
public class ValuesController : Controller
{
    [HttpGet]
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }
}

Postman kullanarak projemizi test edelim.

İlk olarak token almadan ValuesController'a HttpGet request'inde bulunalım ancak token bilgisi set etmediğimizden bize geriye successful bir response (http200) dönmemesi gerekir. Aşağıda görüldüğü üzre response olarak 401 yani Unauthorized cevabı aldık.

Şimdi ise TokenController da bulunan metoda request atarak token response'unu aşağıdaki gibi alalım.

Almış olduğumuz tokenResponse'u göndereceğimiz request'in Authorization header'ına set ederek tekrardan ValuesController'a istekte bulunduğumuzda bu sefer http200 ile geriye value array'ini dönen cevabı almış olacağız.

JWT'nin kullanımı özetle bu şekilde. Sizlerde geliştirdiğiniz bir api projenizi dış dünyaya açarken token-based authentication yapmak istediğinizde implementasyonu oldukça basit olan basit jwt den faydalanabilirsiniz.

Chain of Responsibility Pattern Nedir

Chain of Responsibility pattern behavioral patterns gurubuna ait olan ve özünde birbirini takip eden iş dizisine ait process'leri redirect ve handle etmek yada istekte bulunan-confirm eden süreçleri için çözüm olarak ortaya çıkmış bir tasarım desendir.

Yukarıda tanım yaparken birbirini takip eden iş dizesinden kasıt birbirlerine Loosly Coupled bir şekilde zincir gibi bağlı olan süreçleri process etmek için kullanabileceğimiz bir pattern dir.

Bir örnek ile ele alacak olursak; veznede çalışan bir kişi için günlük nakit para çekim miktarı 40 bin TL olan bir banka düşünelim ve bu bankaya gelen bir müşteri veznede bulunan kişiden 240 bin TL para çekmek istediğini söyledi. Banka kuralları gereği bu işlemin sırasıyla veznedar, yönetici, müdür ve bölge sorumlusu tarafından sırasıyla onaylaması gerekmekte. Bakacak olduğumuzda zincir şeklinde birbirine bağlı olan bir onay yapısı bulunmakta. 

Akış olarak özetleyecek olursak;

  1.  Müşteri 480 bin TL lik para çekme isteğini veznedar'a iletir.
  2.  Veznedar bu isteği alır ve kontrol eder eğer onaylayabileceği bir tutar ise onaylar, onaylayabileceği bir tutar değilse yöneticisine gönderir,
  3.  Yönetici isteği alır  onaylayabileceği bir tutar değilse müdüre iletir,
  4.  Müdür kontrol eder eğer onaylayabileceği bir tutar değilse bölge sorumlusunun onayına gönderir,
  5.  Bölge sorumlusu onaylar ve para müşteriye verilir.

Yukarıda bahsettiğimiz bu örneğimizi Chain of Responsibility pattern uygulayarak geliştirelim.

İlk olarak Withdraw adında domain model tanımlayalım.

   public class Withdraw
    {
        public string CustomerId { get; }
        public decimal Amount { get; }
        public string CurrencyType { get; }
        public string SoruceAccountId { get; }

        public Withdraw(string customerId, decimal amount, string currencyType, string soruceAccountId)
        {
            CustomerId = customerId;
            Amount = amount;
            CurrencyType = currencyType;
            SoruceAccountId = soruceAccountId;
        }
    }

Sornasında abstract bir Employee sınıfı tanımlayalım ve içerisinde aşağıdaki gibi property'lerinı yazalım.

    public abstract class Employee
    {
        protected Employee NextApprover;

        public void SetNextApprover(Employee supervisor)
        {
            this.NextApprover = supervisor;
        }

        public abstract void ProcessRequest(Withdraw req);
    }

Yukarıda bulunan NextApprover isimli property o sınıfa ait kişinin yöneticisi olarak atanan kişidir ve ProcessRequest metodunda ilgili condition'ı yazıp sırasıyla NextApprover'ları call edeceğiz.

Veznedar, Yonetici, Mudur ve BolgeSorumlusu sınıfları yukarıda tanımladığımız Employee sınıfından inherit olacak şekilde aşağıdaki gibi oluşturalım. 

    public class Veznedar : Employee
    {
        public override void ProcessRequest(Withdraw req)
        {
            if (req.Amount <= 40000)
            {
                Console.WriteLine("{0} tarafından para çekme işlemi onaylandı #{1}",
                    this.GetType().Name, req.Amount);
            }
            else if (NextApprover != null)
            {
                Console.WriteLine("{0} TL işlem tutarı {1} max. limitini aştığından işlem yöneticiye gönderildi.", req.Amount, this.GetType().Name);

                NextApprover.ProcessRequest(req);
            }
        }
    }

    public class Yonetici : Employee
    {
        public override void ProcessRequest(Withdraw req)
        {
            if (req.Amount <= 70000)
            {
                Console.WriteLine("{0} tarafından para çekme işlemi onaylandı #{1} TL",
                    this.GetType().Name, req.Amount);
            }
            else if (NextApprover != null)
            {
                Console.WriteLine("{0} TL işlem tutarı {1} max. limitini aştığından işlem yöneticiye gönderildi.", req.Amount, this.GetType().Name);

                NextApprover.ProcessRequest(req);
            }
        }
    }

    public class Mudur : Employee
    {
        public override void ProcessRequest(Withdraw req)
        {
            if (req.Amount <= 150000)
            {
                Console.WriteLine("{0} tarafından para çekme işlemi onaylandı #{1} TL",
                    this.GetType().Name, req.Amount);
            }
            else if (NextApprover != null)
            {
                Console.WriteLine("{0} TL işlem tutarı {1} max. limitini aştığından işlem yöneticiye gönderildi.", req.Amount, this.GetType().Name);

                NextApprover.ProcessRequest(req);
            }
        }
    }

    public class BolgeSorumlusu : Employee
    {
        public override void ProcessRequest(Withdraw req)
        {
            if (req.Amount <= 750000)
            {
                Console.WriteLine("{0} tarafından para çekme işlemi onaylandı #{1} TL",
                    this.GetType().Name, req.Amount);
            }
            else
            {
                throw new Exception(
                    $"Limit banka günlük işlem limitini aştığından para çekme işlemi #{req.Amount} TL onaylanmadı.");
            }
        }
    }

Son olarak ise domain modeli initialize edip chain'i oluşturup process metodunu call edelim.

    static void Main(string[] args)
    {
        var withdraw = new Withdraw("a6e193dc-cdbb-4f09-af1a-dea307a9ed15", 480000, "TRY", "TR681223154132432141412");
 
        Employee veznedar = new Veznedar();
        Employee yonetici = new Yonetici();
        Employee mudur = new Mudur();
        Employee bolgeSorumlusu = new BolgeSorumlusu();
 
        veznedar.SetNextApprover(yonetici);
        yonetici.SetNextApprover(mudur);
        mudur.SetNextApprover(bolgeSorumlusu);
 
 
        withdraw.Process(veznedar);
 
        Console.ReadKey();
    }

Yukarıdaki gibi 480000 TL lik bir işlem için istekte bulunduğumuzda console çıktısı aşağıdaki gibi olacaktır.

İşlem sırasıyla veznedar, yönetici, müdür bölge sorumlusunun önünde düşecektir. Son olarak ise bölge sorumlusunun onaylayabileceği bir tutar olduğundan onay verip banka müşterisine ödeme işlemini yapacaktır.

Chain of Responsibility pattern bir chain halinde process edilmesi gereken operasyonlar için rahatlıkla kullanabileceğimiz bir pattern dir. Yazılım dünyasında kullanım olarak diğer desing pattern'lar arasında %30-%40 lık bir orana sahip olduğu iddia edilir ve sıkça kullanılmaktadır.

Asp.Net Core Middleware Nedir Nasıl Kullanılır

.Net Core Microsoft tarafından open-source olarak geliştirilmiş modüler bir .net framework'dür. Asp.Net Core ise klasik bildiğimiz Asp.Net kütüphanesinin open-source olarak microsoft tarafından release edilmiş halidir.

Daha önceki yazılarda da bahsettiğimiz üzre asp.net core'da bir çok feature yada özellik ayrı bir modül olarak kolayca entegre edilebilecek şekilde geliştirilmiştir. Middleware'de yine bunlardan biri. 

Middleware nedir diye soracak olursak; Middleware asp.net core içerisinde request-response pipeline'ını handle etmemizi sağlayan bir çeşit interceptor görevi gören sınıflardır. Bu sınıfları kullanarak controller metodunuza gelen request'leri veya response'ları modify edebilir, header check yapabilir yada authorization kontrollerini kolayca entegre edebiliriz.

Middleware asp.net core cycle'ın da ki konumunu anlamak için aşağıdaki resime bamak yeterli.

Uygulama içerisinde tanımlı olan middleware'ler register edilme sırasıyla birlikte yukarıdaki resimde olduğu gibi birbirlerini call ederek pipeline'ı tamamlarlar. 

Middleware Imp.

Örneğimiz şu şekilde olsun; bir api projemiz var ve bu projede middleware kullanarak client'dan request header da beklediğimiz Client-Key, Device-Id key-value parametrelerini gönderip göndermediğini kontrol edelim. Göndermediği durumda http400 ile geriye hata dönelim.

Middleware tanımlamanın birkaç farklı yolu var ancak en basit olanını inceleyeceğiz. İlk olarak vs.'da MiddlewareSample adında bir asp.net core projesi oluşturalım. Sonrasında projemize HeaderCheckMiddleware adında bir sınıf ekleyelim.

    public class HeaderCheckMiddleware
    {
        private readonly RequestDelegate _next;
        public HeaderCheckMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext httpContext)
        {
            var key1 = httpContext.Request.Headers.Keys.Contains("Client-Key");
            var key2 = httpContext.Request.Headers.Keys.Contains("Device-Id");

            if (!key1 || !key2)
            {
                httpContext.Response.StatusCode = 400;
                await httpContext.Response.WriteAsync("Missing requeired keys !");
                return;
            }
            else
            {
                //todo
            }
            await _next.Invoke(httpContext);
        }
    }

Yukarıda görüldüğü üzre, Invoke metodu middleware call edildiğinde execute edilecek olan metottur. Bu metot; end-poit'e gelen request'i ve end-point'in return ettiği response'a müdahale edebilmemizi sağlar. Bizde yapılan her httpRequest'inde header de beklediğimiz Client-Key, Device-Id vs. gibi bilgileri kontrol etme işini yukarıda olduğu gibi bu metodun içerisinde yaptık. Eğer bu 3 header key'in den birisi dahi header'da yok ise htpp400 olarak geriye hata mesajı return ettik.

Middleware'imizi tanımladıktan sonra geriye bunu asp.net core projemize register etmek kalıyor. Bunun içinde aşağıdaki gibi bir extension metot yazalım ve register etme işlemini asp.net core ile birlikte gelen Startup.cs de bulunan Configure metodu içerisinde tıpkı projede ayağa kaldırabileceğimiz bir servismiş gibi enable edelim.

    public static class MiddlewareExtension
    {
        public static IApplicationBuilder UseHeaderCheckMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<HeaderCheckMiddleware>();
        }
    }

Son adım olarak ise Configure metodu içerisinde middleware'i aktifleştirelim .

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

Aşağıdaki gibi postman veya herhangi bir rest-call tool'u kullanarak projenizde bulunan herhangi bir end-point'e call yaptığınızda header da beklenen parametreleri göndermezsek hata almış olacağız.

Parametreleri doğru bir şekilde gönderdiğimiz durumda ise sorunsuz şekilde endpoint'e ulaşıp success response alabiliriz.

Middleware asp.net core projelerinde aop özelliklerini uygulayabilmemizi sağlar ve bununla birlikte bizlere projemiz için modüler özellikleri olan küçük feature'lar ekleterek kod tekrarından ve spaghetti code bloklarından bizleri kurtarabilir. Örnekte sadece küçük bir header check işlemi yaptık ancak middleware kullanarak bunu gibi daha bir çok geliştirmede yapabiliriz.

 

Asp.Net Core Logging

Asp.net core yazılarında daha önce asp.net core'a giriş yapmıştık ve devamında build-in container'dan bahsetmiştik. Kısaca tanımlayacak olursak; asp.net core microsoft tarafından open-source olarak geliştirilmiş asp.net'e göre daha modüler bir cross platform web kütüphanesidir. 

Bu yazıda ise asp.net core'da logging nedir nasıl yapılır inceleyeceğiz. 

Log bir uygulama için olmazsa olmazların başında gelir ve projeler için oldukça önemli bir feature'dır. Yukarıda da yazdığımız gibi asp.net core modüler bir framework dür ve logging de asp.net core uygulamanızda kolayca ayağa kaldırabileceğiniz bir service olarak yer almaktadır.

Öncelikle Vs'da AspCoreLogging adında bir web-api projesi oluşturalım. Eğer oluşturduğunuz proje asp.net core 1.x versiyonu ise projemize Microsoft.Extensions.Logging dll'ini referans olarak eklememiz gerekmekte ama eğer asp.net core 2.x versiyonlarından birine ait ise default olarak gelmekte.

Microsoft.Extensions.Logging namespace'i bizim asp.net core içerisinde logging için gerekli olan sınıf&arayüz ve metotları vs. içermekte.

Bunlara bakacak olursak;

  • ILogger
  • ILoggingFactory
  • LoggingFactory
  • ILoggingProvider

built-in class ve interface'leri bu namespace altında bulunmakta.

ILogger interface'i kullanacağımız log-storage'a log kaydetmemizi sağlayan gerekli metotları içerir.

public interface ILogger
{ 
   void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);
   bool IsEnabled(LogLevel logLevel);
   IDisposable BeginScope<TState>(TState state);
}

Geliştireceğimiz custom-logger'ı oluşturmak için ILogger interface'ine ait metotları extend edip metotlarını kullanacağız.

ILoggerFactory interface'i ise yukarıda bahsettiğimiz ILogger arayüzünü extend eden custom-logger'ın instance'ını oluşturmada kullanacağımız interface'dir.

public interface ILoggerFactory : IDisposable
{
    ILogger CreateLogger(string categoryName);
    void AddProvider(ILoggerProvider provider);
}

Asp.net Core içerisinde yukarıda bahsettiğimiz ILoggerFactor interface'ini implement eden LoggerFactory sınıfı bulunmakta. Runtime'da asp.net core framework bu sınıfa ait instance yaratarak default gelen kendi built-in IoC container'ına register eder.

ILoggingProvider interface'i istenilen logging kategorisindeki gerekli logger sınıflarını yaratır ve yönetir. Framework içerisinde default olarak gelen provider'lar şu şekildedir;

  • Console
  • Debug
  • EventSource
  • EventLog
  • TraceSource
  • Azure App Service
public interface ILoggerProvider : IDisposable
{
   ILogger CreateLogger(string categoryName);
}

Bu interface'i bize projede kullanacağımız customLogger'ın instance'ını oluşturmamızı sağlayacak sınıfı tanımlarken kullanacağız.

File Logging Impl.

Şimdi ise yukarıda bahsettiğimiz adımları fileLogging için geliştirmeye başlayalım. İlk olarak projemize ILogger interface'ini implement eden FileLogger sınıfını aşağıdaki gibi tanımlayalım.

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        var message = string.Format("{0}: {1} - {2}", logLevel.ToString(), eventId.Id, formatter(state, exception));
        WriteMessageToFile(message);
    }
    private static void WriteMessageToFile(string message)
    {
        const string filePath = "C:\\AspCoreFileLog.txt";
        using (var streamWriter = new StreamWriter(filePath, true))
        {
            streamWriter.WriteLine(message);
            streamWriter.Close();
        }
    }
    public IDisposable BeginScope<TState>(TState state)
    {
        return null;
    }
    public bool IsEnabled(LogLevel logLevel)
    {
        return true;
    }

Yukarıda da görüldüğü üzre projede üretilen loglar server'da bulunan C sürücüsünde AspCoreFileLog.txt adındaki dosyaya yazılacak. FileLogger sınıfını oluşturduktan sonra bu sınıfı projemize inject etmemizi sağlayacak olan ILoggerProvider interface'ini implement edecek olan FileLogProvider sınıfını tanımlayalım.

public class FileLogProvider : ILoggerProvider
{
    public ILogger CreateLogger(string category)
    {
        return new FileLogger();
    }
    public void Dispose()
    {

    }
}

FileLogProvider sınıfı proje içerisinde tanımlanan logger'ın instance'ının create edilmesini sağlar. 

Son adım olarak yukarıda tanımladığımız FileLogProvider'ı Startup.cs sınıfında bulunan Configure metodunda loggerFactory'nin provider'larına eklememiz kalıyor. Bu işlemi de aşağıdaki gibi startup.cs içerisinde yapalım.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    //provider'ı ekledik
    loggerFactory.AddProvider(new FileLogProvider());

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseMvc();
}

 

Geliştirmemiz bitti artık yazdığımız kodları test edebiliriz. Bunun için ILogger interface'inin projede yer alan aşağıdaki controller'a constructor seviyesinde inject ettikten sonra end-point'lere sırasıyla browser üzerinden get işlemi yapalım.

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly ILogger<ValuesController> _logger;
    public ValuesController(ILogger<ValuesController> logger)
    {
        this._logger = logger;
    }

    // GET api/values
    [HttpGet]
    public IEnumerable<string> Get()
    {
        _logger.LogInformation("Hi from logger !");
        return new string[] { "value1", "value2" };
    }

    // GET api/values/5
    [HttpGet("{id}")]
    public string Get(int id)
    {
        throw new NullReferenceException("Null exp. from myApp !");
        return "value";
    }
}

Http-Get request'i yolladıktan sonra C:\\AspCoreFileLog.txt adresine gittiğimizde içerisinde hem framework'ün ürettiği hemde bizim controller metodunda yazdırdığımız log row'larını görebilirsiniz.

 

 

Yazının başında da bahsettiğimiz üzre Asp.Net Core'da logging moduler bir şekilde ayrı bir service olarak gelmekte ve fileLog dışında database, flat file yada diğer log target türlerini kullanarak logging'i genişletebilirsiniz. Bunun dışında NLog, Serilog yada .net core desteği olan third-party logging provider'ları da kullanabilirsiniz.

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.