Minimal API i routing w ASP.NET Core — dopasowanie ścieżek

Twoje pierwsze API działa, ale pod spodem odpowiada na pytanie, którego jeszcze nie zadałeś: skąd ASP.NET Core wie, że żądanie GET /api/produkty/5 ma trafić akurat do tego endpointu i że 5 to id? To jest routing — warstwa dopasowująca ścieżkę i metodę żądania do konkretnego fragmentu Twojego kodu. Gdy dostajesz 404 na endpoint, który „przecież istnieje”, prawie zawsze zawinił routing, nie logika.
Szablon trasy
Każdy Map* przyjmuje szablon ścieżki. Segmenty w klamrach to parametry trasy:
app.MapGet("/api/produkty", ...); // stała ścieżka
app.MapGet("/api/produkty/{id}", ...); // {id} — parametr
app.MapGet("/api/kategorie/{kat}/produkty", ...); // parametr w środkuWartość parametru trafia do argumentu metody o tej samej nazwie:
app.MapGet("/api/produkty/{id}", (string id) =>
$"Pytasz o produkt {id}");Domyślnie parametr trasy jest tekstem. /api/produkty/5 da id = "5" (string), a /api/produkty/abc też się dopasuje — co zwykle nie jest tym, czego chcesz.
Ograniczenia tras — filtr już na poziomie dopasowania
Do parametru dopisujesz ograniczenie (route constraint), które zawęża, co się dopasuje:
app.MapGet("/api/produkty/{id:int}", (int id) => ...); // tylko liczby całkowite
app.MapGet("/api/faktury/{numer:guid}", (Guid numer) => ...); // tylko GUID
app.MapGet("/api/strony/{n:int:min(1)}", (int n) => ...); // int >= 1| Ograniczenie | Dopasuje | Odrzuci |
|---|---|---|
{id:int} | 5, -3 | abc, 5.0 |
{cena:decimal} | 9.99 | dużo |
{aktywny:bool} | true | 1 |
{numer:guid} | a1b2... | 123 |
{nazwa:alpha} | laptop | laptop2 |
{n:int:min(1)} | 1, 50 | 0, -5 |
Z {id:int} żądanie /api/produkty/abc nie pasuje do trasy i dostaje 404 — a parametr metody może być od razu typu int, bez ręcznego parsowania. To dwie korzyści naraz: silne typowanie i odrzucenie śmieci, zanim dotknie Twojego kodu.
Ograniczenie trasy to nie walidacja biznesowa — to filtr dopasowania.
{id:int}decyduje, *czy* żądanie trafi do endpointu, nie *czy* dane mają sens. Regułę „cena > 0″ nadal sprawdzasz w kodzie.
Skąd binding bierze każdy parametr
ASP.NET Core wypełnia parametry endpointu z różnych źródeł według jasnych reguł:
| Źródło | Kiedy używane | Wymuszenie |
|---|---|---|
| Trasa | nazwa parametru = nazwa w {...} | [FromRoute] |
| Query string | typ prosty spoza trasy | [FromQuery] |
| Body (JSON) | typ złożony | [FromBody] |
| Nagłówek | jawnie | [FromHeader] |
| DI | typ zarejestrowany w kontenerze | [FromServices] |
Zwykle nie musisz nic wymuszać — konwencja wystarcza:
app.MapGet("/api/produkty/{id:int}", (
int id, // z trasy (nazwa pasuje)
bool szczegoly, // z query: ?szczegoly=true
MagazynProduktow magazyn) => // z DI
...);Atrybutów [FromQuery] itp. używasz, gdy nazwa albo źródło jest niejednoznaczne — np. gdy chcesz parametr id wziąć z query, mimo że w trasie też jest {id}.
Grupowanie tras — MapGroup
Powtarzanie prefiksu /api/produkty przy każdym endpoincie się rozłazi. MapGroup wyciąga wspólny prefiks i pozwala dołożyć wspólną konfigurację (autoryzację, filtry) w jednym miejscu:
var produkty = app.MapGroup("/api/produkty");
produkty.MapGet("/", (MagazynProduktow m) => Results.Ok(m.Wszystkie()));
produkty.MapGet("/{id:int}", (int id, MagazynProduktow m) =>
m.Znajdz(id) is { } p ? Results.Ok(p) : Results.NotFound());
produkty.MapPost("/", (NowyProdukt d, MagazynProduktow m) => ...);Cała grupa dziedziczy prefiks, a produkty.RequireAuthorization() zabezpiecza wszystkie endpointy grupy naraz.
TypedResults — wynik, który da się testować
Results.Ok(...) zwraca IResult — nieprzezroczysty typ, po którym test nie pozna, jaki to był status. TypedResults zwraca konkretny typ, który możesz asertować w teście jednostkowym bez stawiania serwera:
app.MapGet("/api/produkty/{id:int}",
(int id, MagazynProduktow m) =>
m.Znajdz(id) is { } p
? TypedResults.Ok(p) // Ok<Produkt>
: TypedResults.NotFound()); // NotFoundSygnatura endpointu staje się Results<Ok<Produkt>, NotFound> — samodokumentująca i testowalna. To ten sam wzorzec, który testy integracyjne ASP.NET Core wykorzystują do sprawdzania odpowiedzi.
Pułapki routingu
Pułapka #1 — brak ograniczenia daje zły status. Bez {id:int} żądanie /api/produkty/abc dopasuje się do endpointu, a próba użycia id jako liczby wyrzuci wyjątek → 500. Z {id:int} dostajesz czyste 404 — bo trasa się nie dopasowała. Zawsze ograniczaj parametry, które mają być liczbami.
Pułapka #2 — niejednoznaczne trasy. Dwa endpointy pasujące do tej samej ścieżki (/api/produkty/{id} i /api/produkty/{nazwa}) to AmbiguousMatchException w czasie żądania — nie przy starcie. Różnicuj ograniczeniami ({id:int} vs {nazwa:alpha}), żeby dopasowanie było jednoznaczne.
Pułapka #3 — kolejność vs specyficzność. Routing w ASP.NET Core dopasowuje po specyficzności, nie po kolejności rejestracji. Trasa /api/produkty/nowe (literał) wygra z /api/produkty/{id} (parametr), niezależnie od tego, którą zarejestrowałeś pierwszą — literał jest bardziej specyficzny. Nie polegaj na kolejności Map*.
Pułapka #4 — złożony typ w GET. Domyślnie typ złożony bindowany jest z body. GET z body to antywzorzec (i wiele klientów go nie wyśle) — filtry przekazuj jako parametry query, nie jako obiekt w body.
Podsumowanie
Routing to warstwa, która z metody i ścieżki żądania wybiera Twój endpoint i wypełnia jego parametry. Kluczowe narzędzia: szablony tras z parametrami {...}, ograniczenia ({id:int}, {numer:guid}) odrzucające śmieci już na etapie dopasowania i dające silne typy, MapGroup eliminujący powtórzony prefiks i wspólną konfigurację, oraz TypedResults czyniący odpowiedzi testowalnymi. Najczęstsze 404 i 500 w młodym API to nie błędy logiki, lecz brakujące ograniczenie trasy albo niejednoznaczne dopasowanie — routing rozwiązany porządnie usuwa całą tę klasę problemów.
Co dalej
Wiesz już, jak żądanie trafia do endpointu. Ale zanim tam dotrze, przechodzi przez łańcuch warstw — uwierzytelnianie, logowanie, obsługę błędów. Pokazuje to Middleware w ASP.NET Core. Warto też wrócić do protokołu HTTP, jeśli semantyka metod i statusów jeszcze nie leży, oraz zajrzeć do wstrzykiwania zależności, na którym stoi bindowanie serwisów.
🚀 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ę →