I think this is why it seems like there are a good handful of Nancy frameworks for F#. Most of them do more than just provide more sugary syntax for defining routes, however, which is all I really wanted for the time being.
So here are my helper functions for making routes, especially async routes, a bit more palatable from F#.
let private addRouteSync path f (router:NancyModule.RouteBuilder) =
router.[path] <-
fun (parameters:obj) ->
f (parameters :?> DynamicDictionary) |> box
// async is the default
let private addRoute path f (router:NancyModule.RouteBuilder) =
router.[path, true] <-
fun (parameters:obj) cancellationToken ->
async { // unwrap and box the result
let! result = f (parameters :?> DynamicDictionary) cancellationToken
return box result
} |> Async.StartAsTask
// more f# friendly methods
type NancyModule with
member me.post path f = me.Post |> addRoute path (f me)
member me.get path f = me.Get |> addRoute path (f me)
member me.put path f = me.Put |> addRoute path (f me)
member me.delete path f = me.Delete |> addRoute path (f me)
member me.patch path f = me.Patch |> addRoute path (f me)
member me.options path f = me.Options |> addRoute path (f me)
member me.postSync path f = me.Post |> addRouteSync path (f me)
member me.getSync path f = me.Get |> addRouteSync path (f me)
member me.putSync path f = me.Put |> addRouteSync path (f me)
member me.deleteSync path f = me.Delete |> addRouteSync path (f me)
member me.patchSync path f = me.Patch |> addRouteSync path (f me)
member me.optionsSync path f = me.Options |> addRouteSync path (f me)
router.[path] <-
fun (parameters:obj) ->
f (parameters :?> DynamicDictionary) |> box
// async is the default
let private addRoute path f (router:NancyModule.RouteBuilder) =
router.[path, true] <-
fun (parameters:obj) cancellationToken ->
async { // unwrap and box the result
let! result = f (parameters :?> DynamicDictionary) cancellationToken
return box result
} |> Async.StartAsTask
// more f# friendly methods
type NancyModule with
member me.post path f = me.Post |> addRoute path (f me)
member me.get path f = me.Get |> addRoute path (f me)
member me.put path f = me.Put |> addRoute path (f me)
member me.delete path f = me.Delete |> addRoute path (f me)
member me.patch path f = me.Patch |> addRoute path (f me)
member me.options path f = me.Options |> addRoute path (f me)
member me.postSync path f = me.Post |> addRouteSync path (f me)
member me.getSync path f = me.Get |> addRouteSync path (f me)
member me.putSync path f = me.Put |> addRouteSync path (f me)
member me.deleteSync path f = me.Delete |> addRouteSync path (f me)
member me.patchSync path f = me.Patch |> addRouteSync path (f me)
member me.optionsSync path f = me.Options |> addRouteSync path (f me)
Here is a dumb example of usage, asynchronous:
m.put "/orders/{id:string}"
<| fun nmodule parameters cancelToken ->
let id = parameters.["id"].ToString()
async { return id } // echo id
<| fun nmodule parameters cancelToken ->
let id = parameters.["id"].ToString()
async { return id } // echo id
Now the function you setup receives the parameters as a DynamicDictionary instead of obj. You can also return whatever you want (e.g. Response), and these helpers will box it for you before providing it back to Nancy. Your function can also directly return an async, and these helpers will convert it to a task type which Nancy expects. I'm also passing in the NancyModule in case your code hangs off an F# module (essentially static code) instead of the Nancy module class itself.
I am basically only use the NancyModule as an entry point (like a static main void) and try to remain functionally-styled with my real code.