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.