0

I'm trying to convert this C# example from the .NET Minimal APIs Quick Reference that sets up a keyed service.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");

var app = builder.Build();

app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));

app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) => smallCache.Get("date"));

app.Run();

public interface ICache
{
    object Get(string key);
}
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

I feel like I've tried everything and it doesn't work. I know that (1) you cannot add attribute for a fun lambda and that (2) you have to pass let bound functions to the Func constructor to get the compiler to see an F# function as a Delegate (otherwise it can't resolve the overload). But none of these approaches work.

This fails at runtime with this exception:

InvalidOperationException: Body was inferred but the method does not allow inferred body parameters.
Below is the list of parameters that we found:

Parameter | Source
---------------------------------------------------------------------------------
delegateArg0 | Body (Inferred)


Did you mean to register the "Body (Inferred)" parameter(s) as a Service or apply the [FromServices] or [FromBody] attribute?
open System
#nowarn 20
open Microsoft.AspNetCore.Builder
open Microsoft.Extensions.Hosting
open Microsoft.Extensions.DependencyInjection

type ICache =
    abstract Get: key: string -> obj

type BigCache() =
    interface ICache with
        member _.Get(key: string) = $"Resolving {key} from big cache."

type SmallCache() =
    interface ICache with
        member _.Get(key: string) = $"Resolving {key} from small cache."

let moduleLevelLet ([<FromKeyedServices("big")>] cache: ICache) =
    cache.Get("data")

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

    builder.Services.AddKeyedSingleton<ICache, BigCache>("big")
    builder.Services.AddKeyedSingleton<ICache, SmallCache>("small")

    let app = builder.Build();

    app.MapGet("/big", Func<ICache,obj>(moduleLevelLet))

    app.Run()

    0 // Exit code

This fails at runtime with the same exception, except Parameter is now bigCache instead of delegateArg0. It also fails if I make them instance variables and pass in AppMethods().getBig:

// -- snip --

type AppMethods =
    static member getBig ([<FromKeyedServices("big")>] bigCache : ICache) =
        bigCache.Get("date")

    static member getSmall ([<FromKeyedServices("small")>] smallCache : ICache) =
        smallCache.Get("date")

[<EntryPoint>]
let main args =
    // -- snip --
    app.MapGet("/big", Func<ICache,obj>(AppMethods.getBig))

    // -- snip --

This actually compiles if it's a top-level let but has the same runtime exception.

let getBigFun = fun ([<FromKeyedServices("big")>] bigCache: ICache) ->
    bigCache.Get("date")

My suspicion is that passing whatever to Func<_, _>(...) causes it to lose its attributes, but I don't know another way around it.

I also know that there are other solutions like Giraffe or using controllers. I'm looking for a vanilla minimal ASP.NET + F# way to do this (since there is a F# template for dotnet new web that defaults to this minimal setup and Microsoft now explicitly recommends the Minimal API for new projects). I'm running .NET 8.

4
  • 3
    Suspect this is relevant: github.com/fsharp/fslang-suggestions/issues/984 Commented Sep 18 at 18:53
  • There's a comment in that thread that shows the decompilation - attributes are indeed stripped. Commented Sep 19 at 10:18
  • You could look at Giraffei which wraps ASP.NET Core in an F# style. Commented Sep 23 at 6:50
  • @Richard my post does mention Giraffe, yes Commented Sep 28 at 0:14

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.