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

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
nextbiegnie na wejściu, kod ponextna 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(); // endpointyPuł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ść | Middleware | Rola |
|---|---|---|
| 1 | UseExceptionHandler | Łapie wyjątki z wszystkiego poniżej |
| 2 | UseHttpsRedirection | Wymusza HTTPS |
| 3 | UseCors | Reguły cross-origin |
| 4 | UseAuthentication | Kim jest użytkownik |
| 5 | UseAuthorization | Czy mu wolno |
| 6 | endpointy (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.
🚀 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.
- 🗺️ Pobierz darmową roadmapę Junior .NET Developer — 12 kroków od podstaw C# do pierwszej pracy: dev-hobby.pl
- 🎬 Subskrybuj kanał YouTube — nowe filmy co tydzień.
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ę →
2 comments