.NET Web II - Filtrowanie requestów

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.

  1. F# z Suave
  2. C# z ASP.NET Core

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:

Oraz zapraszam do przeczytania kolejnego posta: Parametry GET i POST.