Asp.net Core Dependency Injection
Merhaba arkadaşlar, bugünkü yazımda kısaca Dependency injection nedir, kullanım alanları ve yararlarının üzerinden geçerek daha sonra .Net Core ile birlikte built-in olarak Microsoft Dependecny Injection’ı detaylandıracağım.
Microsoft Dependency Injection Tool özelinde bahsedeceğim konuları aşağıdaki şekilde sıralayabiliriz.
.Microsoft DI Tool’un genel kullanımından,
.Microsoft DI Tool’un advance ihiyaçlarda nasıl configure edileceğinden,
.Microsoft DI Tool’un Scrutor Scan özelliğini nasıl kullanacağımızdan
Not: Makalede .net core mvc bilgi sahibi olduğunuz varsayılmıştır.
Dependendeny Injection Nedir, Neden Kullanırız
Dependency Injection, yaptığımız geliştirmelerde loosely coupled code sağlamak için kullanılan ve içerisinde Inversion of Control ve Dependency Inversion prensiplerini barındıran bir design pattern’dir.
Amaç kod içerisindeki bağımlılıkları azaltarak yada ters bağımlık oluşturak da diyebiliriz, gelişime açık esnek yapılar sunmak ile birlikte test edilebilir kod yazmaktır. Başka bir deyiş ile direkt Concrate Classlar ile çalışmak yerine Interface’lerle çalışabilceğimiz, bu interface’lerinden arkasında çalışacak concrate class’ı belirleyebileceğimiz ve bu Concrate Class’ın Instance’ının life time ile ilgili tüm süreci yönettiğimiz bir yapıdır diyebiliriz.
Kısaca Dependency Injection kullanımın yararlarından bahsedecek olur isek;
.Modüller veya companentler arasındaki bağlılığı azaltır
.Uygulamanın bakım ve geliştirme aşamasında kolaylık sağlar
.Kodun test edilebilmesini kolaylaştırır,
.Kodun okunabilirliğini artırır.
Neyseki tüm bunlarla biz uğraşmıyoruz. Tüm bu işleri yöneteceğimiz tool’lar mevcut ve bizde bunlardan biri olan ve .Net core projelerinde built-in olarak gelen Microsoft Dependency Injection’dan bahsedeceğiz.
Asp.net Core Microsoft Dependency Injection Container
Microsot Dependency Injection Container .Net Core framework’ün omurgalarındandır. Bir .Net core projesi ayağa kaldırdığınızda(WebHost Build olduğunda), framework içerisindeki tüm servisler (Logging, Configuration, Routing) kendi Container’ı içerisinde register olur ve ihtiyaç duyulduğunda resolve edilir.
Burada bazı istisnalar var tabi. Mesela, Controller direct olarak Container içerisine register olmaz. Asp.net mvc http request lifecycle içerisinde ActivatorUtilities Classını kullanarak ilgili Controller’ın public Contructor içerisinde ihtiyaç olduğu tum bağımlılıkları Container içerisinden resolve edip instance’nı oluşturur. Bu tip framework’ün lifecycle’ı içerisinde kullanılan componentler için tek bir public Constructor olma zorunluğu vardır. Aksi durumda aşağıda da görebileceğiniz gibi InvalidOperationException hatası alacaksanız.
Servislerimizi Container içerisine nasıl register ederiz
Servislerimizi Startup.cs içerisinde ConfigureServices methodu ile alınan services parametresi ile register ederiz. Services parametre Type’ı IServiceCollection’dır. Servisler IServiceCollection vasıtayısla register olur. Birde servise ihtiyaç olunduğunda reseolve yapılır demiştik. Bu resolve işlemi de IServiceProvider nesnesi vasıtasıyla olmaktadır.
Bir servisi register ederken, ilgili servisin resolve Instance’nın lifetime’nı belirlememiz gerekiyor. Aşağıda da görebilceğiniz gibi ServiceLifetime enum içerisinde 3 geçeneğimiz bulunmakta.
Singleton: Uygulama boyunca tek bir instance olarak çalışır. Static olarak düşünebilirsiniz.
Transient: Adı üzerinde geçici. Her kullanılmak istendiğinde yeni bir instance oluşturur.
Scoped: Asp.net core projesi üzerinden düşünceksek eğer, Request bazlı bir instance oluşturur. Yani request içerisinde ihtiyaç olunduğu anda bir instance oluşturulur ve ilgili request içerisinde aynı instance ile çalışmaya devam eder.
Injection Nedir?
Injection, adından anlaşıldığı gibi ihtiyaç olunan servisin bizim yerimize resolve işlemini yapılıp ilgili parametreye enjecte etme sürecine denir. .Net core pipeline içerisinde 4 farklı injection yöntemi bulunmaktadır.
1.Constructor Injection
İhtiyaç olunan servisin constructor vasıtayısla enjecte olma işlemi.
2.Middleware Injection
İhtiyaç olunan servisin middeware içerisinde enjecte olma işemi. Burada eğer servisinizin lifetime’ı Singleton ise constructor injection da yapabilirsiniz. Eğer singleton değil ise yazdığının middelware’in Invoke metodu vasıtasıyla injection işlemi yapılmış olur.
Not: Konu dışına çıkmamak adına middeware detayına girmiyorum.
3.Action Injection
İhtiyaç olunan servisin Action vasıtayısla enjecte olma işlemi. Bunun için [FromServices] attribute’nden yararlanmamız gerekiyor.
4.View Injection
İhtiyaç olunan servisin view içerisinde enjecte olma işlemi. Bunun için @inject
directivi’nden yararlanmamız gerekiyor.
Artık sıra geldi servislerimizi Startup.cs içerisinde register etmeye. Ben örnek olarak bir GuidGenerator projesi oluşturdum. Visual studio 2019 ortamında .Net Core 3.1 Mvc projesi geliştiriyorum.
Öncelike Services adında bir klasör oluşturup için IGuidGenerator adında bir interface oluşturuyorum. Akabinde aynı klasör içerisinde IGuidGenerator interface’ini imlement eden class oluşturuyorum.
Basit olarak IGuidGenerator için, GuidGenerator class’ını register edecek olur isek;
Çok kullanılmayan ama bilgi olması açısından
veya genel olarak aşağıdaki şekilde tanımlayabilirsiniz.
Bu şekilde register işlemimizi yapıp çalıştırdğımızda ilgili servis her zaman singleton olarak çalışacaktır. Aşağıda da göreceğiniz gibi ilgili servisin tüm lifettime tipleri için ayrıca register işlemi yapılmış olup container’a eklenmiştir. Ancak runtime da (injection anında) en son register olan servis bilgileri ile geçerli olacaktır.
Alternatif olarak olarak Try ile başlayan extension methodlarını kullanabilirsiniz.
Eğer bu şekilde register edip servisleri kullanmak istersek, ilk register işleminden sonra diğer 2 register işlemini yapmayacaktır.
Ben ilgili GuidGenerator class’ını 3 farklı lifetime da Constractor, Action ve View injection şeklinde kullanacağım. Aradaki farkları hep birlikte görelim.
Öncelikle IGuidGenerator.cs’i aşağıdaki şekilde güncelliyoruz. Her lifetime için ayrı bir interface oluşturduk.
GuidGenerator class’ımızı aşağıdaki şekilde güncelliyoruz.
Register işlemi yazdığımız kodu aşağıdaki şekilde güncelliyoruz.
Şimdi register ettiğimiz servisi kullanmak için öncelikle Models klasörü altında GuidModel adında bir class oluşturuyoruz.
Oluşturduğumuz Guid’leri view içerisinde göstermek adına aynı klasör içerisinde IndexViewModel adında bir class oluşturuyoruz.
Sıra geldi Controller içerisindeki geliştirmelere. Ben default template ile birlikte gelen HomeController içerisinde geliştirmeleri yapıyorum. Constructor ve Index action içeriği aşağıdaki gibidir.
Kaldı View tarafındaki geliştirmeler. Index view tarafında gelen Modeli, @Inject directive’i ile manipule ediyorum. Index.cshtml içeriği aşağıdaki gibidir.
Not:sonucu göstermek için oluşturduğum html kodunu dahil etmedim.
Evet artık projemizi ayağa kaldırabiliriz. Projeyi çalıştırdığımızda sonuç aşağıdaki gibi olacaktır.
Gördüğmüz gibi singleton olarak register edilen servisin oluşturduğu tüm Guidler aynı. Scoped olarak register ettiğimiz servisler için de aynı gibi gözükse de bir sonraki request de aynı olmadığını göreceğiz. Transient içinde beklediğimiz gibi tüm aşamalarda ayrı bir guid üretmiştir.
Şimdi tekrardan F5 yapıp çıkan sonucu aşağıda paylaşıyorum.
Singleton altında bir değişikliklik yok. Scoped için bir öncekinden değişik ama aşama bazlı üretilen guid aynı ve Transient içinde yine tüm aşamalarda yeni guid oluştu.
Artık advance ihtiyaçları ortaya atıp register işlemini nasıl yapacağımız ile ilgili kısma geldi sanırım. Diyelim ki oluşan bu guidler kurallardan geçmek zorunda.
Not: Örneklerde mantık aramıyorum :) İhtiyaç oluşturup nasıl configure edeceğimiz ile ilgileniyorum.
Öncelikle Service Klasörü altında Rules adında bir klasör açıyorum. Ardından IGuidRule adında bir interface oluşturuyorum. Ne olduğu önemli olmayan 3 kural ekliyorum.
Evet şimdi IGuidRule için bu 3 kuralı register edeceğiz. Öncelikle daha öncede gördüğümüz gibi aşağıdaki şekilde register edebilirsiniz.
Yada ilerdeki ihtiyaçlarda düşünülerek Scrutor Scan Assembly özelliği ile daha dinamik register işlemleri yapabiliriz. Öncelikle Scrutor paketini projemize indiriyoruz. Daha sonra kurallar için register kodumuzu aşağıdaki gibi değiştiriyoruz.
Sıra geldi ihtiyacımız olan bu kuralları container içerisinden nasıl resolve edeceğimiz(Action Injection ile nasıl erişeceğimizden). Bu şekilde aynı interface üzerinde çalışan tum servilere IEnumrable<T> şeklinde ,injection işlemini gerçekleştirebiliriyoruz.
Index Action son hali aşağıdaki gibidir.
Debug modda Quickwatch ile guidRules value’suna bakacak olur isek sonu aşağıdaki gibi olucaktır.
Şimdi ise var olan instance’ı register esnasında nasıl set edeceğimize bakalım. Diyelim ki singleton olarak register edeceğimiz servisin guid değerine önceden ihtiyacımız var. Nasıl yapacağımıza bakalım hemen.
Öncelikle GuidGenerator class’indan bir instance alıp, var olan instance’ı register edeceğiz. Configure edilmiş kod aşağıdaki gibidir.
Proje geliştirme aşamasında ihtiyaçlarımız üzerine uzunca konuştuk. Büyük projelerde bir çok register işlemi olur ve zamanla startup class’ı okunamaz hale gelir. Extension method kullanarak register işlemlerimizi yapıp startup.cs dosyamızı daha temiz tutabiliriz. Projemize içerisinde static bir class oluşturup içeriğini aşağıdaki şekilde güncelliyoruz.
Şimdi ise yapmamız gereken IServiceCollection nesnesini AddRuidRules ile extend etmek. Startup.cs içerisindeki ConfigureServices methodunun son hali aşağıdaki gibidir.
Sonuç
Dependency Injection, günümüz modern mimarilerin olmazsa olmaz parçalarındandır. Doğru kurgulandığında esnek ve genişletilebilir yapılar kurmamızı kolaylaştırsa da, doğru kullanılmadığında çok can yakabilir.
Projenin kodlarına GitHub adresinen ulaşabilirsiniz.
Bir sonraki makalede görüşmek üzere, sağlıcakla kalın…
Kaynaklar