Giraffe icon indicating copy to clipboard operation
Giraffe copied to clipboard

Routes with named parameters are not matched

Open acatuttle opened this issue 1 month ago • 2 comments

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

  1. 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
  1. 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

acatuttle avatar Dec 08 '25 20:12 acatuttle

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.

64J0 avatar Dec 09 '25 22:12 64J0

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.

64J0 avatar Dec 10 '25 00:12 64J0

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

acatuttle avatar Dec 12 '25 12:12 acatuttle

Indeed. Would you like to work on this?

64J0 avatar Dec 12 '25 12:12 64J0