Header menu logo IcedTasks

How to use CancellableTask in Giraffe

See Giraffe Docs

To use a cancellableTask with Giraffe, we'll need to get the RequestAborted property off the HttpContext.

We'll start off with a slightly longer version but then make a helper for this.

// This just the dance to get Giraffe to compile for this example.
// Really you'd set up these references in an fsproj
#load "../../runtime-scripts/Microsoft.AspNetCore.App-latest-7.fsx"
#r "nuget: Giraffe"
#r "nuget: IcedTasks"

open Microsoft.AspNetCore.Http
open System.Threading
open System.Threading.Tasks
open IcedTasks
open Giraffe

// This is a stand in for some real database call like Npgsql where it would take a CancellationToken
type Database =
    static member Get(query, queryParams, cancellationToken: CancellationToken) =
        task { do! Task.Delay(10) }


module ExampleVerbose =


    // Some function that's doing the real handler's work
    let myRealWork next ctx =
        cancellableTask {
            // use a lamdbda to get the cancellableTask's current CancellationToken
            let! result =
                fun ct -> Database.Get("SELECT foo FROM bar where baz = @0", [ "@0", "buzz" ], ct)

            return! json result next ctx
        }

    // A helper to get the context's RequestAborted CancellationToken which will give the cancellableTask
    // the context to pass long.
    let myCustomHandler next (ctx: HttpContext) =
        task {
            let cancellationToken = ctx.RequestAborted
            return! myRealWork next ctx cancellationToken
        }

    // Some Giraffe App
    let app: HttpFunc -> HttpContext -> HttpFuncResult =
        route "/"
        >=> myCustomHandler

Now that we have the basic outline we can refactor this to make myCustomHandler take any cancellableTask

module ExampleRefactor1 =
    // A helper to get the context's RequestAborted CancellationToken which will give the cancellableTask
    // the context to pass long.
    let inline cancellableHandler
        (cancellableHandler: HttpFunc -> HttpContext -> CancellationToken -> Task<_>)
        (next: HttpFunc)
        (ctx: HttpContext)
        =
        task { return! cancellableHandler next ctx ctx.RequestAborted }

    // Some function that's doing the real handler's work
    let myRealWork next ctx =
        cancellableTask {
            // use a lamdbda to get the cancellableTask's current CancellationToken
            let! result =
                fun ct -> Database.Get("SELECT foo FROM bar where baz = @0", [ "@0", "buzz" ], ct)

            return! json result next ctx
        }


    // Some Giraffe App
    let app: HttpFunc -> HttpContext -> HttpFuncResult =
        route "/"
        >=> cancellableHandler myRealWork
namespace Microsoft
namespace Microsoft.AspNetCore
namespace Microsoft.AspNetCore.Http
namespace System
namespace System.Threading
namespace System.Threading.Tasks
namespace IcedTasks
namespace Giraffe
val query: 'a
val queryParams: 'b
val cancellationToken: CancellationToken
Multiple items
[<Struct>] type CancellationToken = new: canceled: bool -> unit member Equals: other: obj -> bool + 1 overload member GetHashCode: unit -> int member Register: callback: Action -> CancellationTokenRegistration + 4 overloads member ThrowIfCancellationRequested: unit -> unit member UnsafeRegister: callback: Action<obj,CancellationToken> * state: obj -> CancellationTokenRegistration + 1 overload static member (<>) : left: CancellationToken * right: CancellationToken -> bool static member (=) : left: CancellationToken * right: CancellationToken -> bool member CanBeCanceled: bool member IsCancellationRequested: bool ...
<summary>Propagates notification that operations should be canceled.</summary>

--------------------
CancellationToken ()
CancellationToken(canceled: bool) : CancellationToken
val task: TaskBuilder
Multiple items
type Task = interface IAsyncResult interface IDisposable new: action: Action -> unit + 7 overloads member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredTaskAwaitable + 1 overload member ContinueWith: continuationAction: Action<Task,obj> * state: obj -> Task + 19 overloads member Dispose: unit -> unit member GetAwaiter: unit -> TaskAwaiter member RunSynchronously: unit -> unit + 1 overload member Start: unit -> unit + 1 overload member Wait: unit -> unit + 5 overloads ...
<summary>Represents an asynchronous operation.</summary>

--------------------
type Task<'TResult> = inherit Task new: ``function`` : Func<obj,'TResult> * state: obj -> unit + 7 overloads member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredTaskAwaitable<'TResult> + 1 overload member ContinueWith: continuationAction: Action<Task<'TResult>,obj> * state: obj -> Task + 19 overloads member GetAwaiter: unit -> TaskAwaiter<'TResult> member WaitAsync: cancellationToken: CancellationToken -> Task<'TResult> + 4 overloads member Result: 'TResult static member Factory: TaskFactory<'TResult>
<summary>Represents an asynchronous operation that can return a value.</summary>
<typeparam name="TResult">The type of the result produced by this <see cref="T:System.Threading.Tasks.Task`1" />.</typeparam>


--------------------
Task(action: System.Action) : Task
Task(action: System.Action, cancellationToken: CancellationToken) : Task
Task(action: System.Action, creationOptions: TaskCreationOptions) : Task
Task(action: System.Action<obj>, state: obj) : Task
Task(action: System.Action, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task
Task(action: System.Action<obj>, state: obj, cancellationToken: CancellationToken) : Task
Task(action: System.Action<obj>, state: obj, creationOptions: TaskCreationOptions) : Task
Task(action: System.Action<obj>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task

--------------------
Task(``function`` : System.Func<'TResult>) : Task<'TResult>
Task(``function`` : System.Func<obj,'TResult>, state: obj) : Task<'TResult>
Task(``function`` : System.Func<'TResult>, cancellationToken: CancellationToken) : Task<'TResult>
Task(``function`` : System.Func<'TResult>, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : System.Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken) : Task<'TResult>
Task(``function`` : System.Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : System.Func<'TResult>, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : System.Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
Task.Delay(delay: System.TimeSpan) : Task
Task.Delay(millisecondsDelay: int) : Task
Task.Delay(delay: System.TimeSpan, timeProvider: System.TimeProvider) : Task
Task.Delay(delay: System.TimeSpan, cancellationToken: CancellationToken) : Task
Task.Delay(millisecondsDelay: int, cancellationToken: CancellationToken) : Task
Task.Delay(delay: System.TimeSpan, timeProvider: System.TimeProvider, cancellationToken: CancellationToken) : Task
val myRealWork: next: HttpFunc -> ctx: HttpContext -> CancellableTask<HttpContext option>
val next: HttpFunc
val ctx: HttpContext
val cancellableTask: CancellableTaskBuilder
<summary> Builds a cancellableTask using computation expression syntax. </summary>
val result: unit
val ct: CancellationToken
type Database = static member Get: query: 'a * queryParams: 'b * cancellationToken: CancellationToken -> Task<unit>
static member Database.Get: query: 'a * queryParams: 'b * cancellationToken: CancellationToken -> Task<unit>
val json: dataObj: 'T -> HttpFunc -> ctx: HttpContext -> HttpFuncResult
<summary> Serializes an object to JSON and writes the output to the body of the HTTP response. It also sets the HTTP Content-Type header to application/json and sets the Content-Length header accordingly. The JSON serializer can be configured in the ASP.NET Core startup code by registering a custom class of type <see cref="Json.ISerializer" />. </summary>
<param name="dataObj">The object to be send back to the client.</param>
<param name="ctx"></param>
<typeparam name="'T"></typeparam>
<returns>A Giraffe <see cref="HttpHandler" /> function which can be composed into a bigger web application.</returns>
val myCustomHandler: next: HttpFunc -> ctx: HttpContext -> Task<HttpContext option>
type HttpContext = override Abort: unit -> unit member Connection: ConnectionInfo member Features: IFeatureCollection member Items: IDictionary<obj,obj> member Request: HttpRequest member RequestAborted: CancellationToken member RequestServices: IServiceProvider member Response: HttpResponse member Session: ISession member TraceIdentifier: string ...
property HttpContext.RequestAborted: CancellationToken with get, set
val app: (HttpFunc -> HttpContext -> HttpFuncResult)
type HttpFunc = HttpContext -> HttpFuncResult
<summary> A HTTP function which takes an <see cref="Microsoft.AspNetCore.Http.HttpContext" /> object and returns a <see cref="HttpFuncResult" />. The function may inspect the incoming <see cref="Microsoft.AspNetCore.Http.HttpRequest" /> and make modifications to the <see cref="Microsoft.AspNetCore.Http.HttpResponse" /> before returning a <see cref="HttpFuncResult" />. The result can be either a <see cref="System.Threading.Tasks.Task" /> of Some HttpContext or a <see cref="System.Threading.Tasks.Task" /> of None. If the result is Some HttpContext then the Giraffe middleware will return the response to the client and end the pipeline. However, if the result is None then the Giraffe middleware will continue the ASP.NET Core pipeline by invoking the next middleware. </summary>
type HttpFuncResult = Task<HttpContext option>
<summary> A type alias for <see cref="System.Threading.Tasks.Task{HttpContext option}" /> which represents the result of a HTTP function (HttpFunc). If the result is Some HttpContext then the Giraffe middleware will return the response to the client and end the pipeline. However, if the result is None then the Giraffe middleware will continue the ASP.NET Core pipeline by invoking the next middleware. </summary>
val route: path: string -> next: HttpFunc -> ctx: HttpContext -> HttpFuncResult
<summary> Filters an incoming HTTP request based on the request path (case sensitive). </summary>
<param name="path">Request path.</param>
<param name="next"></param>
<param name="ctx"></param>
<returns>A Giraffe <see cref="HttpHandler" /> function which can be composed into a bigger web application.</returns>
module ExampleRefactor1 from Cancellable-Task-In-Giraffe
val cancellableHandler: cancellableHandler: (HttpFunc -> HttpContext -> CancellationToken -> Task<'a>) -> next: HttpFunc -> ctx: HttpContext -> Task<'a>
val cancellableHandler: (HttpFunc -> HttpContext -> CancellationToken -> Task<'a>)

Type something to start searching.