8

Say we have this web server to handle requests:

let webApp = scope {
    get  "/api/zoo/animals/"    (getAllAnimals())
    getf "/api/zoo/animals/%s"  getAnimalInfo
}

This syntax is described in docs and demoed in the example.

Now, what if I want to have a param in the url query, e.g. to filter the results?

http://localhost:8080/api/zoo/animals?type=mammals

This does not do anything:

getf "/api/zoo/animals?type=%s" getAnimalsByType
psfinaki
  • 1,814
  • 15
  • 29
  • 1
    One thing that is different between your question and your answer is the trailing slash in the URL. Giraffe (which Saturn builds on) [treats URLs with and without trailing slashes as different URLs](https://github.com/giraffe-fsharp/Giraffe/blob/master/DOCUMENTATION.md#routex), following the HTTP spec. So if the URL you want to allow is `.../animals?type=mammals`, then your scope needs to include `get ".../animals"` (no trailing slash). I know you mentioned that in your answer, but I want to point it out specifically, for anyone else who might find this question later. – rmunn Jul 14 '18 at 16:59

3 Answers3

6

See the example here:

https://github.com/giraffe-fsharp/Giraffe/blob/master/DOCUMENTATION.md#query-strings

It shows how to bind the data from a query string so you don't have to use GetQueryStringValue

In your case I think something like this might work.

[<CLIMutable>]
type AnimalType =
    { type : string }

let animal (next : HttpFunc) (ctx : HttpContext) =
    // Binds the query string to a Car object
    let animal = ctx.BindQueryString<AnimalType>()

    // Sends the object back to the client
    Successful.OK animal next ctx

let web_app  =

    router {    
        pipe_through (pipeline { set_header "x-pipeline-type" "Api" })
        post "/api/animal" animal
    }
sashang
  • 11,704
  • 6
  • 44
  • 58
5

A way to go is to use function GetQueryStringValue of the context. It returns Result, a struct DU.

So you stay with initial signature (just remove the trailing slash):

get "/api/zoo/animals" (getAnimals())

And you have

let getAnimals() : HttpHandler =
    fun _ ctx -> task { 
        let animalTypeFromQuery = ctx.GetQueryStringValue "type"
        let animalType =
            match animalTypeFromQuery with
            | Ok t    -> Some t
            | Error _ -> None
        ...
    }

I do not know if this is the official practice, I found this practice in some F# github repos.

psfinaki
  • 1,814
  • 15
  • 29
0

struggled with POST parameters in Gigraff POST

#light "off"
open System.Text.RegularExpressions
open System.IO
open System.Text
open Microsoft.AspNetCore.Http
open Giraffe
open FSharp.Data.Sql
open Giraffe.ViewEngine

let indexView = createPage ("test post") [
...
   let row i d = tr [] [
        td [] [ str (string(i)) ];
        td [] [ str d ]] in
   let db_ctx = mssql.GetDataContext() in
   let rows = [ for r in db_ctx.Dbo.Data do 
           (row r.Id r.Data) done ] in
...  
   form [_method "POST"; _action "/"; _enctype "multipart/form-data"] [
       button [] [str "Add row!" ]  
       input [ _type "text"; _name "Id"]  
       input [ _type "text"; _name "Data"]
   ...
       div[] rows

]

and

  let chk s = not (String.IsNullOrEmpty s)
  let indexHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) -> task { return! (
         if ctx.Request.ContentType.StartsWith( "multipart/form-data") then begin 
           let id = (ctx.Request.Form.Item("Id").ToString()) in
           let data = (ctx.Request.Form.Item("Data").ToString()) in
           if chk id && chk data 
              && not (Regex.IsMatch( id,"[^\\d]"))  then begin
              let db_ctx = mssql.GetDataContext() in
              db_ctx.Dbo.Data.Create( int(id),data) |> ignore;
              db_ctx.SubmitUpdates();
           end;
         end;
         htmlView indexView
      ) next ctx
   }

and

GET >=> route "/" >=> indexHandler;
POST >=> route "/" >=> indexHandler;