Generating an href value and route pattern from a common string

Viewed 50

In mvc-movie-giraffe I have a line like this in a view to generate a link to an edit page:

a [ _href ("/Movies/Edit/"    + (string elt.Id)) ] [ encodedText "Edit" ]

Then where I setup the routes, I have the following:

routef "/Movies/Edit/%i"    edit_handler

It would be nice to factor out that string "/Movies/Edit/".

So I added it to a Urls module along with some others:

module Urls =

    let movies = "/Movies"
    let movies_create = "/Movies/Create"
    let movies_edit = "/Movies/Edit/%i"

For the href value:

a [ _href ("/Movies/Edit/"    + (string elt.Id)) ] [ encodedText "Edit" ]

we can do this instead:

a [ _href (sprintf (Printf.StringFormat<int->string>(Urls.movies_edit)) elt.Id) ] [ encodedText "Edit" ]

and for the route case:

routef "/Movies/Edit/%i"    edit_handler

we can do this:

routef (PrintfFormat<obj, obj, obj, obj, int>(Urls.movies_edit))    edit_handler

And this does appear to work!

My question is, is there a better way? Perhaps something more concise? The above is a bit syntax heavy. Or is there already some established best practice around this?

I looked through the Giraffe samples and didn't notice anything like this technique being used.

Update - Approach #2

Here's one approach. Basically, factor out those longer expressions as well:

module Urls =

    let movies = "/Movies"
    let movies_create = "/Movies/Create"

    let movies_edit = "/Movies/Edit/%i"
    let movies_edit_href = sprintf (Printf.StringFormat<int->string>(movies_edit))
    let movies_edit_route = PrintfFormat<obj, obj, obj, obj, int>(movies_edit)

And then reference them in views:

a [ _href (Urls.movies_edit_href elt.Id) ] [ encodedText "Edit" ]

and when setting up routes:

routef Urls.movies_edit_route edit_handler

Update - approach #3

With approach #2, I ended up with this:

module Urls =

    let movies = "/Movies"

    let movies_create = "/Movies/Create"
    
    let movies_edit = "/Movies/Edit/%i"
    let movies_edit_href = sprintf (Printf.StringFormat<int->string>(movies_edit))
    let movies_edit_route = PrintfFormat<obj, obj, obj, obj, int>(movies_edit)

    let movies_details = "/Movies/Details/%i"
    let movies_details_href = sprintf (Printf.StringFormat<int->string>(movies_details))
    let movies_details_route = PrintfFormat<obj, obj, obj, obj, int>   (movies_details)

    let movies_delete = "/Movies/Details/%i"
    let movies_delete_href = sprintf (Printf.StringFormat<int->string>(movies_delete))
    let movies_delete_route = PrintfFormat<obj, obj, obj, obj, int>   (movies_delete)

There's some repetition there so I defined these extensions:

type String with
    member this.route = PrintfFormat<obj, obj, obj, obj, int>(this)
    member this.href = sprintf (Printf.StringFormat<int->string>(this))

Now, the Urls module is just:

module Urls =

    let movies         = "/Movies"
    let movies_create  = "/Movies/Create"
    let movies_edit    = "/Movies/Edit/%i"
    let movies_details = "/Movies/Details/%i"
    let movies_delete  = "/Movies/Details/%i"

And then to get the href string for movies_edit:

a [ _href (Urls.movies_edit.href elt.Id) ] [ encodedText "Edit" ]

And similar for the route:

routef Urls.movies_edit.route edit_handler
1 Answers

I definitely do this at work to share url route templating.

type Route<'t> = PrintfFormat<obj, obj, obj, obj, 't>
let Details = Route<Guid> ("/Details/%O")

and then reference this in my route handlers as well as in my views to generate routes. There are a few caveats though:

  • for values (like let bindings), you can't leave the generic parameters of the PrintfFormat type 'open'. So they get unified to obj, which is what you're seeing.

  • if you need to keep the generic parameters open for some reason, the workaround is to convert the value to an inline function with a unit parameter, then invoke that function at each callsite:

let inline Details () = PrintfFormat<_,_,_,_,Guid> "/Details/%O"

this gets past the 'value restriction' that many ML-family languages have. You can look up that term for literature on why the generic parameters have to remain open on a value-binding.

Related