Какой жизненный цикл у контроллера в ASP.NET Core?

Ответ

Контроллеры в ASP.NET Core по умолчанию имеют жизненный цикл Scoped (с областью действия), что означает создание нового экземпляра для каждого HTTP-запроса. Это обеспечивает изоляцию и безопасность данных между запросами.

Последовательность этапов для одного запроса:

  1. Разрешение зависимостей: DI-контейнер создает экземпляр контроллера, разрешая все зависимости, указанные в его конструкторе (например, сервисы, логгеры).
  2. Выполнение фильтров: Выполняются различные фильтры (авторизации, ресурсов, действий, исключений, результатов) в определенном порядке.
  3. Вызов метода действия (Action Method): Вызывается соответствующий запросу метод контроллера, который выполняет бизнес-логику и возвращает IActionResult.
  4. Выполнение результата: Фреймворк выполняет возвращенный результат (рендерит View, возвращает JSON и т.д.).
  5. Освобождение ресурсов: Если контроллер реализует интерфейс IDisposable или IAsyncDisposable, вызывается соответствующий метод Dispose для очистки неуправляемых ресурсов. После этого экземпляр контроллера готов к сборке мусора.
public class OrderController : Controller, IDisposable
{
    private readonly IOrderService _orderService;
    private readonly ILogger<OrderController> _logger;
    private bool _disposed = false;

    // 1. DI-контейнер инжектирует зависимости при создании
    public OrderController(IOrderService orderService, ILogger<OrderController> logger)
    {
        _orderService = orderService;
        _logger = logger;
    }

    // 3. Вызов метода действия
    public IActionResult Get(int id)
    {
        var order = _orderService.GetOrder(id);
        return Ok(order); // 4. Выполнение результата
    }

    // 5. Освобождение ресурсов (вызывается фреймворком)
    public void Dispose()
    {
        if (!_disposed)
        {
            _logger.LogTrace("OrderController disposed.");
            // Освобождение неуправляемых ресурсов, если они есть
            _disposed = true;
        }
    }
}

Важно: Не храните состояние (данные) в полях контроллера, которое должно сохраняться между запросами, так как для нового запроса будет создан новый экземпляр.

Ответ 18+ 🔞

Ну ты смотри, как эти контроллеры в ASP.NET Core живут, а? Ситуация, в общем-то, простая, но народ иногда путается, думает, что они как синглтоны какие-то, один на всех. Хуй там, блядь!

По умолчанию жизненный цикл у них — Scoped, то есть с областью видимости. Проще говоря, на каждый новый HTTP-запрос тебе выкатывают свеженький, только что с завода, экземпляр контроллера. Это, в принципе, логично и безопасно: один запрос — своя песочница, свои данные, ни с кем не пересекается. Красота.

А вот как это всё по кочкам бежит, когда запрос прилетает:

  1. Втыкаем зависимости. DI-контейнер, этот хитрожопый механизм, смотрит на конструктор контроллера, видит там IOrderService, ILogger и прочую хуйню, и говорит: «Ага, щас мы тебе всё это соберём!». И создаёт инстанс, начиняя его всем необходимым.
  2. Пробегаем через фильтры. Тут начинается цирк с конями: сначала фильтры авторизации («а можно ли тебе вообще сюда?»), потом ресурсов, действий, и если что-то пошло не так — фильтры исключений. Порядок важен, ёпта!
  3. Работа метода. Наконец-то вызывается тот самый метод действия (Action), который ты и писал. Там твоя бизнес-логика, обращения к сервисам — в общем, вся соль. В конце он должен вернуть какой-нибудь IActionResult.
  4. Исполнение результата. Фреймворк берёт этот результат и делает что надо: рендерит вьюху, отдаёт JSON, редиректит — в общем, выполняет свою работу.
  5. Зачистка. Всё, запрос отработан. Если твой контроллер умный и реализует IDisposable, то фреймворк вызовет метод Dispose(), чтобы ты мог прибраться, если использовал какие-нибудь неуправляемые ресурсы. После этого экземпляр отправляется на свалку истории, то есть ждёт сборщика мусора.

Вот, смотри, пример, чтобы вообще всё стало ясно, как божий день:

public class OrderController : Controller, IDisposable
{
    private readonly IOrderService _orderService;
    private readonly ILogger<OrderController> _logger;
    private bool _disposed = false;

    // 1. Сюда DI-контейнер всунет все зависимости при создании
    public OrderController(IOrderService orderService, ILogger<OrderController> logger)
    {
        _orderService = orderService;
        _logger = logger;
    }

    // 3. Вот он, наш метод, который и вызывается
    public IActionResult Get(int id)
    {
        var order = _orderService.GetOrder(id);
        return Ok(order); // 4. И сразу результат
    }

    // 5. А это фреймворк вызовет, когда всё кончится, для прибранья
    public void Dispose()
    {
        if (!_disposed)
        {
            _logger.LogTrace("OrderController disposed.");
            // Тут бы почистил что-то, если бы было что чистить
            _disposed = true;
        }
    }
}

И главное, запомни раз и навсегда: не храни в полях контроллера никакое состояние, которое должно пережить запрос! Не выдумывай. Для нового запроса — новый экземпляр, и все твои прошлые поля — нихуя не значат. Если нужно что-то сохранить между запросами — сессия, куки, кеш, база данных, на худой конец. А контроллер — он одноразовый, как презерватив, блядь. Использовал и выбросил.