Falco exposes a uniform API to obtain typed values from IFormCollection
, IQueryCollection
, RouteValueDictionary
, IHeaderCollection
, and IRequestCookieCollection
. This is achieved by means of the RequestData
type and it's derivative FormData
. These abstractions are intended to make it easier to work with the url-encoded key/value collections.
Take note of the similarities when interacting with the different sources of request data.
RequestData
is supported by a recursive discriminated union called RequestValue
which represents a parsed key/value collection.
The RequestValue
parsing process provides some simple, yet powerful, syntax to submit objects and collections over-the-wire, to facilitate complex form and query submissions.
Keys using dot notation are interpreted as complex (i.e., nested values) objects.
Consider the following POST request:
POST /my-form HTTP/1.1
Host: foo.example
Content-Type: application/x-www-form-urlencoded
Content-Length: 46
user.name=john%20doe&[email protected]
This will be intepreted as the following RequestValue
:
RObject [
"user", RObject [
"name", RString "john doe"
"email", RString "[email protected]"
]
]
See form binding for details on interacting with form data.
Keys using square bracket notation are interpreted as lists, which can include both primitives and complex objects. Both indexed and non-indexed variants are supported.
Consider the following request:
GET /my-search?name=john&season[0]=summer&season[1]=winter&hobbies[]=hiking HTTP/1.1
Host: foo.example
Content-Type: application/x-www-form-urlencoded
Content-Length: 68
This will be interpreted as the following RequestValue
:
RObject [
"name", RString "john"
"season", RList [ RString "summer"; RString "winter" ]
"hobbies", RList [ RString "hking" ]
]
See query binding for details on interacting with form data.
RequestData
provides the ability to safely read primitive types from flat and nested key/value collections.
let requestData : RequestData = // From: Route | Query | Form
// Retrieve primitive options
let str : string option = requestData.TryGetString "name"
let flt : float option = requestData.TryGetFloat "temperature"
// Retrieve primitive, or default
let str : string = requestData.GetString "name"
let strOrDefault : string = requestData.GetString ("name", "John Doe")
let flt : float = requestData.GetFloat "temperature"
// Retrieve primitive list
let strList : string list = requestData.GetStringList "hobbies"
let grades : int list = requestData.GetInt32List "grades"
// Dynamic access, useful for nested/complex collections
// Equivalent to:
// requestData.Get("user").Get("email_address").AsString()
let userEmail = requestData?user?email_address.AsString()
Provides access to the values found in the RouteValueDictionary
.
open Falco
// Assuming a route pattern of /{Name}
let manualRouteHandler : HttpHandler = fun ctx ->
let r = Request.getRoute ctx
let name = r.GetString "Name"
// Or, let name = r?Name.AsString()
// Or, let name = r.TryGetString "Name" |> Option.defaultValue ""
Response.ofPlainText name ctx
let mapRouteHandler : HttpHandler =
Request.mapRoute (fun r ->
r.GetString "Name")
Response.ofPlainText
Provides access to the values found in the IQueryCollection
, as well as the RouteValueDictionary
. In the case of matching keys, the values in the IQueryCollection
take precedence.
open Falco
type Person =
{ FirstName : string
LastName : string }
let form : HttpHandler =
Response.ofHtmlCsrf view
let manualQueryHandler : HttpHandler = fun ctx ->
let q = Request.getQuery ctx
let person =
{ FirstName = q.GetString ("FirstName", "John") // Get value or return default value
LastName = q.GetString ("LastName", "Doe") }
Response.ofJson person ctx
let mapQueryHandler : HttpHandler =
Request.mapQuery (fun q ->
let first = q.GetString ("FirstName", "John") // Get value or return default value
let last = q.GetString ("LastName", "Doe")
{ FirstName = first; LastName = last })
Response.ofJson
Provides access to the values found in he IFormCollection
, as well as the RouteValueDictionary
. In the case of matching keys, the values in the IFormCollection
take precedence.
The FormData
inherits from RequestData
type also exposes the IFormFilesCollection
via the _.Files
member and _.TryGetFile(name : string)
method.
type Person =
{ FirstName : string
LastName : string }
let manualFormHandler : HttpHandler = fun ctx ->
task {
let! f : FormData = Request.getForm ctx
let person =
{ FirstName = f.GetString ("FirstName", "John") // Get value or return default value
LastName = f.GetString ("LastName", "Doe") }
return! Response.ofJson person ctx
}
let mapFormHandler : HttpHandler =
Request.mapForm (fun f ->
let first = f.GetString ("FirstName", "John") // Get value or return default value
let last = f.GetString ("LastName", "Doe")
{ FirstName = first; LastName = last })
Response.ofJson
let mapFormSecureHandler : HttpHandler =
Request.mapFormSecure (fun f -> // `Request.mapFormSecure` will automatically validate CSRF token for you.
let first = f.GetString ("FirstName", "John") // Get value or return default value
let last = f.GetString ("LastName", "Doe")
{ FirstName = first; LastName = last })
Response.ofJson
(Response.withStatusCode 400 >> Response.ofEmpty)
Microsoft defines large upload as anything > 64KB, which well... is most uploads. Anything beyond this size and they recommend streaming the multipart data to avoid excess memory consumption.
To make this process a lot easier Falco's form handlers will attempt to stream multipart form-data, or return an error message indicating the likely problem.
let imageUploadHandler : HttpHandler =
let formBinder (f : FormData) : IFormFile option =
f.TryGetFormFile "profile_image"
let uploadImage (profileImage : IFormFile option) : HttpHandler =
// Process the uploaded file ...
// Safely buffer the multipart form submission
Request.mapForm formBinder uploadImage
let secureImageUploadHandler : HttpHandler =
let formBinder (f : FormData) : IFormFile option =
f.TryGetFormFile "profile_image"
let uploadImage (profileImage : IFormFile option) : HttpHandler =
// Process the uploaded file ...
let handleInvalidCsrf : HttpHandler =
Response.withStatusCode 400 >> Response.ofEmpty
// Safely buffer the multipart form submission
Request.mapFormSecure formBinder uploadImage handleInvalidCsrf
These handlers use the .NET built-in System.Text.Json.JsonSerializer
.
type Person =
{ FirstName : string
LastName : string }
let jsonHandler : HttpHandler =
Response.ofJson {
FirstName = "John"
LastName = "Doe" }
let mapJsonHandler : HttpHandler =
let handleOk person : HttpHandler =
let message = sprintf "hello %s %s" person.First person.Last
Response.ofPlainText message
Request.mapJson handleOk
let mapJsonOptionsHandler : HttpHandler =
let options = JsonSerializerOptions()
options.DefaultIgnoreCondition <- JsonIgnoreCondition.WhenWritingNull
let handleOk person : HttpHandler =
let message = sprintf "hello %s %s" person.First person.Last
Response.ofPlainText message
Request.mapJsonOption options handleOk