Как можно внедрить зависимости в контроллер ASP.NET Core?

Ответ

Основной и рекомендуемый способ — внедрение через конструктор (Constructor Injection). Контейнер зависимостей ASP.NET Core автоматически разрешает и предоставляет все зависимости, объявленные в конструкторе контроллера.

Пример:

[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly IOrderRepository _orderRepository;
    private readonly ILogger<OrdersController> _logger;
    // Контейнер DI внедрит сюда реализацию IOrderRepository и ILogger
    public OrdersController(IOrderRepository orderRepository, ILogger<OrdersController> logger)
    {
        _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
        _logger = logger;
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Order>> GetOrder(int id)
    {
        _logger.LogInformation("Fetching order {OrderId}", id);
        var order = await _orderRepository.GetByIdAsync(id);
        if (order == null) return NotFound();
        return order;
    }
}

Что нужно сделать для работы:

  1. Зарегистрировать сервисы в контейнере (Program.cs или Startup.ConfigureServices).
    builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
    // ILogger регистрируется инфраструктурой автоматически

Альтернативные способы (используйте реже):

  • Внедрение в метод действия через атрибут [FromServices]:
    public IActionResult Get([FromServices] IEmailService emailService) { ... }

    Плюс: Удобно для одноразового использования. Минус: Ухудшает тестируемость и скрывает зависимости класса.

  • Получение сервиса из HttpContext.RequestServices (Service Locator - антипаттерн):
    var service = HttpContext.RequestServices.GetService<IMyService>();

    Избегайте этого, так как это усложняет понимание зависимостей и тестирование.

Вывод: Constructor Injection — стандарт. Он делает зависимости явными, упрощает модульное тестирование (зависимости легко подменить mock-объектами) и следует принципу явных зависимостей.

Ответ 18+ 🔞

А, ну вот, опять про эти ваши зависимости, блядь. Слушай, тут всё просто, как три копейки, но народ умудряется так накосячить, что диву даёшься.

Основной и самый правильный способ — это когда ты внедряешь всё через конструктор (Constructor Injection). Это как в хорошем баре: тебе приносят всё, что заказал, прямо на стол, а не заставляют бегать к стойке за каждым стаканом. Контейнер в ASP.NET Core — он умный, он сам всё подгонит и впихнет куда надо.

Смотри, как это выглядит:

[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly IOrderRepository _orderRepository;
    private readonly ILogger<OrdersController> _logger;
    // Контейнер DI, как хороший официант, принесёт сюда реализацию IOrderRepository и ILogger
    public OrdersController(IOrderRepository orderRepository, ILogger<OrdersController> logger)
    {
        _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
        _logger = logger;
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Order>> GetOrder(int id)
    {
        _logger.LogInformation("Fetching order {OrderId}", id);
        var order = await _orderRepository.GetByIdAsync(id);
        if (order == null) return NotFound();
        return order;
    }
}

Что нужно сделать, чтобы эта магия заработала? Да нихуя сложного!

  1. Зарегистрируй свои сервисы в контейнере (в Program.cs или в Startup.ConfigureServices, смотря на чём проект).
    builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
    // А ILogger инфраструктура сама зарегистрирует, за тебя всё сделают, красота!

А есть другие способы? Есть, конечно, но они как запасной выход — на крайняк.

  • Внедрение прямо в метод через атрибут [FromServices]:
    public IActionResult Get([FromServices] IEmailService emailService) { ... }

    Плюс: Удобно, если сервис нужен один раз и больше нигде. Минус: А потом смотришь на класс и нихуя не понятно, от чего он там зависит. Тестировать такое — отдельный геморрой.

  • Достать сервис вручную из HttpContext.RequestServices (это Service Locator, а он — антипаттерн):
    var service = HttpContext.RequestServices.GetService<IMyService>();

    Блядь, избегай этого как огня! Это пиздец как запутывает код. Зависимости становятся неявными, и когда через полгода надо будет что-то поменять или потестить, ты будешь материться на того идиота, который это написал (а это, скорее всего, будешь ты сам).

Короче, вывод простой, как палка: Constructor Injection — это наш царь и бог. Всё на виду, всё прозрачно, и подсунуть заглушку для тестов — раз плюнуть. Не выёбывайся, используй его.