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.

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.

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.

Asp.Net Core'a Giriş

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

Asp.Net yaklaşık 15 yıl önce Microsoft tarafından geliştirilmiş olup günümüz web teknolojileri arasından oldukça popüler olan bir kütüphanedir. Microsoft open-source dünyanın önlenemez yükselişi ile birlikte daha fazla dayanamadı ve en base de bildiğimiz .Net çatısı altında olan bütün teknolojilerini yeniden yapılandırıp open-souce olarak bütün dünya ile paylaşacağını bizlere 3 yıl önce duyurmuştu. Akabinde sırayla .Net çatısı altındaki framework'ler git-hub üzerinde yayınlanmaya başlandı.

Ms'in web için geliştirdiği kütüphanelerin kronolojik sırasına bakacak olursak

  • 2002 - ASP.Net 
  • 2008 - ASP.Net MVC 
  • 2012 - ASP.Net Web API and SignalR

son olarak ise 2016 yilinda .Net Core 1.0 release oldu ve bunula birlikte Asp.Net Core hayatımıza girdi.

Asp.Net Core Asp.Net'in daha sade ve modern bir yüzü olarak karşımıza çıkıyor. Microsoft'un Zamarin'i satın alması sonrası sahip olduğu cross-platform deneyiminide işin içine katarak Asp.Net Core'u bir Cross Platform kütüphane olarak bizlere sundu. Framework'ü kullanarak IoT uygulamaları, back-end service uygulamaları ve web projeleri geliştirebiliriz.


.Net Standart 2.0

Ms ilk olarak .Net Core 1.0 versiyonunu release etti ancak bu sürüm biz .net developer'lar için biraz alışkın olduğumuzdan farklı bir mimariye sahipti ve geliştirme yaparkende bazı zorluklarıda bulunuyordu. Microsoft 1.0 dan sonra yayınladığı 2.0 versiyonu ile birlikte .Net Standart 2.0 adında bir yenilik daha getirdi. .Net standart microsoft için şu demekti ben bütün .net teknolojilerini, dillerini vs hepsini bir standart altında topluyorum ve bunun adınıda .Net standart 2.0 koyuyorum dedi. Bu standart ile birlikte bütün .net platformlarının base'i .Net Standart olmuş oldu ve CoreCLR adında yeni bir ortak iletişim dili geliştirildi.

Asp.Net Core'un getirdiği yenilikleri ve faydaları sıralayacak olursak

  • Cross platform
  • High performance
  • Flexible deployment
  • Open source
  • Support for built-in dependency injection
  • Light-weight and modular
  • Side by side support
  • Faster development


Mac ve Linux işletim sistemlerinde çalışır hale getirildi ve cross platfrom coverage'ını yükseltmiş oldu.


Geliştirme Yaparken

İlk olarak bilgisayarınızda Vs 2015 update 3 yada vs2017 .Net Core SDK ile birlikte yüklü olması gerekmekte.
Proje oluşturuken ise klasik herzaman yaptığımız gibi new aşağıdaki gibi new project diyerek geliştirmek istediğimiz proje türünü seçerek solution'ımızıo yaratabiliriz.


Bu yazıda Asp.Net Core'a küçük bir giriş yaptık. Diğer yazılarımızda framework'ü örnek uygulamalar ile incelemeye devam edeceğiz.