TB
← Tüm yazılar

Dependency injection derinlemesine

ASP.NET Core DI container'inin yaşam donguleri, servis kayıt kalıplari, keyed services ve yaygın tuzaklari derinlemesine inceliyoruz.

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

  1. Scoped servisi singleton'a inject etmek
  2. IDisposable transient servislerin container tarafindan takip edilmemesi
  3. Circular dependency (constructor zinciri)
  4. God service: tek sinifta cok fazla bağımlılık
  5. Runtime'da servis cozumlemek (service locator)
  6. 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.