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ụ
- Singleton: Một bản sao duy nhất trong toàn bộ ứng dụng.
services.AddSingleton<ICacheService, MemoryCacheService>();
- 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>();
- 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ềnullnế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ểuT.
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!