Hiểu sâu về Dependency Injection trong .NET

Nguyên tắc Inversion of Control (IoC)

Dependency Injection (DI) là một kỹ thuật cụ thể để thực hiện nguyên lý Inversion of Control – đảo ngược kiểm soát. Thay vì các thành phần tự tạo ra phụ thuộc của mình, chúng được cung cấp từ bên ngoài bởi một container quản lý.

Trước đây, lập trình viên phải chủ động khởi tạo đối tượng:

IDbConnection connection = new SqlConnection(connectionString);

Với IoC, chỉ cần khai báo nhu cầu:

public IDbConnection Connection { get; set; }

Container sẽ tự động tiêm vào đúng đối tượng đã được đăng ký.

Các cách triển khai IoC

  • Service Locator: Chủ động yêu cầu dịch vụ từ container.
  • Dependency Injection: Container tự động đưa phụ thuộc vào thông qua constructor hoặc property.

Quản lý dịch vụ với Service Container

Trong .NET, IServiceCollection dùng để đăng ký các dịch vụ và IServiceProvider dùng để phân giải chúng.

Ba chế độ vòng đời dịch vụ

  1. Singleton: Một bản sao duy nhất trong toàn bộ ứng dụng.
services.AddSingleton<ICacheService, MemoryCacheService>();
  1. Scoped: Một bản sao mỗi phạm vi (thường là một request HTTP).
services.AddScoped<IUserService, UserService>();
using var scope = serviceProvider.CreateScope();
var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
  1. Transient: Tạo mới mỗi lần yêu cầu.
services.AddTransient<IRandomGenerator, DefaultRandomGenerator>();

Phương thức truy xuất dịch vụ

  • GetService<T>(): Trả về null nếu không tìm thấy.
  • GetRequiredService<T>(): Ném ngoại lệ nếu không tồn tại dịch vụ.
  • GetServices<T>(): Lấy danh sách tất cả dịch vụ thỏa mãn kiểu T.

Ví dụ minh họa: Gửi email với cấu hình đa nguồn

Xây dựng hệ thống gửi email có khả năng đọc cấu hình từ nhiều nơi như file INI hoặc biến môi trường.

Giao diện cấu hình

public interface IConfigurationSource
{
    string Get(string key);
}

Triển khai từ file INI

public class IniConfigurationSource : IConfigurationSource
{
    private readonly string _filePath;

    public IniConfigurationSource(string filePath)
    {
        _filePath = filePath;
    }

    public string Get(string key)
    {
        return File.ReadAllLines(_filePath)
            .Select(line => line.Split('='))
            .Where(parts => parts.Length == 2)
            .FirstOrDefault(pair => pair[0].Trim() == key)?
            .ElementAt(1)?.Trim();
    }
}

Triển khai từ biến môi trường

public class EnvironmentConfigurationSource : IConfigurationSource
{
    public string Get(string key) => Environment.GetEnvironmentVariable(key);
}

Trình đọc cấu hình tổng hợp

public class CompositeConfigReader
{
    private readonly IEnumerable<IConfigurationSource> _sources;

    public CompositeConfigReader(IEnumerable<IConfigurationSource> sources)
    {
        _sources = sources;
    }

    public string Read(string key)
    {
        foreach (var source in _sources)
        {
            var value = source.Get(key);
            if (!string.IsNullOrEmpty(value))
                return value;
        }
        return null;
    }
}

Dịch vụ ghi log

public interface ILogger
{
    void Info(string message);
}

public class ConsoleLogger : ILogger
{
    public void Info(string message) => Console.WriteLine($"[INFO] {message}");
}

Dịch vụ gửi email

public interface IEmailService
{
    void Send(string from, string to, string content);
}

public class SmtpEmailService : IEmailService
{
    private readonly ILogger _logger;
    private readonly CompositeConfigReader _config;

    public SmtpEmailService(ILogger logger, CompositeConfigReader config)
    {
        _logger = logger;
        _config = config;
    }

    public void Send(string from, string to, string content)
    {
        _logger.Info("Bắt đầu gửi email...");
        var server = _config.Read("SmtpServer");
        var port = _config.Read("SmtpPort");

        Console.WriteLine($"Gửi từ: {from}, Đến: {to}");
        Console.WriteLine($"Server: {server}, Cổng: {port}");
        Console.WriteLine($"Nội dung: {content}");

        _logger.Info("Email đã được gửi.");
    }
}

Đăng ký dịch vụ và chạy chương trình

class Program
{
    static void Main()
    {
        var services = new ServiceCollection();

        // Đăng ký nhiều nguồn cấu hình
        services.AddScoped<IConfigurationSource, EnvironmentConfigurationSource>();
        services.AddScoped<IConfigurationSource, IniConfigurationSource>(
            provider => new IniConfigurationSource("app.ini")
        );

        services.AddScoped<CompositeConfigReader>();
        services.AddScoped<ILogger, ConsoleLogger>();
        services.AddScoped<IEmailService, SmtpEmailService>

        using var provider = services.BuildServiceProvider();

        var emailService = provider.GetRequiredService<IEmailService>();
        emailService.Send("admin@example.com", "user@test.com", "Chào bạn!");
    }
}

Nội dung file app.ini

SmtpServer=smtp.example.com
SmtpPort=587

Kết quả thực thi

Gửi từ: admin@example.com, Đến: user@test.com
Server: smtp.example.com, Cổng: 587
Nội dung: Chào bạn!

Thẻ: Dependency Injection Inversion of Control .net core iservicecollection ServiceProvider

Đăng vào ngày 24 tháng 6 lúc 20:00