Erhan Ballıeker

Asp.Net Core 2.0’a Giriş – Bölüm 4 (Startup.cs, Configure, Middleware hakkında)

Selamlar, Asp.Net Core 2.0 Giriş seri postlarından bu bölümde Startup.cs deki Configure metodundan ve Middleware yapısından bahsedeğim. Middleware yapısı çok Asp.Net Core için çok önemli bir değişiklik ve bunun detaylarına ilerleyen postlarda gireceğiz.

Startup.cs deki Configure metodunun yaptığı iş şudur; Asp.Net Core projenizdeki Http Request pipeline nını konfigüre etmek. Peki ne demek bu Pipeline ı konfigüre etmek bunu aşağıdaki resimle bir inceleyelim. Klasik Asp.Net projelerinde Request in izlediği yol çok uzun ve karmaşıkça idi.Sizin belkide hiç kullanmayacağınız birçok yapıdan request geçiyor ve sizin elinize o şekilde ulaşıyordu. Ama aşağıdaki resimde göreceğiniz gibi, Asp.Net Core pipeline nına siz ne eklerseniz middleware olarak sadece o yapılardan geçecek request. Hiç bir middleware eklenmediğinde hiç birşey olmayacak. Bir önceki örneğimizdeki tek bir middleware eklenirse sadece o middleware den geçecek.

pipeline

Asp.Net Core projelerinde resimde görebileceğiniz gibi, koca bir MVC framework pipeline ı bile ayrıca projeye middleware olarak ekleniyor eğer istenirse. Size kalmış, MVC framework ün imkanlarından faydalanacak olanlar pipeline a bunu ekleyebilir, Bunun öncesinde kendi yazdıkları yada built-in gelen middleware lerden Authentication middleware ini koyup, static file ları serve etmek için (css,javascript,images vs..) Static Files Middleware ini pipeline a ekleyebilirler.

HttpRequest, eklediğiniz sırada middleware lerden geçerek, son middleware den sonra aynı şeklide geri dönerek tüm middleware lerden geçip browser a ulaşır. Bu requestin middleware lerdeki akışı sırasında WebServer feature larına ulaşma imkanı veriyor Asp.Net Core uygulamamız. Bir de daha büyük resimde request in browserdan uygulamamıza gelip tekrar browser a geri döndüğü akışa bakalımpipeh.Bu resmi adım adım izleyecek olursak, sırası ile şunlar oluyor.

  • Request browserdan External Web Server a (bu resimde IIS kullanıldığı varsayıldı, ama bu Nginx, Apache olabilir.) ulaşır.
  • External Web Server (IIS) dotnet runtime çalıştırır.
  • dotnet runtime, CLR ı load edip, sizin Application nınızdaki entry point (Program.cs deki Main() matodu ) i arar ve çalıştırır.
  • dotnet runtime Application ı çalıştırdıktan sonra Internal Web Server olan Kestrel ayağa kalkar.(Kestrel olmak zorunda değil, ama internal bir web server için çok çok iyi bir seçenek çünkü hem cross platform bir web server, hem performansı çok iyi).
  • Main metodunuz çalıştıktan sonra, WebHostBuilder ilgili ayarlar ile ayağa kalkar, Startup dosyası ile beraber uygulamanız konfigüre edildikten sonra, HttpRequest, IIS den Kestret’e yönlendirilir.
  • Startup dosyasındaki eklenen middleware lerinizden geçen request tekrar Kestrel e ulaşır, Kestrel,  External Web Server a response u iletir ve sonunda response browser a gelir.

Bu eski System.Web yaklaşımından çok daha verimli ve çok daha resource friendly dir. Klasik Asp.Net projelerinin Windows bağımlı olma sebebi zaten System.Web idi. System.Web assemly IIS e bağlı, IIS de, Windows a bağlıydı. Bu yüzden asp.Net projelerimiz windows dışında işletim sistemlerinde çalışamıyordu.

Şunu da belirtmekte fayda var. External bir web server a mutlaka ihtiyacınız yok aslında. Ama Kestrel çok performanslı ve cross platform bir web server olsada, IIS,Nginx, Apache gibi tam donanımlı web serverlar kadar yetenekli değil, bu yüzden zorunlu olmamakla beraber yüksek ihtimalle bir external web server kullanıyorsunuz. 🙂

Peki özet teorik bilgiden sonra uygulamamıza geri dönelim. ikinci bir middleware ekleyip, requestin oradan geçişlerini loglayarak, middleware yapısının çalışma şeklini iyice anlamaya çalışalım.

Startup.cs dosyasında ki Configure metodunda aşağıdaki değişiklikleri yapalım.

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

            app.UseStaticFiles();

            app.Use(async (context, next) =>
            {
                logger.LogInformation("First middleware working BEFORE second middleware");
                var serviceProvider = context.RequestServices;
                var userService = serviceProvider.GetRequiredService();
                var users = await userService.GetUsers();
                await context.Response.WriteAsync($"Hello World! - UserCount: {users.Count}");
                await next();
                logger.LogInformation("First middleware working AFTER second middleware");
            });

            app.Run(async (context) =>
            {
                logger.LogInformation("second middleware working");
                await context.Response.WriteAsync("Second middleware done!");
            });
        }

Kod tarafında yaptığımız değişiklikler şunlar;

  1. Configure metoduna ILogger<Startup> objesini inject ediyoruz. Bu daha Default WebHostBuilder konfigüre edilirken oluşturulup uygulamamıza bizim için register edilmiş oluyor Asp.Net Core tarafından
  2. İlk middleware deki app.Run metodunu app.Use olarak değiştiriyoruz. Çünkü Run metodu gelen request i shortcut yapıp sonraki middleware lere iletmeden geri döner. Use metodunda ise, context dışında ikinci bir parametre olarak RequestDelegate objesi gelir(bir sonraki middleware delegesi olarak). buna next  adını verdik, tüm işlemlerimiz bittikten sonra bu delegate i await ediyoruz. Tam bu noktada ikinci middeware miz çalışmaya başlar.
  3. Logları koyduğumuz yerlere dikkat ediniz. Biliyoruz ki middleware ler, uygulamaya eklendiği sırada request leri karşılar, tam tersi sırada da response ları karşılarlar. Projeyi çalıştırdığımızda logların nasıl bir sırayla basılacağına bakalım.

Bu sefer External web server kullanmadan sadece Kestrel i yani Internal Web Server ı kullanarak bu işlemi yapalım. Bunun için Visual Studio da, Run butonun yanındaki oku tıklayıp, IISExpress yerine projenin adının olduğu seçeneği seçmemiz yeterli (örneğimizde MeetupAspNetCore). Bu IIS Express i değil doğrudan Kestrel üzerinden requestleri karşılamamızı ve browser a dönmemizi sağlayacak. Uygulamayı çalıştırıp çıktımıza bakalım.

mware

resimde de göreceğiniz gibi, önce birinci middleware çalıştı, await next() dediğimiz anca ikinci middleware in logu basıldı, ikinci middleware de işini tamamladıkan sonra response geri dönerken,birinci middleware de await next(), dediğimiz yerden sonraki kod bloğunda yazılan log çalışmış oldu.

Bu yazımızı da burada tamamlayabiliriz. Middleware lerden kısaca bahsetmiş olduk, bir AspNet Core projesine gelen request in tüm akışını inceledik ve middleware lerin çalışma şeklini örnekledik.

Asp.Net Core 2.0 yazı serimizde artık temel noktaları gördük diyebiliriz.  Ama henüz Introduction ı tamamen yaptık dememiz için çok şey var 🙂

Bir sonraki yazıda görüşmek üzere.

Asp.Net Core 2.0’a Giriş – Bölüm 2 (Program.cs, WebHostBuilder, Kestrel hakkında)

Selamlar Arkadaşlar, bu yazımda geçen yazıda başladığımız AspNet Core Web Application oluşturma ve solution structer ına bir göz gezdirmeden sonra, Program.cs e ve içeriğindeki metodlara değineceğiz. Sonrasında sırası ile, Startup.cs dosyasına, bakıp AspNetCore Dependency Injection, Middleware yapısı vb konulara değinerek bu seriyi tamamlayacağız.

Öncelikle klasik AspNet ten hatırladığımız System.Web assembly sinden ve ne yaptığından bahsedip Core tarafına o şekilde geçelim.

Klasik Asp.Net projelerinde, bir web projesinin ayağa kalkma süreci System.Web assembly sindeki kodlar tarafından sağlanırdı ve sizin buna müdahele şansınız sadece size Global.asax dosyasında müdahele etmeniz, konfigürasyonlar yapmanız için verilen bazı metodları editlemekle kısıtlıydı. Artık yeni AspNet Core tarafında, application ın tümüyle başlatılma kısmı sizin elinizde. Nasıl olduğuna biraz daha derinlemesine değinelim.

Asp.Net Core projelerinde solution içerisinde bir adet Program.cs dosyası mevcut.  Bu dosya içerisindeki Main() metodu artık sizin application nınızın entry point i. Uygulumanızı başlattığınızda, Runtime uygulama içerisinde ki bu metodu arar ve çalıştırır. Şu aşamada Asp.Net Core Web App projeniz aslında bir Command Line Application olarak yaşamına başlar.

public class Program
{
     public static void Main(string[] args)
     {
         CreateWebHostBuilder(args).Build().Run();
     }

     public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
          WebHost.CreateDefaultBuilder(args)
         .UseStartup();
}

Uygulama çalıştığında Main() metodunuz, CreateWebHostBuilder Metodunu çağırır ve geri dönen IWebHostBuilder interface i üzerindeki Build() ve Run() metodlarını tetikler. Bu IWebHostBuilder metodu üzerindeki Run() metodu tetiklendiği andan itibaren artık uygulamanız gerçekten bir Asp.Net Core Web Application a dönüşür 🙂

Asp.Net Core un open source olmasından faydalanalım ve bu CreateDefaultBuilder(args) metodu arka planda ne yapıyor bir bakalım.

Buradan gördüğümüz gibi CreateDefaultBuilder(args) metodu aşağıdaki işleri yapıyor. Tek tek ne olduklarına değinmeye çalışalım.

public static IWebHostBuilder CreateDefaultBuilder(string[] args)
        {
            var builder = new WebHostBuilder();

            if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey)))
            {
                builder.UseContentRoot(Directory.GetCurrentDirectory());
            }
            if (args != null)
            {
                builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build());
            }

            builder.UseKestrel((builderContext, options) =>
                {
                    options.Configure(builderContext.Configuration.GetSection("Kestrel"));
                })
                .ConfigureAppConfiguration((hostingContext, config) =>
                {
                    var env = hostingContext.HostingEnvironment;

                    config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                          .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);

                    if (env.IsDevelopment())
                    {
                        var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
                        if (appAssembly != null)
                        {
                            config.AddUserSecrets(appAssembly, optional: true);
                        }
                    }

                    config.AddEnvironmentVariables();

                    if (args != null)
                    {
                        config.AddCommandLine(args);
                    }
                })
                .ConfigureLogging((hostingContext, logging) =>
                {
                    logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                    logging.AddConsole();
                    logging.AddDebug();
                    logging.AddEventSourceLogger();
                })
                .ConfigureServices((hostingContext, services) =>
                {
                    // Fallback
                    services.PostConfigure(options =>
                    {
                        if (options.AllowedHosts == null || options.AllowedHosts.Count == 0)
                        {
                            // "AllowedHosts": "localhost;127.0.0.1;[::1]"
                            var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                            // Fall back to "*" to disable.
                            options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" });
                        }
                    });
                    // Change notification
                    services.AddSingleton(
                        new ConfigurationChangeTokenSource(hostingContext.Configuration));

                    services.AddTransient<IStartupFilter, HostFilteringStartupFilter>();
                })
                .UseIIS()
                .UseIISIntegration()
                .UseDefaultServiceProvider((context, options) =>
                {
                    options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
                });

            return builder;
        }

Burada aslına bir takım kontroller ve konfigürasyonların dışında değinmek istediğim yerler şimdilik şunlar ;

  • builder.UseContentRoot
  • builder.UseConfiguration
  • builder.UseKestrel
  • .UseIIS()
  • .UseIISIntegration()
  • .ConfigureServices((hostingContext, services)
  • .UseDefaultServiceProvider((context, options)

bu metodlar ile initialize edilen WebHostBuilder için aşağıdaki ayarlar default olarak yapılıyor.

  • builder.UseContentRoot(Directory.GetCurrentDirectory());
    • Burada projenin content root konfigürasyonu yapılıyor. Project Directory si Content Root olarak set ediliyor.
  •  builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build());
    • Burada projenin settings konfigürasyonu yapılıyor. Default olarak appsettings.json dosyasında konfigürasyonlar okunup initiate ediliyor.
  • builder.UseKestrel((builderContext, options) => { … }
    • Kestrel AspNet Core için yazılmış olan internal bir Web Server. Tabii ki bir IIS, Nginx, Apache gibi donanımlı ve yetenekli değil, ama oldukça lightweight ve performanslı çalışan bir web server. Bunun detaylarına daha sonra gireceğiz ama özetle burada Asp.Net Core Web App projesinin internal olarak Kestrel i kullanması söyleniyor ve Kestrel in bazı konfigürasyonları yapılıyor.
  • .UseDefaultServiceProvider((context, options)
    • Burada WebHostBuilder a söylenen Asp.Net Core projesinin internal Dependency injection container ını kullanması. Dependency injection artık Core ile birlikte built-in gelen bir support. Object Creation lar, ve onların lifetime managementları için bunu doğrudan kullanabiliriz. Yada kendi istediğimiz IoC Containerları kullanmasını söyleyebiliriz. Burada Default olarak built-in gelen container ın kullanılması söyleniyor.
  • .ConfigureServices((hostingContext, services)
    • Burada, Builder a kullanması için verilen IoC Container a, Proje ile alakalı bazı type ların register edilmesi sağlanıyor.
    • örneğin;  services.AddTransient<IStartupFilter, HostFilteringStartupFilter>(); Transient (obje container dan her istendiğine yenisi oluşturulup veriliyor) olarak IStartupFilter istendiğinde, HostFilteringStartupFilter ın proje içerisinde kullanılması ayarları yapılıyor.
  • .UseIIS()
  • .UseIISIntegration()
    • Burada, Web uygulamasının external web server olarak IIS kullanması söyleniyor. Gelen  requestler önce IIS den geçip, internal olan Kestrel e ulaşacak, sonrasında da middleware lerin eklenme sırasına göre her bir middleware de ilgili process ler gerçekleşip, aynı şekilde önce Kestrel e sonra da IIS e response iletilecek, ve response client tarafına dönecek.

 

Bu yazımıza da burada son verelim. Bir uygulamanın hayatına bir command line application olarak başlayıp, nasıl bir Asp.Net Core Web App e dönüştüğünü yüzeysel olarak source kodları inceleyerek anlamaya çalıştık. Bir sonraki yazımızda da Startup.cs dosyasını inceleyip artık bir Asp.Net Core Web Application ın, nasıl oluştuğunu, solution un neler içerdiğini ve konfigürasyonlarının yapılıp hayatına nasıl başladığını tamamen anlamış olacağız.

Bir sonraki yazımda görüşmek üzere.