Header menu logo IcedTasks

How to use CancellableTask in ASP.NET Minimal API

See Minimal API docs

To use a cancellableTask with Minimal APIs, 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.

#load "../../runtime-scripts/Microsoft.AspNetCore.App-latest-8.fsx"
#r "nuget: IcedTasks"


open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Http
open Microsoft.Extensions.Hosting
open System.Threading
open System.Threading.Tasks
open IcedTasks

// 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, cancellationToken) }

module ExampleVerbose =

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

            ctx.Response.ContentType <- "application/json"
            do! ctx.Response.WriteAsJsonAsync(result)
        }

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

    // Minimal API app
    let app =
        let builder = WebApplication.CreateBuilder()
        let app = builder.Build()

        // MapGet requires a RequestDelegate, so we need wrap it since there's no implicit conversion
        app.MapGet("/", RequestDelegate(fun ctx -> myCustomHandler ctx))
        |> ignore

        app

module ExampleRefactor1 =
    open System

    // A helper to get the context's RequestAborted CancellationToken and pass it to any cancellableTask
    // Remember a CancellableTask is a function with the signature of CancellationToken -> Task<'T>
    let inline cancellableHandler (cancellableHandler: HttpContext -> CancellableTask<unit>) =
        //ASP.NET MapGet requires a RequestDelegate, so we need wrap it since there's no implicit conversion
        RequestDelegate(fun ctx -> cancellableHandler ctx ctx.RequestAborted)

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

            ctx.Response.ContentType <- "application/json"
            do! ctx.Response.WriteAsJsonAsync(result)
        }

    // Minimal API app
    let app =
        let builder = WebApplication.CreateBuilder()
        let app = builder.Build()

        app.MapGet("/", cancellableHandler myRealWork)
        |> ignore

        app
namespace Microsoft
namespace Microsoft.AspNetCore
namespace Microsoft.AspNetCore.Builder
namespace Microsoft.AspNetCore.Http
namespace Microsoft.Extensions
namespace Microsoft.Extensions.Hosting
namespace System
namespace System.Threading
namespace System.Threading.Tasks
namespace IcedTasks
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: ctx: HttpContext -> CancellableTask<unit>
val ctx: HttpContext
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 ...
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>
property HttpContext.Response: HttpResponse with get
property HttpResponse.ContentType: string with get, set
(extension) HttpResponse.WriteAsJsonAsync<'TValue>(value: 'TValue, ?cancellationToken: CancellationToken) : Task
(extension) HttpResponse.WriteAsJsonAsync<'TValue>(value: 'TValue, options: System.Text.Json.JsonSerializerOptions, ?cancellationToken: CancellationToken) : Task
(extension) HttpResponse.WriteAsJsonAsync(value: obj, ``type`` : System.Type, ?cancellationToken: CancellationToken) : Task
(extension) HttpResponse.WriteAsJsonAsync<'TValue>(value: 'TValue, options: System.Text.Json.JsonSerializerOptions, contentType: string, ?cancellationToken: CancellationToken) : Task
(extension) HttpResponse.WriteAsJsonAsync<'TValue>(value: 'TValue, jsonTypeInfo: System.Text.Json.Serialization.Metadata.JsonTypeInfo<'TValue>, ?contentType: string, ?cancellationToken: CancellationToken) : Task
(extension) HttpResponse.WriteAsJsonAsync(value: obj, jsonTypeInfo: System.Text.Json.Serialization.Metadata.JsonTypeInfo, ?contentType: string, ?cancellationToken: CancellationToken) : Task
(extension) HttpResponse.WriteAsJsonAsync(value: obj, ``type`` : System.Type, options: System.Text.Json.JsonSerializerOptions, ?cancellationToken: CancellationToken) : Task
(extension) HttpResponse.WriteAsJsonAsync(value: obj, ``type`` : System.Type, options: System.Text.Json.JsonSerializerOptions, contentType: string, ?cancellationToken: CancellationToken) : Task
(extension) HttpResponse.WriteAsJsonAsync(value: obj, ``type`` : System.Type, context: System.Text.Json.Serialization.JsonSerializerContext, ?contentType: string, ?cancellationToken: CancellationToken) : Task
val myCustomHandler: ctx: HttpContext -> Task<unit>
property HttpContext.RequestAborted: CancellationToken with get, set
val app: WebApplication
val builder: WebApplicationBuilder
type WebApplication = interface IHost interface IDisposable interface IApplicationBuilder interface IEndpointRouteBuilder interface IAsyncDisposable member DisposeAsync: unit -> ValueTask member Run: ?url: string -> unit member RunAsync: ?url: string -> Task member StartAsync: ?cancellationToken: CancellationToken -> Task member StopAsync: ?cancellationToken: CancellationToken -> Task ...
WebApplication.CreateBuilder() : WebApplicationBuilder
WebApplication.CreateBuilder(options: WebApplicationOptions) : WebApplicationBuilder
WebApplication.CreateBuilder(args: string array) : WebApplicationBuilder
WebApplicationBuilder.Build() : WebApplication
(extension) AspNetCore.Routing.IEndpointRouteBuilder.MapGet(pattern: string, requestDelegate: RequestDelegate) : IEndpointConventionBuilder
(extension) AspNetCore.Routing.IEndpointRouteBuilder.MapGet(pattern: string, handler: System.Delegate) : RouteHandlerBuilder
type RequestDelegate = new: object: obj * method: nativeint -> unit member BeginInvoke: context: HttpContext * callback: AsyncCallback * object: obj -> IAsyncResult member EndInvoke: result: IAsyncResult -> Task member Invoke: context: HttpContext -> Task
val ignore: value: 'T -> unit
module ExampleRefactor1 from Cancellable-Task-in-Minimal-Api
val cancellableHandler: cancellableHandler: (HttpContext -> CancellationToken -> Task<unit>) -> RequestDelegate
val cancellableHandler: (HttpContext -> CancellationToken -> Task<unit>)
Multiple items
module CancellableTask from IcedTasks.CancellableTasks

--------------------
type CancellableTask = CancellationToken -> Task
<summary> CancellationToken -&gt; Task </summary>

--------------------
type CancellableTask<'T> = CancellationToken -> Task<'T>
<summary> CancellationToken -&gt; Task&lt;'T&gt; </summary>
type unit = Unit
(extension) HttpResponse.WriteAsJsonAsync<'TValue>(value: 'TValue, ?cancellationToken: CancellationToken) : Task
(extension) HttpResponse.WriteAsJsonAsync<'TValue>(value: 'TValue, options: Text.Json.JsonSerializerOptions, ?cancellationToken: CancellationToken) : Task
(extension) HttpResponse.WriteAsJsonAsync(value: obj, ``type`` : Type, ?cancellationToken: CancellationToken) : Task
(extension) HttpResponse.WriteAsJsonAsync<'TValue>(value: 'TValue, options: Text.Json.JsonSerializerOptions, contentType: string, ?cancellationToken: CancellationToken) : Task
(extension) HttpResponse.WriteAsJsonAsync<'TValue>(value: 'TValue, jsonTypeInfo: Text.Json.Serialization.Metadata.JsonTypeInfo<'TValue>, ?contentType: string, ?cancellationToken: CancellationToken) : Task
(extension) HttpResponse.WriteAsJsonAsync(value: obj, jsonTypeInfo: Text.Json.Serialization.Metadata.JsonTypeInfo, ?contentType: string, ?cancellationToken: CancellationToken) : Task
(extension) HttpResponse.WriteAsJsonAsync(value: obj, ``type`` : Type, options: Text.Json.JsonSerializerOptions, ?cancellationToken: CancellationToken) : Task
(extension) HttpResponse.WriteAsJsonAsync(value: obj, ``type`` : Type, options: Text.Json.JsonSerializerOptions, contentType: string, ?cancellationToken: CancellationToken) : Task
(extension) HttpResponse.WriteAsJsonAsync(value: obj, ``type`` : Type, context: Text.Json.Serialization.JsonSerializerContext, ?contentType: string, ?cancellationToken: CancellationToken) : Task
(extension) AspNetCore.Routing.IEndpointRouteBuilder.MapGet(pattern: string, requestDelegate: RequestDelegate) : IEndpointConventionBuilder
(extension) AspNetCore.Routing.IEndpointRouteBuilder.MapGet(pattern: string, handler: Delegate) : RouteHandlerBuilder

Type something to start searching.