How to use CancellableTask in ASP.NET Minimal API
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
[<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>
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
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>
<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
(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
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
(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 -> Task </summary>
--------------------
type CancellableTask<'T> = CancellationToken -> Task<'T>
<summary> CancellationToken -> Task<'T> </summary>
module CancellableTask from IcedTasks.CancellableTasks
--------------------
type CancellableTask = CancellationToken -> Task
<summary> CancellationToken -> Task </summary>
--------------------
type CancellableTask<'T> = CancellationToken -> Task<'T>
<summary> CancellationToken -> Task<'T> </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) 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
(extension) AspNetCore.Routing.IEndpointRouteBuilder.MapGet(pattern: string, handler: Delegate) : RouteHandlerBuilder