0Pricing
C# Academy · Урок

Написание собственного промежуточного ПО

Создавайте встроенное промежуточное ПО с помощью Use/Run/Map и промежуточное ПО на основе классов с InvokeAsync для повторно используемых компонентов.

«Написание собственного промежуточного ПО» — бесплатный урок C# Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.

Зачем нужно пользовательское промежуточное ПО

Пользовательское промежуточное ПО обрабатывает сквозные задачи, общие для многих запросов: журналирование, измерение времени, обработку ошибок, аутентификацию, сжатие и кэширование. Достаточно написать такую логику один раз — она будет автоматически выполняться для каждого подходящего запроса.

Встроенное промежуточное ПО с Use

Самый быстрый способ добавить промежуточное ПО — передать делегат в app.Use(). Это удобно для создания прототипов или действительно простой логики.

app.Use(async (context, next) =>
{
    var start = DateTimeOffset.UtcNow;

    await next(context);

    var elapsed = DateTimeOffset.UtcNow - start;
    context.Response.Headers.Append(
        "X-Elapsed", elapsed.TotalMilliseconds.ToString("F0") + "ms");
});

Промежуточное ПО на основе класса

Обычный класс промежуточного ПО имеет конструктор, принимающий RequestDelegate, и открытый метод InvokeAsync. Дополнительные параметры конструктора можно внедрять из DI.

public class SecurityHeadersMiddleware
{
    private readonly RequestDelegate _next;

    public SecurityHeadersMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context)
    {
        var headers = context.Response.Headers;
        headers.Append("X-Content-Type-Options", "nosniff");
        headers.Append("X-Frame-Options", "DENY");
        headers.Append("X-XSS-Protection", "1; mode=block");
        headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");

        await _next(context);
    }
}

// Extension method for clean registration:
public static class SecurityHeadersExtensions
{
    public static IApplicationBuilder UseSecurityHeaders(
        this IApplicationBuilder app) =>
        app.UseMiddleware<SecurityHeadersMiddleware>();
}

Промежуточное ПО для зависимостей DI с областью действия запроса

Если промежуточному ПО требуются службы с областью действия запроса, например DbContext, реализуйте этот интерфейс. Зарегистрируйте его с областью действия запроса, и платформа будет создавать новый экземпляр для каждого запроса.

public class AuditMiddleware : IMiddleware
{
    private readonly AuditDbContext _db;
    private readonly IHttpContextAccessor _http;

    public AuditMiddleware(AuditDbContext db, IHttpContextAccessor http)
    {
        _db = db;
        _http = http;
    }

    public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
    {
        await next(ctx);

        if (ctx.Request.Method != "GET")
        {
            _db.AuditLogs.Add(new AuditLog
            {
                User   = ctx.User.Identity?.Name,
                Path   = ctx.Request.Path,
                Status = ctx.Response.StatusCode,
                At     = DateTime.UtcNow
            });
            await _db.SaveChangesAsync();
        }
    }
}

builder.Services.AddScoped<AuditMiddleware>();
app.UseMiddleware<AuditMiddleware>();

Промежуточное ПО с параметрами

Передавайте конфигурацию промежуточному ПО через объект параметров в конструкторе. Зарегистрируйте параметры в контейнере DI или передайте их непосредственно при регистрации промежуточного ПО.

public class ThrottleOptions
{
    public int MaxRequestsPerSecond { get; set; } = 100;
    public string[] ExcludedPaths { get; set; } = Array.Empty<string>();
}

public class ThrottleMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ThrottleOptions _options;

    public ThrottleMiddleware(RequestDelegate next, ThrottleOptions options)
    {
        _next   = next;
        _options = options;
    }
    // ...
}

// Registration with options:
app.UseMiddleware<ThrottleMiddleware>(new ThrottleOptions
{
    MaxRequestsPerSecond = 50
});

Промежуточное ПО для обработки исключений

Пользовательское промежуточное ПО для обработки исключений перехватывает все необработанные исключения и возвращает структурированные ответы с ошибками.

public class GlobalExceptionMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<GlobalExceptionMiddleware> _logger;

    public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> l)
    {
        _next = next;
        _logger = l;
    }

    public async Task InvokeAsync(HttpContext ctx)
    {
        try { await _next(ctx); }
        catch (NotFoundException ex)
        {
            ctx.Response.StatusCode = 404;
            await ctx.Response.WriteAsJsonAsync(new { error = ex.Message });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Unhandled exception");
            ctx.Response.StatusCode = 500;
            await ctx.Response.WriteAsJsonAsync(new { error = "Internal server error" });
        }
    }
}

Промежуточное ПО для журналирования запросов

Промежуточное ПО для структурированного журналирования запросов и ответов записывает метод, путь, код состояния и затраченное время для каждого запроса.

public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggingMiddleware> _log;

    public RequestLoggingMiddleware(RequestDelegate n, ILogger<RequestLoggingMiddleware> l)
    { _next = n; _log = l; }

    public async Task InvokeAsync(HttpContext ctx)
    {
        var sw = System.Diagnostics.Stopwatch.StartNew();
        await _next(ctx);
        sw.Stop();

        _log.LogInformation(
            "{Method} {Path} -> {Status} in {Ms}ms",
            ctx.Request.Method,
            ctx.Request.Path,
            ctx.Response.StatusCode,
            sw.ElapsedMilliseconds);
    }
}

Условное промежуточное ПО с UseWhen

UseWhen условно разделяет конвейер на основе предиката, не разделяя его окончательно. После завершения ветвь снова объединяется с основным конвейером.

// Apply authentication middleware only to /api/* routes
app.UseWhen(
    ctx => ctx.Request.Path.StartsWithSegments("/api"),
    apiApp =>
    {
        apiApp.UseAuthentication();
        apiApp.UseAuthorization();
    });

// Public routes (/public/*) bypass authentication
app.MapGet("/public/health", () => "OK");
app.MapGet("/api/data", [Authorize] async (DataService s) => await s.GetAsync());

Промежуточное ПО для кэширования ответов

Встроенное промежуточное ПО для кэширования ответов хранит полные ответы в памяти. Настраивайте политики кэширования для каждой конечной точки с помощью заголовков или атрибутов.

builder.Services.AddResponseCaching();

app.UseResponseCaching();

app.MapGet("/products", async (AppDbContext db) =>
{
    var products = await db.Products.AsNoTracking().ToListAsync();
    return Results.Ok(products);
})
.CacheOutput(p => p.Expire(TimeSpan.FromMinutes(2)).Tag("products"));

Практический пример: промежуточное ПО для режима технического обслуживания

Промежуточное ПО для режима технического обслуживания считывает флаг из конфигурации, поддерживая горячую перезагрузку через IOptionsMonitor, и отклоняет запросы, кроме проверок работоспособности, во время технического обслуживания.

public class MaintenanceMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IOptionsMonitor<MaintenanceOptions> _options;

    public MaintenanceMiddleware(RequestDelegate next,
        IOptionsMonitor<MaintenanceOptions> opts)
    { _next = next; _options = opts; }

    public async Task InvokeAsync(HttpContext ctx)
    {
        if (_options.CurrentValue.IsEnabled
            && !ctx.Request.Path.StartsWithSegments("/health"))
        {
            ctx.Response.StatusCode = 503;
            ctx.Response.Headers.Append("Retry-After", "300");
            await ctx.Response.WriteAsJsonAsync(
                new { message = "Service under maintenance" });
            return;
        }
        await _next(ctx);
    }
}

Быстрая проверка

Почему при необходимости службы с областью действия запроса, например DbContext, следует реализовать IMiddleware, а не использовать обычный подход?

Повторение: написание пользовательского промежуточного ПО

Основные выводы:

  • Встроенный вариант с делегатом Use: быстро, подходит для однострочных решений; вариант на основе класса: повторно используемый и пригодный для тестирования
  • Обычный класс: конструктор принимает RequestDelegate; InvokeAsync(HttpContext)
  • IMiddleware: разрешение зависимостей через DI для каждого запроса — необходимо для зависимостей с областью действия запроса
  • UseWhen: условное ветвление, которое снова объединяется с основным конвейером
  • Сначала создавайте промежуточное ПО для обработки исключений, чтобы оно находилось снаружи и перехватывало все ошибки
  • Создавайте методы расширения (UseXxx) для понятной и удобной регистрации

Часто задаваемые вопросы

Урок «Написание собственного промежуточного ПО» бесплатный?

Да — полный текст урока «Написание собственного промежуточного ПО» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.

Чему я научусь в уроке «Написание собственного промежуточного ПО»?

Создавайте встроенное промежуточное ПО с помощью Use/Run/Map и промежуточное ПО на основе классов с InvokeAsync для повторно используемых компонентов. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C# Academy?

Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Написание собственного промежуточного ПО»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C# Academy?

Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Обзор конвейера ASP.NET Core
  2. Написание собственного промежуточного ПО
  3. Досрочное завершение и ветвление
  4. Порядок промежуточного ПО и встроенные компоненты
← Назад к C# Academy