🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

Middleware w ASP.NET Core — pipeline żądania krok po kroku

Middleware w ASP.NET Core — pipeline żądania od Exception Handler do Endpoint

Zanim żądanie dotrze do Twojego endpointu, przechodzi przez łańcuch warstw: przekierowanie na HTTPS, CORS, uwierzytelnianie, autoryzację, logowanie, obsługę wyjątków. Ten łańcuch to pipeline, a każde jego ogniwo to middleware. Gdy klient dostaje 401 na endpoint bez zabezpieczeń albo błąd znika bez śladu w logach, przyczyna niemal zawsze leży w pipeline — i w jego kolejności.

Pipeline w jednym obrazku

Middleware ustawiasz jeden za drugim. Żądanie płynie w dół łańcucha, odpowiedź wraca w górę — jak zagnieżdżone koperty:

Żądanie →  [Logowanie] → [Auth] → [Routing] → [Endpoint]
Odpowiedź ← [Logowanie] ← [Auth] ← [Routing] ← [Endpoint]

Każdy komponent dostaje HttpContext i delegat next — wskaźnik na kolejne ogniwo. Komponent może zrobić coś przed wywołaniem next (na drodze żądania), oddać sterowanie dalej, a potem coś po (na drodze odpowiedzi):

app.Use(async (context, next) =>
{
    var start = Stopwatch.GetTimestamp();          // PRZED: na wejściu żądania
    await next(context);                            // oddaj sterowanie dalej w łańcuch
    var ms = Stopwatch.GetElapsedTime(start).TotalMilliseconds;
    Console.WriteLine($"{context.Request.Path} → {context.Response.StatusCode} ({ms:F0} ms)"); // PO
});

To gotowy middleware mierzący czas każdego żądania. Kluczowe jest await next(context) — bez niego łańcuch się urywa i żądanie nigdy nie dotrze do endpointu.

Middleware to nie „hook przed kontrolerem”. To warstwa opakowująca całą resztę pipeline — kod przed next biegnie na wejściu, kod po next na wyjściu. Jeden komponent, dwa momenty.

Short-circuit — zatrzymanie łańcucha

Middleware nie musi wołać next. Jeśli go pominie, przerywa pipeline i sam generuje odpowiedź — żądanie nie dotrze głębiej. Tak działa np. blokada po limicie zapytań:

app.Use(async (context, next) =>
{
    if (context.Request.Headers["X-Api-Key"] != oczekiwanyKlucz)
    {
        context.Response.StatusCode = 401;   // short-circuit
        await context.Response.WriteAsync("Brak klucza API");
        return;                              // nie wołamy next → endpoint się nie wykona
    }
    await next(context);
});

Middleware wbudowane — cały ekosystem za jedną linią

Większości middleware nie piszesz — włączasz gotowe metodami Use*:

app.UseExceptionHandler("/error"); // łapie wyjątki z całej reszty pipeline
app.UseHttpsRedirection();         // przekierowuje HTTP → HTTPS
app.UseCors();                     // reguły cross-origin
app.UseAuthentication();           // ustala, KIM jest użytkownik
app.UseAuthorization();            // sprawdza, CZY mu wolno
app.MapControllers();              // endpointy

Pułapka #1: kolejność to nie kosmetyka

Pipeline wykonuje middleware w kolejności rejestracji. Ta kolejność nie jest dowolna — to najczęstsza przyczyna „działało u kolegi, u mnie nie”.

Klasyczny przypadek: UseAuthentication musi poprzedzać UseAuthorization. Autentykacja ustala tożsamość (context.User), autoryzacja ją sprawdza. Odwróć je, a autoryzacja zobaczy pustego użytkownika i odrzuci każde żądanie 401/403 — nawet z poprawnym tokenem:

// ❌ ŹLE — autoryzacja sprawdza tożsamość, której jeszcze nie ustalono
app.UseAuthorization();
app.UseAuthentication();

// ✅ DOBRZE — najpierw kim jesteś, potem czy Ci wolno
app.UseAuthentication();
app.UseAuthorization();

Podobnie UseCors musi stać przed UseAuthentication/UseAuthorization i przed endpointami, a UseExceptionHandler — jako jeden z pierwszych, żeby objąć całą resztę łańcucha. Rekomendowana kolejność:

KolejnośćMiddlewareRola
1UseExceptionHandlerŁapie wyjątki z wszystkiego poniżej
2UseHttpsRedirectionWymusza HTTPS
3UseCorsReguły cross-origin
4UseAuthenticationKim jest użytkownik
5UseAuthorizationCzy mu wolno
6endpointy (Map*)Twoja logika

Middleware jako klasa

Dla logiki wielokrotnego użytku wyciągasz middleware do klasy z konwencjonalną sygnaturą (konstruktor przyjmuje RequestDelegate next, metoda InvokeAsync):

public class KorelacjaZadaniaMiddleware(RequestDelegate next)
{
    public async Task InvokeAsync(HttpContext context)
    {
        var id = context.Request.Headers["X-Correlation-Id"].FirstOrDefault()
                 ?? Guid.NewGuid().ToString();
        context.Response.Headers["X-Correlation-Id"] = id;   // PRZED
        await next(context);                                 // dalej
    }
}

// rejestracja:
app.UseMiddleware<KorelacjaZadaniaMiddleware>();

Taki komponent nadaje każdemu żądaniu identyfikator korelacji — bezcenny przy śledzeniu jednego żądania przez logi wielu serwisów. Do samego logowania sięgniesz raczej po gotowe rozwiązanie z Serilog / ILogger, ale mechanizm jest ten sam.

Pułapka #2: zapomniany await next

Brak await przy next (albo next(context) bez await) sprawia, że reszta pipeline biegnie równolegle do bieżącego middleware zamiast pod jego kontrolą — kod „po next” wykona się, zanim odpowiedź faktycznie powstanie. Objawy: pomiary czasu bez sensu, nagłówki ustawiane za późno, sporadyczne wyjątki „response already started”. Zawsze await next(context).

Pułapka #3: zapis do odpowiedzi po next

Gdy middleware poniżej zaczął już wysyłać odpowiedź, nagłówki są „zamrożone”. Próba ustawienia context.Response.Headers[...] po await next rzuci InvalidOperationException: response has already started. Nagłówki ustawiaj przed next; po next możesz już tylko obserwować.

Podsumowanie

Pipeline ASP.NET Core to łańcuch middleware, przez który przechodzi każde żądanie: kod przed await next biegnie na wejściu, kod po next — na drodze odpowiedzi, a pominięcie next przerywa łańcuch (short-circuit). Większość komponentów włączasz gotowymi Use*; własne piszesz inline albo jako klasę z InvokeAsync. Sercem tematu jest kolejność: UseAuthentication przed UseAuthorization, UseExceptionHandler na początku, UseCors przed autoryzacją. Ogromna część „dziwnych” 401 i znikających błędów to nie bug w logice endpointu, lecz źle ustawiony pipeline — a ten naprawiasz, przestawiając linie, nie przepisując kod.

Co dalej

Pipeline i endpointy potrzebują ustawień — connection stringów, kluczy API, adresów usług. Skąd je bezpiecznie czytać (i dlaczego nie z kodu) pokazuje Konfiguracja w ASP.NET Core. Gdy middleware zwraca 401 lub 403, wróć do znaczenia tych kodów w HTTP dla backendowca. A jak to wszystko poukładać w warstwach większej aplikacji — Clean Architecture w .NET 10 — REST API.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 Co dalej?

Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →