C# Academy · Lekcja

Pisanie niestandardowego middleware

Twórz wbudowane middleware za pomocą Use/Run/Map oraz middleware oparte na klasach z InvokeAsync, aby budować komponenty wielokrotnego użytku.

Lekcja 2 z 412 kroki

Pisanie niestandardowego middleware to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.

Dlaczego własne middleware?

Własne middleware obsługuje przekrojowe aspekty dotyczące wielu żądań: rejestrowanie, pomiar czasu, obsługę błędów, uwierzytelnianie, kompresję i buforowanie. Jednorazowe napisanie takiego kodu sprawia, że logika jest automatycznie wykonywana dla każdego pasującego żądania.

Middleware wbudowane za pomocą Use

Najszybszy sposób dodania middleware: przekazanie delegata do app.Use(). To dobre rozwiązanie podczas prototypowania lub w przypadku naprawdę prostej logiki.

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");
});

Middleware klasowe (konwencja)

Konwencjonalna klasa middleware ma konstruktor przyjmujący RequestDelegate oraz publiczną metodę InvokeAsync. Dodatkowe parametry konstruktora mogą być wstrzykiwane z 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>();
}

IMiddleware dla zależności o zasięgu Scoped

Jeśli middleware wymaga usług o zasięgu Scoped, takich jak DbContext, należy zaimplementować IMiddleware. Należy zarejestrować je z czasem życia Scoped, a framework utworzy jego nową instancję dla każdego żądania.

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>();

Middleware z opcjami

Konfigurację można przekazywać do middleware za pomocą obiektu opcji w konstruktorze. Opcje należy zarejestrować w kontenerze DI albo przekazać je bezpośrednio podczas rejestrowania middleware.

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
});

Middleware obsługi wyjątków

Własne middleware obsługi wyjątków przechwytuje wszystkie nieobsłużone wyjątki i zwraca ustrukturyzowane odpowiedzi błędów.

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" });
        }
    }
}

Middleware rejestrowania żądań

Middleware do strukturalnego rejestrowania żądań i odpowiedzi zapisuje metodę, ścieżkę, kod stanu oraz czas wykonania każdego żądania.

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);
    }
}

Warunkowe middleware za pomocą UseWhen

UseWhen rozgałęzia potok warunkowo na podstawie predykatu, nie dzieląc go trwale. Po zakończeniu obsługi gałąź ponownie łączy się z głównym potokiem.

// 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());

Middleware buforowania odpowiedzi

Wbudowane middleware buforowania odpowiedzi przechowuje pełne odpowiedzi w pamięci. Zasady buforowania dla poszczególnych endpointów można konfigurować za pomocą nagłówków lub atrybutów.

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"));

Praktyczny przykład: middleware trybu konserwacji

Middleware trybu konserwacji odczytuje flagę z konfiguracji, obsługując dynamiczne przeładowywanie za pomocą IOptionsMonitor, i odrzuca żądania inne niż żądania testów kondycji podczas konserwacji.

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);
    }
}

Szybki test

Dlaczego w przypadku usługi o zasięgu Scoped, takiej jak DbContext, należy zaimplementować IMiddleware zamiast korzystać z podejścia opartego na konwencji?

Podsumowanie: tworzenie własnego middleware

Najważniejsze informacje:

  • Wbudowane (delegat Use): szybkie, dobre do pojedynczych instrukcji; klasowe: wielokrotnego użytku i łatwe do testowania
  • Klasa konwencjonalna: konstruktor przyjmuje RequestDelegate; InvokeAsync(HttpContext)
  • IMiddleware: rozwiązywanie zależności przez DI dla każdego żądania — wymagane w przypadku zależności Scoped
  • UseWhen: warunkowe rozgałęzienie, które ponownie łączy się z głównym potokiem
  • Middleware obsługi wyjątków należy umieścić jako pierwsze, czyli najbardziej zewnętrzne, aby przechwytywało wszystkie błędy
  • Warto tworzyć metody rozszerzające (UseXxx), aby rejestrowanie było przejrzyste i łatwe do znalezienia
Bezpłatny start

Ucz się C# dzięki korepetycjom AI — za darmo

Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.

Kursy
93
Lekcje
346

Często zadawane pytania

Czy lekcja „Pisanie niestandardowego middleware” jest bezpłatna?

Tak — pełny tekst „Pisanie niestandardowego middleware” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Pisanie niestandardowego middleware”?

Twórz wbudowane middleware za pomocą Use/Run/Map oraz middleware oparte na klasach z InvokeAsync, aby budować komponenty wielokrotnego użytku. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć C# Academy?

Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.

Ile czasu zajmuje lekcja „Pisanie niestandardowego middleware”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?

Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Przegląd potoku ASP.NET Core
  2. Pisanie niestandardowego middleware
  3. Skracanie i rozgałęzianie potoku
  4. Kolejność middleware i wbudowane middleware
← Powrót do C# Academy