.NET 8 Keyed Service
Herkese merhaba arkadaşlar,
Çok uzun bir aradan sonra yeniden beraberiz. Umarım bundan sonra arayı bu kadar açmayacağız. 😉
Bugünkü konumuz DI(Dependency Injection) ile ilişkili olacak. Bildiğiniz üzere ASP.NET Core built-in bir DI Container ile gelmektedir. Bu sayede uygulamamız içindeki bileşenlerin arasındaki bağımlılıkların yönetilebilir, uygulama kodunun test edilebilir, bakımı daha kolay ve esnek hale gelmesini sağlar. Tabiki built-in gelen bu DI container yerine Autofac, Castle Windsor gibi third party kütüphanelerde kullanabiliriz. Hatta keyed servis özelliği hali hazırda Autofac’de bulunmakta. Fakat ben entegrasyon kolaylığı açısından built-in olanı kullanıyorum.
Not: DI nedir bilmeyenler şu yazıma göz atabilir.
Bugün .NET 8 ile birlikte gelen Keyed Service özelliğine odaklanacağız. Fakat bu özelliğe geçmeden önce .NET 8'de IoC Container’a register işlemini nasıl yaptığımıza dair basit bir örnek vererek açıklayalım. Bu sayede keyed servisin nerede gerekli olduğunu gösterelim ve örneği genişletelim:
Aşağıdaki şekilde bir interface’imiz
Tabiki servislerimizde var:
Program.cs içerisi:
Üç servis ve bir interface tanımladık. Dikkat ederseniz eğer bu üç serviste tek bir interface’i implemente ediyor. Ardından IServiceCollection interface’ini kullanarak da servislerimizi built-in IoC Container’mıza kayıt ediyoruz. Bu durumda bana şu soruyu sorma hakkı doğuyor:
HomeController içindeki Get() endpointinin dönüş değeri ne olacak? Dönüş değeri tabiki “Service C” olacak. Servisler aynı interface’i implemente ettiğinden dolayı IoC Container’dan talep oluşturulduğunda container’a kayıt edilen son servisin nesne örneği oluşturulur. Ve son servis ServiceC’ dir. Şimdi bir tık derine inerek servisleri kayıt ederken arka planda ne yapıldığına bakalım:
Bu register işlemini yaptığımızda arka plada çalışan metod şudur:
Metoda göz gezdirirsek eğer şunu söyleyebiliriz:
IServiceCollection arkaplanda ServiceDescriptor sınıfını kullanarak servislerin register edilerek yapılandırılmasını sağlar.Peki ServiceDescriptor ‘ın aldığı parametreler nedir?
Service Type: Register edilmek istenen türün tipini belirler. Bizim örneğimiz için IService interface’idir. Bu class veya record’da olabilir.
Implementation Type : Service Type parametresinde belirtilen interface’i implemente eden class’ın türüdür. Bizin örneğimiz için ServiceA, ServiceB ve ServiceC Implementation Type’dır.
Life Time : Bu parametre ise servisin yaşam döngüsünü belirler. Bu örnek için “ServiceLiftime.Scoped” ‘ dır.
Buraya kadar anlattıklarımın amacı, giriş seviyede register işleminin nasıl yapıldığı ve buna ek olarak aynı interface’i uygulayan servislerin davranışını ele almaktı. Şimdi ana konumuza dönelim:
Keyed Service Nedir?
Keyed service en temel ifade ile IoC container’a bir interface için birden fazla servis sınıfı kayıt edildiğinde istenilen servisi aynı interface üzerinden bir key (string bir key) aracılığı ile talep etmeyi sağlar. Ve bunu yine yukarıda bahsettiğimiz ServiceDescriptor sınıfını genişleterek sağlar.
Örneğimizde program.cs ve HomeController’ı şu şekilde değişelim:
AddKeyedScope metodunu kullanıyoruz ve parametre olarak her bir register işlemine bir key değeri veriyoruz.
Dikkat ederseniz primary constructor içinde FromKeyedServices ile aynı interface’i implemente eden servislerden hangisinin çağırılacağını string bir key ile belirttik. Bu kullanımda ise result değeri tahmin ederseniz ki “ServiceA” olacaktır.
Arka planda çağrılan ServiceDescriptor sınıfı ise şu şekilde olmakta:
Dikkat ederseniz serviceType, implementationType ve lifetime parametrelerine ek olarak serviceKey değeri eklenmiştir.
Hepinizin aklına şu tür bir soru geldiğini düşünüyoum:
“Şimdiye kadar built-in IoC container kullandık, kimse aynı interface’i kullanan servisleri container’a kayıt edip bunları manipüle edebilmek istemedi mi? Veya bunun için third party kütüphanaler mi kullandı?”
Evet built-in IoC kullandık. Ve third party kütüphane kullanmadan veya keyed service kullanmadan da bu sorunu çözebilmekteyiz. Aynı örnek üzerinden benim şimdiye kadar kullandığım çözümleri sizlerlerle paylaşacağım. Hazırsanız başlayalım:
TryAddEnumerable
Bu sefer IServiceCollection’nın extension metodu olan TryAddEnumerable’ı kullanarak register işlemlerimizi yapacağız:
Keyed Service’de “ServiceA” ’yı çağırmıştık burdada aynı servisi IoC Container’dan talep edelim:
Özetlemek gerekirse, ServiceDescriptor nesnelerini bir dizi halinde oluşturduk. Ardından IoC Container’a Scoped olarak kayıt ettiğimiz servisleri tip kontrolü yaparak çağırdık.
Enum Kullanma
Aynı interface ile kayıt ettiğimiz servislerimizi ayırt etmek için enum kullandığımız durumdur. Ben bunun için TryAddScoped extension metodunun aşağıdaki overload’ını kullanacağım:
Bu metodu şu şekilde özetleyelim, Func<IServiceProvider,TService> türünde bir delegate alıyor. Yani aldığı parametre aslında IServiceProvider alıp geriye TService döndüren bir metod. O zaman kodumuzu yazmaya başlayalım:
Öncelikle dikkat edersek eğer ServiceA, ServiceB, ServiceC servislerimizi herhangi bir interface olmadan direk olarak servise register ediyoruz. Yani bu sefer Service Type’ımız bir interface değil bir sınıftır. Ardından TryAddScoped extension metoduna bir metodu parametre olarak veriyoruz. Aslında TryAddScoped’ın aldığı parametre şuan tam olarak şu şekilde:
Ardından basit bir switch yapısı ile beraber enum tipine bakarak register ettiğimiz sınıfların çağırımını yapıyoruz. Yani constructor’da aşağıdaki şekilde bir talepde bulunduğumuzda TryAddScoped’ın içerisi çalışacak.
Böylelikle result değerimiz yeniden “ServiceA” olmuş olacaktır.
Keyed Service’i inceledik. Alternatif yöntemlerine göz attık. Aslında bunlar tamamiyle benim kullanmış olduğum alternatifler. Bu yöntemler dışında da farklı çözümler elbette vardır. Onlarıda yorum olarak belirtebilirsiniz. Açık söylemek gerekirse ben Keyed Service özelliğinin geç bile geldiğini düşünüyorum. O yüzden gelmesine sevindim diyebilirim. Çünkü kodumuza oldukça esneklik kazandıracak. Tabiki doğru bir şekilde kullanırsak.
Eveeettt. Bu konu hakkında temas edeceğim noktalar bu şekildeydi . Böylelikle uzun bir aradan sonra yeniden bir yazının daha sonuna gelmiş bulunuyorum. Eğer gözümden kaçan yazım hatası veya anlatım bozukluğu olduysa şimdiden kusuruma bakmayın. Yazım umarım faydalı olmuştur. Okuduğunuz için teşekkürler. Bir sonraki yazıda görüşmek üzere!
Yararlandığım Bazı Kaynaklar: