Asp.Net Core 2.0’a Giriş – Bölüm 3 (Startup.cs, Built-In Dependency Injection hakkında)
Selamlar, Asp.Net Core serimize kaldığımız yerden devam ediyoruz. Önceki iki yazımızda, projemizi oluşturduk, solution daki tüm dosyaları inceledik, projenin nasıl ayağa kalktığına değinmeye çalıştık. WebHostBuilder objemizin Run() metoduyla artık Web application a dönüşen projemiz, tam olarak hayatına başlamadan önce son ve önemli ayarlamalarını yapacağımız kısım Startup.cs dosyası olacak. Buna başka bir isim de verebilirdik tabii isme çok takılmayın, ama default gelen ve genelde göreceğiniz ismi ile Startup.cs, DI container ını ve Middleware leri ayarlayacağımız önemli bir dosya olduğunu bilelim 🙂
Serinin başında oluşturduğumuz ve halen boş haliyle duran projemizde ki bu dosyanın içeriğine baktığımızda aşağıdaki gibi 2 temel metod görüyoruz.
public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseStaticFiles(); app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } }
Şimdi bu iki metodu detaylıca inceleyelim.
Configure Services
Bu metod .Net Core ile birlikte gelen, built-in Dependency Injection Container ının ayarlarını yapacağımız kısım olacak. DI klasik Asp.net tarafında optional iken, artık bilmemiz gereken bir durum haline geliyor. Çok kısaca özetlemek gerekirse Dependency Injection için şunu diyebiliriz:
“Uygulama içerisinde ben senden bu (ICriticalService) tipte bir şey istediğimde bana şu (MainCriticalService) tipi ver, bir de şu (MainCriticalService) tipin ne kadar süre hayatta kalacağını ben sana en başında söyleyeyim gerisini sen yönet.”
Birde aşağıdaki resim üzerinden bakalım. Dependency Injection Pattern i, genelde IoC denen (Inversion Of Control) Containerları kullanılarak yapılır. Container’ı uygulama start olduktan sonra ilk iş olarak initialize edilir, ilgili service registration larını yapar uygulama içerisinde bu tipler çağırıldığı zaman gerçek sınıfları Container ın bize verilmesi beklenir. Bu yazının temel amacı DI ı anlatmak olmadığından fazlaca buraya takılmadan Asp.Net Core tarafında bu işler nasıl dönüyor, Farklı Lifetime tipleri neler onlara bakalım.
(Sadece DI konusu tek başına çok derin, en son şunu yazmak isterim güzel bir örnektir.Dependency Injection 5 yaşındaki bir çocuğa nasıl anlatılır? Cevap şuna benzer: Eğer 5 yaşındaki çocuk acıktığı zaman, doğrudan buzdolabına gidip istediği şeyi almaya kalkarsa, yanlış şeyi alabilir, şeklinden, görüntüsünden farklı şey zannedip yanlış şeyleri alabilir, tarihi geçmiş bir içeceği-yiyeceği alabilir, ortalığı batırabilir 🙂 Bunun için aslında yapması gereken, acıktığında istediği şeyi ailesine söylemesi, ailesinin de onun istediği şeyi ona vermesidir. Burada tahmin edeceğiniz üzere çocuk yazılan app, aile IoC Container dır.)
Resimde gördüğümüz gibi bir taraftan genel olarak Interface i yolluyoruz, çağırdığımızda diğer taraftan bize isteiğimiz sınıf geliyor. En başında da bu istediğimiz söylüyoruz tabii. Asp.Net Core tarafında 3 tipte lifetime option var. bunlar
- Transient: Bu yaşam tipi ile bir objeyi register ettiğimiz zaman, Container dan bu obje her istendiğinde yenisi verilir. Her defasında dolaptan yeni bir içecek almak gibi düşünelim.
- Scoped: Bu yaşam tipi ile bir objeyi register ettiğimizde, Container bize ilgili Request sonlana kadar aynı objeyi verir, yeni bir request geldiğinde yeni bir obje oluşturulur. Tek bir öğünde 5 yaşında ki çocuğumuz aynı şey isteyip durursa, dolaptan yenisini almak yerine önünde yarım kalmışı ona geri itelediğimizi düşünebiliriz 🙂
- Singleton: Bu yaşam tipi ile bir obje register ettiğimizde ise, Container dan obje istenildiğinde sadece ilk sefer için oluşturulur, sonrasında da her zaman aynı obje verilir. Dolap örneğine dönersek, çocuğumuz her susadığında ve bizden su istediğinde ona hep aynı bardaktan vermek gibi düşünebiliriz (Bu biraz zorlama oldu sanki 🙂 )
Lifetime ların önemi büyük, özellikle birbirini çağıran metodlar olduğunu düşünürsek (ki bu durum bir uygulama için gayet normal bir durum), singleton register edilmiş bir metot içerisinde scoped bir servis çağırdığımızda çarşı karışacaktır, yaşam döngüleri birbirini etkileyemeye başlayacaktır. Bunlarla ilgili daha derinlemesine detayları başka bir yazıda değineceğim.
Peki bu durum bize ne sağladı?
- Objeler arasındaki sıkı bağlar ortadan kalkmış oldu. Dolayısı ile Unit Testing de kolaylaşmış oldu
- Objelerin lifetime larını yönetme zorluluğu ortadan kalmış oldu (Ama registraion lara, ve birlikte kullanımlara dikkat ederek :))
Şimdi kod tarafına girelim. Boş olan projemize, bir NetStandard kütüphanesi ekleyelim, ve oraya uygulamanın kullanacağı modelleri ve uygulama servislerini (Application Service) lerimizi koyalım. Örneğimiz basit olsun, Sitemizin kullanıcılarını listeleyelim ve onların sitemizdeki rollerini görelim. O halde Solution a bir adet NetStandard2.0 projesi ekliyorum ve içini aşağıdaki gibi dolduruyorum.
Solution ın son hali yukarıdaki resimde göründüğü gibi oldu. Senaryonun son derece basit, User ve Role ler var. bir de bunları bize dönen Application Servicelerimiz var. kodlar şu şekilde;
public class Users { public Users() { UserRoles = new List(); } public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public IEnumerable UserRoles { get; set; } } public class Roles { public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } } public interface IUserService { Task GetUsers(); Task GetRoles(); } public class UserServiceInMemory : IUserService { public UserServiceInMemory() { RoleList = new List { new Roles { Id = 1, Name = "Admin", Description = "Admin" }, new Roles { Id = 2, Name = "Editor", Description = "Content Editor" } }; UserList = new List { new Users { Age = 31, Id = 1, Name = "Erhan Ballıeker", UserRoles = RoleList}, new Users { Age = 30, Id = 2, Name = "Meltem Ballıeker", UserRoles = RoleList.Take(1) } }; } public List UserList { get; set; } public List RoleList { get; set; } public Task<List<Roles>> GetRoles() { return Task.Run(() => RoleList); } public Task<List<Users>> GetUsers() { return Task.Run(() => UserList); } }
Peki hızlıca özetleyelim. 2 adet basit class var User ve Role class ları. bir interface miz var. Gerçek hayat senaryosunda bu UserInterface inin neler yapmasını istiyorsak ilgili metodları buraya koyuyoruz. Sonra sadece test amaçlı kullanacağımız bu interface den türeyen In-Memory çalışacak bir UserServiceInMemory class ı oluşturuyoruz. Şimdi gelelim bunu Asp.Net Core tarafındaki Container a anlatmaya.
Startup.cs dosyasındaki ConfigureServices metodunun bu Container ı konfigüre etmek amaçlı olduğunu söylemiştik. Şimdi registration işlemimizi yapalım. Core projesine NetStandard projesini referans olarak gösterdikten sonra ConfigureServices metoduna aşağıdaki kodu ekliyoruz.
services.AddSingleton<IUserService, UserServiceInMemory>();
Burada dediğimizşey şu: Ben ne zaman senden bir IUserService istersem, bana UserServiceInMemory getir, ve bu service singleton (yani uygulama boyunca 1 kere oluşturulsun ve sonra hep aynı obje kullanılsın) olsun.
Peki bu register ettiğmiz service i nasıl kullanacağız? bu blog post içerisinde Configure metoduna ve dolayısı ile Middleware yapısına çok girmeyeceğim, fakat şu şekilde test edelim. Configure metodu içerisindeki app.Run(…) metodu ile uygulamamıza tek bir middleware eklenmiş. Yani request uygulamamıza geliyor, response a “Hello World!” yazıp geri dönüyoruz. Çok işe yarar bir uygulama değil gibi 🙂 Şimdilik, daha projemize hiçbir Controller vs eklememişken, bu service’i , bu tek middleware içerisinde test edelim. Asp.Net Core projelerinde, register edilen service’leri çağırmanın birkaç yolu var. Bunlardan biri request Context i içerisinden bu service’i almak. Bunun da detayına sonra gireceğim, ama şimdilik sadece görmüş olalım ve service imizi web uygulamamız içerisinde kullanmış olalım amacı ile, tek middleware i aşağıdaki gibi düzenleyebiliriz.
app.Run(async (context) => { var serviceProvider = context.RequestServices; var userService = serviceProvider.GetRequiredService<IUserService>(); var users = await userService.GetUsers(); await context.Response.WriteAsync($"Hello World! - UserCount: {users.Count}"); }); }
Uygulamamızı çalıştırdığımızda browserdaki çıktının aşağıdaki gibi olduğunu görüyoruz.
Evet! servisimiz Container dan istediğimiz gibi bize ulaştı, ilgili metodu çağırıp eklediğimiz userları service den aldık. Ekrana da Count u basmış olduk. Bu yazıda çok şey yapık aslında, WebHostBuilder oluştuktan sonra, Startup dosyamızı kullan diyerek, önce ConfigureServices metodu ile DI a biraz değinip, Asp.Net Core projesi içerisinde nasıl konfigüre ediyoruz onu görmüş olduk, ve tek bir middleware üzerinde register ettiğimiz servisimizi alıp kullanabildik.
Bir sonraki yazımda, yine startup dosyasındaki ikinci bir önemli metod olan Configure ile beraber middleware yapısına değineceğim.
Görüşmek üzere.