Как работает Middleware в ASP.NET Core? Как создать собственный Middleware для логирования запросов?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Основы Middleware в ASP.NET Core
Middleware в ASP.NET Core — это компоненты, которые образуют конвейер обработки HTTP-запросов и ответов. Каждый middleware в этом конвейере может выполнять операции как до, так и после следующего компонента, что позволяет реализовать сквозную функциональность (cross-cutting concerns).
Как работает конвейер Middleware
Конвейер Middleware строится в методе Configure класса Startup (или в Program.cs в .NET 6+). Запрос проходит через middleware последовательно, образуя цепочку вызовов, часто называемую "конвейером" или "цепочкой ответственности":
public void Configure(IApplicationBuilder app)
{
// Каждый Use добавляет middleware в конвейер
app.UseExceptionHandler();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints();
}
Ключевые характеристики Middleware:
- Выполняются в порядке регистрации
- Каждый middleware может обработать запрос и передать его следующему или завершить обработку
- Используют паттерн RequestDelegate для передачи управления
- Поддерживают внедрение зависимостей через конструктор
- Имеют доступ к контексту запроса (
HttpContext)
Создание Middleware для логирования запросов
Способ 1: Классический подход (класс Middleware)
Создадим middleware, который будет логировать информацию о входящих запросах и времени их выполнения:
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 stopwatch = Stopwatch.StartNew();
// Логируем информацию о входящем запросе
_logger.LogInformation(
"Начало обработки запроса: {Method} {Path}",
context.Request.Method,
context.Request.Path);
try
{
// Передаем управление следующему middleware
await _next(context);
stopwatch.Stop();
// Логируем результат и время выполнения
_logger.LogInformation(
"Запрос завершен: {Method} {Path} - {StatusCode} за {ElapsedMs} мс",
context.Request.Method,
context.Request.Path,
context.Response.StatusCode,
stopwatch.ElapsedMilliseconds);
}
catch (Exception ex)
{
stopwatch.Stop();
// Логируем ошибки
_logger.LogError(
ex,
"Ошибка при обработке запроса: {Method} {Path} за {ElapsedMs} мс",
context.Request.Method,
context.Request.Path,
stopwatch.ElapsedMilliseconds);
throw;
}
}
}
Способ 2: Расширение метода для удобной регистрации
public static class RequestLoggingMiddlewareExtensions
{
public static IApplicationBuilder UseRequestLogging(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestLoggingMiddleware>();
}
}
Способ 3: Factory-based middleware (альтернативный подход)
public class RequestLoggingMiddlewareFactory : IMiddleware
{
private readonly ILogger<RequestLoggingMiddlewareFactory> _logger;
public RequestLoggingMiddlewareFactory(
ILogger<RequestLoggingMiddlewareFactory> logger)
{
_logger = logger;
}
public async Task InvokeAsync(
HttpContext context,
RequestDelegate next)
{
// Аналогичная логика обработки
var startTime = DateTime.UtcNow;
await next(context);
var elapsed = DateTime.UtcNow - startTime;
_logger.LogInformation(
"Запрос {Method} {Path} выполнен за {ElapsedMs} мс",
context.Request.Method,
context.Request.Path,
elapsed.TotalMilliseconds);
}
}
Регистрация Middleware в приложении
В файле Program.cs (или Startup.Configure):
var builder = WebApplication.CreateBuilder(args);
// Регистрация сервисов
builder.Services.AddScoped<RequestLoggingMiddlewareFactory>();
var app = builder.Build();
// Использование middleware (порядок важен!)
app.UseMiddleware<RequestLoggingMiddleware>();
// или через extension method
app.UseRequestLogging();
// Альтернативно для factory-based middleware
app.UseMiddleware<RequestLoggingMiddlewareFactory>();
app.MapGet("/", () => "Hello World!");
app.Run();
Усовершенствованная версия с дополнительной функциональностью
public class AdvancedLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<AdvancedLoggingMiddleware> _logger;
public AdvancedLoggingMiddleware(RequestDelegate next, ILogger<AdvancedLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var originalBodyStream = context.Response.Body;
using var responseBody = new MemoryStream();
context.Response.Body = responseBody;
var request = await FormatRequest(context.Request);
_logger.LogInformation($"Request: {request}");
await _next(context);
var response = await FormatResponse(context.Response);
_logger.LogInformation($"Response: {response}");
await responseBody.CopyToAsync(originalBodyStream);
}
private async Task<string> FormatRequest(HttpRequest request)
{
request.EnableBuffering();
var body = await new StreamReader(request.Body).ReadToEndAsync();
request.Body.Position = 0;
return $"{request.Method} {request.Path}{request.QueryString} {body}";
}
private async Task<string> FormatResponse(HttpResponse response)
{
response.Body.Seek(0, SeekOrigin.Begin);
var body = await new StreamReader(response.Body).ReadToEndAsync();
response.Body.Seek(0, SeekOrigin.Begin);
return $"{response.StatusCode}: {body}";
}
}
Ключевые концепции Middleware
Типы middleware:
- Terminal middleware — завершает цепочку обработки (например,
app.Run()) - Conventional middleware — стандартные компоненты конвейера
- Factory-based middleware — реализуют
IMiddlewareинтерфейс
Жизненный цикл:
- Создание при инициализации приложения
- Вызов метода
Invoke/InvokeAsyncдля каждого запроса - Внедрение зависимостей через конструктор (Singleton, Scoped, Transient)
Лучшие практики:
- Всегда использовать
async/awaitдля асинхронных операций - Корректно обрабатывать исключения
- Минимизировать логику в конструкторе middleware
- Учитывать порядок регистрации в конвейере
- Для сложной логики использовать factory-based подход
Middleware в ASP.NET Core — это мощный механизм для реализации сквозной функциональности, который обеспечивает гибкость, производительность и простоту поддержки кода. Логирование запросов — лишь один из множества сценариев использования, включая аутентификацию, кэширование, компрессию и мониторинг производительности.