Middleware: конвейер, порядок, свои middleware

↑ 3.1 Хост, конфигурация и middleware pipeline · ← Предыдущая · → Следующая

Обязательно~4 мин чтенияУровень: middle

Зачем это на собесе

Порядок 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
1UseExceptionHandler / UseHsts
2UseHttpsRedirection, UseForwardedHeaders (за proxy раньше всех)
3UseStaticFiles
4UseRouting (неявно в minimal hosting)
5UseCors
6UseAuthentication
7UseAuthorization
8UseRateLimiter, UseResponseCompression
9MapControllers / 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 выполняется для каждого запроса; кэшируйте и избегайте блокировок.

Практика

  1. Напишите middleware для correlation id и логируйте его во всех записях.
  2. Поменяйте местами UseAuthentication и UseAuthorization, посмотрите поведение.
  3. Сделайте middleware, замеряющий время и пишущий метрику.

Вопросы с ответами

Связанные темы