В какой момент выполняется логика в middleware?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
В какой момент выполняется логика в 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 | Везде | Да | Порядок регистрации |
Краткие выводы
- Middleware выполняется в порядке регистрации — первый UseXXX выполнится первым
- Запрос идёт вверх, ответ идёт вниз — await _next позволяет перехватить ответ
- Порядок важен! — обработка ошибок в начале, routing в конце
- UseXXX обрабатывает ВСЕ запросы — независимо от маршрута
- MapXXX обрабатывает конкретные маршруты — только найденные routes
Золотое правило: Middleware выстраивается как слои луковицы — запрос входит снаружи, проходит через каждый слой внутрь (к endpoint), затем выходит обратно через слои наружу (к клиенту).