Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support F# Async<'T> as an awaitable type in minimal APIs #46773

Closed
1 task done
brianrourkeboll opened this issue Feb 21, 2023 · 6 comments · Fixed by #46898
Closed
1 task done

Support F# Async<'T> as an awaitable type in minimal APIs #46773

brianrourkeboll opened this issue Feb 21, 2023 · 6 comments · Fixed by #46898
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc fsharp Used to flag F# template changes for review by area experts help candidate Indicates that the issues may be a good fit for community to help with. Requires work from eng. team
Milestone

Comments

@brianrourkeboll
Copy link
Contributor

brianrourkeboll commented Feb 21, 2023

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

The F# Async<'T> type is supported as an awaitable return type for ASP.NET Core controller methods, but not for endpoints added via the .Map* minimal APIs extension methods.

Describe the solution you'd like

I'd like to be able to return F# async values and have them awaited when using minimal APIs endpoints just as I can for controller action methods.

Examples

  • Basic controller example:

    type MyController () =
        inherit ControllerBase ()
        // The framework automatically awaits the async value.
        [<HttpGet>]
        member _.Get () = async { return [1 .. 10] }
  • Minimal APIs example:

    // This does not await.
    app.MapGet ("/", Func<Async<int list>> (fun () -> async { return [1 .. 10] }))
    // Converting the F# async value to a task is required.
    app.MapGet ("/", Func<Task<int list>> (fun () -> async { return [1 .. 10] } |> Async.StartAsTask))

Additional context

I can perform the conversion myself using code like this:

open System
open System.Linq.Expressions
open System.Threading
open System.Threading.Tasks

let taskify (f : Delegate) =
    if isNull f then
        nullArg (nameof f)

    let meth = f.Method
    let returnTy = meth.ReturnType
    if returnTy.IsGenericType && returnTy.GetGenericTypeDefinition () = typedefof<Async<_>> then
        let resultTy = returnTy.GetGenericArguments () |> Array.exactlyOne
        let parameters = [| for param in meth.GetParameters () -> Expression.Parameter (param.ParameterType, param.Name) |]
        let lambda =
            Expression.Lambda
                (
                    Expression.Call
                        (
                            (typeof<Async>.GetMethod (nameof Async.StartAsTask)).MakeGenericMethod resultTy,
                            Expression.Invoke
                                (
                                    Expression.Constant f,
                                    box parameters :?> Expression array
                                ),
                            Expression.MakeMemberAccess (null, typeof<TaskCreationOptions option>.GetProperty (nameof None)),
                            Expression.MakeMemberAccess (null, typeof<CancellationToken option>.GetProperty (nameof None))
                        ),
                    parameters
                )

        lambda.Compile ()
    else
        f

let d = Func<_, _> (fun (i : int) -> async { return i })
((taskify d :?> Func<int, Task<int>>).Invoke 3).Result

But if I do so, I lose metadata from the original delegate, including parameter names, which affects the generated Swagger/Open API definition, among other things.

@mitchdenny
Copy link
Member

Unfortunately Async is a language specific type in F#. I don't think we could take a dependency on that.

@brianrourkeboll
Copy link
Contributor Author

Unfortunately Async is a language specific type in F#. I don't think we could take a dependency on that.

Yes, although it is already supported in the framework dynamically for controller action methods.

@En3Tho
Copy link

En3Tho commented Feb 21, 2023

Why not just use task { ... } directly?
Why not just create a set of overloads for async and call StartAsTask inside?

My experience with minimal apis and F# is that you need a dedicated set of Func overloads anyway or else you have to littler your code with explicit Func annotations everywhere

@mkArtakMSFT mkArtakMSFT added area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates fsharp Used to flag F# template changes for review by area experts area-web-frameworks and removed area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates labels Feb 21, 2023
@captainsafia captainsafia added the help wanted Up for grabs. We would accept a PR to help resolve this issue label Feb 21, 2023
@captainsafia
Copy link
Member

Triage: We think we might've missed adding support for this scenario and would welcome any PRs!

@captainsafia captainsafia added this to the Backlog milestone Feb 21, 2023
@ghost
Copy link

ghost commented Feb 21, 2023

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@captainsafia captainsafia added area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc and removed area-web-frameworks labels Jun 6, 2023
@mkArtakMSFT mkArtakMSFT added help candidate Indicates that the issues may be a good fit for community to help with. Requires work from eng. team and removed help wanted Up for grabs. We would accept a PR to help resolve this issue labels Oct 28, 2023
@lucasteles
Copy link

is there any update on this?

@ghost ghost locked as resolved and limited conversation to collaborators Feb 7, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc fsharp Used to flag F# template changes for review by area experts help candidate Indicates that the issues may be a good fit for community to help with. Requires work from eng. team
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants
@mitchdenny @captainsafia @lucasteles @brianrourkeboll @mkArtakMSFT @En3Tho and others