В какой момент выполняется логика в middleware в ASP.NET Core?

Ответ

Логика middleware выполняется в строгом порядке, определяемом конвейером HTTP-запроса (pipeline). Порядок регистрации компонентов в Startup.Configure или Program.cs критичен.

Ключевые моменты:

  1. Последовательное выполнение: Запрос проходит через middleware один за другим, начиная с первого зарегистрированного.
  2. Контроль потока с next(): Каждый middleware решает, передать ли управление следующему компоненту, вызвав await next.Invoke() (или await next()).
  3. Два этапа выполнения:
    • Логика ДО вызова next() — выполняется при прохождении запроса "вниз" по конвейеру (например, аутентификация, логирование).
    • Логика ПОСЛЕ вызова next() — выполняется при обратном проходе ответа "вверх" по конвейеру (например, обработка ошибок, добавление заголовков).
  4. Short-circuiting: Если middleware НЕ вызывает next(), он завершает конвейер, и последующие компоненты не выполняются (пример — middleware для статических файлов или аутентификации при неудаче).

Пример кода, иллюстрирующий поток:

app.Use(async (context, next) =>
{
    // Этап 1: Логика ДО следующего middleware
    Console.WriteLine("Middleware 1: Before next");
    context.Items["StartTime"] = DateTime.UtcNow;

    await next(); // Передача управления следующему middleware в конвейере

    // Этап 2: Логика ПОСЛЕ выполнения остального конвейера
    var duration = DateTime.UtcNow - (DateTime)context.Items["StartTime"];
    Console.WriteLine($"Middleware 1: After next. Request took {duration.TotalMilliseconds}ms");
});

app.Use(async (context, next) =>
{
    Console.WriteLine("Middleware 2: Before next");
    await next();
    Console.WriteLine("Middleware 2: After next");
});

// Output для запроса:
// Middleware 1: Before next
// Middleware 2: Before next
// ... (выполнение конечной точки)
// Middleware 2: After next
// Middleware 1: After next

Практическое правило: Порядок регистрации должен соответствовать логическим зависимостям (например, Middleware для обработки исключений регистрируется первым, чтобы ловить ошибки из всех последующих компонентов).

Ответ 18+ 🔞

Смотри, объясню на пальцах, как этот ваш конвейер запросов работает, а то в документации мозги выносят.

Представь себе заводскую линию, как на "Газели" сборка. Запрос — это голый кузов, который едет по конвейеру. А каждый middleware — это рабочий на своём посту. Порядок, в котором ты этих работников расставишь в Startup.Configure, — это и есть порядок, в котором кузов к ним подъедет. Перепутаешь — и получится, что стекло вклеивают после того, как поставили руль. Пиздец и костыли.

Как оно шевелится:

  1. Очередь — мать порядка. Первый зарегистрированный — первый и обрабатывает запрос. Всё просто, как три копейки.
  2. Волшебная палочка next(). Каждый рабочий решает: позвать следующего пацана по конвейеру или сказать "стоп, дальше не пущаю". Вызвал await next() — передал эстафету. Не вызвал — всё, конвейер упёрся в него, и до конечной точки запрос может вообще не дойти. Так, например, middleware для статических файлов работает: нашёл файл — отдал, и хуй с ним, дальше не пошёл.
  3. Двухтактный движок. Внутри каждого middleware есть два такта:
    • Такт "туда" — код, который перед вызовом next(). Тут обычно проверяют: авторизован ли, залогировать запрос, засечь время.
    • Такт "обратно" — код, который после вызова next(). Это когда запрос уже дошёл до контроллера, тот отработал, и ответ попёр обратно вверх по конвейеру. Тут можно ошибки обработать, заголовки напихать, время выполнения посчитать.

Вот смотри на живом примере, как они друг через друга пролезают:

app.Use(async (context, next) =>
{
    // Такт "туда" первого мужика
    Console.WriteLine("Первый мужик: Щас передам следующему, засекаю время.");
    context.Items["StartTime"] = DateTime.UtcNow;

    await next(); // Кричит: "Эй, второй, на, лови кузов!"

    // Такт "обратно" первого мужика. Ответ уже сформирован и идёт назад.
    var duration = DateTime.UtcNow - (DateTime)context.Items["StartTime"];
    Console.WriteLine($"Первый мужик: Всё, отработали. Гоняли эту хуйню {duration.TotalMilliseconds} мс.");
});

app.Use(async (context, next) =>
{
    Console.WriteLine("Второй мужик: Принял, щас тоже чё-нибудь сделаю и передам.");
    await next();
    Console.WriteLine("Второй мужик: Отдаю обратно первому.");
});

// Что в консоли вылезет при запросе:
// Первый мужик: Щас передам следующему, засекаю время.
// Второй мужик: Принял, щас тоже чё-нибудь сделаю и передам.
// ... (тут где-то в самом конце конвейера контроллер ебёт базу данных)
// Второй мужик: Отдаю обратно первому.
// Первый мужик: Всё, отработали. Гоняли эту хуйню 152.5 мс.

Главное правило, чтобы не вышло пиздеца: Ставь middleware в логическом порядке. Обработчик исключений — всегда самым первым, чтобы ловить все косяки по цепочке. Аутентификацию — до авторизации, ибо как проверить права, не зная, кто ты такой? Статические файлы — до маршрутизации, чтобы зря движок MVC не дергали. Если накосячишь с порядком — будет тебе не приложение, а цирк с конями.