Ответ
Да, создание кастомного middleware — стандартная практика для сквозной функциональности (cross-cutting concerns). Например, для логирования, обработки исключений, кастомной аутентификации или добавления специфичных заголовков.
Пример: Middleware для логирования времени выполнения запроса и добавления корреляционного ID.
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
// Генерация уникального ID для отслеживания цепочки запроса.
var correlationId = Guid.NewGuid().ToString();
context.Items["CorrelationId"] = correlationId;
var stopwatch = Stopwatch.StartNew();
// Логируем начало обработки.
_logger.LogInformation(
"Starting request {Method} {Path} with CorrelationId: {CorrelationId}",
context.Request.Method,
context.Request.Path,
correlationId
);
try
{
// Передаём управление следующему компоненту в конвейере.
await _next(context);
}
finally
{
stopwatch.Stop();
// Логируем завершение обработки и время выполнения.
_logger.LogInformation(
"Completed request {Method} {Path} with status {StatusCode} in {ElapsedMs}ms. CorrelationId: {CorrelationId}",
context.Request.Method,
context.Request.Path,
context.Response.StatusCode,
stopwatch.ElapsedMilliseconds,
correlationId
);
}
}
}
// Метод расширения для удобной регистрации.
public static class RequestLoggingMiddlewareExtensions
{
public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestLoggingMiddleware>();
}
}
Регистрация в конвейере (Program.cs):
var app = builder.Build();
// Порядок важен! Логирование должно быть одним из первых.
app.UseRequestLogging();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Зачем это нужно? Такой middleware централизует логирование для всех запросов, добавляет полезный контекст (CorrelationId) и помогает в диагностике проблем с производительностью, не загрязняя код контроллеров.
Ответ 18+ 🔞
Ну что за вопрос, конечно можно! Это ж как раз та самая магия, когда тебе нужно запилить какую-то общую фигню на все запросы — логирование, проверку прав, обработку ошибок. Чтобы не копипастить одно и то же в каждый контроллер, а просто один раз написал и забыл, как страшный сон.
Вот смотри, например, если тебе нужно залогировать, сколько времени запрос тормозит, и прикрутить к нему уникальный айдишник, чтобы потом в логах не еб**ться, где чьи следы. Делается это просто, как три копейки:
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
// Генерим уникальный ключ для отслеживания этого конкретного запроса по всем сервисам.
var correlationId = Guid.NewGuid().ToString();
context.Items["CorrelationId"] = correlationId;
var stopwatch = Stopwatch.StartNew();
// Пишем в лог, что вот, мол, начали работать, не мешайте.
_logger.LogInformation(
"Starting request {Method} {Path} with CorrelationId: {CorrelationId}",
context.Request.Method,
context.Request.Path,
correlationId
);
try
{
// А теперь пускаем эту телегу дальше по конвейеру.
await _next(context);
}
finally
{
stopwatch.Stop();
// И когда всё закончилось (успешно или нет — неважно), пишем, сколько времени это безобразие длилось.
_logger.LogInformation(
"Completed request {Method} {Path} with status {StatusCode} in {ElapsedMs}ms. CorrelationId: {CorrelationId}",
context.Request.Method,
context.Request.Path,
context.Response.StatusCode,
stopwatch.ElapsedMilliseconds,
correlationId
);
}
}
}
// А это такой красивый метод-расширение, чтобы в основном коде всё выглядело опрятно.
public static class RequestLoggingMiddlewareExtensions
{
public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestLoggingMiddleware>();
}
}
А вот куда эту штуку впихнуть (Program.cs):
var app = builder.Build();
// Важно ставить это одним из первых, чтобы логировалось ВСЁ, что происходит дальше.
app.UseRequestLogging();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
И зачем это всё? Да чтобы не быть идиотом и не писать один и тот же код в каждом методе контроллера! Теперь у тебя для каждого запроса автоматически будет свой CorrelationId, и в логах сразу видно, сколько какая операция жрала времени. Диагностика проблем становится в разы проще — просто находишь по айдишнику всю цепочку событий. Красота, а не жизнь!