dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.19k stars 9.93k forks source link

Minimal API F# RouteValue is Null #38906

Open rfvgyhn opened 2 years ago

rfvgyhn commented 2 years ago

Mapping a route with a route value in F# fails to bind the value when using a named function instead of a anonymous function. I'm not sure if this is an ASP.NET or F# issue (or maybe just a consequence of how F# works with the delegate param).

dotnet new web -lang F#

open System
open Microsoft.AspNetCore.Builder
open Microsoft.Extensions.Hosting

[<EntryPoint>]
let main args =
    let builder = WebApplication.CreateBuilder(args)
    let app = builder.Build()

    let funcB v = v
    let funcC = fun v -> v 

    app.MapGet("/a/{v}", Func<string, _> (fun v -> v)) |> ignore
    app.MapGet("/b/{v}", Func<string, _> funcB) |> ignore
    app.MapGet("/c/{v}", Func<string, _> funcC) |> ignore

    app.Run()

    0 // Exit code

dotnet run

GET /a/test

test
GET /b/test

System.ArgumentNullException: Value cannot be null. (Parameter 'text')
   at Microsoft.AspNetCore.Http.HttpResponseWritingExtensions.WriteAsync(HttpResponse response, String text, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.ExecuteWriteStringResponseAsync(HttpContext httpContext, String text)
   at lambda_method2(Closure , Object , HttpContext )
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.<>c__DisplayClass36_0.<Create>b__0(HttpContext httpContext)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
GET /c/test

System.ArgumentNullException: Value cannot be null. (Parameter 'text')
   at Microsoft.AspNetCore.Http.HttpResponseWritingExtensions.WriteAsync(HttpResponse response, String text, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.ExecuteWriteStringResponseAsync(HttpContext httpContext, String text)
   at lambda_method3(Closure , Object , HttpContext )
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.<>c__DisplayClass36_0.<Create>b__0(HttpContext httpContext)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
$ dotnet --info
.NET SDK (reflecting any global.json):
 Version:   6.0.100
 Commit:    9e8b04bbff

Runtime Environment:
 OS Name:     arch
 OS Version:
 OS Platform: Linux
 RID:         arch-x64
 Base Path:   /usr/share/dotnet/sdk/6.0.100/

Host (useful for support):
  Version: 6.0.0
  Commit:  4822e3c3aa

.NET SDKs installed:
  6.0.100 [/usr/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.0 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.0 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
davidfowl commented 2 years ago

This is because the name of the parameter is lost through in the F# function conversions (as you've pointed out).

cc @dsyme

ghost commented 2 years ago

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.

mchylek commented 1 year ago

As a workaround you can also use wrapper with AsParametersAttribute

public static class Endpoint
{
    public static Delegate Of<TParam, TResult>(FSharpFunc<TParam, TResult> requestDelegate)
    {
        return (HttpContext context, [AsParametersAttribute] TParam parameters) => requestDelegate.Invoke(parameters);
    }
}

And then from F#

app.MapGet("/{name}", Endpoint.Of (fun (req : {| Name : string |}) -> $"Hello {req.Name}!"))

I've made simplistic library for this https://github.com/mchylek/Endpoints.FSharp.Interop

lucasteles commented 10 months ago

Also https://github.com/dotnet/aspnetcore/issues/46551#issuecomment-1810457875