Middleware: конвейер, порядок, свои middleware
↑ 3.1 Хост, конфигурация и middleware pipeline · ← Предыдущая · → Следующая
Зачем это на собесе
Порядок middleware — частая ловушка: «почему авторизация не срабатывает» и «где ставить CORS/обработчик ошибок».
Объяснение
Middleware — компонент, который получает HttpContext и решает: обработать запрос сам или вызвать следующий (next). Конвейер строится как цепочка делегатов; ответ идёт обратно в обратном порядке.
app.Use(async (ctx, next) =>
{
var sw = Stopwatch.StartNew();
await next(ctx); // вызов следующего
ctx.Response.Headers["X-Elapsed-ms"] = sw.ElapsedMilliseconds.ToString();
});
app.Map("/health", b => b.Run(c => c.Response.WriteAsync("ok"))); // ветка
app.Run(ctx => ctx.Response.WriteAsync("terminal")); // терминальныйРекомендуемый порядок
| Позиция | Middleware |
|---|---|
| 1 | UseExceptionHandler / UseHsts |
| 2 | UseHttpsRedirection, UseForwardedHeaders (за proxy раньше всех) |
| 3 | UseStaticFiles |
| 4 | UseRouting (неявно в minimal hosting) |
| 5 | UseCors |
| 6 | UseAuthentication |
| 7 | UseAuthorization |
| 8 | UseRateLimiter, UseResponseCompression |
| 9 | MapControllers / MapGet (endpoints) |
Правило: аутентификация → авторизация → endpoint; CORS до авторизации, чтобы preflight-запросы не отклонялись.
Свой middleware
public class CorrelationIdMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext ctx)
{
var id = ctx.Request.Headers["X-Correlation-Id"].FirstOrDefault() ?? Guid.NewGuid().ToString();
ctx.Response.Headers["X-Correlation-Id"] = id;
using (logger.BeginScope(...)) { } // по желанию
await next(ctx);
}
}
app.UseMiddleware<CorrelationIdMiddleware>();Scoped-зависимости нужно получать в параметрах InvokeAsync, а не в конструкторе: middleware создаётся один раз (как singleton).
Нюансы и подводные камни
- Нельзя писать в
Responseпосле того, как заголовки отправлены (Response.HasStarted). - Не забывайте
await next(ctx); иначе цепочка обрывается, и часть логики не выполнится. - Исключение в middleware выше обработчика ошибок попадёт клиенту как 500 без обработки.
IMiddleware(фабричный) резолвится из DI на каждый запрос и позволяет scoped-зависимости в конструкторе.- Тяжёлая работа в middleware выполняется для каждого запроса; кэшируйте и избегайте блокировок.
Практика
- Напишите middleware для correlation id и логируйте его во всех записях.
- Поменяйте местами
UseAuthenticationиUseAuthorization, посмотрите поведение. - Сделайте middleware, замеряющий время и пишущий метрику.
Вопросы с ответами
Почему важен порядок middleware?
Каждый компонент видит запрос в порядке регистрации, а ответ в обратном. Например, авторизация без аутентификации не знает пользователя, а обработчик ошибок должен идти первым, чтобы перехватывать исключения остальных.
Чем Use, Run и Map отличаются?
Useвызывает следующий,Run— терминальный,Mapсоздаёт ветку конвейера по префиксу пути.
Как получить scoped-сервис в middleware?
Через параметр
InvokeAsync, либо реализовавIMiddleware.
Middleware или фильтр MVC?
Middleware работает для всех запросов и не знает о контроллерах; фильтры — только для MVC/endpoints с доступом к контексту action и модели.