Asp.Net Core Mvc Request Life Cycle
Merhaba arkadaşlar, bir süredir yeni bir şey öğrenmek yerine(hafta sonu dilim golang hariç 😊) var olan bilgilerimi tazeleyip, doğru bildiklerimi teyit edip yanlış bildiklerimi düzeltme üzerine çalışıyorum. Bununla birlikte kullandığım dil, tool, teknoloji vs her ne ise daha iyi nasıl kullanabilirim ile ilgili çalışmalar yapıyorum. Şunu görüyorum ki kullandığın her ne ise (dil, tool, teknoji), hakim oldukça craftsman(usta) oluyorsun.
Konuyu bağlayacak olursam; ben de bundan yola çıkarak daha iyi uygulamalar geliştirmek için, Asp.Net Core Mvc-Web api framework’ünün request life cycle’ını anlatmaya çalışacağım.
Duruma göre 2 seri olarak yazmayı planladığım ilk yazımda, Bir mvc projesinin ayağa kalkma sürecinin üzerinden geçtikten sonra, register işlemlerine dokunup, nasıl customize ederiz gibi detaylara girmeyip, Asp.Net Core Mvc’in kendine gelen bir requesti nasıl handle ettiğinden, hangi adımlardan geçtiğinden, hangi adımlarda nasıl araya girebileceğimizden bahsediyor olacağım.
Durduğumuz kabahat hadi başlayalım😊
Program.cs ( WebHostBuilder oluşturulması ve Uygulamanın çalıştırılması)
Uygulamamın başlangıç noktasıdır diyebiliriz. Console uygulamalarından alışık olduğumuz main methodu bulunmaktadır. Daha da doğrusu oluşturduğumuz .Net Core projesi bir nevi console uygulamasıdır. Uygulamanın http requestleri handle etmesi için webhost’un oluşturulduğu yerdir. Self hosting gibi düşünebiliriz. Uygulama ayağa kalkmadan önce yapmamız gereken bir şey var ise burada yapabiliriz. Bununla birlikte startup içerisinde bulunan ConfigureService, ve Confgure metodlarının tetiklendiği yerdir.
CreateDefaultBuilder Methodu Ne Yapıyor?
Yukarıda gördüğünüz gibi WebhostBuilder nesnesi oluşturulup, Kestral, ContentDirectory ve IIS entegresi için ayarlar yapılıyor. Bununla birlikte log çıktıları için Console ve Debug providerları set ediliyor. Devam edecek olursak ConfigureAppConfiguration ile konfigurasyon bilgilerini appsettings.json dosyasından, varsa, environment bazlı değişkenleri de bağlı olduğu appsettings.Environment.json dosyasından alıyor. Son olarak framework ile ilgili servisleri de DI Container içine register ediyor.
Kestral: Asp.net Core için oluşturulmuş cross-platform open source bir web serverdır. Şimdilik bir proxy arkasında çalışacabilecek yetenekleri vardır. Henüz tam gelişmiş bir web server olmadığı için Ngnix veya IIS‘in arkasında gelen requetleri pipeline içerisinde nasıl handle edileceği ile ilgilenir diyebiliriz.
Asp.net Core Ugulamasının Ayağa Kalkma Süreci
Bir Asp.net core uygulamasının ayağa kalmak sürecisini toparlayacak olursak 3 aşamadan geçiyor diyebiliriz
- IHostBuilder nesnesinin Create Edilmesi
CreateDefaultBuilder ile ilgili işlemlerdir. Host nesnesinin oluşturulması ile ilgili ayarların(Kestral’ın register edilmesi, Configurasyon bilgilerinin load edilmesi, framework servislerin register edilmesi)yapıldığı yerdir
2. Host Nesnesinin oluşturulması
Bu aşamada host nesnesi build edilir. Bununla birlikte Startup.cs ConfigureService methodu IServiceColection parametresi geçilerek tetiklenir. Burada uygulama için gerekli servislerin register edilmesi sağlanır. .Net core’un en büyük faydalarından biri modüler olmasıdır. ConfigureService methodu da, ihtiyacımız olan modül her ne ise register etmemizi sağlıyor.
Yukarıda da gördüğünüz gibi 3 adet service register edilmiştir. Bir mvc projesi oluşturuyorsanız AddContorllerWithViews() register etmemiz gerekirken bir Web-Api projesi için AddContorllers() yeterlidir.
Servislerin register edilme konusuna girip, Dependency Injection konusuna girmek olmaz ancak konun dağılmaması adına ben girmeyeceğim 😊 Merak ediyorsanız buradaki yazıma alayım.
3. Host’un çalıştırılması
Burada host nesnesi ayağa kalkmak ile birlikte hemen öncesinde startup.cs Configure methodu ile Middleware componentleri register edilerek bir http pipeline oluşturuluyor.
Middlewarelerin register edilme sırası önemlidir. Http pipeline bu sıra ile işlemlerini yürütür.
Default olarak oluşturulan Configure methodu değerlendiriecek olursak;
- Development ortamında ise DeveloperExceptionPage değilse ExceptionHandle middleware’i ile başlıyor süreç. Burada development ortamında hatalar açık şekilde gösterilirken diğer ortamlarlarda(prod ve stage) daha genel bir sayfaya yönlendirilmektedir.
- Daha sonra Https yönlendirmesi ile ilgili middleware devreye giriyor.
- Static dosyalarının kullanılması için gerekli ayarların yapıldığı middleware register edilmektedir. Bilgindiği gibi Asp.Net Core ile birlikte static dosyalar www altında barındırılmaktadır.
- Routing middleware redister edilmektedir. Bu middleware gelen requestin ile uyuşan endpointi’i eşletiririr.
- Endpoints middleware’ini register eder. Bu middlare eşleşen endpoint’in execute edilmesinden sorumludur.
Bir asp.net core uygulamsının ayağa kalma sürecinden bahsettik. Asp.Net Core framework’ü genişletilebilir bir yapıya sahiptir. Bundan yola çıkarak herhangi bir aşamada kendi middleware’imizi yazıp devreye girebiliriz.
Asp.Net Core Mvc Request Life Cycle
Kısaca Asp.net Core Request Life Cycle nedir? diyecek olursak; Gelen bir http isteğin alınıp, Client’a cevap dönünceye kadarki süreçlerin toplamıdır diyebiliriz.
Peki bu süreçler nelerdir.
Yukarıda gördüğünüz gibi Middleware ile başlayan süreç Result execution ile tamamlanıyor.
Middleware
.Net Core ile gelen en önemli konseptlerden biridir. Bununla birlikte Request Life cycle içerinde önemli rol oynar. Uygulamanın yaşam döngüsünü oluşturan componentler olarak adlandırabilir. Startup içersindeki Configure methodu ile pipeline’a dahil edilmektedir. Her Middleware requestin devamı için süreci, kendinden sonraki middleware aktarmak yada bitirmekle sorumludur. Yani http://url/style.css gibi bir request geldiğinde bu request StaticFiles middleware’inden dönerken, http://url/product/1 ile gelen request, StaticFiles bu requesti Routing middlware’ine aktarır. Routing middleware bu requesti Mvc entgerasyonu(Detaylara bir sonraki yazıda gireceğim)ile response’u döner.
Asp.net core içerisinde bir çok özellik middleware vasıtasıyla projeye dahil edilmektedir. Örnek verecek olursak; Cache, CORS, Authanticate, Routing built-in olarak gelen middleware’lerdir.
Pipeline içerisinde bir middleware tanımlamak istersek, 3(run,use,map) seçeneğimiz bulunmaktadır.
Run()=> bu middleware requesti sonlandırıp response döndürür.
app.Run(async requestDelegate =>
{
await requestDelegate.Response.WriteAsync("request sonlandırıldı.");
});
Use()=> bu middleware kendine gelen sonraki requesti tetiklemek ile sorumludur. Aynı şekilde gelen request veya response’u manipule edebilir. Request-Response log veya exception handler işlemlerinizi bu middleware seçeneği ile yapabilirsiniz.
Map()=> Bu middleware türü ReRoute işlemi yapmaktadır. Yani gelen request belirlenen routing patterne uyuyor ise response dönülür.
app.Map("/secret", appBuilder =>{appBuilder.Run(async handler =>{await handler.Response.WriteAsync("this place is secret");});});
Routing
Routing, gelen request’in en uygun olan enpoint’e eşlemesi ile ilgili sürece denir. Buradaki endopont’i Controller-Action olarak algılayabilirsiniz.
Conventional ve Attribute olmak üzere 2 farklı şekilde routing tanımlayabilirsiniz.
Conventional Routing
Uygulama bazlı tanımlanan Url’yi Controller ve action ile eşleştiren yöntemdir. Default olarak Endpoint middleware ile tanımlanan routinglerdir.
Attibute Routing
Contoller veya actionlar için attibute aracılığıyla tanımlanan routinglerdir. Özellikle web-api lar için en çok tercih edilen routing türüdür.
Not:Routing sonrası devreye endpoint execution süreci giriyor. Özetleyecek olursak Controller Intialize ile başlayan Result execution ile biten süreç mvc framework’ün adımlarıdır.
Controller Intialization
Burada olayı başlatan ve sonlandıran middleware EndpointMiddleware’dir. ActionEndpointFactory içerisinde CreateRequestDelegate ile oluşan delegate tetiklenir. Bununla birlikte ControllerActinInvoker devreye girer. İşte bu noktada geleneksel MVC pipeline’nını ControllerActionInvoker yönetir. Aslında ControllerActionInvoker görevi tam olarak CreateController ile birlikte devralıyor. Bunun öncesinde ki işlemler miras aldığı ResourceInvoker tarafında gerçekleşiyor.
Not: ControllerActinInvoker, ResourceInvoker abstract class’ından türemiştir.
ControllerActinInvoker, AuthFilters ile başlar, Result Execution ile sonlanır. Aslında Result Filter tamamlanır dersek daha doğru olur. Result Filter, Result Execution’dan önce ve sonra çalışır.
Authorization Filters
Mvc pipeline içerisinde ilk execute edilen filter’dır. Requestin yetki geçerliliği olup olmadığını belirler. Invoke edilecek endpoint’ten önce çalışır. Authorize olmadığı durumda request devam etmez. Auth filter içerisinde throw edilen exception, Exception filter içerisinde handle edilemediğinden, uzak durulması öneriliyor.
Authorization filter, IAuthorizationFilter interface’ini imlemente eder. Custom bir authorization filter yazmak isterseniz bu interface’i implemente etmeniz gerekmektedir.
Resource Filters
İlk karşılaştığımda nedir, nasıl kullanabiliriz gibi soru işaratleri vardı. Şuanda da çok fazla kullanılacağı kanaatinde değilim 😊 Resource filter tüm action Invoke sürecini içine kalan filter’dır. Yani Auth sonrası-Middleware filter öncesi ve Result Filter sonrası devreye girer. Specific actionlar cache vs gibi durumlar için kullanılabilir. Ek olarak resquest gelen data yı manipule edebilirsiniz.
Custom bir resource filter yazmak için IResourceFilter veya IAsyncResourceFilter interface’ini implemente etmek gerekiyor. Filter pipeline içerisinde, tüm process’i wrap’leyen tek filter’dır. Öncesinde Auth çalışır ama bu filter sadece öncesinde çalışır, sonraki filter ve execution pipeline’ını wrap’lemez.
Middleware Filters
Middleware koşullarını mvc pipeline içinde sağlayan componenttir. Middleware içerisinden mvc context’ine sınırlı olarak müdahale edebiliyoruz. Bu sebep ile controller sevisesinde logic vs var ise bu filter vasıtasıyla çalıştırılabilir.
MiddlewareFilter attibute’ini kullanarak ilgili middleware type’ınını verip kullanabilirsiniz. Henüz bir proje içerisinde kullanmadığımı söyleyebilirim.
Not: Auhorization Filters ile başlayıp buraya kadar gelen süreç ResorceInvoker tarafında gerçekleşiyor. Bundan sonra ControllerActionInvoker devreye giriyor.
Controller Action Invoker(Action Workflow)
Action’ları, Controller içerisinde public olarak tanımlanmış olup, gelen requesti handle etmek için bir routinge bağlı olan methodlar olarak düşünebiliriz.
ControllerActionInvoker’da Action execution ile ilgili sorumluluğu devralır. Conroller’ın create edilmesinden sonraki model binding ile başlayan bu sureç, ilk devreye giren Action Filter’ın OnActionExecuted methodun execute edilmesi ile tamamlanır. Action Method’un görevi gelen requesti işleyip, Action Result tarafında render edilmek üzere Response data ve Type’ı sağlamaktadır. Bu response objesi, browserda gösterilmek üzere Result Execution(Action Result) aşamasında render edilir.
Model Binding
Action Methodlar, otomatik olarak model binding tarafından kendine pass edilen parametreleri kabul eder. Model binding, request ile birlikte gelen datayı(url, form data, header, request payload), action methodlar için belirtilmiş parametrelere set etmekle mükelleftirler.
Mvc’nin kendi kodundan bir parça bırakıyorum buraya. Action Invoke edilmeden önce, 9. satırda controller create ediliyor ve 15. satırda parametreler bind ediliyor.(Model binding)
Burada önemli olan 2 interface’den bahsetmek istiyorum. IModelBinderProvider ve IModelBinder. IModelBinderProvider gelen paramtereye göre hangi modelBinder’ın kullanacağını belirler. Geriye IModelBinder döner. Bu model binder ile de ilgili parametre için value atama işlemi gerçekleşir.
Mvc’nin kendi içinde default olarak bir çok model BindingProvidder kullanılmakta. Action için belirlediğiniz bir IFormFile parametresi için FormFileModelProvider devreye girerken, bir Complex Type parametresi için ComplexTypeModelBinderProvider devreye girmekte.
Action Filters
Action Filter, mvc projelerinde en çok kullanılan filter türüdür dersem yanılmam. Yaptığımız uygulamalarda kullanmayan yoktur diye düşünüyorum. Action çalışmadan hemen önce veya çalıştıktan hemen sonra çalıştıracağınız bir logic var ise burada yapabilirsiniz.
IActionFilter interface’ini implemente edererek custom Action Fiter yazabilirsiniz. OnActionExecuting içerisinde actiondan önce çalışacak kodunuzu, OnActionExecuted methodunu da Action’dan sonra çalışacak kodunuz için kullanabilirsiniz. Bu filter içerisinde, Action Result nesnesini sağlayarak requesti sonlandırabilirsiniz.
Result Execution
Pipeline içerindeki son aşamadır. Action methoddan dönen response objesini render etme ile ilgilenir. Bu süreci kendi içinde 2'ye ayırabiliriz. Sonuç olarak sadece data dönen(json,xml,text) ve Razor wiew engine ile result’ın html’e render edilme süreci.
Tüm Action Result tipleri IActionResult interface’ini implemente etmiştir. ExecuteResultAsync methodu, reqest sonucu oluşan result çıktısını oluşturur. IActionResult Interface’ini implemente ederek kendi custom Action Result’ımızı yazabiliriz. Bu aşama içinde herhangi bir ihtiyacım olmadı açıkçası.
Result Filter
Action Filter için anlattığım şeyler tam olarak bunun içinde de geçerli. Action değilde Action dan dönen Result Execute olurken ve sonrasında devreye girer.
Sonuç
Asp.net Core’un en önemli özellklerinden biri de modüler olması idi. Bende bundan yola çıkacak bir request hangi süreçlerden anlatmak istedim. Bir kitaba sığacak konuyu bir yazıya sığdırmak kolay olmasa da, umarım keyif aldığınız bir yazı olmuştur. Hangi aşamada nasıl devreye gireceğimizi anlatacağım bir sonraki yazıda görüşmek üzere.
Sağlıcakla kalın,
Kaynaklar