Skip to content

Latest commit

 

History

History
3938 lines (2955 loc) · 142 KB

DOCUMENTATION.md

File metadata and controls

3938 lines (2955 loc) · 142 KB

Giraffe Documentation

An in depth functional reference to all of Giraffe's default features.

Table of contents

Fundamentals

HttpHandler

The main building block in Giraffe is a so called HttpHandler:

type HttpFuncResult = Task<HttpContext option>
type HttpFunc = HttpContext -> HttpFuncResult
type HttpHandler = HttpFunc -> HttpContext -> HttpFuncResult

A HttpHandler is a simple function which takes two curried arguments, a HttpFunc and a HttpContext, and returns a HttpContext (wrapped in an option and Task workflow) when finished.

On a high level a HttpHandler function receives and returns an ASP.NET Core HttpContext object, which means every HttpHandler function has full control of the incoming HttpRequest and the resulting HttpResponse.

Each HttpHandler can process an incoming HttpRequest before passing it further down the Giraffe pipeline by invoking the next HttpFunc or short circuit the execution by returning an option of Some HttpContext.

If a HttpHandler doesn't want to process an incoming HttpRequest at all, then it can return None instead. In this case a surrounding HttpHandler might pick up the incoming HttpRequest or the Giraffe middleware will defer the request to the next RequestDelegate from the ASP.NET Core pipeline.

The easiest way to get your head around a Giraffe HttpHandler is to think of it as a functional equivalent to the ASP.NET Core middleware. Each handler has the full HttpContext at its disposal and can decide whether it wants to return Some HttpContext, None or pass it on to the "next" HttpFunc.

Giraffe pipeline vs. ASP.NET Core pipeline

The Giraffe pipeline is a (sort of) functional equivalent of the (object oriented) ASP.NET Core pipeline. The ASP.NET Core pipeline is defined by nested middleware and the Giraffe pipeline is defined by HttpHandler functions. The Giraffe pipeline is plugged into the wider ASP.NET Core pipeline through the GiraffeMiddleware itself and therefore an addition to it rather than a replacement.

If the Giraffe pipeline didn't process an incoming HttpRequest (because the final result was None and not Some HttpContext) then other ASP.NET Core middleware can still process the request (e.g. static file middleware or another web framework plugged in after Giraffe).

This architecture allows F# developers to build rich web applications through a functional composition of HttpHandler functions while at the same time benefiting from the wider ASP.NET Core eco system by making use of already existing ASP.NET Core middleware.

Combinators

compose (>=>)

The compose combinator combines two HttpHandler functions into one.

It is the main combinator in Giraffe which allows composing many smaller HttpHandler functions into a bigger web application:

let app = compose (route "/") (Successful.OK "Hello World")

A slightly more convenient and more commonly used form of compose is the fish operator >=>:

let app = route "/" >=> Successful.OK "Hello World"

There is no limit to how many HttpHandler functions can be chained with compose or the fish operator:

let app =
    route "/"
    >=> setHttpHeader "X-Foo" "Bar"
    >=> setStatusCode 200
    >=> setBodyFromString "Hello World"

If you would like to learn more about the >=> (fish) operator then please check out Scott Wlaschin's blog post on Railway oriented programming.

choose

The choose combinator function iterates through a list of HttpHandler functions and invokes each individual handler until the first HttpHandler returns a positive result:

let app =
    choose [
        route "/foo" >=> text "Foo"
        route "/bar" >=> text "Bar"
    ]

Warbler

If your route is not returning a static response, then you should wrap your function with a warbler:

// ('a -> 'a -> 'b) -> 'a -> 'b
let warbler f a = f a a

Functions in F# are eagerly evaluated and a normal route will only be evaluated the first time. A warbler will ensure that a function will get evaluated every time the route is hit:

// unit -> string
let time() = System.DateTime.Now.ToString()

let webApp =
    choose [
        route "/normal"  >=> text (time())
        route "/warbler" >=> warbler (fun _ -> text (time()))
    ]

Tasks

Another important aspect of Giraffe is that it natively works with .NET's Task and Task<'T> objects instead of relying on F#'s async {} workflows. The main benefit of this is that it removes the necessity of converting back and forth between tasks and async workflows when building a Giraffe web application (because ASP.NET Core only works with tasks out of the box).

For this purpose Giraffe uses the task {} computation expression which comes with the TaskBuilder.fs NuGet package. Syntactically it works identical to F#'s async workflows (after opening the FSharp.Control.Tasks.V2.ContextInsensitive module):

open FSharp.Control.Tasks.V2.ContextInsensitive
open Giraffe

let personHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            let! person = ctx.BindModelAsync<Person>()
            return! json person next ctx
        }

The task {} CE is an independent project maintained by Robert Peele and can be used from any other F# application as well. All you have to do is add a reference to the TaskBuilder.fs NuGet library and open the FSharp.Control.Tasks.V2 module:

open FSharp.Control.Tasks.V2

let readFileAndDoSomething (filePath : string) =
    task {
        use stream = new FileStream(filePath, FileMode.Open)
        use reader = new StreamReader(stream)
        let! contents = reader.ReadToEndAsync()

        // do something with contents

        return contents
    }

For more information please visit the official TaskBuilder.fs GitHub repository.

IMPORTANT NOTICE

If you have do! bindings in your Giraffe web application then you must open the FSharp.Control.Tasks.ContextInsensitive namespace to resolve any type inference issues:

open FSharp.Control.Tasks.V2.ContextInsensitive

Basics

Plugging Giraffe into ASP.NET Core

Install the Giraffe NuGet package:

PM> Install-Package Giraffe

Create a web application and plug it into the ASP.NET Core middleware:

open Giraffe

let webApp =
    choose [
        route "/ping"   >=> text "pong"
        route "/"       >=> htmlFile "/pages/index.html" ]

type Startup() =
    member __.ConfigureServices (services : IServiceCollection) =
        // Register default Giraffe dependencies
        services.AddGiraffe() |> ignore

    member __.Configure (app : IApplicationBuilder)
                        (env : IHostingEnvironment)
                        (loggerFactory : ILoggerFactory) =
        // Add Giraffe to the ASP.NET Core pipeline
        app.UseGiraffe webApp

[<EntryPoint>]
let main _ =
    WebHostBuilder()
        .UseKestrel()
        .UseStartup<Startup>()
        .Build()
        .Run()
    0

Instead of creating a Startup class you can also add Giraffe in a more functional way:

open Giraffe

let webApp =
    choose [
        route "/ping"   >=> text "pong"
        route "/"       >=> htmlFile "/pages/index.html" ]

let configureApp (app : IApplicationBuilder) =
    // Add Giraffe to the ASP.NET Core pipeline
    app.UseGiraffe webApp

let configureServices (services : IServiceCollection) =
    // Add Giraffe dependencies
    services.AddGiraffe() |> ignore

[<EntryPoint>]
let main _ =
    WebHostBuilder()
        .UseKestrel()
        .Configure(Action<IApplicationBuilder> configureApp)
        .ConfigureServices(configureServices)
        .Build()
        .Run()
    0

Dependency Management

ASP.NET Core has built in dependency management which works out of the box with Giraffe.

Registering Services

Registering services is done the same way as it is done for any other ASP.NET Core web application:

let configureServices (services : IServiceCollection) =
    // Add default Giraffe dependencies
    services.AddGiraffe() |> ignore

    // Add other dependencies
    // ...

[<EntryPoint>]
let main _ =
    WebHostBuilder()
        .UseKestrel()
        .Configure(Action<IApplicationBuilder> configureApp)
        // Calling ConfigureServices to set up dependencies
        .ConfigureServices(configureServices)
        .ConfigureLogging(configureLogging)
        .Build()
        .Run()
    0

Retrieving Services

Retrieving registered services from within a Giraffe HttpHandler function is done through the built in service locator (RequestServices) which comes with a HttpContext object:

let someHttpHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        let fooBar =
            ctx.RequestServices.GetService(typeof<IFooBar>)
            :?> IFooBar
        // Do something with `fooBar`...
        // Return a Task<HttpContext option>

Giraffe has an additional HttpContext extension method called GetService<'T> to make the code less cumbersome:

let someHttpHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        let fooBar = ctx.GetService<IFooBar>()
        // Do something with `fooBar`...
        // Return a Task<HttpContext option>

There's a handful more extension methods available to retrieve a few default dependencies like an IHostingEnvironment or ILogger object which are covered in the respective sections of this document.

Multiple Environments and Configuration

ASP.NET Core has built in support for working with multiple environments and configuration management, which both work out of the box with Giraffe.

Additionally Giraffe exposes a GetHostingEnvironment() extension method which can be used to easier retrieve an IHostingEnvironment object from within a HttpHandler function:

let someHttpHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        let env = ctx.GetHostingEnvironment()
        // Do something with `env`...
        // Return a Task<HttpContext option>

Configuration options can be retrieved via the GetService<'T> extension method:

let someHttpHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        let settings = ctx.GetService<IOptions<MySettings>>()
        // Do something with `settings`...
        // Return a Task<HttpContext option>

Logging

ASP.NET Core has a built in Logging API which works out of the box with Giraffe.

Configuring logging providers

One or more logging providers can be configured during application startup:

let configureLogging (builder : ILoggingBuilder) =
    // Set a logging filter (optional)
    let filter (l : LogLevel) = l.Equals LogLevel.Error

    // Configure the logging factory
    builder.AddFilter(filter) // Optional filter
           .AddConsole()      // Set up the Console logger
           .AddDebug()        // Set up the Debug logger

           // Add additional loggers if wanted...
    |> ignore

[<EntryPoint>]
let main _ =
    WebHostBuilder()
        .UseKestrel()
        .Configure(Action<IApplicationBuilder> configureApp)
        .ConfigureServices(configureServices)
        // Calling ConfigureLogging to set up logging providers
        .ConfigureLogging(configureLogging)
        .Build()
        .Run()
    0

Just like dependency management the logging API is configured the same way as it is done for any other ASP.NET Core web application.

Logging from within a HttpHandler function

After one or more logging providers have been configured you can retrieve an ILogger object (which can be used for logging) through the GetLogger<'T>() or GetLogger (categoryName : string) extension methods:

let someHttpHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        // Retrieve an ILogger through one of the extension methods
        let loggerA = ctx.GetLogger<ModuleName>()
        let loggerB = ctx.GetLogger("someHttpHandler")

        // Log some data
        loggerA.LogCritical("Something critical")
        loggerB.LogInformation("Logging some random info")
        // etc.

        // Return a Task<HttpContext option>

Error Handling

Giraffe exposes a separate error handling middleware which can be used to configure a functional error handler, which can react to any unhandled exception of the entire ASP.NET Core web application.

The Giraffe ErrorHandler function accepts an Exception object and a default ILogger and returns a HttpHandler function:

type ErrorHandler = exn -> ILogger -> HttpHandler

Because the Giraffe ErrorHandler returns a HttpHandler function it is possible to create anything from a simple error handling function to a complex error handling application.

Simple ErrorHandler example

This simple errorHandler function writes the entire Exception object to the logs, clears the response object and returns a HTTP 500 server error response:

let errorHandler (ex : Exception) (logger : ILogger) =
    logger.LogError(EventId(), ex, "An unhandled exception has occurred while executing the request.")
    clearResponse
    >=> ServerErrors.INTERNAL_ERROR ex.Message

Registering the Giraffe ErrorHandler middleware

In order to enable the error handler you have to configure the GiraffeErrorHandlerMiddleware in your application startup:

// Define the error handler function
let errorHandler (ex : Exception) (logger : ILogger) =
    logger.LogError(EventId(), ex, "An unhandled exception has occurred while executing the request.")
    clearResponse
    >=> ServerErrors.INTERNAL_ERROR ex.Message

// Register all ASP.NET Core middleware
let configureApp (app : IApplicationBuilder) =
    // Register the error handler first, so that all exceptions from other middleware can bubble up and be caught by the ErrorHandler function:
    app.UseGiraffeErrorHandler(errorHandler)
       .UseGiraffe webApp

[<EntryPoint>]
let main _ =
    WebHostBuilder()
        .UseKestrel()
        // Calling Configure to set up all middleware
        .Configure(Action<IApplicationBuilder> configureApp)
        .ConfigureServices(configureServices)
        .ConfigureLogging(configureLogging)
        .Build()
        .Run()
    0

... or the equivalent by using a Startup class:

type Startup() =
    member __.ConfigureServices (services : IServiceCollection) =
        // Register default Giraffe dependencies
        services.AddGiraffe() |> ignore

    member __.Configure (app : IApplicationBuilder)
                        (env : IHostingEnvironment)
                        (loggerFactory : ILoggerFactory) =
        app.UseGiraffeErrorHandler errorHandler
           .UseGiraffe webApp

[<EntryPoint>]
let main _ =
    WebHostBuilder()
        .UseKestrel()
        .UseStartup<Startup>()
        .Build()
        .Run()
    0

It is recommended to set the error handler as the first middleware in the ASP.NET Core pipeline, so that any unhandled exception from other middleware can be caught and processed by the error handling function.

Web Request Processing

Giraffe comes with a large set of default HttpContext extension methods as well as default HttpHandler functions which can be used to build rich web applications.

HTTP Headers

Working with HTTP headers in Giraffe is plain simple. The TryGetRequestHeader (key : string) extension method tries to retrieve the value of a given HTTP header and then returns either Some string or None:

let someHttpHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        let someValue =
            match ctx.TryGetRequestHeader "X-MyOwnHeader" with
            | None -> "default value"
            | Some headerValue -> headerValue

        // Do something with `someValue`...
        // Return a Task<HttpContext option>

This method is useful when trying to retrieve optional HTTP headers from within a HttpHandler.

If a HTTP header is mandatory then the GetRequestHeader (key : string) extension method might be a better fit. Instead of returning an Option<string> object it will return a Result<string, string> type:

let someHttpHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        match ctx.GetRequestHeader "X-MyOwnHeader" with
        | Error msg ->
            // Mandatory header is missing.
            // Log error message
            // Return error response to the client.
        | Ok headerValue ->
            // Do something with `headerValue`...
            // Return a Task<HttpContext option>

Setting a HTTP header in the response can be done via the SetHttpHeader (key : string) (value : obj) extension method:

let someHttpHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        ctx.SetHttpHeader "X-CustomHeader" "some-value"
        // Do other stuff...
        // Return a Task<HttpContext option>

You can also set a HTTP header via the setHttpHeader http handler:

let notFoundHandler : HttpHandler =
    setHttpHeader "X-CustomHeader"
    >=> RequestErrors.NOT_FOUND "Not Found"

let webApp =
    choose [
        route "/foo" >=> text "Foo"
        route "/bar" >=> text "Bar"
        notFoundHandler
    ]

Please note that these are additional Giraffe functions which complement already existing HTTP header functionality in the ASP.NET Core framework. ASP.NET Core offers higher level HTTP header functionality through the ctx.Request.GetTypedHeaders() method.

HTTP Verbs

Giraffe exposes a set of HttpHandler functions which can filter a request based on the request's HTTP verb:

  • GET
  • POST
  • PUT
  • PATCH
  • DELETE
  • HEAD
  • OPTIONS
  • TRACE
  • CONNECT

This can be useful when implementing a different HttpHandler function for the same route, but for different verbs:

let submitFooHandler : HttpHandler =
    // Do something

let submitBarHandler : HttpHandler =
    // Do something

let webApp =
    choose [
        // Filters for GET requests
        GET  >=> choose [
            route "/foo" >=> text "Foo"
            route "/bar" >=> text "Bar"
        ]
        // Filters for POST requests
        POST >=> choose [
            route "/foo" >=> submitFooHandler
            route "/bar" >=> submitBarHandler
        ]
        // If the HTTP verb or the route didn't match return a 404
        RequestErrors.NOT_FOUND "Not Found"
    ]

If you need to check the request's HTTP verb from within a HttpHandler function then you can use the default ASP.NET Core HttpMethods class:

open Microsoft.AspNetCore.Http

let someHttpHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        if HttpMethods.IsPut ctx.Request.Method then
            // Do something
        else
            // Do something else
        // Return a Task<HttpContext option>

HTTP Status Codes

Setting the HTTP status code of a response can be done either via the SetStatusCode (httpStatusCode : int) extension method or with the setStatusCode (statusCode : int) function:

let someHttpHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        ctx.SetStatusCode 200
        // Return a Task<HttpContext option>

// or...

let someHttpHandler : HttpHandler =
    setStatusCode 200
    >=> text "Hello World"

Giraffe also offers a default set of pre-defined HttpHandler functions, which can be used to return a response with a specific HTTP status code.

These HttpHandler functions are categorised in four sub modules:

For the majority of status code HttpHandler functions (except the Intermediate module) there are two versions for each individual status code available - a lower case and an upper case function (e.g. Successful.ok and Successful.OK).

The lower case version let's you combine the HttpHandler function with another HttpHandler function:

Successful.ok (text "Hello World")

This is basically a shorter (and more explicit) version of:

setStatusCode 200 >=> text "Hello World"

On the other hand the upper case version can be used to send an object directly to the client:

[<CLIMutable>]
type Person =
    {
        FirstName : string
        LastName  : string
    }

let johnDoe =
    {
        FirstName = "John"
        LastName  = "Doe"
    }

let app = choose [
    route `/`     >=> Successful.OK "Hello World"
    route `/john` >=> Successful.OK johnDoe
]

The upper case function is the equivalent and shorter version of:

setStatusCode 200 >=> negotiate johnDoe

The negotiate handler attempts to return an object back to the client based on the client's accepted mime types (see Content Negotiation).

The following sub modules and status code HttpHandler functions are available out of the box:

Please note that there is no module for 3xx HTTP status codes available, instead it is recommended to use the redirectTo http handler for redirection functionality (see Redirection).

Intermediate

HTTP Status Code Function name Example
100 CONTINUE route "/" >=> Intermediate.CONTINUE
101 SWITCHING_PROTO route "/" >=> Intermediate.SWITCHING_PROTO

Successful

HTTP Status Code Function name Example
200 ok route "/" >=> Successful.ok (text "Hello World")
200 OK route "/" >=> Successful.OK "Hello World"
201 created route "/" >=> Successful.created (json someObj)
201 CREATED route "/" >=> Successful.CREATED someObj
202 accepted route "/" >=> Successful.accepted (xml someObj)
202 ACCEPTED route "/" >=> Successful.ACCEPTED someObj
204 NO_CONTENT route "/" >=> Successful.NO_CONTENT

RequestErrors

HTTP Status Code Function name Example
400 badRequest route "/" >=> RequestErrors.badRequest (text "Don't like it")
400 BAD_REQUEST route "/" >=> RequestErrors.BAD_REQUEST "Don't like it"
401 unauthorized route "/" >=> RequestErrors.unauthorized "Basic" "MyApp" (text "Don't know who you are")
401 UNAUTHORIZED route "/" >=> RequestErrors.UNAUTHORIZED "Basic" "MyApp" "Don't know who you are"
403 forbidden route "/" >=> RequestErrors.forbidden (text "Not enough permissions")
403 FORBIDDEN route "/" >=> RequestErrors.FORBIDDEN "Not enough permissions"
404 notFound route "/" >=> RequestErrors.notFound (text "Page not found")
404 NOT_FOUND route "/" >=> RequestErrors.NOT_FOUND "Page not found"
405 methodNotAllowed route "/" >=> RequestErrors.methodNotAllowed (text "Don't support this")
405 METHOD_NOT_ALLOWED route "/" >=> RequestErrors.METHOD_NOT_ALLOWED "Don't support this"
406 notAcceptable route "/" >=> RequestErrors.notAcceptable (text "Not having this")
406 NOT_ACCEPTABLE route "/" >=> RequestErrors.NOT_ACCEPTABLE "Not having this"
409 conflict route "/" >=> RequestErrors.conflict (text "some conflict")
409 CONFLICT route "/" >=> RequestErrors.CONFLICT "some conflict"
410 gone route "/" >=> RequestErrors.gone (text "Too late, not here anymore")
410 GONE route "/" >=> RequestErrors.GONE "Too late, not here anymore"
415 unsupportedMediaType route "/" >=> RequestErrors.unsupportedMediaType (text "Please send in different format")
415 UNSUPPORTED_MEDIA_TYPE route "/" >=> RequestErrors.UNSUPPORTED_MEDIA_TYPE "Please send in different format"
422 unprocessableEntity route "/" >=> RequestErrors.unprocessableEntity (text "Can't do anything with this")
422 UNPROCESSABLE_ENTITY route "/" >=> RequestErrors.UNPROCESSABLE_ENTITY "Can't do anything with this"
428 preconditionRequired route "/" >=> RequestErrors.preconditionRequired (test "Please do something else first")
428 PRECONDITION_REQUIRED route "/" >=> RequestErrors.PRECONDITION_REQUIRED "Please do something else first"
429 tooManyRequests route "/" >=> RequestErrors.tooManyRequests (text "Slow down champ")
429 TOO_MANY_REQUESTS route "/" >=> RequestErrors.TOO_MANY_REQUESTS "Slow down champ"

Note that the unauthorized and UNAUTHORIZED functions require two additional parameters, an authentication scheme and a realm.

ServerErrors

HTTP Status Code Function name Example
500 internalError route "/" >=> ServerErrors.internalError (text "Ops, something went wrong")
500 INTERNAL_ERROR route "/" >=> ServerErrors.INTERNAL_ERROR "Not implemented"
501 notImplemented route "/" >=> ServerErrors.notImplemented (text "Not implemented")
501 NOT_IMPLEMENTED route "/" >=> ServerErrors.NOT_IMPLEMENTED "Ops, something went wrong"
502 badGateway route "/" >=> ServerErrors.badGateway (text "Bad gateway")
502 BAD_GATEWAY route "/" >=> ServerErrors.BAD_GATEWAY "Bad gateway"
503 serviceUnavailable route "/" >=> ServerErrors.serviceUnavailable (text "Service unavailable")
503 SERVICE_UNAVAILABLE route "/" >=> ServerErrors.SERVICE_UNAVAILABLE "Service unavailable"
504 gatewayTimeout route "/" >=> ServerErrors.gatewayTimeout (text "Gateway timeout")
504 GATEWAY_TIMEOUT route "/" >=> ServerErrors.GATEWAY_TIMEOUT "Gateway timeout"
505 invalidHttpVersion route "/" >=> ServerErrors.invalidHttpVersion (text "Invalid HTTP version")

Routing

Giraffe offers a variety of routing HttpHandler functions to accommodate the majority of use cases.

route

The simplest form of routing can be done with the route http handler:

let webApp =
    choose [
        route "/foo" >=> text "Foo"
        route "/bar" >=> text "Bar"

        // If none of the routes matched then return a 404
        RequestErrors.NOT_FOUND "Not Found"
    ]

routeCi

The route http handler expects an exact match. If the HTTP request was made to a slightly different route (e.g. /Bar or /bAr) then the route "/bar" handler will not serve the request.

This can be avoided by using the case insensitive routeCi http handler:

let webApp =
    choose [
        route   "/foo" >=> text "Foo"
        routeCi "/foo" >=> text "Bar"

        // If none of the routes matched then return a 404
        RequestErrors.NOT_FOUND "Not Found"
    ]

In the example above a request made to https://example.org/FOO would return Bar in the response.

routex

According to the HTTP specification a route with a trailing slash is not equivalent to the same route without a trailing slash:

https://example.org/foo
https://example.org/foo/

A web server might (rightfully) want to serve a different response for each route:

let webApp =
    choose [
        route "/foo"  >=> text "Foo"
        route "/foo/" >=> text "Bar"

        // If none of the routes matched then return a 404
        RequestErrors.NOT_FOUND "Not Found"
    ]

However many web applications choose to treat both routes as the same. If you would like to achieve this behaviour by using a single route in Giraffe then you can use the routex http handler which accepts a Regex string for matching routes:

let webApp =
    choose [
        routex "/foo(/?)" >=> text "Bar"

        // If none of the routes matched then return a 404
        RequestErrors.NOT_FOUND "Not Found"
    ]

The (/?) regex pattern specifies that a / can occur zero or one time at the end of the route, which means it would successfully match the following two routes:

https://example.org/foo
https://example.org/foo/

However, this example wouldn't match a request made to https://example.org/foo///. If you want to match any number of trailing slashes then you must use (/*) instead:

let webApp =
    choose [
        routex "/foo(/*)" >=> text "Bar"

        // If none of the routes matched then return a 404
        RequestErrors.NOT_FOUND "Not Found"
    ]

Please be aware that such a routex can create a conflict and unexpected behaviour if you have a similar matching routef (see routef):

let webApp =
    choose [
        routex "/foo(/*)" >=> text "Bar"
        routef "/foo/%s/%s/%s" (fun s1, s2, s3 -> text (sprintf "%s%s%s" s1 s2 s3))

        // If none of the routes matched then return a 404
        RequestErrors.NOT_FOUND "Not Found"
    ]

In the above scenario it is not clear which one of the two http handlers a user want to be invoked when a request is made to https://example.org/foo///.

If you want to learn more about Regex please check the Regular Expression Language Reference.

routeCix

The routeCix http handler is the case insensitive version of routex:

let webApp =
    choose [
        routex   "/foo(/?)" >=> text "Foo"
        routeCix "/foo(/?)" >=> text "Bar"

        // If none of the routes matched then return a 404
        RequestErrors.NOT_FOUND "Not Found"
    ]

In the example above a request made to https://example.org/FOO/ would return Bar in the response.

routef

If a route contains user defined parameters then the routef http handler can be handy:

let fooHandler (first : string)
               (last  : string)
               (age   : int)
               : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        (sprintf "First: %s, Last: %s, Age: %i" first last age
        |> text) next ctx

let webApp =
    choose [
        routef "/foo/%s/%s/%i" fooHandler
        routef "/bar/%O" (fun guid -> text (guid.ToString()))

        // If none of the routes matched then return a 404
        RequestErrors.NOT_FOUND "Not Found"
    ]

The routef http handler takes two parameters - a format string and a HttpHandler function.

The format string supports the following format chars:

Format Char Type
%b bool
%c char
%s string
%i int
%d int64
%f float/double
%O Guid (including short GUIDs*)
%u uint64 (formatted as a short ID*)

*) Please note that the %O and %u format characters also support URL friendly short GUIDs and IDs.

The %O format character supports GUIDs in the format of:

  • 00000000000000000000000000000000
  • 00000000-0000-0000-0000-000000000000
  • Xy0MVKupFES9NpmZ9TiHcw

The last string represents an example of a Short GUID which is a normal GUID shortened into a URL encoded 22 character long string. Routes which use the %O format character will be able to automatically resolve a Short GUID as well as a normal GUID into a System.Guid argument.

The %u format character can only resolve an 11 character long Short ID (aka YouTube ID) into a uint64 value.

Short GUIDs and short IDs are popular choices to make URLs shorter and friendlier whilst still mapping to a unique System.Guid or uint64 value on the server side.

Short GUIDs and IDs can also be resolved from query string parameters by making use of the ShortGuid and ShortId helper modules.

routeCif

The case insensitive version of routef is routeCif:

let webApp =
    choose [
        routeCif "/foo/%s/bar" (fun str -> text str)

        // If none of the routes matched then return a 404
        RequestErrors.NOT_FOUND "Not Found"
    ]

Please be aware that a case insensitive URL matching will return unexpected results in combination with case sensitive arguments such as short GUIDs and short IDs.

routeBind

If you need to bind route parameters directly to a type then you can use the routeBind<'T> http handler. Unlike routef or routeCif which work with a format string the routeBind<'T> http handler tries to match named parameters to the properties of a given type 'T:

[<CLIMutable>]
type Person =
    {
        FirstName : string
        LastName  : string
    }

let personHandler (person : Person) : HttpHandler =
    sprintf "Hello %s %s" person.FirstName person.LastName
    |> Successful.OK

let webApp =
    choose [
        routeBind<Person> "/p/{firstName}/{lastName}" personHandler
    ]

The routeBind<'T> http handler can also contain valid Regex code to match a variety of different routes.

For example by definition (according to the spec) a route with a trailing slash is not the same as the equivalent route without a trailing slash. Therefore it is perfectly valid if a web server doesn't serve (or serves a different response) for the following two routes:

/p/{firstName}/{lastName}
/p/{firstName}/{lastName}/

However many web applications choose to treat both URLs as the same. The routeBind<'T> http handler can make use of Regex to enable such use cases:

[<CLIMutable>]
type Blah =
    {
        Foo : string
        Bar : string
    }

let blahHandler (blah : Blah) : HttpHandler =
    sprintf "Hello %s %s" blah.Foo blah.Bar
    |> Successful.OK

let webApp =
    choose [
        routeBind<Blah> "/p/{foo}/{bar}(/?)" blahHandler
    ]

By appending the Regex code (/?) to the end of the route we tell the routeBind<'T> handler to match any route which has either zero or one trailing slash.

If any number of trailing slashes should be allowed then you can swap the ? with a * in the Regex:

routeBind<Blah> "/p/{foo}/{bar}(/*)" blahHandler

For a complete list of valid Regex codes please visit the official Regular Expression Language Reference.

routeStartsWtih

Sometimes it can be useful to pre-filter a route in order to enable certain functionality which should only be applied to a specific collection of routes.

The routeStartsWith http handler does just that:

let webApp =
    routeStartsWith "/api/" >=>
        // Pre-filter because only API calls require Auth
        requiresAuthentication (challenge "Cookie") >=>
            choose [
                route "/api/v1/foo" >=> text "Foo"
                route "/api/v1/bar" >=> text "Bar"
            ]

routeStartsWithCi

The case insensitive version of routeStartsWith is routeStartsWithCi:

let webApp =
    routeStartsWithCi "/api/" >=>
        // Pre-filter because only API calls require Auth
        requiresAuthentication (challenge "Cookie") >=>
            choose [
                route "/api/v1/foo" >=> text "Foo"
                route "/api/v1/bar" >=> text "Bar"
            ]

Please note that the routeStartsWith and routeStartsWithCi http handlers do not change how subsequent routing functions are matched. The final URL to get a "Foo" response is still http[s]://your-domain.com/api/v1/foo (single /api) and not http[s]://your-domain.com/api/api/v1/foo (double /api).

subRoute

In contrast to routeStartsWith the subRoute http handler let's you categorise routes without having to repeat already pre-filtered parts of the route:

let webApp =
    subRoute "/api"
        (choose [
            subRoute "/v1"
                (choose [
                    route "/foo" >=> text "Foo 1"
                    route "/bar" >=> text "Bar 1" ])
            subRoute "/v2"
                (choose [
                    route "/foo" >=> text "Foo 2"
                    route "/bar" >=> text "Bar 2" ]) ])

In this example the final URL to retrieve "Bar 2" would be http[s]://your-domain.com/api/v2/bar.

subRouteCi

As you might expect the subRouteCi http handler is the case insensitive version of subRoute:

let webApp =
    subRouteCi "/api"
        (choose [
            subRouteCi "/v1"
                (choose [
                    route ""     >=> text "Default"
                    route "/foo" >=> text "Foo 1"
                    route "/bar" >=> text "Bar 1" ])
            subRouteCi "/v2"
                (choose [
                    route "/foo" >=> text "Foo 2"
                    route "/bar" >=> text "Bar 2" ]) ])

Please note that only the path specified for subRouteCi is case insensitive. Nested routes after subRouteCi will be evaluated as per definition of each individual route.

Note: If you wish to have a default route for any subRoute handler (e.g. /api/v1 from the above example) then you need to specify the route as route "" and not as route "/", because /api/v1/ is a fundamentally different than /api/v1 according to the HTTP specification.

subRoutef

The subRoutef http handler is a combination of the routef and the subRoute http handler:

let app =
    GET >=> choose [
        route "/"    >=> text "index"
        route "/foo" >=> text "bar"

        subRoutef "/%s/api" (fun lang ->
            requiresAuthentication (challenge "Cookie") >=>
                choose [
                    route  "/blah" >=> text "blah"
                    routef "/%s" (fun n -> text (sprintf "Hello %s! Lang: %s" n lang))
                ])
        setStatusCode 404 >=> text "Not found" ]

This can be useful when an application has dynamic parameters at the beginning of each route (e.g. language parameter):

https://example.org/en/users/John
https://example.org/de/users/Ryan
https://example.org/fr/users/Nicky
...

routePorts

If your web server is listening to multiple ports through WebHost.UseUrls then you can use the routePorts http handler to filter incoming requests based on their port:

let guiApp =
    choose [
        route   "/"    >=> text "Hello World"
        routeCi "/foo" >=> text "Bar"
    ]

let apiApp =
    subRoute "/api"
        (choose [
            subRoute "/v1"
                (choose [
                    route "/foo" >=> text "Foo 1"
                    route "/bar" >=> text "Bar 1" ])
            subRoute "/v2"
                (choose [
                    route "/foo" >=> text "Foo 2"
                    route "/bar" >=> text "Bar 2" ]) ])

let webApp =
    routePorts [
        (9001, guiApp)
        (9002, apiApp)
    ]

Query Strings

Working with query strings is very similar to working with HTTP headers in Giraffe. The TryGetQueryStringValue (key : string) extension method tries to retrieve the value of a given query string parameter and then returns either Some string or None:

let someHttpHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        let someValue =
            match ctx.TryGetQueryStringValue "q" with
            | None   -> "default value"
            | Some q -> q

        // Do something with `someValue`...
        // Return a Task<HttpContext option>

This method is useful when trying to retrieve optional query string parameters from within a HttpHandler.

If a query string parameter is mandatory then the GetQueryStringValue (key : string) extension method might be a better fit. Instead of returning an Option<string> object it will return a Result<string, string> type:

let someHttpHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        match ctx.GetQueryStringValue "q" with
        | Error msg ->
            // Mandatory query string value is missing.
            // Log error message
            // Return error response to the client.
        | Ok q ->
            // Do something with `q`...
            // Return a Task<HttpContext option>

You can also access the query string through the ctx.Request.Query object which returns an IQueryCollection object which allows you to perform more actions on it.

Last but not least there is also a HttpContext extension method called BindQueryString<'T> which let's you bind an entire query string to an object of type 'T (see Binding Query Strings).

Model Binding

Giraffe offers out of the box a few default HttpContext extension methods and equivalent HttpHandler functions which make it possible to bind the payload or query string of a HTTP request to a custom object.

Binding JSON

The BindJsonAsync<'T>() extension method can be used to bind a JSON payload to an object of type 'T:

[<CLIMutable>]
type Car =
    {
        Name   : string
        Make   : string
        Wheels : int
        Built  : DateTime
    }

let submitCar : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Binds a JSON payload to a Car object
            let! car = ctx.BindJsonAsync<Car>()

            // Sends the object back to the client
            return! Successful.OK car next ctx
        }

let webApp =
    choose [
        GET >=>
            choose [
                route "/"    >=> text "index"
                route "ping" >=> text "pong"
            ]
        POST >=> route "/car" >=> submitCar
    ]

Alternatively you can also use the bindJson<'T> http handler:

[<CLIMutable>]
type Car =
    {
        Name   : string
        Make   : string
        Wheels : int
        Built  : DateTime
    }

let webApp =
    choose [
        GET >=>
            choose [
                route "/"    >=> text "index"
                route "ping" >=> text "pong"
            ]
        POST
        >=> route "/car"
        >=> bindJson<Car> (fun car -> Successful.OK car)
    ]

Both, the HttpContext extension method as well as the HttpHandler function will try to create an instance of type 'T regardless if the submitted payload contained a complete representation of 'T or not. The parsed object might only contain partial data (where some properties might be null) and additional null checks might be required before further processing.

Please note that in order for the model binding to work the record type must be decorated with the [<CLIMutable>] attribute, which will make sure that the type will have a parameterless constructor.

The underlying JSON serializer can be configured as a dependency during application startup (see JSON).

Binding XML

The BindXmlAsync<'T>() extension method binds an XML payload to an object of type 'T:

[<CLIMutable>]
type Car =
    {
        Name   : string
        Make   : string
        Wheels : int
        Built  : DateTime
    }

let submitCar : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Binds an XML payload to a Car object
            let! car = ctx.BindXmlAsync<Car>()

            // Sends the object back to the client
            return! Successful.OK car next ctx
        }

let webApp =
    choose [
        GET >=>
            choose [
                route "/"    >=> text "index"
                route "ping" >=> text "pong"
            ]
        POST >=> route "/car" >=> submitCar
    ]

Alternatively you can also use the bindXml<'T> http handler:

[<CLIMutable>]
type Car =
    {
        Name   : string
        Make   : string
        Wheels : int
        Built  : DateTime
    }

let webApp =
    choose [
        GET >=>
            choose [
                route "/"    >=> text "index"
                route "ping" >=> text "pong"
            ]
        POST
        >=> route "/car"
        >=> bindXml<Car> (fun car -> Successful.OK car)
    ]

Both, the HttpContext extension method as well as the HttpHandler function will try to create an instance of type 'T regardless if the submitted payload contained a complete representation of 'T or not. The parsed object might only contain partial data (where some properties might be null) and additional null checks might be required before further processing.

Please note that in order for the model binding to work the record type must be decorated with the [<CLIMutable>] attribute, which will make sure that the type will have a parameterless constructor.

The underlying XML serializer can be configured as a dependency during application startup (see XML).

Binding Forms

The BindFormAsync<'T> (?cultureInfo : CultureInfo) extension method binds form data to an object of type 'T. You can also specify an optional CultureInfo object for parsing culture specific data such as DateTime objects or floating point numbers:

[<CLIMutable>]
type Car =
    {
        Name   : string
        Make   : string
        Wheels : int
        Built  : DateTime
    }

let submitCar : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Binds a form payload to a Car object
            let! car = ctx.BindFormAsync<Car>()

            // or with a CultureInfo:
            let british = CultureInfo.CreateSpecificCulture("en-GB")
            let! car2 = ctx.BindFormAsync<Car>(british)

            // Sends the object back to the client
            return! Successful.OK car next ctx
        }

let webApp =
    choose [
        GET >=>
            choose [
                route "/"    >=> text "index"
                route "ping" >=> text "pong"
            ]
        POST >=> route "/car" >=> submitCar
    ]

Alternatively you can use the bindForm<'T> http handler (which also accepts an additional parameter of type CultureInfo option):

[<CLIMutable>]
type Car =
    {
        Name   : string
        Make   : string
        Wheels : int
        Built  : DateTime
    }

let british = CultureInfo.CreateSpecificCulture("en-GB")

let webApp =
    choose [
        GET >=>
            choose [
                route "/"    >=> text "index"
                route "ping" >=> text "pong"
            ]
        POST
        >=> route "/car"
        >=> bindForm<Car> (Some british) (fun car -> Successful.OK car)
    ]

Just like in the previous examples the record type must be decorated with the [<CLIMutable>] attribute in order for the model binding to work.

The BindFormAsync<'T> extension method and the bindForm<'T> http handler are both very loose model binding functions, which means they will try to create an instance of type 'T even if some data was missing or provided in the wrong format (in which case it will just skip parsing the field).

While this has its own advantages it is not very idiomatic to functional programming.

For a more stricter (and more functional) model binding you can use the TryBindFormAsync<'T> extension method or the tryBindForm<'T> http handler function.

They are both very similar to the previous binding methods, except that they will not create an instance of type 'T if the submitted payload did not contain all mandatory fields (any field which is not an F# option type) or had badly formatted data.

The TryBindFormAsync<'T> method returns an object of type Result<'T, string>. If the model binding was successful then the result will contain an instance of type 'T, otherwise a string value containing the parsing error message:

[<CLIMutable>]
type Car =
    {
        Name   : string
        Make   : string
        Wheels : int
        Built  : DateTime
    }

let submitCar : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Binds a form payload to a Car object
            let! result = ctx.TryBindFormAsync<Car>()

            // or with a CultureInfo:
            let british = CultureInfo.CreateSpecificCulture("en-GB")
            let! result2 = ctx.TryBindFormAsync<Car>(british)

            return!
                (match result2 with
                | Ok car -> Successful.OK car
                | Error err -> RequestErrors.BAD_REQUEST err) next ctx
        }

let webApp =
    choose [
        GET >=>
            choose [
                route "/"    >=> text "index"
                route "ping" >=> text "pong"
            ]
        POST >=> route "/car" >=> submitCar
    ]

The tryBindForm<'T> http handler is very similar, but instead of returning a Result<'T, string> object it will invoke an error handler function if the model binding does not succeed:

[<CLIMutable>]
type Car =
    {
        Name   : string
        Make   : string
        Wheels : int
        Built  : DateTime
    }

let british = CultureInfo.CreateSpecificCulture("en-GB")
let parsingError (err : string) = RequestErrors.BAD_REQUEST err

let webApp =
    choose [
        GET >=>
            choose [
                route "/"    >=> text "index"
                route "ping" >=> text "pong"
            ]
        POST
        >=> route "/car"
        >=> tryBindForm<Car> parsingError (Some british) (fun car -> Successful.OK car)
        RequestErrors.NOT_FOUND "Not found"
    ]

In this example if a Car object could not be successfully created then the parsingError handler will get invoked which will return a HTTP Bad Request response with the parsing error message.

Binding Query Strings

The BindQueryString<'T> (?cultureInfo : CultureInfo) extension method binds query string parameters to an object of type 'T. An optional CultureInfo object can be specified for parsing culture specific data such as DateTime objects and floating point numbers:

[<CLIMutable>]
type Car =
    {
        Name   : string
        Make   : string
        Wheels : int
        Built  : DateTime
    }

let submitCar : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        // Binds the query string to a Car object
        let car = ctx.BindQueryString<Car>()

        // or with a CultureInfo:
        let british = CultureInfo.CreateSpecificCulture("en-GB")
        let car2 = ctx.BindQueryString<Car>(british)

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

let webApp =
    choose [
        GET >=>
            choose [
                route "/"    >=> text "index"
                route "ping" >=> text "pong"
                route "/car" >=> submitCar
            ]
    ]

Alternatively you can use the bindQuery<'T> http handler (which also accepts an additional parameter of type CultureInfo option):

[<CLIMutable>]
type Car =
    {
        Name   : string
        Make   : string
        Wheels : int
        Built  : DateTime
    }

let british = CultureInfo.CreateSpecificCulture("en-GB")

let webApp =
    choose [
        GET >=>
            choose [
                route "/"    >=> text "index"
                route "ping" >=> text "pong"
            ]
        POST
        >=> route "/car"
        >=> bindQuery<Car> (Some british) (fun car -> Successful.OK car)
    ]

Just like in the previous examples the record type must be decorated with the [<CLIMutable>] attribute in order for the model binding to work.

The BindQueryString<'T> extension method and the bindQuery<'T> http handler are both very loose model binding functions, which means they will try to create an instance of type 'T even if some data was missing or provided in the wrong format (in which case it will just skip parsing the field).

While this has its own advantages it is not very idiomatic to functional programming.

For a more stricter (and more functional) model binding approach you can use the TryBindQueryString<'T> extension method or the tryBindQuery<'T> http handler function.

They are both very similar to the previous binding methods, except that they will not create an instance of type 'T if the submitted query string did not contain all mandatory fields (any field which is not an F# option type) or had badly formatted data.

The TryBindQueryString<'T> method returns an object of type Result<'T, string>. If the model binding was successful then the result will contain an instance of type 'T, otherwise a string value containing the parsing error message:

[<CLIMutable>]
type Car =
    {
        Name   : string
        Make   : string
        Wheels : int
        Built  : DateTime
    }

let submitCar : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        // Binds a form payload to a Car object
        let result = ctx.TryBindQueryString<Car>()

        // or with a CultureInfo:
        let british = CultureInfo.CreateSpecificCulture("en-GB")
        let result2 = ctx.TryBindQueryString<Car>(british)

        (match result2 with
        | Ok car -> Successful.OK car
        | Error err -> RequestErrors.BAD_REQUEST err) next ctx

let webApp =
    choose [
        GET >=>
            choose [
                route "/"    >=> text "index"
                route "ping" >=> text "pong"
            ]
        POST >=> route "/car" >=> submitCar
    ]

The tryBindQuery<'T> http handler is very similar, but instead of returning a Result<'T, string> object it will invoke an error handler function if the model binding does not succeed:

[<CLIMutable>]
type Car =
    {
        Name   : string
        Make   : string
        Wheels : int
        Built  : DateTime
    }

let british = CultureInfo.CreateSpecificCulture("en-GB")
let parsingError (err : string) = RequestErrors.BAD_REQUEST err

let webApp =
    choose [
        GET >=>
            choose [
                route "/"    >=> text "index"
                route "ping" >=> text "pong"
            ]
        POST
        >=> route "/car"
        >=> tryBindQuery<Car> parsingError (Some british) (fun car -> Successful.OK car)
        RequestErrors.NOT_FOUND "Not found"
    ]

In this example if a Car object could not be successfully created then the parsingError handler will get invoked which will return a HTTP Bad Request response containing the parsing error message.

Special note

Aleksander Heintz has created a Gist which contains a re-usable query string API based on how Chiron works, which allows one doing something like the following:

type Report =
  { author: string option
    project: string option
    week: int option
    summary: string option
    progress: string list
    comments: string list
    plan: string list }

  static member FromQuery (_: Report) =
        fun author project week summary progress comments plan ->
          { author = author
            project = project
            week = week
            summary = summary
            progress = progress
            comments = comments
            plan = plan }
    <!> Query.read "author"
    <*> Query.read "project"
    <*> Query.read "week"
    <*> Query.read "summary"
    <*> Query.read "progress"
    <*> Query.read "comments"
    <*> Query.read "plan"

let reportRoute = route "/report" >=> Query.bind (fun (r: Report) -> text <| sprintf "%A" r)

Even though this API didn't quite fit with Giraffe's existing tryBindQuery and model validation function it is a nice example of how Giraffe can be extended to do similar things in different ways.

If you prefer this API you can either copy paste Aleksander's code from the provided Gist or find the contents of the Gist in the appendix of this document (in case the Gist gets ever deleted).

Binding Models (catch all)

The BindModelAsync<'T> (?cultureInfo : CultureInfo) method is a generic model binding function which will try to pick the right model parsing function based on a request's HTTP verb and Content-Type header. With the help of BindModelAsync<'T> it is possible to create a single endpoint which can bind JSON, XML, form and query string data:

[<CLIMutable>]
type Car =
    {
        Name   : string
        Make   : string
        Wheels : int
        Built  : DateTime
    }

let submitCar : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Binds a Car object
            let! car = ctx.BindModelAsync<Car>()

            // or with a CultureInfo:
            let british = CultureInfo.CreateSpecificCulture("en-GB")
            let! car2 = ctx.BindModelAsync<Car>(british)

            // Sends the object back to the client
            return! Successful.OK car next ctx
        }

let webApp =
    choose [
        GET >=>
            choose [
                route "/"    >=> text "index"
                route "ping" >=> text "pong"
            ]
        route "/car" >=> submitCar
    ]

Alternatively you can use the bindModel<'T> http handler:

[<CLIMutable>]
type Car =
    {
        Name   : string
        Make   : string
        Wheels : int
        Built  : DateTime
    }

let british = CultureInfo.CreateSpecificCulture("en-GB")

let webApp =
    choose [
        GET >=>
            choose [
                route "/"    >=> text "index"
                route "ping" >=> text "pong"
            ]
        POST
        >=> route "/car"
        >=> bindModel<Car> (Some british) (fun car -> Successful.OK car)
    ]

Again like before, the record type 'T must be decorated with the [<CLIMutable>] attribute in order for the model binding to work.

Model Validation

Giraffe exposes an IModelValidation<'T> interface and an accompanying validateModel<'T> http handler which can be used to validate a model in a more functional way.

Let's take a look at the following example:

[<CLIMutable>]
type Adult =
    {
        FirstName  : string
        MiddleName : string option
        LastName   : string
        Age        : int
    }
    override this.ToString() =
        sprintf "Name: %s%s %s, Age: %i"
            this.FirstName
            (if this.MiddleName.IsSome then " " + this.MiddleName.Value else "")
            this.LastName
            this.Age

module WebApp =
    let textHandler (x : obj) = text (x.ToString())
    let parsingError err = RequestErrors.BAD_REQUEST err

    let webApp _ =
        choose [
            route "/person"
            >=> tryBindQuery<Adult> parsingError None textHandler
            RequestErrors.NOT_FOUND "Not found"
        ]

The Adult type is a normal F# record type which defines four properties (one of them optional) and an override of the ToString() method.

The /person route will try to bind a query string to an object of type Adult before invoking the textHandler which will eventually output the model by calling its ToString() method.

The model has three mandatory properties (FirstName, LastName and Age) and only one optional property MiddleName, which means that a query string must contain at least the fields for the first- and last name, as well as the age for the model binding to succeed.

However, what if someone wants to define additional validation logic before responding with a HTTP 200 to a client?

For example the Adult type could have an additional validation method called HasErrors:

[<CLIMutable>]
type Adult =
    {
        FirstName  : string
        MiddleName : string option
        LastName   : string
        Age        : int
    }
    override this.ToString() =
        sprintf "Name: %s%s %s, Age: %i"
            this.FirstName
            (if this.MiddleName.IsSome then " " + this.MiddleName.Value else "")
            this.LastName
            this.Age

    member this.HasErrors() =
        if      this.FirstName.Length < 3  then Some "First name is too short."
        else if this.FirstName.Length > 50 then Some "First name is too long."
        else if this.LastName.Length  < 3  then Some "Last name is too short."
        else if this.LastName.Length  > 50 then Some "Last name is too long."
        else if this.Age < 18              then Some "Person must be an adult (age >= 18)."
        else if this.Age > 150             then Some "Person must be a human being."
        else None

The HasError method is checking business logic which is specific to the type Adult. For instance if Age is less than 18 then the person is not an adult and therefore HasErrors would return an F# option type with Some "Person must be an adult (age >= 18).".

It is a generic validation method which can be used from anywhere in an F# application to validate if a given Adult object has logically correct data.

In order to make use of that validation method from within a Giraffe HttpHandler one could create a custom handler to invoke the method:

module WebApp =
    let adultHandler (adult : Adult) : HttpHandler =
        match adult.HasErrors() with
        | Some msg -> RequestErrors.BAD_REQUEST msg
        | None     -> text (adult.ToString())

    let parsingError err = RequestErrors.BAD_REQUEST err

    let webApp _ =
        choose [
            route "/person"
            >=> tryBindQuery<Adult> parsingError None adultHandler
            RequestErrors.NOT_FOUND "Not found"
        ]

If an application has only one model to deal with then this is fairly straight forward, but if an application has more models which require additional data validation steps like in the case of Adult then you'll quickly end up writing a lot of boilerplate code. This can be avoided with the help of IModelValidation<'T> and validateModel<'T>:

[<CLIMutable>]
type Adult =
    {
        FirstName  : string
        MiddleName : string option
        LastName   : string
        Age        : int
    }
    override this.ToString() =
        sprintf "Name: %s%s %s, Age: %i"
            this.FirstName
            (if this.MiddleName.IsSome then " " + this.MiddleName.Value else "")
            this.LastName
            this.Age

    member this.HasErrors() =
        if      this.FirstName.Length < 3  then Some "First name is too short."
        else if this.FirstName.Length > 50 then Some "First name is too long."
        else if this.LastName.Length  < 3  then Some "Last name is too short."
        else if this.LastName.Length  > 50 then Some "Last name is too long."
        else if this.Age < 18              then Some "Person must be an adult (age >= 18)."
        else if this.Age > 150             then Some "Person must be a human being."
        else None

    interface IModelValidation<Adult> with
        member this.Validate() =
            match this.HasErrors() with
            | Some msg -> Error (RequestErrors.badRequest (text msg))
            | None     -> Ok this

module WebApp =
    let textHandler (x : obj) = text (x.ToString())

    let parsingError err = RequestErrors.BAD_REQUEST err

    let webApp _ =
        choose [
            route Urls.person
            >=> tryBindQuery<Adult> parsingError None (validateModel textHandler)
        ]

Now the Adult type has implemented the IModelValidation<'T> interface from where it was able to re-use the already existing HasErrors method to either return a validated object of type Adult or an error of type HttpHandler.

The validateModel method has now been added between the tryBindQuery<Adult> and textHandler functions, which means it will validate the model using its IModelValidation<Adult>.Validate() method.

On success the textHandler will be executed as normal and on error it will invoke the error handler returned from Validate().

File Uploads

ASP.NET Core makes it really easy to process uploaded files.

The HttpContext.Request.Form.Files collection can be used to process one or many small files which have been sent by a client:

open Giraffe

let fileUploadHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            return!
                (match ctx.Request.HasFormContentType with
                | false -> RequestErrors.BAD_REQUEST "Bad request"
                | true  ->
                    ctx.Request.Form.Files
                    |> Seq.fold (fun acc file -> sprintf "%s\n%s" acc file.FileName) ""
                    |> text) next ctx
        }

let webApp = route "/upload" >=> fileUploadHandler

You can also read uploaded files by utilizing the IFormFeature and the ReadFormAsync method:

let fileUploadHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            let formFeature = ctx.Features.Get<IFormFeature>()
            let! form = formFeature.ReadFormAsync CancellationToken.None
            return!
                (form.Files
                |> Seq.fold (fun acc file -> sprintf "%s\n%s" acc file.FileName) ""
                |> text) next ctx
        }

let webApp = route "/upload" >=> fileUploadHandler

For large file uploads it is recommended to stream the file in order to prevent resource exhaustion.

See also large file uploads in ASP.NET Core on StackOverflow.

Authentication and Authorization

ASP.NET Core has a wealth of Authentication and Authorization options which work out of the box with Giraffe.

Additionally Giraffe offers a few HttpHandler functions which make it easier to work with ASP.NET Core's authentication and authorization APIs in a functional way.

requiresAuthentication

The requiresAuthentication (authFailedHandler : HttpHandler) http handler validates if a user has been authenticated by one of ASP.NET Core's authentication middleware. If the identity of a user could not be established then the authFailedHandler will be executed:

let notLoggedIn =
    RequestErrors.UNAUTHORIZED
        "Basic"
        "Some Realm"
        "You must be logged in."

let mustBeLoggedIn = requiresAuthentication notLoggedIn

let webApp =
    choose [
        route "/"     >=> text "Hello World"
        route "/user" >=>
            mustBeLoggedIn >=>
                choose [
                    GET  >=> readUserHandler
                    POST >=> submitUserHandler
                ]
    ]

requiresRole

The requiresRole (role : string) (authFailedHandler : HttpHandler) http handler checks if an authenticated user is part of a given role. If a user fails to be in a certain role then the authFailedHandler will be executed:

let notLoggedIn =
    RequestErrors.UNAUTHORIZED
        "Basic"
        "Some Realm"
        "You must be logged in."

let notAdmin =
    RequestErrors.FORBIDDEN
        "Permission denied. You must be an admin."

let mustBeLoggedIn = requiresAuthentication notLoggedIn

let mustBeAdmin = requiresRole "Admin" notAdmin

let webApp =
    choose [
        route "/"     >=> text "Hello World"
        route "/user" >=>
            mustBeLoggedIn >=> mustBeAdmin >=>
                choose [
                    routef "/user/%s/edit"   editUserHandler
                    routef "/user/%s/delete" deleteUserHandler
                ]
    ]

requiresRoleOf

The requiresRoleOf (roles : string list) (authFailedHandler : HttpHandler) http handler checks if an authenticated user is part of a list of given roles. If a user fails to be in at least one of the roles then the authFailedHandler will be executed:

let notLoggedIn =
    RequestErrors.UNAUTHORIZED
        "Basic"
        "Some Realm"
        "You must be logged in."

let notProUserOrAdmin =
    RequestErrors.FORBIDDEN
        "Permission denied. You must be a pro user or admin."

let mustBeLoggedIn = requiresAuthentication notLoggedIn

let mustBeProUserOrAdmin =
    requiresRoleOf [ "ProUser"; "Admin" ] notProUserOrAdmin

let webApp =
    choose [
        route "/"     >=> text "Hello World"
        route "/user" >=>
            mustBeLoggedIn >=> mustBeProUserOrAdmin >=>
                choose [
                    routef "/user/%s/edit"   editUserHandler
                    routef "/user/%s/delete" deleteUserHandler
                ]
    ]

authorizeRequest

The authorizeRequest (predicate : HttpContext -> bool) (authFailedHandler : HttpHandler) http handler validates a request based on a given predicate. If the predicate returns false then the authFailedHandler will get executed:

let apiKey = "some-secret-key-1234"

let validateApiKey (ctx : HttpContext) =
    match ctx.TryGetRequestHeader "X-API-Key" with
    | Some key -> apiKey.Equals key
    | None     -> false

let accessDenied   = setStatusCode 401 >=> text "Access Denied"
let requiresApiKey =
    authorizeRequest validateApiKey accessDenied

let webApp =
    choose [
        route "/" >=> text "Hello World"
        route "/private"
        >=> requiresApiKey
        >=> protectedResource
    ]

authorizeUser

The authorizeUser (policy : ClaimsPrincipal -> bool) (authFailedHandler : HttpHandler) http handler checks if an authenticated user meets a given user policy. If the policy cannot be satisfied then the authFailedHandler will get executed:

let notLoggedIn =
    RequestErrors.UNAUTHORIZED
        "Basic"
        "Some Realm"
        "You must be logged in."

let accessDenied = setStatusCode 401 >=> text "Access Denied"

let mustBeLoggedIn = requiresAuthentication notLoggedIn

let mustBeJohn =
    authorizeUser (fun u -> u.HasClaim (ClaimTypes.Name, "John")) accessDenied

let webApp =
    choose [
        route "/" >=> text "Hello World"
        route "/john-only"
        >=> mustBeLoggedIn
        >=> mustBeJohn
        >=> userHandler
    ]

authorizeByPolicyName

The authorizeByPolicyName (policyName : string) (authFailedHandler : HttpHandler) http handler checks if an authenticated user meets a given authorization policy. If the policy cannot be satisfied then the authFailedHandler will get executed:

let notLoggedIn =
    RequestErrors.UNAUTHORIZED
        "Basic"
        "Some Realm"
        "You must be logged in."

let accessDenied = setStatusCode 401 >=> text "Access Denied"

let mustBeLoggedIn = requiresAuthentication notLoggedIn

let mustBeOver21 =
    authorizeByPolicyName "MustBeOver21" accessDenied

let webApp =
    choose [
        route "/" >=> text "Hello World"
        route "/adults-only"
        >=> mustBeLoggedIn
        >=> mustBeOver21
        >=> userHandler
    ]

authorizeByPolicy

The authorizeByPolicy (policy : AuthorizationPolicy) (authFailedHandler : HttpHandler) http handler checks if an authenticated user meets a given authorization policy. If the policy cannot be satisfied then the authFailedHandler will get executed.

See authorizeByPolicyName for more information.

challenge

The challenge (authScheme : string) http handler will challenge the client to authenticate with a specific authScheme. This function is often used in combination with the requiresAuthentication http handler:

let webApp =
    choose [
        route "/"     >=> text "Hello World"
        route "/user" >=>
            requiresAuthentication (challenge "Cookie") >=>
                choose [
                    GET  >=> readUserHandler
                    POST >=> submitUserHandler
                ]
    ]

In this example the client will be challenged to authenticate with a scheme called "Cookie". The scheme name must match one of the registered authentication schemes from the configuration of the ASP.NET Core auth middleware.

signOut

The signOut (authScheme : string) http handler will sign a user out from a given authScheme:

let logout = signOut "Cookie" >=> redirectTo false "/"

let webApp =
    choose [
        route "/"     >=> text "Hello World"
        route "/user" >=>
            requiresAuthentication (challenge "Cookie") >=>
                choose [
                    GET  >=> readUserHandler
                    POST >=> submitUserHandler
                    route "/user/logout" >=> logout
                ]
    ]

Conditional Requests

Conditional HTTP headers (e.g. If-Match, If-Modified-Since, etc.) are a common pattern to improve performance (web caching), to combat the lost update problem or to perform optimistic concurrency control when a client requests a resource from a web server.

Giraffe offers the validatePreconditions http handler which can be used to run HTTP pre-validation checks against a given ETag and/or Last-Modified value of an incoming HTTP request:

let someHandler (eTag         : string)
                (lastModified : DateTimeOffset)
                (content      : string) =
    let eTagHeader = Some (EntityTagHeaderValue.FromString true eTag)
    validatePreconditions eTagHeader (Some lastModified)
    >=> setBodyFromString content

The validatePreconditions handler takes in two optional parameters - an eTag and a lastMofified date time value - which will be used to validate a conditional HTTP request. If all conditions can be met, or if no conditions have been submitted, then the next http handler (of the Giraffe pipeline) will get invoked. Otherwise, if one of the pre-conditions fails or if the resource hasn't changed since the last check, then a 412 Precondition Failed or a 304 Not Modified response will get returned.

The ETag (Entity Tag) value is an opaque identifier assigned by a web server to a specific version of a resource found at a URL. The Last-Modified value provides a timestamp indicating the date and time at which the origin server believes the selected representation was last modified.

Giraffe's validatePreconditions http handler validates the following conditional HTTP headers:

  • If-Match
  • If-None-Match
  • If-Modified-Since
  • If-Unmodified-Since

The If-Range HTTP header will not get validated as part the validatePreconditions http handler, because it is a streaming specific check which gets handled by Giraffe's Streaming functionality.

Alternatively Giraffe exposes the HttpContext extension method ValidatePreconditions (eTag) (lastModified) which can be used to create a custom conditional http handler. The ValidatePreconditions method takes the same two optional parameters and returns a result of type Precondition.

The Precondition union type contains the following cases:

Case Description and Recommended Action
NoConditionsSpecified No validation has taken place, because the client didn't send any conditional HTTP headers. Proceed with web request as normal.
ConditionFailed At least one condition couldn't be sastisfied. It is advised to return a 412 status code back to the client (you can use the HttpContext.PreconditionFailedResponse() method for that purpose).
ResourceNotModified The resource hasn't changed since the last visit. The server can skip processing this request and return a 304 status code back to the client (you can use the HttpContext.NotModifiedResponse() method for that purpose).
AllConditionsMet All pre-conditions were satisfied. The server should continue processing the request as normal.

The validatePreconditions http handler as well as the ValidatePreconditions extension method will not only validate all conditional HTTP headers, but also set the required ETag and/or Last-Modified HTTP response headers according to the HTTP spec.

Both functions follow latest HTTP guidelines and validate all conditional headers in the correct precedence as defined in RFC 2616.

Example of HttpContext.ValidatePreconditions:

// Pass an optional eTag and lastModified timestamp into the handler, because generating an eTag might require to load the entire resource into memory and therefore this is not something which should be done on every request.
let someHttpHandler eTag lastModified : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            match ctx.ValidatePreconditions eTag lastModified with
            | ConditionFailed     -> return ctx.PreconditionFailedResponse()
            | ResourceNotModified -> return ctx.NotModifiedResponse()
            | AllConditionsMet | NoConditionsSpecified ->
                // Continue as normal
                // Do stuff
        }

let webApp =
    choose [
        route "/"    >=> text "Hello World"
        route "/foo" >=> someHttpHandler
    ]

Response Writing

Sending a response back to a client in Giraffe can be done through a small range of HttpContext extension methods and and their equivalent HttpHandler functions.

Writing Bytes

The WriteBytesAsync (bytes : byte[]) extension method and the setBody (bytes : byte array) http handler both write a byte array to the response stream of the HTTP request:

let someHandler (bytes : byte array) : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteBytesAsync bytes
        }

// or...

let someHandler (bytes : byte array) : HttpHandler =
    // Do stuff
    setBody bytes

Both functions will also set the Content-Length HTTP header to the length of the byte array.

Writing Strings

The WriteStringAsync (str : string) extension method and the setBodyFromString (str : string) http handler are both small helper functions which UTF8 decode the string into a byte array and subsequently write the byte array to the response stream of the HTTP request.

Both functions will also set the Content-Length HTTP header to the correct length of the response:

let someHandler (str : string) : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteStringAsync str
        }

// or...

let someHandler (str : string) : HttpHandler =
    // Do stuff
    setBodyFromString str

The setBody and setBodyFromString http handlers (and their HttpContext extension method equivalents) are useful when you want to create your own response writing function for a specific media type which is not provided by Giraffe yet.

For example Giraffe doesn't have any functionality for serializing and writing a YAML response back to a client. However, you can reference another third party library which can serialize an object into a YAML string and then create your own yaml http handler like this:

let yaml (x : obj) : HttpHandler =
    setHttpHeader "Content-Type" "text/yaml"
    >=> setBodyFromString (YamlSerializer.toYaml x)

Writing Text

The WriteTextAsync (str : string) extension method and the text (str : string) http handler are the same as writing strings except that they will also set the Content-Type HTTP header to text/plain in the response:

let someHandler (str : string) : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteTextAsync str
        }

// or...

let someHandler (str : string) : HttpHandler =
    // Do stuff
    text str

Writing JSON

The WriteJsonAsync<'T> (dataObj : 'T) extension method and the json<'T> (dataObj : 'T) http handler will both serialize an object to a JSON string and write the output to the response stream of the HTTP request. They will also set the Content-Length HTTP header and the Content-Type header to application/json in the response:

let someHandler (animal : Animal) : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteJsonAsync animal
        }

// or...

let someHandler (animal : Animal) : HttpHandler =
    // Do stuff
    json animal

The underlying JSON serializer can be configured as a dependency during application startup (see JSON).

The WriteJsonChunkedAsync<'T> (dataObj : 'T) extension method and the jsonChunked (dataObj : 'T) http handler write directly to th response stream of the HTTP request without extra buffering into a byte array. They will not set a Content-Length header and instead set the Transfer-Encoding: chunked header and Content-Type: application/json:

let someHandler (person : Person) : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteJsonChunkedAsync person
        }

// or...

let someHandler (person : Person) : HttpHandler =
    // Do stuff
    jsonChunked person

Writing XML

The WriteXmlAsync (dataObj : obj) extension method and the xml (dataObj : obj) http handler will both serialize an object to an XML string and write the output to the response stream of the HTTP request. They will also set the Content-LengthHTTP header and the Content-Type header to application/xml in the response:

let someHandler (dataObj : obj) : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteXmlAsync dataObj
        }

// or...

let someHandler (dataObj : obj) : HttpHandler =
    // Do stuff
    xml dataObj

The underlying XML serializer can be configured as a dependency during application startup (see XML).

Writing HTML

The WriteHtmlFileAsync (filePath : string) extension method and the htmlFile (filePath : string) http handler will both read a file from the local file system and write the content to the response stream of the HTTP request. They will also set the Content-Length HTTP header and the Content-Type header to text/html:

let someHandler (dataObj : obj) : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteHtmlFileAsync "index.html"
        }

// or...

let someHandler (dataObj : obj) : HttpHandler =
    // Do stuff
    htmlFile "index.html"

Both functions accept either a relative or an absolute path to the HTML file.

Writing HTML Strings

The WriteHtmlStringAsync (html : string) extension method and the htmlString (html : string) http handler are both equivalent to writing strings except that they will also set the Content-Type header to text/html:

let someHandler (dataObj : obj) : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteHtmlStringAsync "<html><head></head><body>Hello World</body></html>"
        }

// or...

let someHandler (dataObj : obj) : HttpHandler =
    // Do stuff
    htmlString "<html><head></head><body>Hello World</body></html>"

Writing HTML Views

Giraffe comes with its own extremely powerful view engine for functional developers (see Giraffe View Engine). The WriteHtmlViewAsync (htmlView : XmlNode) extension method and the htmlView (htmlView : XmlNode) http handler will both compile a given html view into valid HTML code and write the output to the response stream of the HTTP request. Additionally they will both set the Content-Length HTTP header to the correct value and set the Content-Type header to text/html:

let indexView =
    html [] [
        head [] [
            title [] [ str "Giraffe" ]
        ]
        body [] [
            h1 [] [ str "Giraffe" ]
            p [] [ str "Hello World." ]
        ]
    ]

let someHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteHtmlViewAsync indexView
        }

// or...

let someHandler : HttpHandler =
    // Do stuff
    htmlView indexView

Content Negotiation

Giraffe's default response writers will always send a response in a specific media type regardless of a client's own requirements. Content negotiation on the other hand allows a Giraffe web server to examine a web request's Accept HTTP header and decide an appropriate data representation on the fly.

The NegotiateAsync (responseObj : obj) extension method and the negotiate (responseObj : obj) http handler will both pick the most appropriate data representation based on a request's Accept HTTP header and write a data object to the response stream of a HTTP request:

[<CLIMutable>]
type Person =
    {
        FirstName : string
        LastName  : string
    }

let johnDoe =
    {
        FirstName = "John"
        LastName  = "Doe"
    }

let someHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Do stuff
            return! ctx.NegotiateAsync johnDoe
        }

// or...

let webApp =
    choose [
        route "/foo" >=> negotiate johnDoe
        route "/bar" >=> someHandler
    ]

Currently Giraffe only supports plain text, JSON and XML responses during content negotiation out of the box. If a client doesn't accept any of these media types then the default negotiation function will return a 406 Unacceptable HTTP response.

Configuring Content Negotiation

The default negotiation behaviour can be customized by creating a new class which implements the INegotiationConfig interface and set up a new dependency of that type during application startup.

The INegotiationConfig has two members which must be implemented:

  • Rules of type IDictionary<string, obj -> HttpHandler>
  • UnacceptableHandler of type HttpHandler

The Rules property is of type IDictionary<string, obj -> HttpHandler> and represents a key/value dictionary, where the key denotes a supported Content-Type and the value represents a function which turns a given obj into a HttpHandler.

For example the rules of the DefaultNegotiationConfig are as following:

dict [
    "*/*"             , json
    "application/json", json
    "application/xml" , xml
    "text/xml"        , xml
    "text/plain"      , fun x -> x.ToString() |> text
]

As you can see from the example above the default dictionary uses the json and xml http handlers to define the response handler for the respective media types. If a client requests a text/plain response then a new function had to be created which accepts an obj and uses the .ToString() method in combination with the text http handler to return a plain text response.

If a client has no particular preference (*/*) then the default response is json.

The UnacceptableHandler is a http handler which will be invoked if none of the client's accepted media types are supported by the web server and therefore the request cannot be satisfied.

Example: Adding BSON support to content negotiation

Let's assume you have created your own bson http handler which can serialize an object into BSON and write the contents to the response stream of the request:

let bson (o : obj) : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        // Implement BSON handler here

In order for negotiate and NegotiateAsync to support the new bson http handler we need to create a new type which implements INegotiationConfig:

type CustomNegotiationConfig (baseConfig : INegotiationConfig) =
    let plainText x = text (x.ToString())

    interface INegotiationConfig with

        member __.UnacceptableHandler =
            baseConfig.UnacceptableHandler

        member __.Rules =
                dict [
                    "*/*"             , json
                    "application/json", json
                    "application/xml" , xml
                    "text/xml"        , xml
                    "application/bson", bson
                    "text/plain"      , plainText
                ]

Then register an instance of the newly created class during application startup:

let configureServices (services : IServiceCollection) =
    // First register all default Giraffe dependencies
    services.AddGiraffe() |> ignore

    // Now register your custom INegotiationConfig
    services.AddSingleton<INegotiationConfig>(
        CustomNegotiationConfig(
            DefaultNegotiationConfig())
    ) |> ignore

[<EntryPoint>]
let main _ =
    WebHost.CreateDefaultBuilder()
        .Configure(Action<IApplicationBuilder> configureApp)
        .ConfigureServices(configureServices)
        .ConfigureLogging(configureLogging)
        .Build()
        .Run()
    0

In this example the CustomNegotiationConfig uses composition to re-use the UnacceptableHandler from the DefaultNegotiationConfig without having to use inheritance.

Configuring content negotiation through partial application

Alternatively you can also use the NegotiateWithAsync extension method or the negotiateWith http handler to configure content negotiation through partial function application:

let customNegotiationRules =
    dict [
        "*/*"             , json
        "application/json", json
        "application/xml" , xml
        "text/xml"        , xml
        "application/bson", bson
        "text/plain"      , plainText
    ]

let customUnacceptableHandler =
    setStatusCode 406
    >=> text "Request cannot be satisfied by the web server."

// Override the default negotiate handler with a new custom implementation
let negotiate =
    negotiateWith
        customNegotiationRules
        customUnacceptableHandler

Streaming

Sometimes a large file or block of data has to be send to a client and in order to avoid loading the entire data into memory a Giraffe web application can use streaming to send a response in a more efficient way.

The WriteStreamAsync extension method and the streamData http handler can be used to stream an object of type Stream to a client.

Both functions accept the following parameters:

  • enableRangeProcessing: If true a client can request a sub range of data to be streamed (useful when a client wants to continue streaming after a paused download, or when internet connection has been lost, etc.)
  • stream: The stream object to be returned to the client.
  • eTag: Entity header tag used for conditional requests (see Conditional Requests).
  • lastModified: Last modified timestamp used for conditional requests (see Conditional Requests).

If the eTag or lastModified timestamp are set then both functions will also set the ETag and/or Last-Modified HTTP headers during the response:

let someStream : Stream = ...

let someHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteStreamAsync
                true // enableRangeProcessing
                someStream
                None // eTag
                None // lastModified
        }

// or...

let someHandler : HttpHandler =
    // Do stuff
    streamData
        true // enableRangeProcessing
        someStream
        None // eTag
        None // lastModified

In most cases a web application will want to stream a file directly from the local file system. In this case you can use the WriteFileStreamAsync extension method or the streamFile http handler, which are both the same as WriteStreamAsync and streamData except that they accept a relative or absolute filePath instead of a Stream object:

let someHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteFileStreamAsync
                true // enableRangeProcessing
                "large-file.zip"
                None // eTag
                None // lastModified
        }

// or...

let someHandler : HttpHandler =
    // Do stuff
    streamFile
        true // enableRangeProcessing
        "large-file.zip"
        None // eTag
        None // lastModified

All streaming functions in Giraffe will also validate conditional HTTP headers, including the If-Range HTTP header if enableRangeProcessing has been set to true.

Redirection

The redirectTo (permanent : bool) (location : string) http handler can be used to redirect a client to a different location when handling an incoming web request:

let webApp =
    choose [
        route "/new" >=> text "Hello World"
        route "/old" >=> redirectTo true "https://myserver.com/new"
    ]

Please note that if the permanent flag is set to true then the Giraffe web application will send a 301 HTTP status code to browsers which will tell them that the redirection is permanent. This often leads to browsers cache the information and not hit the deprecated URL a second time any more. If this is not desired then please set permanent to false in order to guarantee that browsers will continue hitting the old URL before redirecting to the (temporary) new one.

Response Caching

ASP.NET Core comes with a standard Response Caching Middleware which works out of the box with Giraffe.

If you are not already using one of the two ASP.NET Core meta packages (Microsoft.AspNetCore.App or Microsoft.AspNetCore.All) then you will have to add an additional reference to the Microsoft.AspNetCore.ResponseCaching NuGet package.

After adding the NuGet package you need to register the response caching middleware inside your application's startup code before registering Giraffe:

let configureServices (services : IServiceCollection) =
    services
        .AddResponseCaching() // <-- Here the order doesn't matter
        .AddGiraffe()         // This is just registering dependencies
    |> ignore

let configureApp (app : IApplicationBuilder) =
    app.UseGiraffeErrorHandler(errorHandler)
       .UseStaticFiles()     // Optional if you use static files
       .UseAuthentication()  // Optional if you use authentication
       .UseResponseCaching() // <-- Before UseGiraffe webApp
       .UseGiraffe webApp

After setting up the ASP.NET Core response caching middleware you can use Giraffe's response caching http handlers to add response caching to your routes:

// A test handler which generates a new GUID on every request
let generateGuidHandler : HttpHandler =
    warbler (fun _ -> text (Guid.NewGuid().ToString()))

let webApp =
    GET >=> choose [
        route "/route1" >=> publicResponseCaching 30 None >=> generateGuidHandler
        route "/route2" >=> noResponseCaching >=> generateGuidHandler
    ]

Requests to /route1 can be cached for up to 30 seconds whilst requests to /route2 have response caching completely disabled.

Note: if you test the above code with Postman then make sure you disable the No-Cache feature in Postman in order to test the correct caching behaviour.

Giraffe offers in total 4 http handlers which can be used to configure response caching for an endpoint.

In the above example we used the noResponseCaching http handler to completely disable response caching on the client and on any proxy server. The noResponseCaching http handler will send the following HTTP headers in the response:

Cache-Control: no-store, no-cache
Pragma: no-cache
Expires: -1

The publicResponseCaching or privateResponseCaching http handlers will enable response caching on the client and/or on proxy servers. The publicResponseCaching http handler will set the Cache-Control directive to public, which means that not only the client is allowed to cache a response for the given cache duration, but also any intermediary proxy server as well as the ASP.NET Core middleware. This is useful for HTTP GET/HEAD endpoints which do not hold any user specific data, authentication data or any cookies and where the response data doesn't change frequently.

The privateResponseCaching http handler sets the Cache-Control directive to private which means that only the end client is allowed to store the response for the given cache duration. Proxy servers and the ASP.NET Core response caching middleware must not cache the response.

Both http handlers require the cache duration in seconds and an optional vary parameter:

// Cache for 10 seconds without any vary headers
publicResponseCaching 10 None

// Cache for 30 seconds with Accept and Accept-Encoding as vary headers
publicResponseCaching 30 (Some "Accept, Accept-Encoding")

The vary parameter specifies which HTTP request headers must be respected to vary the cached response. For example if an endpoint returns a different response (Content-Type) based on the client's Accept header (= content negotiation) then the Accept header must also be considered when returning a response from the cache. The same applies if the web server has response compression enabled. If a response varies based on the client's accepted compression algorithm then the cache must also respect the client's Accept-Encoding HTTP header when serving a response from the cache.

VaryByQueryKeys

The ASP.NET Core response caching middleware offers one more additional feature which is not part of the response's HTTP headers. By default, if a route is cachable then the middleware will try to returnn a cached response even if the query parameters were different.

For example if a request to /foo/bar has been cached, then the cached version will also be returned if a request is made to /foo/bar?query1=a or /foo/bar?query1=a&query2=b.

Sometimes this is not desired and the VaryByQueryKeys feature lets the middleware vary its cached responses based on a request's query keys.

The generic responseCaching http handler is the most basic response caching handler which can be used to configure custom response caching handlers as well as make use of the VaryByQueryKeys feature:

responseCaching
    (Public (TimeSpan.FromSeconds (float 5)))
    (Some "Accept, Accept-Encoding")
    (Some [| "query1"; "query2" |])

The first parameter is of type CacheDirective which is defines as following:

type CacheDirective =
    | NoCache
    | Public  of TimeSpan
    | Private of TimeSpan

The second parameter is an string option which defines the vary parameter.

The third and last parameter is a string list option which defines an optional list of query parameter values which must be used to vary a cached response by the ASP.NET Core response caching middleware. Please be aware that this feature only applies to the ASP.NET Core response caching middleware and will not be respected by any intermediate proxy servers.

Response Compression

ASP.NET Core has its own Response Compression Middleware which works out of the box with Giraffe. There's no additional functionality or http handlers required in order to make it work with Giraffe web applications.

Giraffe View Engine

Giraffe has its own functional view engine which can be used to build rich UIs for web applications. The single biggest and best contrast to other view engines (e.g. Razor, Liquid, etc.) is that the Giraffe View Engine is entirely functional written in normal (and compiled) F# code.

This means that the Giraffe View Engine is by definition one of the most feature rich view engines available, requires no disk IO to load a view and views are automatically compiled at build time.

The Giraffe View Engine uses traditional functions and F# record types to generate rich HTML/XML views.

HTML Elements and Attributes

HTML elements and attributes are defined as F# objects:

let indexView =
    html [] [
        head [] [
            title [] [ str "Giraffe Sample" ]
        ]
        body [] [
            h1 [] [ str "I |> F#" ]
            p [ _class "some-css-class"; _id "someId" ] [
                str "Hello World"
            ]
        ]
    ]

A HTML element can either be a ParentNode, a VoidElement or a Text element.

For example the <html> or <div> tags are typical ParentNode elements. They can hold an XmlAttribute list and a second XmlElement list for their child elements:

let someHtml = div [] []

All ParentNode elements accept these two parameters:

let someHtml =
    div [ _id "someId"; _class "css-class" ] [
        a [ _href "https://example.org" ] [ str "Some text..." ]
    ]

Most HTML tags are ParentNode elements, however there is a few HTML tags which cannot hold any child elements, such as <br>, <hr> or <meta> tags. These are represented as VoidElement objects and only accept the XmlAttribute list as single parameter:

let someHtml =
    div [] [
        br []
        hr [ _class "css-class-for-hr" ]
        p [] [ str "bla blah" ]
    ]

Attributes are further classified into two different cases. First and most commonly there are KeyValue attributes:

a [
    _href "http://url.com"
    _target "_blank"
    _class "class1" ] [ str "Click here" ]

As the name suggests, they have a key, such as class and a value such as the name of a CSS class.

The second category of attributes are Boolean flags. There are not many but some HTML attributes which do not require any value (e.g. async or defer in script tags). The presence of such an attribute means that the feature is turned on, otherwise it is turned off:

script [ _src "some.js"; _async ] []

There's also a wealth of accessibility attributes available under the Giraffe.GiraffeViewEngine.Accessibility module (needs to be explicitly opened).

Text Content

Naturally the most frequent content in any HTML document is pure text:

<div>
    <h1>This is text content</h1>
    <p>This is even more text content!</p>
</div>

The Giraffe View Engine lets one create pure text content as a Text element. A Text element can either be generated via the rawText or encodedText (or the short alias str) functions:

let someHtml =
    div [] [
        p [] [ rawText "<div>Hello World</div>" ]
        p [] [ encodedText "<div>Hello World</div>" ]
    ]

The rawText function will create an object of type XmlNode where the content will be rendered in its original form and the encodedText/str function will output a string where the content has been HTML encoded.

In this example the first p element will literally output the string as it is (<div>Hello World</div>) while the second p element will output the value as HTML encoded string &lt;div&gt;Hello World&lt;/div&gt;.

Please be aware that the the usage of rawText is mainly designed for edge cases where someone would purposefully want to inject HTML (or JavaScript) code into a rendered view. If not used carefully this could potentially lead to serious security vulnerabilities and therefore should be used only when explicitly required.

Most cases and particularly any user provided content should always be output via the encodedText/str function.

Naming Convention

The Giraffe View Engine has a naming convention which lets you easily determine the correct function name without having to know anything about the view engine's implementation.

All HTML tags are defined as XmlNode elements under the exact same name as they are named in HTML. For example the <html> tag would be html [] [], an <a> tag would be a [] [] and a <span> or <canvas> would be the span [] [] or canvas [] [] function.

HTML attributes follow the same naming convention except that attributes have an underscore prepended. For example the class attribute would be _class and the src attribute would be _src in Giraffe.

The underscore does not only help to distinguish an attribute from an element, but also avoid a naming conflict between tags and attributes of the same name (e.g. <form> vs. <input form="form1">).

If a HTML attribute has a hyphen in the name (e.g. accept-charset) then the equivalent Giraffe attribute would be written in camel case notion (e.g. acceptCharset).

Should you find a HTML tag or attribute missing in the Giraffe View Engine then you can either create it yourself or send a pull request on GitHub.

View Engine Best Practices

Due to the huge amount of available HTML tags and their fairly generic (and short) names (e.g. <form>, <option>, <select>, etc.) there is a significant danger of accidentally overriding a function of the same name in an application's codebase. For that reason the Giraffe View Engine becomes only available after opening the GiraffeViewEngine module.

As a measure of good practice it is recommended to create all views in a separate module:

module MyWebApplication

module Views =
    open Giraffe.GiraffeViewEngine

    let index =
        html [] [
            head [] [
                title [] [ str "Giraffe Sample" ]
            ]
            body [] [
                h1 [] [ str "I |> F#" ]
                p [ _class "some-css-class"; _id "someId" ] [
                    str "Hello World"
                ]
            ]
        ]

    let other = //...

This ensures that the opening of the GiraffeViewEngine is only contained in a small context of an application's codebase and therefore less of a threat to accidental overrides. In the above example views can always be accessed through the Views sub module (e.g. Views.index).

Custom Elements and Attributes

Adding new elements or attributes is normally as simple as a single line of code:

open Giraffe.GiraffeViewEngine

// If there was a new <foo></foo> HTML element:
let foo = tag "foo"

// If <foo> is an element which cannot hold any content then create it as voidTag:
let foo = voidTag "foo"

// If <foo> has a new attribute called bar then create a new bar attribute:
let _bar = attr "bar"

// if the bar attribute is a boolean flag:
let _bar = flag "bar"

Alternatively you can also create new elements and attributes from inside another element:

let someHtml =
    div [] [
        tag "foo" [ attr "bar" "blah" ] [
            voidTag "otherFoo" [ flag "flag1" ]
        ]
    ]

Rendering Views

Rendering views in Giraffe is done through one of the following functions:

  • renderHtmlDocument
  • renderHtmlNodes
  • renderHtmlNode
  • renderXmlNodes
  • renderXmlNode

The Giraffe View Engine cannot only be used to render HTML views, but also for any other XML based content such as <svg> images or other arbitrary XML based data.

The renderHtmlDocument function takes a single XmlNode as input parameter and renders a HTML page with a DOCTYPE declaration. This function should be used for rendering a complete HTML document. The WriteHtmlViewAsync extension method and the htmlView http handler both use the renderHtmlDocument function under the covers.

The renderHtmlNodes function takes an XmlNode list as input parameter and will output a single HTML string containing all the rendered HTML code. The renderHtmlNode function renders a single XmlNode element into a valid HTML string. Both, the renderHtmlNodes and renderHtmlNode function are useful for use cases where a HTML snippet needs to be created without a DOCTYPE declaration (e.g. templated emails, etc.).

The renderXmlNodes and renderXmlNode function are identical to renderHtmlNodes and renderHtmlNode, except that they will render void elements differently:

let someTag = voidTag "foo"
let someContent = someTag []

// Void tag will be rendered to valid HTML: <foo>
let output1 = renderHtmlNode someContent

// Void tag will be rendered to valid XML: <foo />
let output2 = renderXmlNode someContent

All GiraffeViewEngine http handlers are using a thread static StringBuilderPool to avoid the creation of large StringBuilder objects for each render call and dynamically grow/shrink that pool based on the application's needs. However if the application is running into any memory issues then this performance feature can be disabled by setting StringBuilderPool.IsEnabled <- false.

Additionally with Giraffe 3.0.0 or higher there is a new module called ViewBuilder under the Giraffe.GiraffeViewEngine namespace. This module exposes additional view rendering functions which compile a view into a StringBuilder object instead of returning a single string:

  • ViewBuilder.buildHtmlDocument
  • ViewBuilder.buildHtmlNodes
  • ViewBuilder.buildHtmlNode
  • ViewBuilder.buildXmlNodes
  • ViewBuilder.buildXmlNode

The ViewBuilder.build[...] functions can be useful if there is additional string processing required before/after composing a view by the GiraffeViewEngine (e.g. embedding HTML snippets in an email template, etc.). These functions also serve as the lower level building blocks of the equivalent render[...] functions.

Example usage:

open System.Text
open Giraffe.GiraffeViewEngine

let someHtml =
    div [] [
        tag "foo" [ attr "bar" "blah" ] [
            voidTag "otherFoo" [ flag "flag1" ]
        ]
    ]

let sb = new StringBuilder()

// Perform actions on the `sb` object...
sb.AppendLine "This is a HTML snippet inside a markdown string:"
  .AppendLine ""
  .AppendLine "```html" |> ignore

let sb' = ViewBuilder.buildHtmlNode sb someHtml

// Perform more actions on the `sb` object...
sb'.AppendLine "```" |> ignore

let markdownOutput = sb'.ToString()

Common View Engine Features

The Giraffe View Engine doesn't have any specially built functions for commonly known features such as master pages or partial views, mainly because the nature of the view engine itself doesn't require it in most cases.

Master Pages

Creating a master page is a simple matter of piping two functions together:

module Views =
    open Giraffe.GiraffeViewEngine

    let master (pageTitle : string) (content: XmlNode list) =
        html [] [
            head [] [
                title [] [ str pageTitle ]
            ]
            body [] content
        ]

    let index =
        let pageTitle = "Giraffe Sample"
        [
            h1 [] [ str pageTitle ]
            p [] [ str "Hello world!" ]
        ] |> master pageTitle

... or even have multiple nested master pages:

module Views =
    open Giraffe.GiraffeViewEngine

    let master1 (pageTitle : string) (content: XmlNode list) =
        html [] [
            head [] [
                title [] [ str pageTitle ]
            ]
            body [] content
        ]

    let master2 (content: XmlNode list) =
        [
            main [] content
            footer [] [
                p [] [
                    str "Copyright ..."
                ]
            ]
        ]

    let index =
        let pageTitle = "Giraffe Sample"
        [
            h1 [] [ str pageTitle ]
            p [] [ str "Hello world!" ]
        ] |> master2 |> master1 pageTitle

Partial Views

A partial view is nothing more than one function or object being called from within another function:

module Views =
    open Giraffe.GiraffeViewEngine

    let partial =
        footer [] [
            p [] [
                str "Copyright..."
            ]
        ]

    let master (pageTitle : string) (content: XmlNode list) =
        html [] [
            head [] [
                title [] [ str pageTitle ]
            ]
            body [] content
            partial
        ]

    let index =
        let pageTitle = "Giraffe Sample"
        [
            h1 [] [ str pageTitle ]
            p [] [ str "Hello world!" ]
        ] |> master pageTitle

Working with Models

A view which accepts a model is basically a function with an additional parameter:

module Views =
    open Giraffe.GiraffeViewEngine

    let partial =
        footer [] [
            p [] [
                str "Copyright..."
            ]
        ]

    let master (pageTitle : string) (content: XmlNode list) =
        html [] [
            head [] [
                title [] [ str pageTitle ]
            ]
            body [] content
            partial
        ]

    let index (model : IndexViewModel) =
        [
            h1 [] [ str model.PageTitle ]
            p [] [ str model.WelcomeText ]
        ] |> master model.PageTitle

If Statements, Loops, etc.

Things like if statements, loops and other normal F# language constructs work just as expected:

let partial (books : Book list) =
    ul [] [
        yield!
            books
            |> List.map (fun b -> li [] [ str book.Title ])
    ]

Overall the Giraffe View Engine is extremely flexible and feature rich by nature based on the fact that it is generated via normal compiled F# code.

Serialization

JSON

By default Giraffe uses Newtonsoft JSON.NET for (de-)serializing JSON content. An application can modify the default serializer by registering a new dependency which implements the IJsonSerializer interface during application startup.

Customizing Giraffe's JSON serialization can either happen via providing a custom object of JsonSerializerSettings when instantiating the default NewtonsoftJsonSerializer or by swapping in an entire different JSON library by creating a new class which implements the IJsonSerializer interface.

By default Giraffe offers two IJsonSerializer implementations out of the box:

Name Description Default
NewtonsoftJsonSerializer Uses Newtonsoft.Json aka JSON.Net for JSON (de-)serialization in Giraffe. It is the most downloaded library on NuGet, battle tested by millions of users and has great support for F# data types. Use this json serializer for maximum compatibility and easy adoption. True
Utf8JsonSerializer Uses Utf8Json for JSON (de-)serialization in Giraffe. This is the fastest JSON serializer written in .NET with huge extensibility points and native support for directly serializing JSON content to the HTTP response stream via chunked encoding. This serializer has been specifically crafted for maximum performance and should be used when that extra perf is important. False

The Utf8JsonSerializer can be used instead of the NewtonsoftJsonSerializer by registering a new dependency of type IJsonSerializer during application configuration:

let configureServices (services : IServiceCollection) =
    // First register all default Giraffe dependencies
    services.AddGiraffe() |> ignore

    // Now register Utf8JsonSerializer
    this.AddSingleton<IJsonSerializer>(Utf8JsonSerializer(Utf8JsonSerializer.DefaultResolver)) |> ignore

Customizing JsonSerializerSettings

You can change the default JsonSerializerSettings of the NewtonsoftJsonSerializer by registering a new instance of NewtonsoftJsonSerializer during application startup:

let configureServices (services : IServiceCollection) =
    // First register all default Giraffe dependencies
    services.AddGiraffe() |> ignore

    // Now customize only the IJsonSerializer by providing a custom
    // object of JsonSerializerSettings
    let customSettings = JsonSerializerSettings(
        Culture = CultureInfo("de-DE"))

    services.AddSingleton<IJsonSerializer>(
        NewtonsoftJsonSerializer(customSettings)) |> ignore

[<EntryPoint>]
let main _ =
    WebHost.CreateDefaultBuilder()
        .Configure(Action<IApplicationBuilder> configureApp)
        .ConfigureServices(configureServices)
        .ConfigureLogging(configureLogging)
        .Build()
        .Run()
    0

Using a different JSON serializer

You can change the entire underlying JSON serializer by creating a new class which implements the IJsonSerializer interface:

type CustomJsonSerializer() =
    interface IJsonSerializer with
        // Use different JSON library ...
        member __.SerializeToString<'T>      (x : 'T) = // ...
        member __.SerializeToBytes<'T>       (x : 'T) = // ...
        member __.SerializeToStreamAsync<'T> (x : 'T) = // ...

        member __.Deserialize<'T> (json : string) = // ...
        member __.Deserialize<'T> (bytes : byte[]) = // ...
        member __.DeserializeAsync<'T> (stream : Stream) = // ...

Then register a new instance of the newly created type during application startup:

let configureServices (services : IServiceCollection) =
    // First register all default Giraffe dependencies
    services.AddGiraffe() |> ignore

    // Now register your custom IJsonSerializer
    services.AddSingleton<IJsonSerializer, CustomJsonSerializer>() |> ignore

[<EntryPoint>]
let main _ =
    WebHost.CreateDefaultBuilder()
        .Configure(Action<IApplicationBuilder> configureApp)
        .ConfigureServices(configureServices)
        .ConfigureLogging(configureLogging)
        .Build()
        .Run()
    0

Retrieving the JSON serializer from a custom HttpHandler

If you need you retrieve the registered JSON serializer from a custom HttpHandler function then you can do this with the GetJsonSerializer extension method:

let customHandler (dataObj : obj) : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        let serializer = ctx.GetJsonSerializer()
        let json = serializer.Serialize dataObj
        // ... do more...

XML

By default Giraffe uses the System.Xml.Serialization.XmlSerializer for (de-)serializing XML content. An application can modify the serializer by registering a new dependency which implements the IXmlSerializer interface during application startup.

Customizing Giraffe's XML serialization can either happen via providing a custom object of XmlWriterSettings when instantiating the default DefaultXmlSerializer or swap in an entire different XML library by creating a new class which implements the IXmlSerializer interface.

Customizing XmlWriterSettings

You can change the default XmlWriterSettings of the DefaultXmlSerializer by registering a new instance of DefaultXmlSerializer during application startup:

let configureServices (services : IServiceCollection) =
    // First register all default Giraffe dependencies
    services.AddGiraffe() |> ignore

    // Now customize the IXmlSerializer
    let customSettings =
        XmlWriterSettings(
                Encoding           = Encoding.UTF8,
                Indent             = false,
                OmitXmlDeclaration = true
            )

    services.AddSingleton<IXmlSerializer>(
        DefaultXmlSerializer(customSettings)) |> ignore

[<EntryPoint>]
let main _ =
    WebHost.CreateDefaultBuilder()
        .Configure(Action<IApplicationBuilder> configureApp)
        .ConfigureServices(configureServices)
        .ConfigureLogging(configureLogging)
        .Build()
        .Run()
    0

Using a different XML serializer

You can change the entire underlying XML serializer by creating a new class which implements the IXmlSerializer interface:

type CustomXmlSerializer() =
    interface IXmlSerializer with
        // Use different XML library ...
        member __.Serialize (o : obj) = // ...
        member __.Deserialize<'T> (xml : string) = // ...

Then register a new instance of the newly created type during application startup:

let configureServices (services : IServiceCollection) =
    // First register all default Giraffe dependencies
    services.AddGiraffe() |> ignore

    // Now register your custom IXmlSerializer
    services.AddSingleton<IXmlSerializer, CustomXmlSerializer>() |> ignore

[<EntryPoint>]
let main _ =
    WebHost.CreateDefaultBuilder()
        .Configure(Action<IApplicationBuilder> configureApp)
        .ConfigureServices(configureServices)
        .ConfigureLogging(configureLogging)
        .Build()
        .Run()
    0

Retrieving the XML serializer from a custom HttpHandler

If you need you retrieve the registered XML serializer from a custom HttpHandler function then you can do this with the GetXmlSerializer extension method:

let customHandler (dataObj : obj) : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        let serializer = ctx.GetXmlSerializer()
        let xml = serializer.Serialize dataObj
        // ... do more...

Miscellaneous

On top of default HTTP related functions such as HttpContext extension methods and HttpHandler functions Giraffe also provides a few other helper functions which are commonly required in Giraffe web applications.

Short GUIDs and Short IDs

The ShortGuid and ShortId modules offer helper functions to work with Short GUIDs and Short IDs inside Giraffe.

ShortGuid

The ShortGuid.fromGuid function will convert a System.Guid into a URL friendly 22 character long string value.

The ShortGuid.toGuid function will convert a 22 character short GUID string into a valid System.Guid object. This function can be useful when converting a string query parameter into a valid Guid argument:

let someHttpHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        let guid =
            match ctx.TryGetQueryStringValue "id" with
            | None           -> Guid.Empty
            | Some shortGuid -> ShortGuid.toGuid shortGuid

        // Do something with `guid`...
        // Return a Task<HttpContext option>

ShortId

The ShortId.fromUInt64 function will convert an uint64 into a URL friendly 11 character long string value.

The ShortId.toUInt64 function will convert a 11 character short ID string into a uint64 value. This function can be useful when converting a string query parameter into a valid uint64 argument:

let someHttpHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        let id =
            match ctx.TryGetQueryStringValue "id" with
            | None         -> 0UL
            | Some shortId -> ShortId.toUInt64 shortId

        // Do something with `id`...
        // Return a Task<HttpContext option>

Short GUIDs and short IDs can also be automatically resolved from route arguments.

Common Helper Functions

Additional useful HttpContext extension methods

The GetRequestUrl extension method of the HttpContext type can be used to retrieve the entire URL of the HTTP request as a string value:

let someHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        let requestUrl = ctx.GetRequestUrl()
        text (sprintf "The request URL is: %s" requestUrl) next ctx

DateTime Extension methods

Giraffe automatically adds the ToHtmlString() extension method to DateTime and DateTimeOffset objects which formats a given timestamp into an RFC3339 formatted string:

let now = DateTimeOffset.UtcNow
let htmlFormattedTimestamp = now.ToHtmlString()

isNotNull

The F# language provides an isNull function for checking null values when interoping with other .NET languages. Unfortunately there is no isNotNull function by default. Giraffe closes that gap by providing an additional isNotNull function:

if isNotNull someObj then
    // ... do stuff here
else
    // ... do other stuff here

strOption

An F# application often has to check if a string value is null when interoping with other .NET languages. Representing an optionally missing value with null is unnatural in F# and therefore Giraffe provides the strOption function which can convert a string into an Option<string> value for a more natural F# experience. If a string is null then the strOptoin function will return None, otherwise Some string:

let someDateTime =
    match strOption someString with
    | Some str -> DateTimeOffset.Parse str
    | None     -> DateTimeOffset.UtcNow

readFileAsStringAsync

Reading a file from the local file system is often a common use case in a web application. The readFileAsStringAsync function will asynchronously read the entire content of a given filePath from the local file system:

let someFunction =
    task {
        let! content = readFileAsStringAsync "myfile.txt"
        // ... do stuff
    }

Computation Expressions

Giraffe provides two additional computation expressions which can be used with Option<'T> and Result<'T, 'TError> objects.

The opt {} computation expression can be used to bind options and the res {} computation expression can be used to bind result objects:

open Giraffe.ComputationExpressions

let someHttpHandler : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        let result =
            res {
                let! header1 = ctx.GetRequestHeader "X-Header-1"
                let! header2 = ctx.GetRequestHeader "X-Header-2"
                let! header3 = ctx.GetRequestHeader "X-Header-3"
                return (header1, header2, header3)
            }
        match result with
        | Ok (h1, h2, h3) ->
            sprintf "%s, %s, %s" h1 h2 h3
            |> ctx.WriteTextAsync
        | Error msg -> RequestErrors.BAD_REQUEST msg next ctx

Additional Features

There's more features available for Giraffe web applications through additional NuGet packages:

TokenRouter

The Giraffe.TokenRouter NuGet package exposes an alternative routing HttpHandler which is based on top of a Radix Tree. Several routing handlers (e.g.: routef and subRoute) have been overridden in such a way that path matching and value parsing are significantly faster than using the basic choose function.

This implementation assumes that additional memory and compilation time is not an issue. If speed and performance of parsing and path matching is required then the Giraffe.TokenRouter can be a much better fit.

Please check the official Giraffe TokenRouter GitHub repository for more information.

Razor

The Giraffe.Razor NuGet package adds fully featured Razor support to Giraffe web applications.

For more information please visit the official Giraffe Razor GitHub repository.

DotLiquid

The Giraffe.DotLiquid NuGet package adds DotLiquid support to Giraffe web applications.

For more information please visit the official Giraffe DotLiquid GitHub repository.

Special Mentions

Saturn

Saturn is an opinionated, web development framework built on top of Giraffe which implements the server-side, functional MVC pattern for F#.

Saturn is not directly part of Giraffe but builds a Phoenix inspired MVC pattern on top of Giraffe. It is being developed and maintained by the author of the Ionide project.

Appendix

Aleksander Heintz's query string binder API

[<AutoOpen>]
module Giraffe.Query

open Aether
open Microsoft.AspNetCore.Http

module Helpers =
  let konst v _ = v

  [<RequireQualifiedAccess>]
  module Option =
    let inline ofBool b = if b then Some [] else None

open Helpers

[<AutoOpen>]
module Values =
  type QueryValue = string list option

  [<RequireQualifiedAccess>]
  module QueryValue =

    let inline private (|Empty|NonEmpty|) xs =
      match xs with
      | [] -> Empty
      | _  -> NonEmpty xs

    (* Epimorphisms *)

    let private Zero__ =
      (function | None -> Some ()
                | _    -> None), konst None

    let private Bool__ =
      (function | None       -> Some false
                | Some Empty -> Some true
                | _          -> None), Option.ofBool

    let private String__ =
      (function | Some [v] -> Some v
                | _        -> None), List.singleton >> Some

    let private List__ =
      (function | None    -> Some []
                | Some vs -> Some vs), Some

    (* Prisms *)

    let Zero_ =
      Prism.ofEpimorphism Zero__

    let Bool_ =
      Prism.ofEpimorphism Bool__

    let String_ =
      Prism.ofEpimorphism String__

    let List_ =
      Prism.ofEpimorphism List__

  (* Functional *)
  [<AutoOpen>]
  module Functional =
    type QueryValueResult<'a> = Result<'a, string>
    type QueryValue<'a> = QueryValue -> QueryValueResult<'a> * QueryValue

    (* Functions *)

    [<RequireQualifiedAccess>]
    module QueryValue =
      let inline unit (a: 'a) : QueryValue<_> =
        fun value ->
          Ok a, value

      let zero = unit ()

      let inline error (e: string) : QueryValue<_> =
        fun value ->
          Error e, value

      let inline internal ofResult result =
        fun value ->
          result, value

      let inline bind (m: QueryValue<'a>) (f: 'a -> QueryValue<'b>) : QueryValue<'b> =
        fun value ->
          match m value with
          | Ok a, value    -> f a value
          | Error e, value -> Error e, value

      let inline apply (f: QueryValue<'a -> 'b>) (m: QueryValue<'a>) : QueryValue<'b> =
        bind f (fun f' ->
          bind m (f' >> unit))

      let inline map (f: 'a -> 'b) (m: QueryValue<'a>) : QueryValue<'b> =
        bind m (f >> unit)

      let inline map2 (f: 'a -> 'b -> 'c) (m1: QueryValue<'a>) (m2: QueryValue<'b>) : QueryValue<'c> =
        apply (apply (unit f) m1) m2

    (* Operators *)

    module Operators =
      let inline (>>=) m f =
        QueryValue.bind m f

      let inline (=<<) f m =
        QueryValue.bind m f

      let inline (<*>) f m =
        QueryValue.apply f m

      let inline (<!>) f m =
        QueryValue.map f m

      let inline ( *>) m1 m2 =
        QueryValue.map2 (konst id) m1 m2

      let inline (<* ) m1 m2 =
        QueryValue.map2 konst m1 m2

      let inline (>=>) f g =
        fun x -> f x >>= g

      let inline (<=<) g f =
        fun x -> f x >>= g

  module Builder =
    open Operators

    type QueryValueBuilder () =
      member inline __.Bind (m1, f) = m1 >>= f

      member inline __.Combine (m1, m2) = m1 *> m2

      member inline __.Delay f = QueryValue.zero >>= f

      member inline __.Return x = QueryValue.unit x

      member inline __.Zero () = QueryValue.zero

  let queryValue = Builder.QueryValueBuilder ()

  [<AutoOpen>]
  module Optic =

    [<RequireQualifiedAccess>]
    module QueryValue =

      [<RequireQualifiedAccess>]
      module Optic =

        type Get =
          | Get with

          static member (^.) (Get, l: Lens<QueryValue, 'b>) : QueryValue<_> =
            fun value ->
              Ok (Optic.get l value), value

          static member (^.) (Get, p: Prism<QueryValue, 'b>) : QueryValue<_> =
            fun value ->
              match Optic.get p value with
              | Some x -> Ok x, value
              | None   -> Error (sprintf "Couldn't use Prism %A on query string value: '%A'" p value), value

        let inline get o : QueryValue<_> =
          (Get ^. o)

        type TryGet =
          | TryGet with

          static member (^.) (TryGet, l: Lens<QueryValue, 'b>) : QueryValue<_> =
            fun value ->
              Ok (Some (Optic.get l value)), value

          static member (^.) (TryGet, p: Prism<QueryValue, 'b>) : QueryValue<_> =
            fun value ->
              Ok (Optic.get p value), value

        let inline tryGet o : QueryValue<_> =
          (TryGet ^. o)

        let inline set o v : QueryValue<_> =
          fun query ->
            Ok (), Optic.set o v query

        let inline map o f : QueryValue<_> =
          fun query ->
            Ok (), Optic.map o f query

  [<AutoOpen>]
  module Mapping =
    open Operators

    (* From *)

    (* Defaults *)

    type FromQueryValueDefaults = FromQueryValueDefaults with

      (* Basic Types *)

      static member inline FromQueryValue (_: unit) =
        QueryValue.Optic.get QueryValue.Zero_

      static member inline FromQueryValue (_: bool) =
        QueryValue.Optic.get QueryValue.Bool_

      static member inline FromQueryValue (_: string) =
        QueryValue.Optic.get QueryValue.String_

      static member inline FromQueryValue (_: QueryValue) =
        QueryValue.Optic.get id_

    (* Mapping Functions *)

    let inline internal fromQueryValueDefaults (a: ^a, _: ^b) =
      ((^a or ^b) : (static member FromQueryValue: ^a -> ^a QueryValue) a)

    let inline internal fromQueryValue x =
      fst (fromQueryValueDefaults (Unchecked.defaultof<'a>, FromQueryValueDefaults) x)

    let inline internal fromQueryValueFold xs =
      List.fold (fun r x ->
        match r with
        | Error e -> Error e
        | Ok xs   ->
          match fromQueryValue x with
          | Ok x    -> Ok (x :: xs)
          | Error e -> Error e) (Ok []) (xs |> List.map (List.singleton >> Some) |> List.rev)

    let inline private tryParse name f =
      fun value ->
        match f value with
        | true, v -> fun value -> Ok v, value
        | _       -> fun value -> Error (sprintf "Failed to parse '%A' as %s" value name), value

    (* Defaults *)

    open System
    type FromQueryValueDefaults with

      (* Numbers *)

      static member inline FromQueryValue (_: float) =
            tryParse "float" Double.TryParse
        =<< QueryValue.Optic.get QueryValue.String_

      static member inline FromQueryValue (_: decimal) =
            tryParse "decimal" Decimal.TryParse
        =<< QueryValue.Optic.get QueryValue.String_

      static member inline FromQueryValue (_: int) =
            tryParse "int" Int32.TryParse
        =<< QueryValue.Optic.get QueryValue.String_

      static member inline FromQueryValue (_: int16) =
            tryParse "int16" Int16.TryParse
        =<< QueryValue.Optic.get QueryValue.String_

      static member inline FromQueryValue (_: int64) =
            tryParse "int64" Int64.TryParse
        =<< QueryValue.Optic.get QueryValue.String_

      static member inline FromQueryValue (_: float32) =
            tryParse "float32" Single.TryParse
        =<< QueryValue.Optic.get QueryValue.String_

      static member inline FromQueryValue (_: uint16) =
            tryParse "uint16" UInt16.TryParse
        =<< QueryValue.Optic.get QueryValue.String_

      static member inline FromQueryValue (_: uint32) =
            tryParse "uint32" UInt32.TryParse
        =<< QueryValue.Optic.get QueryValue.String_

      static member inline FromQueryValue (_: uint64) =
            tryParse "uint64" UInt64.TryParse
        =<< QueryValue.Optic.get QueryValue.String_

      (* Lists *)

      static member inline FromQueryValue (_: 'a list) : QueryValue<'a list> =
            fromQueryValueFold >> QueryValue.ofResult
        =<< QueryValue.Optic.get QueryValue.List_

      static member inline FromQueryValue (_: 'a array) : QueryValue<'a array> =
            fromQueryValueFold >> Result.map Array.ofList >> QueryValue.ofResult
        =<< QueryValue.Optic.get QueryValue.List_


      (* Set *)

      static member inline FromQueryValue (_: Set<'a>) : QueryValue<Set<'a>> =
            fromQueryValueFold >> Result.map Set.ofList >> QueryValue.ofResult
        =<< QueryValue.Optic.get QueryValue.List_


      (* Options *)

      static member inline FromQueryValue (_: 'a option) : QueryValue<'a option> =
        fun value ->
          match fromQueryValue value with
          | Ok v    -> Ok (Some v), value
          | _       -> Ok None, value

type Query = Map<string, string list>

module Convert =
  open Microsoft.AspNetCore.WebUtilities

  let toQuery (qs: QueryString) : Query =
    QueryHelpers.ParseQuery qs.Value
    |> Seq.map (fun kvp -> kvp.Key, kvp.Value |> List.ofSeq)
    |> Map.ofSeq

(* Functional *)

[<AutoOpen>]
module Functional =
  type QueryResult<'a> = Result<'a, string>
  type Query<'a> = Query -> QueryResult<'a> * Query

  (* Functions *)

  [<RequireQualifiedAccess>]
  module Query =
    let inline unit x : Query<_> =
      fun query ->
        Ok x, query

    let zero = unit ()

    let inline error e : Query<_> =
      fun query ->
        Error e, query

    let inline internal ofResult result =
      fun query ->
        result, query

    let inline bind (m: Query<'a>) (f: 'a -> Query<'b>) : Query<'b> =
      fun query ->
        match m query with
        | Ok a, query    -> f a query
        | Error e, query -> Error e, query

    let inline apply f m =
      bind f (fun f' ->
        bind m (f' >> unit))

    let inline map f m =
      bind m (f >> unit)

    let inline map2 f m1 m2 =
      apply (apply (unit f) m1) m2

(* Operators *)

module Operators =
  let inline (>>=) m f =
    Query.bind m f

  let inline (=<<) f m =
    Query.bind m f

  let inline (<*>) f m =
    Query.apply f m

  let inline (<!>) f m =
    Query.map f m

  let inline ( *>) m1 m2 =
    Query.map2 (konst id) m1 m2

  let inline (<* ) m1 m2 =
    Query.map2 konst m1 m2

  let inline (>=>) f g =
    fun x -> f x >>= g

  let inline (<=<) g f =
    fun x -> f x >>= g

(* Builder *)

module Builder =
  open Operators

  type QueryBuilder () =
    member inline __.Bind (m1, f) = m1 >>= f

    member inline __.Combine (m1, m2) = m1 *> m2

    member inline __.Delay f = Query.zero >>= f

    member inline __.Return x = Query.unit x

    member inline __.Zero () = Query.zero

let query = Builder.QueryBuilder ()

[<AutoOpen>]
module Mapping =
  open Operators

  (* From *)

  (* Defaults *)

  type FromQueryDefaults = FromQueryDefaults with

    static member inline FromQuery (_: Query) : Query<Query> =
      fun query -> Ok query, query

    static member inline FromQuery (_: Map<string, string>) : Query<Map<string, string>> =
      fun query ->
        let ret =
          query
          |> Map.filter (konst (function | [_] -> true | _ -> false))
          |> Map.map (konst List.head)
        Ok ret, query

  (* Mapping Functions *)

  let inline internal fromQueryDefaults (a: ^a, _: ^b) =
    ((^a or ^b) : (static member FromQuery: ^a -> ^a Query) a)

  let inline internal fromQuery x =
    fst (fromQueryDefaults (Unchecked.defaultof<'a>, FromQueryDefaults) x)

  (* Functions *)

  [<RequireQualifiedAccess>]
  module Query =

    (* Read *)

    let private readValue key =
      fun query ->
        Ok (Map.tryFind key query), query

    let readMemberWith fromQueryValue key =
         readValue key
      >>= fun value ->
         match fromQueryValue value with
         | Ok v    -> Query.unit v
         | Error e -> Query.error (sprintf "%s: %s" key e)

    let inline readWith fromQueryValue key =
      readMemberWith fromQueryValue key

    let inline read key =
      readWith fromQueryValue key

    let inline parse qs =
      fromQuery (Convert.toQuery qs)
      |> function | Ok a         -> a
                  | Error e      -> failwith e

    let inline tryParse qs =
      fromQuery (Convert.toQuery qs)
      |> function | Ok a         -> Some a
                  | Error _      -> None

[<AutoOpen>]
module HttpHandlers =
  open Giraffe.HttpHandlers
  open Giraffe.Tasks
  open System.Threading.Tasks

  module Query =

    let inline bind (f: ^a -> HttpHandler) : HttpHandler =
      fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
          match Query.tryParse ctx.Request.QueryString with
          | None   -> return None
          | Some a -> return! f a next ctx
        }

    let inline bindTask (f: ^a -> Task<HttpHandler>) : HttpHandler =
      fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
          match Query.tryParse ctx.Request.QueryString with
          | None   -> return None
          | Some a ->
            let! handler = f a
            return! handler next ctx
        }