Routes with named parameters are not matched
Describe the bug
Calls to a route with a named parameter, e.g. "/pet/%i:petId" are not matched.
Expected behavior
Calls should be matched as described in the named parameter example in the documentation for routef.
To Reproduce
- Set up a minimal web app:
// GiraffeApp.fsproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Giraffe" Version="7.0.2" />
</ItemGroup>
</Project>
// Program.fs
open Microsoft.AspNetCore.Hosting
open Microsoft.AspNetCore.Builder
open Microsoft.Extensions.Hosting
open Microsoft.Extensions.DependencyInjection
open Giraffe
let webApp =
choose [
// Named parameter example:
routef "/pet/%i:petId" (fun (petId: int) -> text (sprintf "PetId: %i" petId))
// If none of the routes matched then return a 404
RequestErrors.NOT_FOUND "Not Found"
]
let configureApp (app : IApplicationBuilder) =
// Add Giraffe to the ASP.NET Core pipeline
app.UseGiraffe webApp
let configureServices (services : IServiceCollection) =
// Add Giraffe dependencies
services.AddGiraffe() |> ignore
[<EntryPoint>]
let main _ =
Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(
fun webHostBuilder ->
webHostBuilder
.Configure(configureApp)
.ConfigureServices(configureServices)
|> ignore)
.Build()
.Run()
0
- Start the app and make a call to the endpoint:
PowerShell 7.5.4 PS C:\> curl -X GET "https://localhost:7162/pet/1" -k "Not Found"
Exceptions
None.
Environment:
- OS: Windows 11
- .NET version: 9.0.308
Hello @acatuttle, thanks for opening this issue. Indeed, this is not working.
Consulting the PR that added this feature (https://github.com/giraffe-fsharp/Giraffe/pull/656), I found out that it was added only to the EndpointRouting router. So this documentation is wrong.
For now, I think it doesn't make sense to update the Giraffe router to "support" named parameters because they're not really used. At least considering this PR: https://github.com/giraffe-fsharp/Giraffe/pull/705.
A wiser approach might be to just update the documentation.
Feel free to share your opinion @acatuttle.
I think it sounds like a good idea to update the documentation to avoid confusion. I played around and arrived at a working example which I think should be added in some fashion and mentioned in the section for routeBind (since routeBind does not work with endpoint routing?)
Also I feel like the tryBindRoute function from the example below is missing from the library?
// Program.fs
open Microsoft.AspNetCore.Hosting
open Microsoft.AspNetCore.Builder
open Microsoft.Extensions.Hosting
open Microsoft.Extensions.DependencyInjection
open Giraffe
open Giraffe.EndpointRouting
open Microsoft.AspNetCore.Routing
open Microsoft.AspNetCore.Http
open Microsoft.Extensions.Primitives
open System.Collections.Generic
[<CLIMutable>]
type Person =
{
FirstName : string
LastName : string
}
let tryBindRoute<'T> (ctx: HttpContext) : 'T option =
let routeData =
ctx.GetRouteData().Values
|> Seq.map (fun kvp -> KeyValuePair(kvp.Key, StringValues(kvp.Value :?> string)))
|> fun kvps -> Dictionary<string, StringValues>(kvps) :> IDictionary<string, StringValues>
match ModelParser.tryParse<'T> None routeData with
| Ok model -> Some model
| Error _ -> None
let bindRouteHandler<'T> (handler: 'T -> HttpHandler) : HttpHandler =
fun next ctx ->
match tryBindRoute<'T> ctx with
| Some model -> handler model next ctx
| None -> RequestErrors.BAD_REQUEST "Failed to bind route parameters" next ctx
let personHandler (person : Person) : HttpHandler =
sprintf "Hello %s %s" person.FirstName person.LastName
|> Successful.OK
let endpoints =
[
GET [
route "/p/{firstName}/{lastName}" (bindRouteHandler<Person> personHandler)
]
]
let configureApp (appBuilder : IApplicationBuilder) =
appBuilder
.UseRouting()
.UseEndpoints(fun e -> e.MapGiraffeEndpoints(endpoints))
|> ignore
let configureServices (services : IServiceCollection) =
// Add Giraffe dependencies
services.AddGiraffe() |> ignore
[<EntryPoint>]
let main _ =
Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(
fun webHostBuilder ->
webHostBuilder
.Configure(configureApp)
.ConfigureServices(configureServices)
|> ignore)
.Build()
.Run()
0
Indeed. Would you like to work on this?