ASP.NET Core'un yerleşik Dependency Injection (DI) container'i, modern .NET uygulamalarinin omurgasidir. Servis kayitlari, yaşam donguleri ve cozumleme kurallari uygulamanin test edilebilirligini, performansini ve modulerligini dogrudan etkiler. Microsoft.Extensions.DependencyInjection basit ve hızlı bir container sunar; ancak ileri senaryolarda davranisini iyi anlamak gerekir. Bu yazida DI'nin derinliklerini, pratik kayıt kalıplarini ve sik yapılan hatalari ele aliyoruz.
Servis yaşam donguleri
UC temel yaşam dongusu vardir:
- Transient: Her cozumlemede yeni instance. Hafif, stateless servisler icin.
- Scoped: HTTP istegi veya scope basina bir instance. DbContext icin varsayilan.
- Singleton: Uygulama omru boyunca tek instance. Thread-safe olmali.
Yanlış yaşam dongusu seçimi en yaygın DI hatasidir. Örneğin DbContext'i singleton kaydetmek ciddi bug'lara yol acar. Scoped servisi singleton'a inject etmek captive dependency oluşturur: scoped servis singleton omur boyunca yasar ve state sizintisi riski dogar.
Kayıt kalıplari
Temel kayıt:
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
Convention tabanli tarama:
builder.Services.Scan(scan => scan
.FromAssemblyOf<OrderRepository>()
.AddClasses(c => c.AssignableTo(typeof(IRepository<>)))
.AsImplementedInterfaces()
.WithScopedLifetime());
Factory kaydi:
builder.Services.AddScoped<IPaymentGateway>(sp =>
{
var config = sp.GetRequiredService<IOptions<PaymentOptions>>().Value;
return config.Provider switch
{
"Stripe" => sp.GetRequiredService<StripeGateway>(),
"Iyzico" => sp.GetRequiredService<IyzicoGateway>(),
_ => throw new InvalidOperationException("Unknown provider")
};
});
Options pattern entegrasyonu
Yapılandırma degerleri strongly typed options siniflarina baglanir:
builder.Services.Configure<CacheOptions>(builder.Configuration.GetSection("Cache"));
builder.Services.AddSingleton<ICacheService, RedisCacheService>();
IOptions<T> singleton snapshot sağlar; IOptionsMonitor<T> degisiklikleri izler; IOptionsSnapshot<T> scoped yenileme sunar. Hangi arayuzun kullanilacagi yenileme ihtiyacina gore belirlenir.
Keyed services (.NET 8+)
Ayni arayuzun birden fazla implementasyonu keyed registration ile ayrilir:
builder.Services.AddKeyedScoped<INotifier, EmailNotifier>("email");
builder.Services.AddKeyedScoped<INotifier, SmsNotifier>("sms");
public class AlertService
{
private readonly INotifier _email;
public AlertService([FromKeyedServices("email")] INotifier email)
=> _email = email;
}
Bu özellik factory delegate veya marker sınıf ihtiyacini azaltir.
Decorator ve pipeline
Scrutor kutuphanesi ile decorator kaydi yapilabilir:
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.Decorate<IProductService, CachingProductServiceDecorator>();
Decorator, cross-cutting concern'leri (cache, log, metrik) domain servisini degistirmeden ekler.
Modül bazli kayıt
Her modül extension metodu ile servislerini kaydeder:
public static class CatalogModuleExtensions
{
public static IServiceCollection AddCatalogModule(
this IServiceCollection services, IConfiguration config)
{
services.AddScoped<IProductRepository, ProductRepository>();
services.Configure<CatalogOptions>(config.GetSection("Catalog"));
return services;
}
}
Program.cs sadece modül cagrilarini birlestirir; kayitlar daginiklasMAZ.
Validation at startup
.NET 8'de ValidateOnBuild ve ValidateScopes development'ta açık olmalidir:
builder.Host.UseDefaultServiceProvider(options =>
{
options.ValidateOnBuild = true;
options.ValidateScopes = true;
});
Bu sayede captive dependency ve cozulemeyen servisler uygulama baslarken yakalanir.
Performans
DI cozumlemesi uygulama basinda veya ilk istekte yapilir. Singleton kayitlar tercih edilebilir ancak state yönetimi dikkat ister. Transient servislerin asiri kullanımı allocation artirir. GetRequiredService yerine constructor injection tercih edilmeli; service locator anti-pattern'den kacinilmalidir.
Test icin DI
Entegrasyon testlerinde WebApplicationFactory uzerinde servis değiştirme:
builder.ConfigureTestServices(services =>
{
services.RemoveAll<IEmailSender>();
services.AddSingleton<IEmailSender, FakeEmailSender>();
});
Birim testlerde manuel constructor ile mock inject edilir; container gerekmez.
Third-party container
Autofac, Lamar gibi container'lar gelismis özellikler sunar (property injection, conditional registration). Yerleşik container çoğu senaryo icin yeterlidir. Autofac'e geçiş maliyeti ve ekip bilgisi değerlendirilmelidir.
Yaygın tuzaklar
- Scoped servisi singleton'a inject etmek
- IDisposable transient servislerin container tarafindan takip edilmemesi
- Circular dependency (constructor zinciri)
- God service: tek sinifta cok fazla bağımlılık
- Runtime'da servis cozumlemek (service locator)
- HttpContext'e singleton'dan erişim
Circular dependency çözümü icin arayüz ayirma, lazy wrapper veya event tabanli gevşetme kullanılabilir.
HttpContext ve IHttpContextAccessor
Singleton servislerde kullanıcı bilgisine ihtiyaç varsa IHttpContextAccessor kullanılır ancak bu tercih edilen yol degildir. Daha iyi yaklaşım, kullanıcı bilgisini scoped bir ICurrentUser servisi ile request basinda set etmektir.
Özet
ASP.NET Core DI'yi etkin kullanmak icin yaşam dongulerini doğru sec, modül bazli kayıt yap, startup validation ac, constructor injection'i surdur, keyed services ve decorator ile esneklik sagla, test icin servis degistirmeyi planla. DI bir organizasyon aracidir; iyi tasarlanmis arayüzler ve küçük servisler olmadan container alone mimari kalite saglamaz.
Generic hosting ve worker template
Console veya worker uygulamalarinda Host.CreateApplicationBuilder ayni DI container'i kullanır. Web olmayan projelerde scoped yaşam dongusu manuel scope ile simule edilir. Generic host logging, configuration ve DI'yi birlestirir.
Source generator ve compile-time DI
Microsoft.Extensions.DependencyInjection.Abstractions ile source generator tabanli kayıt denemeleri yapilmaktadir. Compile-time cozumleme runtime hatalarini azaltir. Ekosistem olgunlastikca büyük projelerde degerlendirilebilir.
Service provider anti-pattern
BuildServiceProvider'i ConfigureServices icinde cagirmak captive dependency ve double container sorununa yol acar. Sadece design-time tooling (EF Core design-time factory) icin kabul edilebilir pattern'ler vardir.
IServiceCollection vs IServiceProvider
Kayıt asamasi IServiceCollection uzerinde yapilir; cozumleme IServiceProvider uzerinden gerçekleşir. ConfigureServices icinde BuildServiceProvider cagirmak ikinci bir container oluşturur ve scoped servislerin yanlış omurde cozulmesine yol acar. Bu anti-pattern'den kacinilmalidir.
Func ve Lazy cozumleme
Func<T> factory delegate ile servis ihtiyaç aninda cozulur. Circular dependency veya pahali servisler icin kullanisli olabilir. Lazy<T> benzer şekilde gec cozumleme sağlar ancak thread safety dikkat gerektirir.
Open generic registration
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>)); seklinde açık generic kayıt tekrar eden repository kayitlarini azaltir. Closed generic siniflar otomatik olarak bu kayittan turetilir.
Conditional registration
Ortam veya feature flag'e gore farklı implementasyon kaydetmek icin extension metotlari kullanılır. if (builder.Environment.IsDevelopment()) blogu Program.cs'te tutulmali; servis siniflari ortam kontrolu icermemelidir.
DI graf analizi
Büyük projelerde servis bağımlılık grafigini gorsellestirmek circular dependency ve god service tespitini kolaylastirir. NDepend veya custom Roslyn analyzer ile constructor bağımlılık sayisi izlenebilir. Bir sinifta on uzerinde bağımlılık refactoring sinyali olarak değerlendirilmelidir.