← Назад к вопросам

Как работает Middleware в ASP.NET Core? Как создать собственный Middleware для логирования запросов?

2.0 Middle🔥 141 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Основы 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 интерфейс

Жизненный цикл:

  1. Создание при инициализации приложения
  2. Вызов метода Invoke/InvokeAsync для каждого запроса
  3. Внедрение зависимостей через конструктор (Singleton, Scoped, Transient)

Лучшие практики:

  • Всегда использовать async/await для асинхронных операций
  • Корректно обрабатывать исключения
  • Минимизировать логику в конструкторе middleware
  • Учитывать порядок регистрации в конвейере
  • Для сложной логики использовать factory-based подход

Middleware в ASP.NET Core — это мощный механизм для реализации сквозной функциональности, который обеспечивает гибкость, производительность и простоту поддержки кода. Логирование запросов — лишь один из множества сценариев использования, включая аутентификацию, кэширование, компрессию и мониторинг производительности.

Как работает Middleware в ASP.NET Core? Как создать собственный Middleware для логирования запросов? | PrepBro