Dependency Injection (Singleton, Scoped, Transient) Masterclass

โšก C# 12 & .NET 8 ๐ŸŸข Chapter 31 of 35 ๐Ÿ“‚ Phase 11: .NET Platform & Architecture ๐Ÿ“… 2026 Edition
๐Ÿ“Œ Covered in this chapter: Dependency Injection ยท Tight vs Loose Coupling ยท IServiceCollection ยท Singleton ยท Scoped ยท Transient ยท Constructor Injection ยท Interface Design ยท IoC Container

Welcome to Phase 11 (Chapter 31): C# Dependency Injection, Service Lifetimes & Interface-Based Design Masterclass! Dependency Injection (DI) is a software design pattern where objects receive their dependencies from external sources rather than creating them internally. .NET's built-in IoC container manages object creation, lifetime, and disposal automatically. In this chapter, we cover tight vs loose coupling, service registration, Singleton, Scoped, Transient lifetimes, constructor injection, and interface-based design for testable code.

1Dependency Injection Ante Enti? โ€” Tight vs Loose Coupling

Without DI (Tight Coupling): Classes directly instantiate their own dependencies using new, making them impossible to swap, test, or mock independently.

With DI (Loose Coupling): Dependencies are injected through constructor parameters as interface abstractions, enabling easy swapping of implementations and mock injection during unit tests.

C# โ€” Tight vs Loose Coupling โ–ถ Run in Compiler
// โŒ TIGHT COUPLING โ€” Hard to test or swap EmailService
public class OrderService
{
    private EmailService emailService = new EmailService(); // Direct creation!

    public void PlaceOrder(string product)
    {
        emailService.SendConfirmation(product);
    }
}

// โœ… LOOSE COUPLING โ€” Constructor Injection via Interface
public interface IEmailService
{
    void SendConfirmation(string product);
}

public class EmailService : IEmailService
{
    public void SendConfirmation(string product)
    {
        Console.WriteLine($"Email sent for order: {product}");
    }
}

public class OrderService
{
    private readonly IEmailService _emailService; // Depends on INTERFACE, not concrete class

    public OrderService(IEmailService emailService) // Injected via constructor
    {
        _emailService = emailService;
    }

    public void PlaceOrder(string product) => _emailService.SendConfirmation(product);
}
2Service Registration & Lifetimes in ASP.NET Core
LifetimeRegistration MethodInstance Creation StrategyBest Used For
SingletonAddSingleton<I, T>()ONE instance created for the entire app lifetimeConfig services, caches, DB connection pools
ScopedAddScoped<I, T>()ONE instance per HTTP request scopeEntity Framework DbContext, per-request services
TransientAddTransient<I, T>()NEW instance every time it is requestedLightweight stateless services, email senders
C# โ€” Service Registration in Program.cs โ–ถ Run in Compiler
var builder = WebApplication.CreateBuilder(args);

// Register services with their lifetimes
builder.Services.AddSingleton<IConfigService, AppConfigService>();
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
builder.Services.AddTransient<IEmailService, SmtpEmailService>();

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.MapControllers();
app.Run();
3Technical FAQs

Q1: What is an IoC Container?

An Inversion of Control (IoC) Container is an engine that manages object creation and dependency wiring automatically. .NET's built-in IServiceCollection is the IoC container.

Q2: What is the Captive Dependency problem?

It occurs when a Singleton service depends on a Scoped or Transient service, capturing a short-lived service inside a long-lived container. This causes stale state bugs. Always inject only same-lifetime or longer-lived services into Singleton.