.NET Web III - Parametry GET i POST

W komunikacji HTTP występują różne czasowniki. Najbardziej popularne to GET i POST. Dzisiaj dowiemy się jak odczytywać parametry tych zapytań w naszej akcji.

To trzeci 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ę filtrować zapytania w zależności od ścieżki i czasownika HTTP.

Ogólna teoria

Zanim zobaczymy jak uzyskać te parametry zapytań, powiemy sobie szybko o tym czym te zapytania się różnią.

Przede wszystkim, twórcy standardu HTTP założyli pewną logikę. Zapytanie z czasownikiem GET ma za zadanie odczytać pewną informację i zwrócić ją użytkownikowi. To jest podstawowa czynność jaką się wykonuje i to właśnie za jej pomocą otwierasz strony w przeglądarce. Zapytania typu POST mają za to w założeniu modyfikację stanu strony internetowej przez dodanie pewnej informacji. Są też inne czasowniki takie jak PUT (aktualizacja informacji) i DELETE (usunięcie informacji).

Często jednak nie używa się tych czasowników ściśle z ich przeznaczeniem. Jednak pewna reguła pozostaje: jeśli chcemy coś odczytać bez wprowadzania zmian, to powinniśmy użyć czasownika GET, a w przeciwnym wypadku czasownika POST.

Przeglądarki internetowe domyślnie wspierają tylko te dwa czasowniki. Odwiedzając stronę zawsze wywołamy GET. Żeby wywołać POST możemy użyć formy w HTMLu:

<form method="POST">

Parametry zapytań

Jak wyglądają parametry tych zapytań? W przypadku zapytania GET jest prosty schemat. Jeśli czytając ścieżkę zapytania napotkamy znak ? to od niego zaczyna się lista parametrów w postaci key=value oddzielonych od siebie znakiem ampersand &. Np:

GET /search.php?name=Bob&age=24

Jak widać te parametry są częścią URI, która wyświetla nam się na pasku przeglądarki. Nie możemy więc przesyłać tą drogą hasła. Wtedy użyjemy zapytania POST, który swoje parametry ma w ciele zapytania HTTP, niewidocznym dla zwykłego użytkownika.

Tu już jest trochę ciężej, bo te parametry mogą mieć różną formę

Content-Type: application/x-www-form-urlencoded
    name=Bob&age=24

Content-Type: multipart/form-data; boundary=--------43365972754266
    --------43365972754266
    Content-Disposition: form-data; name="name"

    Bob
    --------43365972754266
    Content-Disposition: form-data; name="age"

    24
    --------43365972754266--

Content-Type: application/json
    { "name" : "Bob", "age" : 24 }

W zależności od tego jaki będzie typ zawartości naszego zapytania, parametry przyjmą różną formę. Przeglądarka przez wysłanie formy może wysłać albo application/x-www-form-urlencoded albo multipart/form-data. Natomiast kiedy sami piszemy kod tworzący takie zapytanie przy użyciu JavaScriptu to łatwiej będzie nam posłuzyć się notacją JSON.

Skoro już wiemy jak to mniej więcej wygląda to czas zabrać się do kodu.

F# z Suave

W Suave zarówno do przechowywania parametrów GET jak i POST został użyty słownik (w postaci listy par). Nasz obiekt HttpRequest ma w sobie metody do uzyskania tych parametrów. Zwracają one Choice, czyli dwie możliwości: Choice1Of2 od wartości pod tym kluczem, albo Choice2Of2 od komunikatu o błędzie.

Aby dostać parametr GET użyjemy metody queryParam na obiekcie HttpRequest, który uzyskamy korzystając z funkcji request, która przyjmuje funkcję HttpRequest->WebPart i zwraca WebPart.

let action =
    GET >=> request (fun req -> 
            let name = req.queryParam "name"
            match name with
            | Choice1Of2 name_ -> OK (sprintf "Hello %s" name_)
            | Choice2Of2 _ -> OK "Provide a name"
        )

W przypadku zapytania POST możemy stworzyć sobie pomocniczą funkcję

let getPostParam request key =
    match request.header "content-type" with
    | "application/x-www-form-urlencoded" -> request.formData key
    | "multipart/form-data" | _ -> request.fieldData key

Dostaniemy z niej Choice i możemy dalej go użyć jak wyżej.

W przypadku JSONa musielibyśmy utworzyć typ opisujący nasze argumenty, a następnie możemy użyć modułu Suave.Json

type MyJsonType = { name : string; age : int }
let getMyJsonValue reuqest =
    Json.fromJson<MyJsonType> request.rawForm

Mi raczej niewygodnie korzysta się z typu Choice więc moje funkcje wyciągające parametry używają takiej funkcji

let optionOfChoice c =
    match c with
    | Choice1Of2 x -> Some x
    | _ -> None

I mogę wtedy korzystać z takich funkcji jak Option.map lub Option.get, aby dalej manipulować tą wartością.

Jeśli potrzebujemy jakiegoś parametru obowiązkowo, to kiedy go nie dotaniemy możemy zwrócić WebPart o wartości never. Wtedy wartością zwróconą (HttpContext option) będzie None.

let mustHaveIdParamFilter =
    request (fun req -> 
        match req.queryParam "id" with
        | Choice1Of2 _ -> fun ctx -> async { return (Some ctx) }
        | _ -> never
     )

Co dalej?

Spróbuj swoich sił wykonując zadanie albo przeczytaj następny artykuł: za tydzień.

C# z ASP.NET MVC

Podobnie jak tydzień temu odczytywaliśmy parametry URL przez argumenty naszej metody, tak samo będzie z parametrami GET i POST.

W przypadku paramterów GET będziemy mieli doczynienia z typami prostymi: int, string, bool i jeśli jakiś parametr będzie opcjonalny to użyjemy typu, który może być nullem.

public IActionResult MyAction(string name, int age, int? type)
{
    ...
}

I tyle. Nie musimy robić nic więcej, bo silnik ASP.NET sam się domyśli jak te parametry nam odpowienio wstrzyknąć.

W przypadku parametrów POST, czyli tych znajdujących się w ciele zapytania, musimy dodać odpowiedni atrybut do argumentu

private class MyParams
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public IActionResult MyAction([FromBody] MyParams params)
{
    ...
}

Nie musimy się martwić o typ parametrów (json/form-data/urlencoded), bo ASP.NET podejmuje odpowiednią decyzję za nas.

Jest również atrybut [FromForm], który pozwala na wyciągnięcie konkretnej wartości z formularza

public IActionResult MyAction([FromForm] string name, [FromForm] int age)
{
    ...
}

Co dalej?

Zachęcam do dalszej lektury o tym jak działa wiązanie parametrów w ASP.NET Core

Spróbuj swoich sił wykonując zadanie albo przeczytaj następny artykuł: za tydzień.

Zadanie

Utwórz prostą aplikację webową, która będzie przechowywać w pliku bazę danych w postaci

pokój|mebel
pokój|mebel
...

Modyfikacja i odczyt pliku będzie odbywać się przez aplikację webową. Będziemy chcieli mieć następujące akcje

  • GET /room - zwraca listę pokoji
  • GET /room/{pokój} - zwraca listę mebli w pokoju
  • GET /room?num=n - zwraca n-ty z kolei pokój
  • GET /room/{pokój}?num=n - zwraca n-ty z kolei mebel w pokoju
  • POST /room z danymi name=pokój - tworzy nowy pokój
  • POST /room/{pokój} z danymi name=mebel - tworzy nowy mebel w pokoju

Do testowania programu możesz użyć aplikacji Postman.