Caner Tosuner

Leave your code better than you found it

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.