← Назад к вопросам

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

3.0 Senior🔥 201 комментариев
#ASP.NET и Web API

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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

Определение

Middleware — это компонент, который обрабатывает HTTP запрос или ответ на его пути через приложение. Middleware выстраивается в цепочку (pipeline), и каждый middleware может обработать запрос, передать его дальше, и затем обработать ответ.

Проще говоря: middleware — это "таможня", через которую проходит каждый запрос и ответ.

Жизненный цикл запроса

Запрос проходит через цепочку middleware в одну сторону (вверх), а ответ проходит в другую сторону (вниз):

Запрос ↓                          Ответ ↑
────────────────────────────────────────────
    ↓
[Middleware 1 - Before]
    ↓
[Middleware 2 - Before]
    ↓
[Middleware 3 - Before]
    ↓
    Endpoint (Controller/Handler)
    ↓
[Middleware 3 - After]
    ↓
[Middleware 2 - After]
    ↓
[Middleware 1 - After]
    ↓
────────────────────────────────────────────

Порядок регистрации влияет на порядок выполнения

Middleware выполняется в порядке их регистрации:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Порядок регистрации = порядок выполнения
app.UseMiddleware<AuthenticationMiddleware>();  // 1. Выполнится первым
app.UseMiddleware<LoggingMiddleware>();         // 2. Выполнится вторым
app.UseMiddleware<ErrorHandlingMiddleware>();   // 3. Выполнится третьим

app.MapControllers();

app.Run();

Практический пример: как работает middleware

Пример 1: Простой middleware с логированием

public class LoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<LoggingMiddleware> _logger;
    
    public LoggingMiddleware(RequestDelegate next, ILogger<LoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }
    
    public async Task InvokeAsync(HttpContext context)
    {
        // ЭТАП 1: Логирование ВХОДЯЩЕГО запроса
        _logger.LogInformation($"Запрос: {context.Request.Path}");
        Console.WriteLine("→ Middleware: обработка запроса");
        
        // ЭТАП 2: Передача запроса следующему middleware
        await _next(context);
        
        // ЭТАП 3: Логирование ИСХОДЯЩЕГО ответа
        _logger.LogInformation($"Ответ: {context.Response.StatusCode}");
        Console.WriteLine("← Middleware: обработка ответа");
    }
}

// Регистрация
app.UseMiddleware<LoggingMiddleware>();
app.MapControllers();

Вывод при запросе:

→ Middleware: обработка запроса      (перед контроллером)
GET /users
[Controller обрабатывает запрос]
← Middleware: обработка ответа       (после контроллера)
200 OK

Цепочка middleware

Пример: Три middleware

// Middleware 1: Аутентификация
public class AuthMiddleware
{
    private readonly RequestDelegate _next;
    
    public AuthMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    
    public async Task InvokeAsync(HttpContext context)
    {
        Console.WriteLine("1. Auth: проверка токена");
        
        if (!context.Request.Headers.ContainsKey("Authorization"))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Unauthorized");
            return;  // Останавливаем обработку
        }
        
        await _next(context);
        
        Console.WriteLine("1. Auth: ответ готов");
    }
}

// Middleware 2: Логирование
public class LoggingMiddleware
{
    private readonly RequestDelegate _next;
    
    public LoggingMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    
    public async Task InvokeAsync(HttpContext context)
    {
        Console.WriteLine("2. Logging: запрос получен");
        
        await _next(context);
        
        Console.WriteLine("2. Logging: ответ отправлен");
    }
}

// Middleware 3: Обработка ошибок
public class ErrorMiddleware
{
    private readonly RequestDelegate _next;
    
    public ErrorMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    
    public async Task InvokeAsync(HttpContext context)
    {
        Console.WriteLine("3. Error: обработка начата");
        
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"3. Error: поймано исключение {ex.Message}");
            context.Response.StatusCode = 500;
        }
        
        Console.WriteLine("3. Error: обработка завершена");
    }
}

// Регистрация
app.UseMiddleware<AuthMiddleware>();     // 1-я
app.UseMiddleware<LoggingMiddleware>();  // 2-я
app.UseMiddleware<ErrorMiddleware>();    // 3-я

app.MapControllers();

// GET /users (с заголовком Authorization)
// Вывод:
// 1. Auth: проверка токена
// 2. Logging: запрос получен
// 3. Error: обработка начата
// [Controller]
// 3. Error: обработка завершена
// 2. Logging: ответ отправлен
// 1. Auth: ответ готов

Встроенные middleware ASP.NET Core

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Порядок имеет значение!

// 1. Обработка исключений (должна быть в начале)
app.UseExceptionHandler("/error");

// 2. HTTPS redirect
app.UseHttpsRedirection();

// 3. Кэширование
app.UseResponseCaching();

// 4. Статические файлы
app.UseStaticFiles();

// 5. Routing
app.UseRouting();

// 6. CORS
app.UseCors();

// 7. Аутентификация
app.UseAuthentication();

// 8. Авторизация
app.UseAuthorization();

// 9. Наши middleware
app.UseMiddleware<LoggingMiddleware>();

// 10. Endpoints
app.MapControllers();

app.Run();

Важный момент: app.UseXXX vs app.MapXXX

// UseXXX — это middleware (обрабатывает ВСЕ запросы)
app.UseLogging();  // Обрабатывает КАЖДЫЙ запрос

// MapXXX — это endpoint (обрабатывает конкретные routes)
app.MapControllers();  // Обрабатывает только matched routes

Практический пример: Middleware для измерения времени

public class TimingMiddleware
{
    private readonly RequestDelegate _next;
    
    public TimingMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    
    public async Task InvokeAsync(HttpContext context)
    {
        // ЭТАП 1: Начало отсчёта
        var stopwatch = System.Diagnostics.Stopwatch.StartNew();
        
        try
        {
            // ЭТАП 2: Передача запроса дальше
            await _next(context);
        }
        finally
        {
            // ЭТАП 3: Остановка отсчёта и логирование
            stopwatch.Stop();
            Console.WriteLine($"Запрос обработан за {stopwatch.ElapsedMilliseconds}ms");
            
            // Добавляем заголовок в ответ
            context.Response.Headers.Add("X-Response-Time", stopwatch.ElapsedMilliseconds.ToString());
        }
    }
}

// Регистрация — ВАЖНО: до MapControllers
app.UseMiddleware<TimingMiddleware>();
app.MapControllers();

Inline Middleware (без отдельного класса)

// Простой способ добавить middleware
app.Use(async (context, next) =>
{
    Console.WriteLine("→ Перед запросом");
    
    await next.Invoke();  // Передаём управление дальше
    
    Console.WriteLine("← После ответа");
});

app.MapControllers();

Когда выполняется логика

ПЕРЕД app.MapControllers():

  • app.Use/UseMiddleware — выполняется ДО того, как маршрут найден
  • Обрабатывает ВСЕ запросы (независимо от маршрута)

ПОСЛЕ app.MapControllers():

  • app.MapXXX — выполняется ТОЛЬКО для найденных маршрутов

Пример:

app.Use(async (context, next) =>
{
    Console.WriteLine("1. Всегда выполняется (middleware)");
    await next.Invoke();
});

app.MapGet("/users", () => "Users list");

app.Use(async (context, next) =>
{
    Console.WriteLine("2. Может не выполниться (после MapGet)");
    await next.Invoke();
});

// GET /users:
// Вывод:
// 1. Всегда выполняется (middleware)
// Users list
// 2 НЕ выполнится (находится ПОСЛЕ endpoint)

Таблица выполнения

КомпонентКогдаВсе ли запросыПорядок
UseMiddlewareПеред endpointДаПорядок регистрации
MapControllersПосле middlewareТолько matchedПоследний
app.UseВездеДаПорядок регистрации

Краткие выводы

  1. Middleware выполняется в порядке регистрации — первый UseXXX выполнится первым
  2. Запрос идёт вверх, ответ идёт вниз — await _next позволяет перехватить ответ
  3. Порядок важен! — обработка ошибок в начале, routing в конце
  4. UseXXX обрабатывает ВСЕ запросы — независимо от маршрута
  5. MapXXX обрабатывает конкретные маршруты — только найденные routes

Золотое правило: Middleware выстраивается как слои луковицы — запрос входит снаружи, проходит через каждый слой внутрь (к endpoint), затем выходит обратно через слои наружу (к клиенту).

В какой момент выполняется логика в middleware? | PrepBro