Ответ
Middleware — это компоненты, которые образуют конвейер обработки HTTP-запроса и ответа в приложении ASP.NET Core. Каждый middleware выполняет определенную задачу (логирование, аутентификация, обработка ошибок) и либо передает запрос дальше по цепочке, либо завершает обработку.
Ключевые принципы:
- Конвейер: Middleware регистрируются в
Program.cs(илиStartup.Configure) в определенном порядке. Этот порядок критически важен. - Вызов
next: Middleware решает, передать ли управление следующему компоненту в конвейере, вызвавnext.Invoke(). - Короткое замыкание: Middleware может не вызывать
next, тем самым завершая конвейер и отправляя ответ клиенту (например, middleware для статических файлов или аутентификации при неудаче).
Типичная структура конвейера:
// Program.cs
var app = builder.Build();
// 1. Обработка исключений (должен быть первым, чтобы ловить ошибки со всех последующих этапов)
app.UseExceptionHandler("/Error");
// 2. Обслуживание статических файлов (CSS, JS, изображения)
app.UseStaticFiles();
// 3. Маршрутизация (определяет, какой контроллер/метод запустить)
app.UseRouting();
// 4. Аутентификация и авторизация
app.UseAuthentication();
app.UseAuthorization();
// 5. Конечные точки (запуск контроллеров, Minimal API)
app.MapControllers();
app.Run();
Создание собственного Middleware: Есть два основных способа:
- Класс Middleware:
public class RequestTimingMiddleware { private readonly RequestDelegate _next; private readonly ILogger<RequestTimingMiddleware> _logger; public RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { var sw = Stopwatch.StartNew(); await _next(context); // Передача управления дальше sw.Stop(); _logger.LogInformation($"Запрос {context.Request.Path} выполнен за {sw.ElapsedMilliseconds} мс"); } } // Регистрация: app.UseMiddleware<RequestTimingMiddleware>(); - Inline Middleware (удобно для простой логики):
app.Use(async (context, next) => { // Код до вызова next (обработка запроса) context.Response.Headers.Add("X-Request-Time", DateTime.UtcNow.ToString("o")); await next(); // Код после вызова next (обработка ответа) });
Встроенные Middleware: UseHttpsRedirection, UseCors, UseResponseCompression, UseSession и многие другие.
Ответ 18+ 🔞
А, middleware в ASP.NET Core, да? Ну это ж классика, как водка под селёдку — без неё никуда. Слушай, представь себе конвейер на заводе, только вместо колбасы по нему HTTP-запросы едут. Каждый middleware — это такой работяга на своём участке: один логи пишет, другой права проверяет, третий ошибки ловит. И главное — порядок, блядь, порядок! Расстановка — это всё. Если поставить аутентификацию после того, как запрос уже на контроллер ушёл, то это как замок на дверь вешать после того, как тебя уже обокрали. Бесполезно, епта.
Вот смотри, как обычно это в Program.cs выглядит:
var app = builder.Build();
// 1. По-хорошему, первым делом — обработчик ошибок. Чтобы если где-то дальше пиздец случится, мы его красиво поймали.
app.UseExceptionHandler("/Error");
// 2. Потом статика. Картинки, стили, скрипты. Чтобы если файл есть — сразу отдать и не грузить всю эту движуху с контроллерами.
app.UseStaticFiles();
// 3. А вот теперь маршрутизация. Решаем, куда запрос вообще идёт.
app.UseRouting();
// 4. И только потом проверяем, а кто это к нам пришёл-то? И можно ли ему?
app.UseAuthentication();
app.UseAuthorization();
// 5. Ну и финальный аккорд — запускаем сами обработчики (контроллеры или Minimal API).
app.MapControllers();
app.Run();
Если порядок нарушить — получится ерунда. Например, UseStaticFiles после маршрутизации — и он будет пытаться искать style.css как контроллер. Пиздец, а не сайт.
А если хочешь свой middleware запилить — вообще пара пустяков. Вот, смотри, два способа.
Первый — как взрослые дяди, классом:
public class RequestTimingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestTimingMiddleware> _logger;
public RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var sw = Stopwatch.StartNew(); // Засекаем время
await _next(context); // Пускаем запрос дальше по конвейеру
sw.Stop(); // Стоп!
_logger.LogInformation($"Запрос {context.Request.Path} выполнен за {sw.ElapsedMilliseconds} мс");
// Теперь видно, какой маршрут сколько тормозит. Очень полезно, когда всё лагает.
}
}
// В Program.cs потом: app.UseMiddleware<RequestTimingMiddleware>();
Второй способ — inline, на коленке, прямо в Program.cs:
app.Use(async (context, next) =>
{
// Тут мы до того, как запрос пошёл дальше
context.Response.Headers.Add("X-Our-Super-Header", "Hello from middleware!");
await next(); // Отпускаем его
// А тут — уже после того, как все отработали, можно что-то с ответом сделать
});
Главная магия — в этом вызове next(). Если его не вызвать — конвейер обрывается, и запрос дальше не пойдёт. Это как охранник на входе, который может сказать: «Стой, дальше нельзя!» и сразу отправить ответ обратно. Так работает, например, middleware для статики или когда аутентификация провалилась.
А вообще, встроенных middleware — овердохуища: редиректы на HTTPS, CORS, сжатие ответов, сессии — всё это они, родные. Ставишь в нужном месте — и работает, как часы. Главное — не накосячить с порядком, а то будет тебе хиросима, а не веб-приложение.