Czym jest routing i jak działają controllery w ASP.NET Core

Pokażę ci od środka jak działa routing w ASP.NET Core MVC, jak budujesz controllery, czym są akcje i jak przekazywać parametry przez URL.

JAJarosław Armatys 8 min czytania
Wersja wideo tego wpisu — obejrzyj na YouTube

Cześć, z tej strony Jarmatys. To druga lekcja kursu o podstawach ASP.NET Core MVC. Tutaj bierzemy na warsztat dwa filary, bez których żadna aplikacja webowa w .NET nie ruszy z miejsca: routing i controllery. Zobaczysz jak zbudowane są pliki Program.cs i Startup.cs, jak działa domyślny pattern URL-a, czym różni się akcja od metody i jak zmienić routing przez atrybut [Route]. Zrzucę z tego mgłę tak, żebyś potem swobodnie poruszał się po każdym projekcie .NET.

Program.cs i Startup.cs — co tu się w ogóle dzieje

Gdy odpalasz nowy projekt ASP.NET Core, widzisz dwa pliki, do których rzadko będziesz zaglądać, ale warto wiedzieć, co siedzi w środku.

Program.cs jest bardzo krótki. Jedyne, co tam się dzieje, to stworzenie hosta i uruchomienie aplikacji. Metoda Main buduje hosta, który podczas startu serwera pociąga konfigurację z klasy Startup. Tyle. Na tym etapie kursu to wszystko, co musisz wiedzieć.

Startup.cs jest ciekawszy i to z nim będziesz gadać najczęściej. Zobacz jego trzy sekcje:

  • Konstruktor — przez DI wstrzykiwana jest tu IConfiguration, która trafia do publicznej właściwości. Będziesz z tego korzystać w różnych miejscach (appsettings, zmienne środowiskowe itp.).
  • ConfigureServices — wpada tu kolekcja serwisów. Domyślnie zobaczysz coś w stylu services.AddControllersWithViews(). To ustawienie mówi .NET-owi: "obsługuj mi controllery (folder Controllers) i widoki (folder Views)".
  • Configure — tu kleisz pipeline HTTP. Sprawdzasz IsDevelopment(), włączasz UseDeveloperExceptionPage, redirect na HTTPS, UseStaticFiles, UseRouting, UseAuthorization i na końcu — to dla nas dzisiaj najważniejsze — UseEndpoints.

Na produkcji nie chcesz, żeby użytkownik dostał brzydką stronę z wyjątkiem z twojej aplikacji — dlatego exception page pokazujesz tylko w developmencie.

Fragment Configure, który nas interesuje, wygląda mniej więcej tak:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

I to jest właśnie routing konwencjonalny.

Jak czytać domyślny pattern URL-a

Wzorzec {controller=Home}/{action=Index}/{id?} mówi trzy rzeczy:

  1. Pierwszy segment URL to nazwa controllera. Domyślnie Home.
  2. Drugi segment to akcja (czyli metoda w tym controllerze). Domyślnie Index.
  3. Trzeci segment to opcjonalny parametr id (znak ? oznacza opcjonalny).

Dzięki temu, jak wchodzisz na https://localhost/, .NET wie, że ma pociągnąć HomeController.Index(). Jak usuniesz domyślne wartości z patternu i zostawisz sam {controller}/{action}/{id?}, to na gołym URL-u dostaniesz 404 — bo .NET nie ma pojęcia, do kogo cię kierować. Musiałbyś wpisać /Home/Index ręcznie. Ktoś ci kiedyś powie "link do mojej strony to moja-domena.pl" i ma się to działać — więc bez domyślnych wartości ani rusz.

Routing to nic innego jak mapa. Mówisz .NET-owi: "jak ktoś wejdzie na taki URL, zawołaj taką akcję". A akcja to zwykła publiczna metoda w klasie controllera.

Controller, akcja i zwracanie widoku

Controller to po prostu klasa, która dziedziczy po Controller (albo ControllerBase) i ma w środku metody — te metody nazywamy akcjami. Każda akcja zwraca jakiś typ, który .NET zamienia na odpowiedź HTTP: widok, redirect, JSON, status itd.

Zobacz prosty przykład — HomeController z akcją Product, która przyjmuje parametr name:

public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    public IActionResult Product(string name)
    {
        return View();
    }
}

Jak teraz wejdę na /Home/Product, .NET złapie mnie w tę akcję. Jeśli nie ma widoku Product.cshtml w Views/Home/, dostaniesz exception z informacją, że widok nie został znaleziony — i od razu masz podpowiedź, w których folderach szukał. Visual Studio daje ci wtedy skrót: prawy przycisk na nazwie akcji → Add View. Postawi ci plik Product.cshtml we właściwym folderze, z referencją do _Layout i pustym <h1>, gotowy do wypełnienia.

Żeby przekazać parametr, możesz pójść najprostszą drogą — query string: /Home/Product?name=coś. Po postawieniu breakpointa w akcji zobaczysz, że name ma wartość "coś". Tyle że takie URL-e są brzydkie. Ładniej wyglądałoby /Home/Product/coś, gdzie coś siedzi jako segment ścieżki, nie query. Żeby tak zrobić, musisz zmienić routing.

Attribute routing — jak przejąć pełną kontrolę

Domyślny pattern jest wygodny, ale czasem chcesz mieć swój, ładny URL — np. /polityka-prywatnosci albo /product/{name} bez części z Home. Do tego służy attribute routing: dekorujesz akcję atrybutem [Route("...")] i on nadpisuje konwencjonalny routing dla tej metody.

public class HomeController : Controller
{
    [Route("product/{name}")]
    public IActionResult Product(string name)
    {
        return View();
    }

    [Route("polityka-prywatnosci")]
    public IActionResult Privacy()
    {
        return View();
    }

    public IActionResult GoToPrivacy()
    {
        return RedirectToAction("Privacy");
    }
}

Co tu się dzieje:

  • [Route("product/{name}")] mówi: "adres to /product/{name} licząc od domeny, nie od nazwy controllera". Wchodzisz na /product/jakis-produkt i trafiasz w akcję, a name = "jakis-produkt".
  • [Route("polityka-prywatnosci")] to URL zupełnie bez udziału nazwy controllera/akcji. Super rzecz do landing page'y i stron statycznych.
  • RedirectToAction("Privacy") przekierowuje w obrębie tego samego controllera. Jak chcesz przekierować do akcji w innym controllerze, podajesz drugi parametr: RedirectToAction("Index", "Product").

Attribute routing bierze pierwszeństwo. W momencie gdy przykleisz [Route(...)] do akcji, stary /Home/Product przestaje działać — .NET kieruje się już tylko tym, co napisałeś w atrybucie.

Pamiętaj, że RedirectToAction masz za darmo, bo twój controller dziedziczy po Controller. W podpowiedziach IntelliSense zobaczysz też Redirect (do dowolnego URL-a), RedirectToRoute, Json, Content, File itd. Akcja nie musi zwracać widoku — może zwrócić cokolwiek, byle było typu IActionResult.

Nowy controller i domyślna akcja Index

Dodajmy drugi controller, żeby zobaczyć, jak to się układa. Prawy przycisk na folderze Controllers → Add → Controller → Empty. Visual Studio wygeneruje ci boilerplate:

public class ProductController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}

Dwie rzeczy, które warto zapamiętać:

  • Sufiks Controller w nazwie klasy jest obowiązkowy. .NET patrzy na typ i tnie ten sufiks, żeby wyciągnąć "segment URL". ProductController → w URL-u piszesz product.
  • Akcja Index jest domyślna. Jeśli wejdziesz na /product, trafisz do ProductController.Index(). Jeśli dopiszesz inną metodę, np. Details, to musisz ją wywołać jawnie jako /product/details.

Dokładasz widok analogicznie jak wcześniej — prawy przycisk na akcji → Add View — i Views/Product/Index.cshtml jest gotowy. Wpisujesz /product w przeglądarce, masz swój nowy controller w akcji.

Co dalej?

To był fundament, na którym zbudujemy wszystko, co dalej. W następnej lekcji biorę się za modele i widoki — pokażę ci, jak przekazać dane z akcji do widoku, jak silnie typować View(model) i jak renderować listy i formularze. Po tym routing i controllery wpadną ci już zupełnie naturalnie.

Kod do tej lekcji masz na GitHubie: asp-net-core-podstawy / Lekcja-2. Jakbyś miał pytania albo coś zgrzytało — walnij komentarz pod filmem, odpiszę. Trzymaj się i do zobaczenia w następnym wpisie.

Tagi:#course-aspnet#dotnet#routing#mvc#controllery

Masz pytanie do tego wpisu?

Napisz do mnie — chętnie porozmawiam o szczegółach technicznych, albo o tym jak zastosować to u Ciebie w projekcie.