ASP.NET Core & Dependency Injection

Semih Elitaş
Ada Yazılım
Published in
8 min readApr 17, 2020

Bu makale Yazılım Mimari Thanh Le tarafından yazılan “[.NET Core] Dependency Injection in ASP .NET Core — “Old but gold”” makalesinin Türkçe çevirisidir. Kendisine bu makaleyi Türkçeye çevirmem için izin ve destek verdiği için teşekkür ediyorum. Standart haline gelmiş bazı kelimeleri çevirmediğimi göreceksinizdir makale içerisinde, anlam bütünlüğünün ve standarta uymak açısından bazı kelimeleri çevirmeden sizlere sundum.

Çay veya kahvenizi aldıysanız, başlayalım! Keyifli okumalar…

Photo by Toby Christopher on Unsplash

Bu makalede Dependency Inversion Principle (DIP), Inversion of Control (IoC) ve Dependency Injection (DI) hakkında konuşacağız. Bu konuların ardından, .NET Core’un özelliklerinden yararlanarak farklı yollarla (Controller Constructor Injection, Controller Method Injection ve View Injection) basit bir örnek yaparak, Dependency Injection uygulanışı hakkında bilgi sahibi olacağız.

Not: Eğer Dependency Inversion Principle (DIP), Inversion of Control (Ioc) ve Dependency Injection (DI) konularını zaten biliyorsanız, rahatlıkla bu kısımları geçebilirsiniz.

Dependency Inversion Principle (DIP)

görsel kaynağı: internet

Dependency Inversion Prensibi, SOLID Yazılım Geliştirme Prensiplerindeki “D” prensibidir. Bu prensip 90'lı yıllarda Robert C Martin tarafından tanıtıldı. Prensibin tanıtıldığı orjinal dökümana buradan ulaşabilirsiniz.

Not: Yazının bu kısmından sonra Dependency Inversion Prensibini “DIP” olarak kısaltmakla beraber, abstract class ve concrete class gibi özel terimleri “soyut sınıf” ve “somut sınıf” olarak çevireceğim.

DIP, esnek sınıflar yazmamıza yardımcı olan bir yazılım tasarımıdır. Wiki’deki DIP tanımına göre:

Yüksek seviyeli modüller, düşük seviyeli modüllere bağlı olmamalıdır. Her ikisi de soyutlamalara bağlı olmalıdır.

Soyutlamalar detaylara bağlı olmamalıdır. Ayrıntılar soyutlamalara bağlı olmalıdır.

Yukarıdaki tanımları daha iyi anlamak için size bir örnek vereyim:

Bir siparişi bitirdiğinde, sistemin son kullanıcıya e-posta göndermesine izin veren bir fonksiyon yazmanız gerektiğini düşünelim. Biri sipariş için, diğeri de e-postayı göndermek için olmak üzere 2 tane sınıf(class) oluşturmalıyız.

görsel 1: SendingEmail ve Ordering sınıfı

İlk olarak, kod mantığında herhangi bir sorun yok, son kullanıcı siparişini bitirdikten sonra “Send” fonksiyonu tetiklenecektir. Ancak, bu Dependency Inversion Prensibini ihlal ediyor çünkü SendingEmail sınıfı ve ona bağlı olan Ordering sınıfı soyut değil, somut sınıflardır. Peki buradaki problem nedir? Farz edelim ki iş ekibinizden, E-posta yerine SMS kullanmak için iletişim türünü değiştirmenizi gerektiren yeni bir istek aldınız, ne olur?

görsel 2: SendingSMS sınıfı ekliyoruz

Sonuç olarak, SendingSMS sınıfı oluşturmanız ve Ordering sınıfında bunun bir örneğini bildirmeniz gerekir. Ve her farklı iletişim türü eklediğimizde bunu teklarlamak zorundasınız. Bunun sonucunda ise, SMS veya E-posta’yı iletişim aracı olarak kullanmaya karar vermek için “IF ELSE” koşul ifadeleri kullanmak zorunda kalırsınız. Fakat, E-posta ve SMS’nin yanında daha fazla seçeneğiniz olduğunda bu durum gittikçe daha da kötüleşir. Bu, Ordering sınıfında daha fazla yeni sınıf tanımlamak gerektiği anlamına gelir.

Dependency Inversion Prensibi, sistemi/programı daha yüksek seviyeli modüller (bu örneğimizde Ordering sınıfı) olan soyutlamalara bağlı olacak ve somut sınıflar yerine kullanacak şekilde ayırmamız gerektiğini söylüyor. Bu soyutlama, asıl kod mantığını gerçekleştirecek olan gerçek somut sınıfıyla eşleştirilecektir. (Sonraki kısımlarda örneğini görebilirsiniz)

Inversion of Control (IoC)

Inversion of Control (IoC), alt seviye modüllerin somut uygulamasından ziyade soyutlamalara bağlı olarak daha yüksek seviye modüller yapmamıza yardımcı olan tekniktir. Başka bir deyişle, Dependency Inversion Prensibinin uygulanmasına yardımcı olur. Hadi yukarıda incelediğimiz örneğe geri dönelim ve IoC’yi uygulayalım.

İlk olarak, üst düzey Ordering sınıfının bağlı olacağı bir soyutlama yaratmamız gerekir.

görsel 3: ICustomerCommunication arayüzü (soyutlama örneği)

Soyutlama için arayüzümüzü oluşturduktan sonra, ICustomerCommunication arayüzünden miras almaları için “SendingEmail” ve “SendingSMS” sınıfını güncelleyin.

görsel 4: SendingEmail ve SendingSMS sınıfı güncellendi

Şimdi daha düşük seviyeli somut sınıftan ziyade, yüksek seviyeli modül, Ordering sınıfını bu soyutlamayı kullanacak şekilde değiştirelim.

görsel 5: Yüksek seviyeli sınıf güncellendi

Sonuç olarak, tasarım şöyle görünecektir:

görsel 6: UML Diagram

Burada yaptığımız şey, DIP’ye uymak için kod işleyiş kontrolünü tersine çevirmektir. Şimdi yüksek seviyeli modüllerimiz, sadece DIP’nin belirttiği gibi daha düşük seviyeli somut uygulamalarına değil, sadece soyutlamalara bağımlıdır.

Dependency Injection (DI)

Yukarıdaki örneğimizde IoC’yi uygulayıp, Ordering sınıfını ICustomerCommunication arayüzüne/soyutlamasına bağlı hale getirdik. Ancak hala Ordering sınıfında (daha üst düzey bir modül) somut sınıflar kullanıyoruz. Bu, sınıfları tamamen birbirinden ayırmamızı engeller.

görsel 7: Ordering sınıfındaki somut sınıflar

Tam burada Dependency Injection (DI) devreye giriyor!

DI temel olarak somut uygulamayı, soyutlama kullanan bir sınıfa enjekte etmek içindir (i.e. ICustomerCommunication arayüzü). DI’nin ana fikri, sınıflar arasındaki bağlantıyı azaltmak, soyutlamanın ve somut uygulamanın bağlayıcılığını bağımlı sınıfın dışına taşımaktır. DI’yi 3 şekilde uygulayabiliriz:

Constructor Injection

Method Injection

Property Injection

  1. Constructor Injection

Bu yaklaşımda, somut sınıfın nesnesini bağımlı sınıfın yapıcı fonksiyonuna (constructor) geçireceğiz ve onu kullandığı arayüze atayacağız.

görsel 8: Constructor Injection

Yukarıdaki kodda, yapıcı fonksiyon (constructor), somut sınıf objesini alacak ve arayüz örneğine bağlayacak. Eğer SendingSMS’in somut örneğini bu sınıfa geçirmemiz gerekirse, tek yapmamız gereken SendingSMS sınıfının bir örneğini yaratmak ve ardından onu aşağıdaki gibi Ordering sınıfının yapıcı fonksiyonuna (constructor) parametre olarak iletmektir:

görsel 9: Constructor Injection kullanımı

2. Method Injection

Constructor Injection kullanırken, somut sınıf (Ordering sınıfının ömrü boyunca SendingSMS veya SendingEmail sınıfı) örneğini kullanmak zorundayız. Şimdi ise somut sınıf örneğini uygulamanın her metoduna geçirmek istiyorsak Method Injection yöntemini kullanmalıyız.

görsel 10: Method Injection

Ve Method Injection yöntemini aşağıdaki gibi kullanacağız:

görsel 11: Method Injection kullanımı

3. Property Injection

Artık bağımlı sınıfın Constructor Injection ile tüm yaşam döngüsü boyunca bir somut sınıf kullanacağını ve Method Injection’ın sadece “method” seviyesinde etki edeceğini biliyoruz. Ancak, ya somut sınıf örneği ve metodun çağrılmasının uygulamaları/sorumluluğu ayrı yerlerde bulunuyorsa. Bu gibi durumlarda, Property Injection kullanımına ihtiyacımız var.

Bu yaklaşımla, somut sınıfın nesnesini bağımlı sınıf tarafından ortaya çıkarılan bir setter property üzerinden geçiriyoruz/uyguluyoruz.

görsel 12: Property Injection

Ve Property Injection yöntemini aşağıdaki gibi kullanacağız:

görsel 13: User property injection

Constructor Injection, DI’nin uygulanması söz konusu olduğunda en çok kullanılan injection yöntemidir. Eğer her method çağrısına farklı bağımlılıklar geçirmemiz gerekiyorsa, Method Injection kullanırız. Property Injection, diğer yöntemlere göre daha az kullanılır.

Şu andan itibaren, yeni başlayan biriyseniz artık DIP, IoC ve DI hakkında bilgi sahibisiniz. Bir sonraki kısımda, bir .NET Core projesi içerisine Dependency Injection (DI) implemente edeceğiz/uygulayacağız.

ASP .NET Core’da Dependency Injection

Photo by Anton Nazaretian on Unsplash

Dependency Injection, ASP.NET Core içerisinde bulunan ve desteklenen bir özelliktir. Bu destek sadece ara katmanlarla (Middlewares) sınırlı değil, ayrıca Model, View ve Controllerlar içerisinde de mevcut. ASP.NET Core tarafından sağlanan, iki tipte servis konteyneri(Service Container) bulunmaktadır: Framework Services and Application Services.

Framework Servisleri, ILogger gibi ASP.NET Core tarafından desteklenen servislerdir.

Application Servisleri ise ihtiyaçlara/gereksinimlere göre oluşturulan özel servislerdir.

görsel 14: Framework servisleri — ILogger

Controllerda Dependency Injection

ASP.NET Core, yapıcı fonksiyon (constructor) tabanlı bağımlılığa destek vermektedir. Controller’ın gerektirdiği bağımlılık, yalnızca yapıcıdaki controllera bir servis türü eklemektir. ASP.NET Core, servis tipini tanımlar ve tipi çözümlemeye çalışır. Evet böyle tanımlamalarla aklımızda tam olarak şekillendiremiyoruz bu yüzden haydi bir örnekle anlamaya çalışalım!

İlk olarak, “IWelcomeMessage” arayüzünden miras alan “WelcomeMessage” adlı bir somut sınıf oluşturacağız (öğrendiğimiz üzere bu bir soyutlama (abstraction) yöntemidir).

görsel 15: IWelcomeMessage arayüzü ve WelcomeMessage sınıfı

Şimdi bu servisi servis konteynerine eklemeliyiz, böylece Controller servise istekte bulunabilir ve istediğinde onu kullanabilir. Servisimizi, Startup.cs sınıfının ConfigureServices metodu içerisinde tanımlayarak, servis konteynerine (Service Container) ekleyebiliriz. Bu servislerin 3 farklı yaşam opsiyonu mevcuttur: Transient, Scoped, and Singleton (Bu opsiyonlara önümüzdeki kısımlarda değineceğiz).

görsel 16: Startup.cs içerisindeki ConfigureServices metodunu güncelliyoruz.

Son olarak, yapıcı fonksiyon (constructor) aracılığıyla servisimiz controllera enjekte edilir.

görsel 17: Controller içerisinde Dependency Injection

Artık sonucu görebiliriz:

görsel 18: Dependency Injection sonucu

Lütfen Dependency Injection’ı Startup sınıfının ConfigureServices metodu içerisine kaydettiğinizden emin olun, aksi takdirde aşağıdaki gibi bir hata ile karşılacaksınız:

görsel 19: DI hatası

Controller Metodlarına/Actionlarına Bağımlılığı Enjekte Etme — Method Injection

ASP.NET Core, “FromServices” attribute’u kullanarak belirli action’a bağımlılığı enjekte etmemizi sağlar. Bu attribute, ASP.NET Core framework’une, parametrenin servis konteynerinden (Service Container) alınması gerektiğini bildirir.

görsel 20: Method injection

Ve sonuç hala aynı:

görsel 21: Method Injection sonucu

Not: Property Injection, ASP.NET Core tarafından desteklenmemektedir.

Servisi Manuel Olarak Enjekte Etmek

Bu metotda servis, Controller constructorına veya Controller actionına parametre olarak enjekte edilmez. “HttpContext.RequestServices” özelliğinin “GetService” metodunu kullanarak servis konteyneri ile yapılandırılmış bağımlı servisleri alabiliriz.

görsel 22: Servisi manuel olarak enjekte etmek

Servisi Viewlara Enjekte Etmek

ASP.NET Core, View bağımlılığını da enjekte edebilir. Bu, View ile ilgili “yerelleştirme(localization)” gibi bir servisi enjekte etmek için çok kullanışlıdır. @İnject direktifini kullanarak servis bağımlılığını view’e enjekte edebiliriz.

görsel 23: Servisi viewlara enjekte etmek

View Injection, dropdown menü gibi UI öğelerini doldurmak için kullanılabilir. Örnek vermek gerekirse ülkelerin bulunduğu bir liste servisten dropdown listesine doldurulabilir. Bu tür şeyleri servisten uygulamak ASP.NET Core’daki standart yaklaşımdır. Alternatif olarak, dropdown menüyü doldurmak için ViewBag ve ViewData’yı kullanabiliriz. @İnject direktifi, enjekte edilen servisi geçersiz kılmak (override) için de kullanılır. Örneğin, dropdown, textbox vb. gibi Html etiketlerini oluşturmak için Html helper servisini kullanıyoruz. @İnject direktifini kullanarak bu servisi kendi servisimizle değiştirebiliriz.

Service Lifetime (Servis Ömrü)

Photo by Chris Lawton on Unsplash

ASP.NET Core, kayıtlı servislerin ömrünü belirlememize izin verir. Servis örneği, belirtilen yaşam süresine göre otomatik olarak imha(disposed) edilir. Bu nedenle bu bağımlılığın temizlenmesini umursamıyoruz, ASP.NET Core framework’ü bizim yerimize bununla ilgilenecektir. Önceden bahsettiğimiz gibi 3 tip servis yaşam süresi bulunmaktadır.

Singleton

Uygulama, yaşam ömrü boyunca servisin tek bir örneğini(instance) oluşturur ve paylaşır. Servis, IServiceCollection’ın AddSingleton metodu kullanılarak singleton olarak eklenebilir. ASP.NET Core, kayıt sırasında servis örneği oluşturur ve sonraki istekte(request) bu hizmet örneğini kullanır.

görsel 24: Singleton yaşam ömrü

Scoped

ASP.NET Core, uygulama için istek(request) başına servisin bir örneğini oluşturur ve paylaşır. Bu, istek başına tek bir servis örneğinin mevcut olduğu anlamına gelir. Her yeni istek için yeni bir örnek oluşturur. Servis, ConfigureServices (Startup class) içindeki IServiceCollection’ın AddScoped metodunu kullanılarak kapsam dahilinde(scoped) eklenebilir.

görsel 25: Scoped yaşam ömrü

Transient

ASP.NET Core, istediğimiz zaman uygulamaya her seferinde bir servis örneği oluşturur ve paylaşır. Servis, IServiceCollection’ın AddTransient metodu ile Transient(geçiçi/süreksiz) olarak eklenebilir. Bu yaşam süresi stateless serviste kullanılabilir. Bu yaşam ömrü tipi, lightweight(hafif) servis eklemenin bir yoludur.

görsel 26: Transient yaşam ömrü

Sonuç

Dependency Injection (DI) yazılım geliştirme tasarım kalıplarından en önemlilerinden biridir. Bu, daha fazla esneklik, sürdürülebilirlik, test edilebilirlik ve tekrar kullanılabilirlik sağlaması için esnek bir şekilde uygulama oluşturmamıza yardımcı olacaktır. ASP.NET Core’da yerleşik olarak bulunması ve desteklenmesiyle beraber, kolayca uygulamamıza Dependency Injection uygulayabiliriz.

Referanslar

--

--