.Net 8 Ioc, Dependecy Injection ve KeyedServices
.Net ve nesne yönelimli programlama dünyasında IoC (inversion of control) ve dependecy injection çok önemli bir yer tutar. Nasıl çalıştığı ne anlama geldiğini, .net 8 ile gelen yeni di özelliklerini ve scope, transient, singleton arasındaki farkları bu gönderide örneklerle beraber inceleyeceğiz.
IoC (Inversion of Control) Nedir?
IoC bir tasarım prensibidir. Nesne yönelimli tasarımları birbirine sıkı sıkıya bağlı olmamasını sağlar. Kısa bir örnekle açıklarsak bir aracınız var ve sürdüğünüzü düşünelim, tamamen kontrol sizde. Ioc prensibine göre ise araç yerine taksiye binmeniz ve sizin sürmemeniz gerekir sadece yolu tarif etmeniz yeterlidir böylece bir araca sıkı sıkıya bağımlı olmamış oluruz.
Aynı şekilde objelerimizi de iş akışlarına çok sıkı sıkıya bağlı olmasını istemeyiz. Bunun yerine daha esnek bir yapı tercih etmeliyiz, böylece daha kontrol edilebilir bir yapı olur.
Dependecy Injection Nedir?
Yazılım geliştirme sürecinde kullanılan bir tasarım desenidir. Bu desen, bir sınıfın başka bir sınıfa olan bağımlılığını azaltır ve yönetmek için kullanılır. Araba’nın temel özellikleri sürüş ve park olsun, ben iş süreçlerimde araba sınıfını referans alarak kullanayım arabayı hangi nesneye (bmw, mercedes vb.) bağlamışsam onu kullanmış olurum. Büyük resimden sürüş ve park özellikleri olan bir arabayı kullanmışımdır biri daha hızlı gider biri daha iyi park eder sadece performansı değişir.

.Net 8.0 Dependecy Injection
.net core’dan beri temel 3 tane di yöntemi var. AddScoped, AddTransient, ve AddSingleton. .net 8 ile AddKeyedScoped, AddKeyedTransient ve AddKeyedSingleton tipleri de eklendi. Şimdi bu yöntemler nasıl ve neden kullanılıyor örneklerle inceleyeceğiz. Önce temel olarak bir kaç sınıf tasarlayalım.
1 2 3 4 5 6 |
public interface IFileStorage { string Name { get; set; } Task Save(); object Get(string fileKey); } |
iki tane de bu interface’den türeyen sınıf olsun
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public class AmazonFileStorage : IFileStorage { public string Name { get; set; } = "test"; public object Get(string fileKey) { return new { provider = "Amazon" }; } public async Task Save() { //save file } } public class AzureFileStorage : IFileStorage { public string Name { get; set; } = "test2"; public object Get(string fileKey) { return new { provider = "Azure" }; } public async Task Save() { //save file } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class FileController(IFileStorage fileStorage,IServiceProvider serviceProvider,IServiceScopeFactory factory) : ControllerBase { [HttpGet] public IActionResult Get(string key) { var file = fileStorage.Get(key); fileStorage.Name = "new name"; var name = GetName(); var name2 = GetName2(); return Ok(file); } private string GetName() { using var scope = factory.CreateScope(); var fileStorage = scope.ServiceProvider.GetRequiredService<IFileStorage>(); return fileStorage.Name; } private string GetName2() { var fileStorage = serviceProvider.GetRequiredService<IFileStorage>(); return fileStorage.Name; } } |
AddScoped ile bağlayalım
1 |
builder.Services.AddScoped<IFileStorage,AmazonFileStorage>(); |
İlk çağrı atılır atılmaz .net bir scope oluşturur. FileStorage nesnesini ve constrcutor injection ile tanımladığımız sınıfların örneğini oluşturur ve request bitene kadar oluşturduğu örneği kullanır.
GetName() bu durumda atanmış değeri getirir.
GetName2() ise yeni scope açar ve eski veriyi getirir.
AddTransient
1 |
builder.Services.AddTransient<IFileStorage,AmazonFileStorage>(); |
Sınıfı her çağırdığında yeni bir örnek oluşturur. Yukarıdaki aynı örneği test ettiğimiz zaman GetName() ve GetName2() de eski değeri getirdiği sonucunu görürüz.
AddSingleton
Singleton uygulama ayağa kalktığında sınıfı oluşturur. istek bitse bile bellekte fonksiyon saklanır.
1 |
builder.Services.AddSingleton<IFileStorage,AmazonFileStorage>(); |
yeni bir request daha ekleyelim
1 2 3 4 5 |
[HttpGet("test")] public IActionResult Get2() { return Ok(fileStorage.Name); } |
Önce new name ataması yapan isteği gerçekleştirelim. Daha sonra da file/test adresine istek atalım. Sonuç new name olacaktır yani güncellenmiş değeri gösterecektir.
Key ile Dependecy Injection
.net 8 ile gelen yeni özelliklerdir bir interface’i birden çok sınıfa bağlama imkanı sunar. Sadece scope değil aynı şekilde transient ve singleton da kullanılabilir.
1 2 |
builder.Services.AddKeyedScoped<IFileStorage,AmazonFileStorage>("amazon"); builder.Services.AddKeyedScoped<IFileStorage, AzureFileStorage>("azure"); |
Kullanımı:
1 2 3 4 5 6 7 8 9 10 11 12 |
public class FileController( [FromKeyedServices("azure")] IFileStorage azureFileSStorage, [FromKeyedServices("amazon")]IFileStorage amazonFileStorage) : ControllerBase { [HttpGet] public IActionResult Get(string key) { var azureName = azureFileSStorage.Name; var amazonName = amazonFileStorage.Name; return Ok(azureName); } } |
FromKeyedServices attribute ile kayıt ettiğimiz keyi parametre olarak vererek servisleri çağırabiliriz.
Scope, Transient ve Singleton ne zaman kullanılmalı?
Örneğin ef core kullandığımız düşünelim bir post işlemi başlattık ve context üzerinden bir veri çağırarak transcationı başlattık bu transaction üzerindeki her değişiklik context üzerinde saklanacaktır transaction bitene kadar yani commit işlemi yapılana kadar değişiklikler üzerinde işlem yaparız. Context’i transient olarak kaydettiğimiz durumda aynı istek içinde farklı bir servisde contexti çağırdığımız zaman yeni bir context oluşturacaktır ve contextleri çoklayacaktır. Bu durumda en ideali scope’dur.
İhtiyaçlarınıza göre bu yöntemleri seçebilirsiniz.
Başka bir gönderide görüşmek üzere esen kalın 🙂