Dzisiaj dowiemy się jak kierować zapytanie HTTP, żeby dotarło tam gdzie chcemy, czyli żeby obsłużył je właściwy kontroler. W tym celu dowiemy się jak działa Routing i filtrowanie zapytań GET/POST oraz tworzenie parametryzowanych ścieżek.
To drugi post z serii “.NET Web”, w której nauczymy się budować aplikacje webowe. Równolegle będziemy pracować w dwóch różnych językach, w dwóch różnych frameworkach. Jeśli interesuje was tylko jeden, kliknijcie link poniżej by od razu przeskoczyć do odpowiedniego rozdziału.
Najpierw zapoznaj się z poprzednim postem, gdzie nauczyliśmy się tworzyć nowy projekt.
F# z Suave
Ponieważ F# jest językiem przede wszystkim funkcyjnym, to będziemy w sposób funkcyjny tworzyli naszą aplikację.
Pomyślmy, czym jest aplikacja webowa? W najprostszym ujęciu jest to funkcja, która dla pewnego zapytania zwraca jakąś odpowiedź. W Suave zostało to zawarte w typie WebPart
, który przekształca kontekst, czyli request i response.
type WebPart = HttpContext -> Async<HttpContext option>
Nasze funkcje zazwyczaj nie będą aż tak niskopoziomowe, żeby samemu tworzyć asynchroniczne procedury, Suave dostarcza nam odpowiednie abstrakcje.
Filtrowanie
Żeby zrozumieć jak działa filtrowanie w aplikacji Suave, rzućmy okiem jeszcze raz na sygnaturę funkcji typu WebPart
. Zwraca ona asynchronicznie opcję kontekstu HTTP. To oznacza, że może zwrócić wartość None
.
Ten mechanizm wykorzystamy w funkcji choose
, która przyjmuje listę funkcji typu WebPart
i zwraca funkcję takiego typu. W środku, choose
aplikuje dostarczony później kontekst do kolejnych funkcji z listy i zwraca wynik pierwszej, która zwróci coś innego niż None
.
Kolejny krok do zrozumienia jak to wszystko działa, to przeczytanie mojego blogposta o monadach. W naszym przypadku WebPart
będzie monadą. Dwie funkcje połączymy operatorem compose >=>
, w taki sposób, że jeśli wynikiem pierwszej funkcji jest Some(context)
, to wyciągamy ten kontekst i aplikujemy go do drugiej funkcji, inaczej zwracamy dalej None
.
Dobra, do rzeczy, jak się tego używa?
path: string -> WebPart
Funkcja path
dostaje string, ścieżkę w naszej aplikacji webowej (to co jest za domeną w URL), i zwraca WebPart
, który zwraca ten sam kontekst, który dostał, jeśli ścieżka się zgadza albo None
jeśli ścieżka się nie zgadza.
My następnie podłączymy się do tego w ten sposób
let homePage = OK "Home page"
let aboutPage = OK "About this site"
let app =
choose [
path "/" >=> homePage
path "/about" >=> aboutPage
]
Żeby to wszystko działało, to musimy jeszcze otworzyć moduł Suave.Filters
.
Następnie mieliśmy filtrować zapytania z różnymi czasownikami, do tego użyjemy gotowych funkcji GET
i POST
.
let app =
choose [
path "/" >=> GET >=> homePage
path "/submit" >=> POST >=> submitHandler
]
Jeśli spróbujemy wejść z przeglądarki na “/submit” to nic nie zobaczymy. Ale jeśli wyślemy zapytanie POST z formularza na stronie głównej, to to zapytanie zostanie obslużone, przez niezdefiowaną tu funkcję submitHandler
.
O tym jak dobrać się do parametrów GET i POST dowiesz się z Parametry GET i POST.
Zostało nam jeszcze omówienie tworzenia parametryzowanych ścieżek. Do tego celu posłużymy sie funkcją
pathScan: PrintfFormat<?> -> (? -> WebPart) -> WebPart
Ta funkcja korzysta ze świetnego mechanizmu wbudowanego w F#, który pozwala na sprawdzanie typów w formacie printf w trakcie kompilacji. Mamy więc dwa argumenty: format i funkcję, która przyjmuje takie argumenty, jakie określi nasz format.
pathScan "/users/%d/%s" (fun id attribute -> ...)
Nasza funkcja przyjmuje w tym konkretnym przypadku argument typu int
i argument typu string
. A zwrócić ma oczywiście WebPart
. Przykładem ścieżki, która zostanie zaakceptowana przez tą funkcję jest “/users/9/name”.
Jako bonus dorzucę jeszcze dwa słowa o funkcji pathRegex
.
pathRegex: string -> WebPart
Ta funkcja działa bardzo podobnie do path
tyle tylko, że ścieżka, którą przekażemy w parametrze jest pewnym wyrażeniem regularnym. Można tego użyć na wiele sposobów, ale najbardziej przydatny to taki:
pathRegex "(.*)\.(css|jpg|svg|png|gif|js)" >=> Files.browseHome
To wyrażenie mówi, że jeśli napotkasz ścieżkę kończącą się jednym z podanych rozszerzeń, to przeskanuj folder w którym uruchomiono aplikację i jeśli znajdziesz plik o podanej nazwie (w podfolderze zgodnie ze ścieżką w zapytaniu) to zwróć jego zawartość.
Co dalej?
Aby dowiedzieć się jakie inne filtry dostarcza nam Suave i jak one działają pod spodem, polecam poczytać kod źródłowy na GitHubie (trzeba trochę przescrollować).
Oraz zapraszam do przeczytania kolejnego posta: Parametry GET i POST.
C# z ASP.NET MVC
W ASP.NET MVC spotkamy się w wzorcem projektowym Model-View-Controller, o którym możecie się dowiedzieć z mojej prezentacji na ten temat. Tutaj będziemy mieli klasę dziedziczącą po klasie Controller
dostarczonej przez ASP.NET, której publiczne metody będą oznaczały kolejne akcje jakie będziemy mogli wywołać.
Zaczniemy od takiej klasy
using Microsoft.AspNetCore.Mvc;
public class PageController : Controller
{
public IActionResult Index() { return View(); }
public IActionResult About() { return View(); }
}
Teraz będziemy chcieli dodać nasze własne ścieżki routowania. Domyślnie w ASP.NET ścieżka wygląda tak: "/{controller}/{action}/"
, czyli w tym przypadku np. “/Page/Index”. My chcemy to zmienić.
W tym celu posłużymy się atrybutem Route
. Jeśli chcesz dowiedzieć się co to są atrybuty w .NET to przeczytaj mój blogpost o atrybutach.
[Route("/")]
public IActionResult Index() { return View(); }
[Route("/about")]
public IActionResult About() { return View(); }
Możemy też określić główną ścieżkę kontrollera i podścieżki dla jego akcji
[Route("Page")]
public class PageController : Controller
{
[Route("")]
public IActionResult Index() { return View(); }
[Route("About")]
public IActionResult About() { return View(); }
}
W takim układzie metoda Index
ma ścieżkę “/Page”, a metoda About
ścieżkę “/Page/About”.
Kiedy chcemy filtrować zapytania GET i POST, to zamiast atrybutu Route
posłużymy się atrybutami HttpGet
i HttpPost
, które również mogą przyjąć ścieżkę jako pierwszy parametr (można ich używać bez parametru obok atrybutu Route
).
[Route("/")]
[HttpGet]
public IActionResult Index() { return View(); }
[HttpGet("/about")]
public IActionResult About() { return View(); }
[HttpPost("/submit")]
public IActionResult Submit() { return View(); }
O tym jak wyciągać dane z parametrów GET i POST dowiesz się z Parametry GET i POST.
Jeśli chcemy sparametryzować naszą ścieżkę, to w parametr w ścieżce umieścimy w nawiasach klamrowych, a jego typ określimy w argumencie metody.
[HttpGet("/users/{id}/{attribute}")]
public IActionResult GetUserAttribute(int id, string attribute)
{ ... }
Co dalej?
Polecam zapoznać się głębiej z dokumentacją ASP.NET dotyczącą routowania:
- Routing in ASP.NET Core
- Routing to Controller Actions (więcej o atrybutach)
Oraz zapraszam do przeczytania kolejnego posta: Parametry GET i POST.