Hardware, Software & Product Development | Sparx EngineeringHardware, Software & Product Development | Sparx EngineeringHardware, Software & Product Development | Sparx EngineeringHardware, Software & Product Development | Sparx Engineering
  • Home
  • Expertise
    • Software Engineering
    • Electrical Engineering
    • Chemical Products and Services
    • Biomedical Engineering
    • Mechanical Engineering
    • Production Management
    • Automation
    • Industrial Design
  • Blog
  • Careers
  • About Us
NextPrevious

Routing in Nancy from F#

By dfohl | Software | 0 comment | 4 March, 2015 | 0

Nancy is my web framework of choice for .NET. It is free, lightweight, easy to use, and comes without all the bloat of the more common .NET frameworks.

Nancy makes routing in C# simple. If you need to handle a form post, you simply set up the POST endpoint, and access the form variables via the dynamic Form object. For example if you’ve got a form with firstName and lastName, and need to respond with a “Hello”, here’s now that would be done.

1
2
3
4
5
6
7
// This code would go in the constructor of your NancyModule:
Post["/submit"] = _ => {
dynamic form = this.Request.Form;
string firstName = form.firstName;
string lastName = form.lastName;
return "Hello " + firstName + " " + lastName;
};

When using from F# however, there is no “dynamic” functionality, so it’s not so simple. The common advice is to create an operator that mimics the dynamic functionality of C# as follows.

1
2
3
let (?) (parameters : obj) param =
let dic = parameters :?> DynamicDictionary
dic.[param]

Then you can use that operator like this.

1
2
3
4
5
self.Post.["/submit"] <- fun _ ->
let form = self.Request.Form :?> Nancy.DynamicDictionary
let firstName = (form?firstName).ToString()
let lastName = (form?lastName).ToString()
box <| "Hello " + firstName + " " + lastName

Okay, well, that works, but it’s not pretty. All that casting to and from String pollutes the code, and the ? operator just looks out-of-place. It can get especially unwieldy in a long module with many endpoints, since all of that code must go into the constructor of your NancyModule, and ends up being one big long block of mess. Externalizing these handlers into separate functions is an option, but then you’re passing dynamic variables around into the heart of your app, which is very antithetical to F#.

Fortunately there’s a better way. First off, use JSON encoding on your HTML form rather than URL encoding. This will mean a few extra lines of Javascript on your form, but this is standard with frameworks like AngularJS anyway. Now you can create a helper function using the excellent JSON.NET library to parse the JSON into a record. There’s also a function here to convert the result back to JSON:

1
2
3
4
5
6
7
8
9
let parseBody<'a>(m: NancyModule) =
use rdr = new StreamReader(m.Request.Body)
let s = rdr.ReadToEnd()
let result = JsonConvert.DeserializeObject<'a> s
result
 
let toResp o =
if obj.ReferenceEquals(o, null) then box Response.NoBody
else box <| JsonConvert.SerializeObject o

Now within your NancyModule constructor, you can add the following inline function that maps an endpoint to a handler function.

1
2
let map endpoint f =
self.Post.[endpoint] <- fun _ -> toResp(f(parseBody self))

What does this do? It sets up your endpoint to parse the body into a record, call your handler function f with that record, and return the response. So now, assuming we have a record type Person = {firstName: string; lastName: string}, we can write the handler as:

1
2
map "/submit" (fun user ->
"Hello " + user.firstName + " " + user.lastName)

What’s especially nice is this can now be externalized to a function, so if we define let sayHi user = "Hello " + user.firstName + " " + user.lastName then our form handler becomes simply

1
map "/submit" sayHi

All in all, this pattern makes API creation so simple and declarative, I find it much better than even the C# dynamic pattern. Your handlers go from being line-per-variable messes, to a simple line-per-endpoint mapping. You can put your handlers and datatypes in different files and namespaces too, to organize things even better. The full code is here, along with an async option as well. You can see how much easier this makes the API endpoint mapping to read, and how well componentized your code becomes, especially as the list of endpoints continues to grow.

DataTypes.fs:

1
2
3
4
module DataTypes
type Person =
{ firstName: string
lastName: string }

Handlers.fs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module Handlers
open DataTypes
 
let sayHi user =
"Hello " + user.firstName + " " + user.lastName
 
let sayBye user =
"Goodbye " + user.firstName + " " + user.lastName
 
let sayHiAsync user =
async {
do! Async.Sleep 100
return "Hello " + user.firstName + " " + user.lastName
}
 
let sayByeAsync user =
async {
do! Async.Sleep 100
return "Goodbye " + user.firstName + " " + user.lastName
}

NancyHelp.fs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
module NancyHelp
open Nancy
open Nancy.Responses
open Newtonsoft.Json
open System.IO
 
let parseBody<'a>(m: NancyModule) =
use rdr = new StreamReader(m.Request.Body)
let s = rdr.ReadToEnd()
let result = JsonConvert.DeserializeObject<'a> s
result
 
let toResp o =
if obj.ReferenceEquals(o, null) then box Response.NoBody
else box <| JsonConvert.SerializeObject o
 
let getAsync nancy f =
let req = parseBody nancy
async { let! resp = f req
return toResp resp }
 
let map (m:NancyModule) endpoint f =
m.Post.[endpoint] <- fun _ ->
toResp <| f(parseBody m)
 
let mapAsync (m:NancyModule) endpoint f =
m.Post.[endpoint, true] <- fun _ _ ->
getAsync m f |> Async.StartAsTask

API.fs:

1
2
3
4
5
6
7
8
9
10
11
12
13
module API
open Nancy
open Nancy.Responses
 
type LinusModule() as self =
inherit NancyModule()
let map = NancyHelp.map self
let map_async = NancyHelp.mapAsync self
do
map "/login" Handlers.sayHi
map "/logout" Handlers.sayBye
map_async "/login-async" Handlers.sayHiAsync
map_async "/logout-async" Handlers.sayByeAsync

C#, F#, Nancy, Programming, Web, Web Framework

dfohl

More posts by dfohl

Related Posts

  • Reading line-by-line from a serial port (or other byte-oriented stream)

    By Ben Voigt | 10 comments

    With many .NET developers moving from the traditional (and broken) System.IO.Ports.SerialPort DataReceived event handling to either the correct and more efficient BaseStream.BeginRead / BaseStream.EndRead pair I promoted in my last post or the newer BaseStream.ReadAsyncRead more

  • How to Model NPT Threads in Solidworks

    By rmontifar | 2 comments

    National Pipe Thread Taper or NPT threaded pipes and fittings are deployed in a variety of fields where transportation or containment of liquids, gases, steam, or hydraulic fluid is required. The NPT geometry allows internalRead more

  • Multi-Tiered Linux Backup System – Part I

    By dreynolds | 0 comment

    Backing up important data and memories is an important task that should not be neglected. Just as important as performing Linux backups is verifying that the backups made are good and can be used toRead more

  • Clojure: An improved workflow

    By dfohl | 0 comment

    Like many beginning Clojure programmers, I started off following Stuart Sierra’s “Reloaded” workflow guide. While it was a great starting point, there were a number of things that I wanted to change. If the projectRead more

  • Start Zoneminder Recordings with Vera Events

    By dsmoot | 4 comments

    In a previous post I explained how you could configure the security DVR software Zoneminder to trigger recordings from a network connection. While a neat trick, I never really explained why I set this up.Read more

Leave a Comment

Cancel reply

Your email address will not be published. Required fields are marked *

NextPrevious
  • Home
  • Expertise
  • Blog
  • Careers
  • About Us
Sparx Technologies, LLC. dba Sparx Engineering © 2009 - 2022 | All Rights Reserved
  • Home
  • Expertise
    • Software Engineering
    • Electrical Engineering
    • Chemical Products and Services
    • Biomedical Engineering
    • Mechanical Engineering
    • Production Management
    • Automation
    • Industrial Design
  • Blog
  • Careers
  • About Us
Hardware, Software & Product Development | Sparx Engineering