Ответ
В ASP.NET Core существует три основных подхода внедрения зависимостей (DI), каждый из которых имеет свои сценарии применения.
1. Внедрение через конструктор (Constructor Injection)
Наиболее предпочтительный и рекомендуемый подход. Зависимости объявляются как параметры конструктора класса, обычно через интерфейсы. Контейнер DI автоматически предоставляет реализацию при создании экземпляра.
public class OrderService
{
private readonly ILogger<OrderService> _logger;
private readonly IEmailService _emailService;
// Зависимости внедряются через конструктор
public OrderService(ILogger<OrderService> logger, IEmailService emailService)
{
_logger = logger;
_emailService = emailService;
}
public void ProcessOrder(Order order)
{
_logger.LogInformation("Processing order {OrderId}", order.Id);
// ... логика
_emailService.SendConfirmation(order);
}
}
Преимущества: Явные зависимости, класс легко тестировать (можно передавать mock-объекты), состояние объекта валидно сразу после создания.
2. Внедрение через метод (Method Injection)
Используется, когда зависимость требуется только для выполнения одного конкретного метода, а не для всего жизненного цикла объекта.
public class ReportGenerator
{
// Зависимость передается как параметр метода
public string GenerateReport(ReportData data, IFormatter formatter)
{
return formatter.Format(data);
}
}
Сценарий использования: Полезен для стратегий или сервисов, которые могут меняться в зависимости от контекста вызова.
3. Внедрение через свойство (Property Injection)
Наименее предпочтительный подход. Зависимости устанавливаются через публичные свойства после создания объекта. Контейнеры, такие как IServiceProvider, могут это делать автоматически, если настроены соответствующим образом.
public class NotificationService
{
// Зависимость устанавливается через свойство
public ILogger<NotificationService> Logger { get; set; }
}
Недостатки: Делает зависимости неявными, объект может находиться в невалидном состоянии до установки свойства, усложняет тестирование. Используется в основном для совместимости с legacy-кодом или в некоторых фреймворках (например, в Razor Pages для [Inject]).
Регистрация в DI-контейнере
Независимо от подхода, зависимости должны быть зарегистрированы в Program.cs или Startup.cs:
builder.Services.AddScoped<IEmailService, SmtpEmailService>();
builder.Services.AddSingleton<ICacheService, DistributedCacheService>();
builder.Services.AddTransient<IReportFormatter, PdfReportFormatter>();
Лучшие практики:
- Всегда предпочитайте внедрение через конструктор.
- Программируйте на основе интерфейсов, а не конкретных классов.
- Избегайте Service Locator (например, вызов
GetServiceизIServiceProvider) как антипаттерна, так как это скрывает зависимости и усложняет код.