Dependency Injection kavramı, frontend ve backend geliştiriciler için gerçek hayat örnekleriyle anlaşılır bir şekilde açıklanmış. Bağımlılık yönetimi, IoC container, service lifetime, tight/loose coupling ve ASP.NET Core DI container kullanımı ile kapsamlı rehber.
Dependency Injection
DI
IoC
Inversion of Control
Service Lifetime
Scoped
Singleton
Transient
Service Container
Software Architecture
Frontend
Backend
Service Pattern
Tight Coupling
Loose Coupling
ASP.NET Core
Interface
Constructor Injection
DI Container
Lifecycle Management
Backend dünyasına girince bir sürü kavram kafana hücum ediyor: Service, Repository, Singleton, Dependency Injection... Özellikle C# yazarken builder.Services.AddScoped<> gibi şeyler görüp “Bu ne ya?” diye bakakaldıysan, yalnız değilsin. Bugün bunlardan en önemlisini, yani Dependency Injection’ı anlatacağız. Ama klasik “bağımlılıkların enjekte edilmesidir” gibi süslü cümlelerle değil; gerçekten neden DI diye bir şeye ihtiyaç duyuyoruz onu öğreneceğiz.
Frontend’de de Yaşıyorduk Aslında Bu Sorunu (Fark Etmeden)
Düşün: React/Vue/Angular yazarken hiç böyle bir kod yazdın mı?
bash
functionUserProfile(){const api = new Axios(); // her component'te kendi axios'unu oluşturuyor
api.get("/user");}
Sonra bir gün dedin ki: “Axios'u her yerde new lemekten bıktım, baseURL değiştirsem 20 dosyayı ellemem lazım.”
Ve çözüm olarak ne yaptın?
React kullanıyorsan → Context açtın.
Vue kullanıyorsan → Provide/Inject kullandın.
Angular kullanıyorsan → Dependency Injection zaten default geliyor.
Yani frontend’de aslında fark etmeden aynı problemi yaşayıp çözümünü uygulamıştın.
Backend’deki Dependency Injection da bunun çok daha gelişmiş ve sistematik hali.
Asıl Soru: “Bağımlılık” Nedir?
Yazılımda bağımlılık (dependency) dediğimiz şey aslında çok basit: Bir sınıf ya da fonksiyon, çalışmak için başka bir sınıfa/fonksiyona ihtiyaç duyuyorsa, ona bağımlıdır.**
Gerçek Hayat Analojisiyle Düşünelim
Çay yapmak istiyorsun → Su ısıtıcısına bağımlısın.
Su ısıtıcısı çalışıyor → Elektriğe bağımlı.
Elektrik geliyor → Elektrik şirketine bağımlı.
Yani bir şeyi yapmak için başka bir şeye ihtiyacın varsa, bağımlısındır. Bu kaçınılmazdır. Sorun bağımlı olmakta değil; nasıl bağımlı olduğundadır.
bash
functionRegisterForm(){ const emailService = new EmailService(); // Her yerde new! const handleRegister =()=>{ emailService.send("user@example.com", "Hoş geldiniz!");}
Buradaki problem ne?
Bu component sadece EmailService ile çalışabiliyor.
Yarın SMS ile bildirim göndermek istersen, bu component'i değiştirmek zorundasın.
Yani "RegisterForm = EmailService" şeklinde sıkı sıkıya bağlanmış durumda.
İleride mail göndermek yerine SMS göndermek istedin o zaman ne yapman gerekecek ?
❌ Ya EmailService’i değiştireceksin
❌ Ya if-else ekleyip çorba yapacaksın
❌ Ya da tamamen başka bir sınıf yazacaksın
Çözüm: Karar Vermeyi Sınıfın İçinden Al, Dışarı Taşı
Problem şuydu: RegisterForm component’i nasıl bildirim gönderileceğine kendisi karar veriyordu. Bu da component’i sadece EmailService’e sıkı sıkıya bağlı hale getiriyordu. Yani esnekliği yoktu, test yazmak zordu ve ileride yeni bildirim tipleri eklemek maliyetli oluyordu.
Çözüm basit: Component’in içinde new EmailService() oluşturmak yerine, bağımlılığı dışarıdan veriyoruz. Böylece component “bildirim gönderilmesini istiyorum” diyor, “nasıl gönderildiği” ile ilgilenmiyor.
bash
Bu örnek, yazılım geliştirmede bağımlılıkların nasıl yönetildiğini ve neden esnek olmasının önemli olduğunu gösteriyor. Artık RegisterForm veya UserService sadece kendi işine odaklanıyor; “nasıl” yapıldığıyla ilgilenmiyor. Bu yaklaşım, hem test yazmayı kolaylaştırıyor, hem de uygulamayı değişime ve yeni gereksinimlere açık hale getiriyor. Örneğin ileride SMS, push notification veya başka bir servis eklemek istediğinde, mevcut component veya sınıfın içini değiştirmene gerek kalmadan sadece yeni servisi uygulayıp dışarıdan enjekte etmek yeterli oluyor. Kısacası, bu yöntem yazılımın sürdürülebilirliğini ve yeniden kullanılabilirliğini ciddi şekilde artırıyor.
Bağlılık Dereceleri: Tight vs Loose Coupling
Şimdi öğrendiklerimizi biraz daha teknik bir çerçeveye oturtalım. Yazılımda bir sınıf veya component’in bağımlılıkları iki uçta değerlendirilir:
Component veya sınıf, belirli bir başka sınıfa bağımlıdır.
Değişiklik gerektiğinde bağımlı olduğu sınıfı veya component’i değiştirmek zorunda kalırsın.
Örnek: RegisterForm içinde new EmailService() kullanmak.
bash
functionRegisterForm(){const emailService = new EmailService(); // Sabit bağımlılık
const handleRegister =()=>{emailService.send("user@example.com", "Hoş geldiniz!");};return<button onClick={
Loosely Coupled (Gevşek Bağlılık)
Component veya sınıf, ihtiyaç duyduğu işlevi bir interface veya dışarıdan verilen servis üzerinden kullanır.
Yeni ihtiyaçlar veya değişiklikler geldiğinde sadece dışarıdan sağlanan bağımlılığı değiştirirsin, component’in içi bozulmaz.
Örnek: RegisterForm’a messageService prop olarak vermek, UserService’e IMessageService constructor ile enjekte etmek.
bash
Tightly Coupled (Sıkı Bağlılık)
Özellik
Tightly Coupled
Loosely Coupled
Esneklik
Düşük
Yüksek
Test kolaylığı
Zor
Kolay
Değişiklik maliyeti
Yüksek (component/sınıf içini değiştir)
Düşük (sadece bağımlılığı değiştir)
Sınıfın sorumluluğu
Hem iş hem bağımlılığı yönetir
Sadece işi yönetir
Bu kavramı kavradığında, dependency injection’ın neden hayat kurtardığını çok daha iyi görebilirsin: Loose coupling, yazılımı esnek, test edilebilir ve genişletilebilir kılar
Backend’de Bağımlılık Problemi
Backend tarafına geçtiğinde, yazdığın her sınıfın veya servisin başka sınıflara ihtiyaç duyduğunu fark edeceksin. Örneğin bir kullanıcı kaydı yapan servisin, kayıt işlemi tamamlandıktan sonra bir bildirim göndermesi gerekiyor. İlk akla gelen çözüm genellikle şuna benzer:
bash
public class UserService{ private readonly EmailService _emailService = new EmailService(); public void RegisterUser(string email){ _emailService.Send(email, "Hoş geldin!");}}
Burada sorun şunlar:
UserService, mutlaka EmailService kullanmak zorunda.
Yani sınıfın sorumluluğu sadece kullanıcı kaydını yapmak değil, aynı zamanda hangi servisi kullanacağına da karar vermek zorunda. Bu durum, sınıfın esnekliğini azaltır ve bakım maliyetini artırır.
Çözüm: Karar Vermeyi Sınıfın İçinden Al, Dışarı Taşı
Backend’de yaşadığımız problem şuydu: UserServicenasıl bildirim gönderileceğine kendisi karar veriyordu. Bu durum sınıfı EmailService’e sıkı sıkıya bağlı hale getiriyordu. Çözüm basit: sınıfın kendi içinde bağımlılığı yaratmak yerine, bağımlılığı dışarıdan veriyoruz.
Interface ile Soyutlama
bash
//Ufak bir kod örneği
public interface IMessageService
{
Şimdiye kadar gördük ki, bağımlılıkları component veya sınıfın içinde kendimiz yaratmak, esnekliği azaltıyor ve test yazmayı zorlaştırıyor. Frontend’de prop veya context ile, backend’de interface + constructor injection ile loosely coupled yapılar oluşturabildik. Peki, her sınıf için manuel olarak new EmailService() veya new SmsService() yazmak yerine bu bağımlılıkları otomatik olarak yönetmenin bir yolu yok mu? İşte bu noktada IoC (Inversion of Control) ve DI Container devreye giriyor.
IoC, kontrolün tersine çevrilmesi yada bağımlılıkların tersine çevirilmesi demek: yani bir sınıf kendi bağımlılıklarını yaratmak yerine, ihtiyaç duyduğu servisler dışarıdan sağlanır. DI Container ise bu süreci otomatikleştirir: uygulama çalıştığında gerekli servisleri oluşturur ve doğru sınıflara enjekte eder. Bu sayede hem kod daha temiz ve test edilebilir olur, hem de servislerin yaşam döngülerini merkezi olarak yönetebilirsin.
Şimdi adım adım ilerleyelim ve ASP.NET Core’da IoC ve DI Container kullanımını örneklerle açıklayalım. Önce temel kavramları netleştirelim daha sonra ise yaşam döngülerini inceleyerek konuyu daha net kavramaya çalışalım.
IoC (Inversion of Control) Nedir?
C# tarafında IoC, yani “Kontrolün Tersine Çevrilmesi”, yazılım mimarisinde çok kritik bir kavramdır.
“Bir sınıf kendi bağımlılıklarını yaratmak yerine, ihtiyaç duyduğu bağımlılıkların kendisine dışarıdan sağlanmasıdır.”
Backend tarafında yazdığın servislerin çoğu başka servis veya sınıflara ihtiyaç duyar. Örneğin bir UserService, kullanıcı kaydı yaparken bir mail servisi kullanabilir. Eğer UserService her zaman kendi içinde new EmailService() oluşturuyorsa, sınıf hem kendi işini yapmakla hem de hangi servisin kullanılacağını yönetmekle yükümlüdür. Bu durum, sınıfın sorumluluğunu gereksiz yere artırır, test yazmayı zorlaştırır ve değişiklikleri maliyetli hale getirir.
IoC sayesinde, sınıfın sorumluluğu sadece kendi işi ile sınırlı kalır; bağımlılıkları yönetmek artık sınıfın işi değildir. Bu prensip, yazılımın esnekliğini ve sürdürülebilirliğini artırır.
Biraz yukarıda Tightly Coupled (Sıkı Bağlı) başlığı altında beraber incelediğimizUserService, sadece kullanıcı kaydı yapmakla kalmıyor, aynı zamanda hangi servisin kullanılacağını da kendisi belirleyecek şekilde geliştirilmişti. Eğer ileride SMS göndermek veya farklı bir mesaj servisi eklemek istersek, sınıfın içine dokunmamız gerekiyordu. İsterseniz tekrar hatırlayalım.
bash
public class UserService
{ private readonly EmailService _emailService = new EmailService(); public void RegisterUser(string email){ _emailService.Send(email, "Hoş geldiniz!");}}
Peki biz burada nasıl bir çözüm üretilmişti ?
Hatırlarsanız öncelikle IMessageService adında bir interface tanımlamıştık.
Daha sonra bu interface'i implement eden 2 farklı sınıf oluşturduk. EmailService ve SmsService olmak üzere.
Daha sonra UserService içinde Constructor üzerinden bağımlılığı alıyorduk değil mi ?
İşte biz burada farkında olmadan aslında IOC kavramını uygulamış olduk. Yani yazılım dilinde yaptığımız bu bağımlılık yönetimine karşılık gelen kavram tam olarak IoC yani Kontrolün Tersine Çevrilmesi oluyor.
Özetleyecek olursak
Interface ile Soyutlama = IoC’un Temel Mantığı
IMessageService oluşturduğunda aslında sınıfın bağımlılığı soyutlanıyor.
UserService, hangi servis kullanılacağını bilmek zorunda değil; sadece bir mesaj servisi ile çalışması gerektiğini biliyor.
Yani bağımlılığı kendisi yaratmak yerine dışarıdan alıyor. Bu zaten IoC’un temel prensibi.
Constructor Injection = IoC’un Uygulama Şekli
Interface’i oluşturduk ama UserService’e hangi somut servisin geleceğini bir şekilde vermeliyiz.
bash
“Artık UserService örneğinde gördüğümüz gibi, interface ile bağımlılığı soyutlamak ve constructor üzerinden enjekte etmek IoC prensibini manuel olarak uygulamamıza olanak sağladı. Peki ya büyük bir projede her sınıf için sürekli new EmailService() veya new SmsService() yazmak zorunda olsaydık? Bu hem zahmetli olur hem de servislerin yaşam döngülerini yönetmeyi çok karmaşık hale getirirdi. İşte bu noktada DI Container (Dependency Injection Container) devreye giriyor. DI Container, IoC mantığını otomatikleştirerek, uygulamanın ihtiyacı olan servisleri oluşturur, yönetir ve doğru sınıflara enjekte eder.”
DI Container Nedir?
DI Container, uygulamadaki servislerin ve bağımlılıkların merkezi bir yerden yönetilmesini sağlayan bir sistemdir. Düşün ki uygulaman birçok farklı sınıf ve servis içeriyor: bir sınıf başka bir sınıfa ihtiyaç duyuyor, o sınıf başka bir sınıfa ihtiyaç duyuyor… Eğer her sınıf kendi bağımlılıklarını kendisi oluşturursa (new ile), kod karmaşıklaşır ve bakımı zorlaşır.
DI Container bu sorunu çözer:
Servisleri merkezi olarak oluşturur: Uygulama başlarken “Bu servisler mevcut olacak” der ve hazırlar.
Bağımlılıkları otomatik verir: Bir sınıf bir servise ihtiyaç duyduğunda, container bunu kendisi sağlar, sınıfın bunu kendisi oluşturmasına gerek yoktur.
Yaşam döngüsünü yönetir: Bir servisin ne zaman oluşturulacağını ve ne zaman yok edileceğini kontrol eder. (Örneğin, her kullanımda yeni örnek mi olacak, uygulama boyunca tek örnek mi olacak gibi)
Özetle, DI Container sayesinde sınıflar kendi bağımlılıklarını kendileri yönetmek zorunda kalmaz. Bu işleri merkezi olarak container yapar. Böylece kod daha temiz, esnek ve bakımı kolay olur.
DI Container Çalışma Mantığı
Dependency Injection (DI) ve DI Container kavramları, modern yazılım geliştirme pratiklerinde özellikle büyük projelerde hayat kurtarıcıdır. DI Container, uygulamadaki servislerin oluşturulması, yönetilmesi ve ihtiyaç duyulan yerlere otomatik olarak enjekte edilmesini sağlayan merkezi bir sistemdir. Bunu anlamak için üç temel aşamayı incelemek gerekir: servis kayıtları, bağımlılık çözümü ve servislerin yaşam döngüsü yönetimi.
1️⃣ Servis Kayıtları (Registration)
DI Container’ın çalışmasının ilk adımı, hangi servislerin container tarafından yönetileceğini belirlemektir. ASP.NET Core uygulamalarında bu işlem genellikle Program.cs veya Startup.cs dosyasında yapılır. Burada temel olarak üç farklı yaşam döngüsüne sahip servis kaydı yapabiliriz:
bash
var builder = WebApplication.CreateBuilder(args);// Scoped: her HTTP isteğinde yeni örnek
builder.Services.AddScoped<IMessageService, EmailService>();// Singleton: uygulama boyunca tek bir örnek
builder.Services.AddSingleton<ILogger, ConsoleLogger>();// Transient: her kullanımda yeni örnek
builder.Services.AddTransient<NotificationService>();
AddScoped: Her HTTP isteğinde bir örnek oluşturur. Bu, özellikle isteğe özel kaynaklar (örneğin DbContext) için uygundur.
AddSingleton: Uygulama başladığında bir örnek oluşturulur ve uygulama boyunca aynı örnek kullanılır. Bu, örnekler arası veri paylaşımı veya kaynak maliyetinin azaltılması gereken durumlar için idealdir.
AddTransient: Her bağımlılık talebinde yeni bir örnek oluşturulur. Stateless veya kısa ömürlü servisler için uygundur.
Container, bu kayıtları hafızasında tutar ve ihtiyaç duyulduğunda ilgili servisi üretir. Bu sayede, sınıfların kendi bağımlılıklarını yönetmesine gerek kalmaz.( Bu kavramları birazdan daha net ve anlaşılır şekilde derinlemesine inceleyeceğiz. )
2️⃣ Bağımlılık Çözümü (Resolution)
DI Container’ın en önemli özelliklerinden biri, bir sınıfın ihtiyaç duyduğu bağımlılığı otomatik olarak çözebilmesidir. Örneğin:
bash
public class UserService
{ private readonly IMessageService _messageService; public UserService
3️⃣ Servis Yaşam Döngüsü (Lifecycle Management)
DI Container’ın en kritik görevlerinden biri, yalnızca hangi servisi enjekte edeceğine karar vermek değil; bu servislerin ne kadar süre bellekte tutulacağını da yönetmektir. Aynı servis, yanlış yaşam döngüsü ile tanımlandığında bellek sızıntılarına, yanlış veri paylaşımına veya istenmeyen performans kaybına yol açabilir. Bu yüzden lifecycle seçimi geliştirici için basit bir detay değil, stratejik bir mimari karardır.
Şimdi her bir yaşam döngüsünü gündelik hayata benzeterek ve farklı senaryolarda nasıl çalıştığını göstererek açıklayalım.
a) Singleton — "Her yerde tek, herkes onu kullansın"
Singleton servisler, uygulama ayakta kaldığı sürece yalnızca bir kere oluşturulan ve tekrar tekrar kullanılan servislerdir. En basit haliyle, evde tek bir modem olduğunu düşün. Evdeki herkes (telefonlar, bilgisayarlar, tabletler) o tek modem üzerinden internete bağlanır. Her bağlanan için yeni bir modem açılmaz – paylaşımlı ve merkezi bir kaynak vardır.
Yani , uygulama başladığında servis oluşturulur. uygulama boyunca tek bir örnek kullanılır.
Avantaj: Kaynak kullanımını azaltır, örnekler arası veri paylaşımı kolaydır.
Dezavantaj: Global state oluşabilir, dikkatli kullanılmalı.
Özet: DI Container Avantajları
IoC’u otomatikleştirir: Sınıflar kendi bağımlılıklarını yönetmek zorunda kalmaz.
Test yazmayı kolaylaştırır: Mock veya farklı servisleri enjekte etmek basittir.
Kod okunabilir ve sürdürülebilir olur: Bağımlılıklar merkezi olarak yönetildiği için karmaşa azalır.
Servis yaşam döngülerini yönetir: Singleton, Scoped, Transient gibi farklı ihtiyaçlara göre esneklik sağlar.
//Bu interface'i kullanan her class Send işlemi yapmak zorunda.
}
Bu bir soyutlama. Yani IMessageService diyor ki: “Beni implement eden bir sınıf varsa, Send metodunu içermeli.” Ama interface kendisi nasıl gönderileceğini bilmez. Sadece bir sözleşme tanımlar. IMessageService interface’i sayesinde UserService’in hangi mesaj servisi kullanılacağını bilmesine gerek yok. Artık sadece “bir mesaj gönderilmesini istiyorum” diyor ne şekilde nasıl gönderildiği beni ilgilendirmez demek istiyor .
Farklı Servisler
bash
public class EmailService : IMessageService
{ public void Send(string to, string message){ Console.WriteLine($"{to} adresine mail gönderildi: {message}");}}public class SmsService : IMessageService
{ public void Send(string to, string message){ Console.WriteLine($"{to} numarasına SMS gönderildi: {message}");}}
İkisi de IMessageService sözleşmesini uyguluyor yani daha önceden oluşturduğımuz bu interface'i implement ediyoruz ama içlerinde farklı davranış var:
EmailService → mail gönderiyor
SmsService → SMS gönderiyor
UserService’e Enjekte Etme
bash
public class UserService
{ private readonly IMessageService _messageService; public UserService(IMessageService messageService) // Dependency Injection burada gerçekleşiyor
{ _messageService = messageService;} public void RegisterUser(string email){ _messageService.Send(email, "Hoş geldin!");}}
Burada önemli olan nokta UserService, kendisi hangi servisin kullanılacağını bilmez.
_messageServiceinterface türünde (IMessageService) tutuluyor. Gerçek servis örneği (EmailService veya SmsService) dışarıdan veriliyor (constructor üzerinden).
UserService artık loosely coupled: sadece kullanıcı kaydını yapıyor, mesaj servisi seçimi dışarıya bırakılmış.
Test veya değişiklik için sınıfın içine dokunmana gerek yok.
Kullanım Örneği
bash
var userService1 = new UserService(new EmailService());userService1.RegisterUser("deneme@mail.com");var userService2 = new UserService(new SmsService());userService2.RegisterUser("5551234567");
Email veya SMS fark etmez, kullanım tamamen esnek.
Testte mock bir servis enjekte ederek işlevleri kolayca test edebilirsin.
Artık ne kazandık?
Sınıf artık tek bir sorumluluğa sahip: kullanıcı kaydı yapmak.
Mesaj gönderme detayları dışarıya taşındı → loosely coupled oldu.
Test ve değişiklikler çok daha kolay.
var userService1 = new UserService(new EmailService());var userService2 = new UserService(new SMSService());var userService3 = new UserService(new NotificationService());
Burada hepsi aslında kendine dışarıdan verilen servis ile çalışacak kendi içerisinde new yapılarak bir instance üretmeyecek.
UserService 1 EmailService ile çalışacak
UserService 2 SMSService ile çalışaca
UserService 3 NotificationServiceile çalışacak
Yani sınıfın içinde hiçbir zaman “hangi servisi kullanacağım” sorusu yok, sadece işi yapıyor.
(
IMessageService messageService
)
{
_messageService
=
messageService
;
}
public void RegisterUser
(
string email
)
{
_messageService.Send
(
email,
"Hoş geldiniz!"
)
;
}
}
Burada UserService, bir IMessageService bağımlılığına ihtiyaç duyuyor. DI Container, uygulama başında IMessageService için hangi implementasyonun (bu örnekte EmailService) kullanılacağını biliyor ve bunu otomatik olarak UserService sınıfının constructor’ına enjekte ediyor.
Bu sayede UserService içinde hangi servisin kullanılacağına dair hiçbir karar bulunmaz. Bu, Inversion of Control (IoC) prensibinin pratik bir uygulamasıdır: kontrol, sınıfın kendisinden alınmış ve container’a verilmiştir. Sonuç olarak kod daha esnek, test edilebilir ve sürdürülebilir olur.
bash
builder.Services.AddSingleton<ILogger, ConsoleLogger>();//Uygulama boyunca loglamak için sadece tek bir örnek olması yeterlidir.
Ne zaman kullanılır?
Konfigürasyon veya Setting okuma servisleri
Logger / Audit Servisleri
MemoryCache / In-Memory Data tutan servisler
3rd party client’lar (Mail, Payment Gateway vs.)
Örnek Kullanım : Global Ayar Servisi
bash
public class AppSettings
{ public string ApplicationName { get;set;}="MyApp"; public DateTime StartedAt { get;}= DateTime.Now;}
bash
//`Program.cs` veya `Startup.cs` dosyasında ayarlamamızı yapalım.
builder.Services.AddSingleton<AppSettings>();
bash
public class HomeController : Controller
{private readonly AppSettings _settings;public HomeController(AppSettings settings)=> _settings = settings;- [HttpGet("/info")]public IActionResult Info()=> Ok(_settings);}
Başka bir Controller:
bash
public class AdminController : Controller
{private readonly AppSettings _settings;public AdminController(AppSettings settings)=> _settings = settings;- [HttpGet("/admin-info")]public IActionResult Info()=> Ok(_settings);}
👉 Her iki endpoint de aynı StartedAt değerini döner. Çünkü servis uygulama boyunca bir kez oluşturulmuştur.
b) Scoped — "Her müşteri için ayrı masa"
Scoped servisler, her HTTP isteği başladığında oluşturulur ve o istek bitene kadar aynı örnek kullanılır. Bu davranışı bir restoranda her müşteri için ayrı masa açılması gibi düşünebilirsin. Aynı masaya gelen herkes aynı servisi (örneği) paylaşır. Ama yeni müşteri geldiğinde yeni masa açılır – eski masa kullanılmaz.
Yani, her HTTP isteğinde yeni bir örnek oluşturulur. Aynı istekte, aynı servis örneği kullanılır.
Avantaj: İstek başına izole servis sağlar, state yönetimi daha güvenlidir.
Kullanım: Web uygulamalarında DbContext gibi isteğe özel kaynaklar için ideal.
bash
builder.Services.AddScoped<IMessageService, EmailService>();//Her HTTP isteği için bir kere kullanılır ve daha sonra otomatik olarak garbage collector tarafından silinir.
HTTP isteği tamamlandığında, container scoped örneği temizler. Yani bellekte artık bu servis örneği tutulmaz, garbage collector tarafından bellek serbest bırakılır. Böylece istek bazlı izolasyon sağlanır ve servisler birbirine karışmaz.
Ne zaman kullanılır?
DbContext (Entity Framework gibi ORM’lerde neredeyse her zaman scoped olur)
//Ufak bir kod örneği
public class RequestContext
{ public Guid RequestId { get;}= Guid.NewGuid();}
bash
//`Program.cs` veya `Startup.cs` dosyasında ayarlamamızı yapalım.
builder.Services.AddScoped<RequestContext>();
bash
public class OrderController : Controller
{private readonly RequestContext _context1;private readonly RequestContext _context2;- public OrderController(RequestContext c1, RequestContext c2
👉 Aynı endpoint’e bir istek attığında iki RequestId de aynı çıkar.
👉 Farklı bir istek attığında yeni bir RequestId gelir.
c) Transient — "Her kullanımda yepyeni bir şey"
Transient servisler tamamen stateless olmalıdır. Çünkü her kullanıldığında yeni örnek oluşturulur, hiçbir paylaşım yapılmaz. Bu davranış plastik çatala benzer: kullandın → attın → yenisini aldın.
Her bağımlılık talebinde yeni bir örnek oluşturulur. Yani bir sınıf veya controller, bir transient servisi her kullandığında, DI Container yeni bir instance (örnek) üretir. Hiçbir şekilde daha önce oluşturulmuş örnek paylaşılmaz.
Avantaj: Stateless servisler için uygundur, tam izolasyon sağlar.
Dezavantaj: Çok sık kullanılırsa performans maliyeti olabilir.
bash
//`Program.cs` veya `Startup.cs` dosyasında ayarlamamızı yapalım.
builder.Services.AddTransient<NotificationService>();
Ne zaman kullanılır?
Basit yardımcı servisler (StringFormatter, EmailBuilder, CalculationService vb.)
Her çağrıda farklı olması gereken servisler
Örnek Kullanım – Hesaplama Servisi
bash
//Ufak bir kod örneği
public class PriceCalculator
{ public Guid InstanceId { get;}= Guid.NewGuid();}
bash
//`Program.cs` veya `Startup.cs` dosyasında ayarlamamızı yapalım.
builder.Services.AddTransient<PriceCalculator>();
bash
public class CartController : Controller
{private readonly PriceCalculator _p1;private readonly PriceCalculator _p2;- public CartController(PriceCalculator p1, PriceCalculator p2