Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Dependency Injection

3 Loại Lifecycle

Singleton

// Một instance duy nhất cho toàn bộ application lifetime
builder.Services.AddSingleton<IEmailService, EmailService>();

// Ví dụ sử dụng
public class ProductService
{
    private readonly IEmailService _emailService;
    
    public ProductService(IEmailService emailService)
    {
        _emailService = emailService; // Cùng instance xuyên suốt
    }
}

Khi nào dùng:

  • Configuration services
  • Logger (ILogger)
  • Caching services
  • Services không có state hoặc share state toàn cục

Scoped

// Một instance per HTTP request
builder.Services.AddScoped<IProductRepository, ProductRepository>();

// Ví dụ sử dụng
public class OrderService
{
    private readonly IProductRepository _productRepo;
    
    public OrderService(IProductRepository productRepo)
    {
        _productRepo = productRepo; // Cùng instance trong 1 request
    }
}

Khi nào dùng:

  • DbContext (EF Core)
  • Services cần per-request state
  • Business logic services

Transient

// Instance mới mỗi lần được yêu cầu
builder.Services.AddTransient<IReportGenerator, ReportGenerator>();

// Ví dụ sử dụng
public class DashboardService
{
    private readonly IReportGenerator _reportGenerator;
    
    public DashboardService(IReportGenerator reportGenerator)
    {
        _reportGenerator = reportGenerator; // Instance mới mỗi lần
    }
}

Khi nào dùng:

  • Lightweight, stateless services
  • Services với expensive initialization
  • Khi cần instance riêng biệt cho mỗi lần sử dụng

Ví dụ thực tế

// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Singleton - Configuration, Logger
builder.Services.AddSingleton<IConfiguration>(builder.Configuration);
builder.Services.AddSingleton<ILogger<Program>, Logger<Program>>();

// Scoped - DbContext, Repositories
builder.Services.AddScoped<AppDbContext>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();

// Transient - Small, stateless services
builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.AddTransient<IDateTimeProvider, DateTimeProvider>();

var app = builder.Build();

Lifecycle Diagram

┌─────────────────────────────────────────────────────────────────┐
│                    APPLICATION LIFETIME                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                    SINGLETON                            │   │
│   │  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐   │   │
│   │  │ Request │  │ Request │  │ Request │  │ Request │   │   │
│   │  │    1    │  │    2    │  │    3    │  │    4    │   │   │
│   │  └─────────┘  └─────────┘  └─────────┘  └─────────┘   │   │
│   │       │            │            │            │        │   │
│   │       └────────────┴────────────┴────────────┘        │   │
│   │       (Cùng một instance cho tất cả requests)         │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                    SCOPED                               │   │
│   │  ┌─────────┐              ┌─────────┐                  │   │
│   │  │ Request │              │ Request │                  │   │
│   │  │    1    │              │    2    │                  │   │
│   │  │ ┌─────┐ │              │ ┌─────┐ │                  │   │
│   │  │ │ Svc │ │              │ │ Svc │ │                  │   │
│   │  │ └─────┘ │              │ └─────┘ │                  │   │
│   │  └─────────┘              └─────────┘                  │   │
│   │ (Instance mới cho mỗi request, reuse trong request)    │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                   TRANSIENT                             │   │
│   │  ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐            │   │
│   │  │ S1 │ │ S2 │ │ S3 │ │ S4 │ │ S5 │ │ S6 │            │   │
│   │  └────┘ └────┘ └────┘ └────┘ └────┘ └────┘            │   │
│   │  (Instance mới mỗi lần được request)                   │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Anti-Pattern: Scoped vào Singleton

Tại sao KHÔNG NÊN?

// ❌ BAD - Injecting Scoped service vào Singleton
builder.Services.AddSingleton<BadSingletonService>();

public class BadSingletonService
{
    private readonly AppDbContext _context;
    
    public BadSingletonService(AppDbContext context) // Scoped!
    {
        _context = context; // Sẽ gây lỗi!
    }
}

Vấn đề: Singleton tồn tại xuyên suốt application lifetime, nhưng Scoped service (DbContext) chỉ valid trong một HTTP request. Khi request kết thúc, DbContext bị disposed nhưng Singleton vẫn giữ reference, gây ObjectDisposedException.

Giải pháp

// ✅ GOOD - Inject IServiceProvider vào Singleton
builder.Services.AddSingleton<GoodSingletonService>();

public class GoodSingletonService
{
    private readonly IServiceProvider _serviceProvider;
    
    public GoodSingletonService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    
    public void DoWork()
    {
        // Tạo scope mới để resolve scoped services
        using var scope = _serviceProvider.CreateScope();
        var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        
        // Sử dụng context trong scope
        var data = context.Products.ToList();
    }
}

Best Practices

  1. Tuân thủ Service Lifetime Hierarchy:

    • ✅ Singleton → Singleton
    • ✅ Singleton → Scoped (thông qua IServiceProvider)
    • ✅ Scoped → Scoped
    • ✅ Scoped → Transient
    • ✅ Transient → Transient
    • ❌ Scoped → Singleton (không có vấn đề về mặt kỹ thuật)
    • ❌ Singleton → Scoped (không nên)
    • ❌ Singleton → Transient (có thể accept)
  2. Register services với interface:

    // ✅ Tốt
    builder.Services.AddScoped<IProductRepository, ProductRepository>();
    
    // ❌ Tránh
    builder.Services.AddScoped<ProductRepository>();
    
  3. Constructor Injection thay vì Property Injection:

    // ✅ Tốt
    public class Service
    {
        private readonly IOtherService _other;
        public Service(IOtherService other) => _other = other;
    }
    
    // ❌ Tránh
    public class Service
    {
        public IOtherService Other { get; set; }
    }