F# and Nancy - beyond Hello World
I have been trying to find some examples on how to create a web app using F# with Nancy. Most of the tutorials on the web cover simple “Hello World!” app, only the basics. In this post, I want to go beyond “Hello World” and show real life examples.
F# and Nancy - Where to start ?
Most basic app “Hello World”, is simple.
type App() as this =
inherit NancyModule()
do
this.Get.["/"] <- fun _ -> "Hello World!" :> obj
[<EntryPoint>]
let main args =
let nancy = new Nancy.Hosting.Self.NancyHost(new Uri("http://localhost:" + "8100"))
nancy.Start()
while true do Console.ReadLine() |> ignore
0
Nancy will automatically load up the App class. To those familiar with Nancy on C# it looks almost the same. The only noticeable difference is the lambda function declaration, plus different syntax for inheritance. Also there is this weird smile face ‘:>’ at the end. This is just a up-casting operator. For some reason Get function has to return object. In F# you also use different syntax for list / array indexing. Instead of Get[”/”], you need to use Get.[”/”]
GET Examples
this.Get.["/Fsharp"] <- fun _ -> "I can into F#" :> obj
this.Get.["/Json"] <- fun _ -> this.Response.AsJson([ "Test" ]) :> obj
this.Get.["/Complex"] <- fun _ ->
let response = this.Response.AsJson(["This is my Response"])
response.ContentType <- "application/json"
response.Headers.Add("Funky-Header", "Funky-Header-Value")
response :> obj
POST
this.Post.["/Post/{test}"] <- fun parameters ->
let value = (parameters :?> Nancy.DynamicDictionary).["test"]
let response = this.Response.AsJson([ sprintf "test %O" value ])
response :> obj
To extract the parameters I had to cast the input param, which is of obj type, to Nancy.DynamicDictonary. It doesn’t look great but there is other way.
this.Post.["/Post/{name}"] <- fun parameters ->
let response = this.Response.AsJson([ sprintf "name %O" parameters?name ])
response :> obj
How to achieve syntax like that - parameters?name ?
let (?) (parameters:obj) param =
(parameters :?> Nancy.DynamicDictionary).[param]
This part of code is creating new “let-bound operator”. It hides the casting logic and makes the code look cleaner.
Error Codes
this.Get.["/500"] <- fun _ -> 500 :> obj
this.Get.["/404"] <- fun _ -> 500 :> obj
Views
this.Get.["/View"] <- fun _ ->
this.View.["View.html"] :> obj
Syntax is simple and looks basically the same as in C#. There is only one little detail. By default Nancy looks for views in /Views folder. Currently in VS F# project there is no way to create folders from within VS. In order to do this I had to manually, create folder, add file and edit *.fsproj file. Hopefully this will be fixed in the future.
<None Include="Views/View.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
ModelBinding
In C# syntax, you would normally use the base method Bind
The type to bind
And the code to bind to the model.
this.Post.["/Model"] <- fun parameters ->
let model = this.Bind<TestModel>()
this.Response.AsJson(model) :> obj
Unfortunately, it won’t work like that. My “TestModel” type is missing default parameter-less constructor and Nancy is throwing “No parameter less constructor defined for this object” Exception.
type TestModel =
val TestValue1:string
new () = {
TestValue1 = ""
}
There is a constructor, no Exception, but the value is not bound and is always empty. To fix this, I had to go and look through
Nancy code. By default Nancy is binding to property. My val declaration is not a property.
type TestModel() =
let mutable testValue = ""
member this.Testvalue1
with get () = testValue
and set (value) = testValue <- value
Syntax for properties is a little different, but nothing serious here. I need to make my value mutable so I can modify the state.
The Application OnError pipeline
To modify the Pipeline I had to add include Nancy.Bootstrapper it has IApplicationStartup interface which can be used to hook into OnError pipeline
type AppStartup =
interface IApplicationStartup with
member this.Initialize pipelines =
pipelines.OnError.AddItemToStartOfPipeline(new Func<NancyContext, exn, Response>(fun ctx _ -> new Response()))
Nancy will automatically pick-up this class. The syntax for interface implementation is different, a lot. There is no += operator when working with Events and I had to use ADD method. With this example I got an exception “unable to resolve type AppStartup”. It was a problem of missing parameter less constructor.
type AppStartup() =
interface IApplicationStartup with
member this.Initialize pipelines =
pipelines.OnError.AddItemToStartOfPipeline(new Func<NancyContext, exn, Response>(fun ctx _ -> new Response()))
The End
Those examples are not really showing the power of F#. This power lays in domain-business logic, not in simple endpoint declaration. It is also the OOP approach with F# syntax. There are other web frameworks that have more functional approach. You can check
Fancy which is a nice wrapper around Nancy. In a future, I might do a comparison with some purely functional web framework.