So you want to program the web with FSharp?

functional   OWIN   Simple.Web   FSharp  

I couldn't find any really great how tos about doing web programming with FSharp, so I thought I would figure it out myself my own way.

The problem

The problem with web programming and FSharp is two as I see it right now. One is regarding the tooling, the default tooling from Visual Studio which I think most people use doesn't have a project template for web together with FSharp. The second part is that a lot of web frameworks are mutable in one way or another so it is hard doing pure development. With that said I'll show I way/work around in how to do web development on OWIN in general and with Simple.Web to be more specific.

FSharp and OWIN

To get started you start of as you should with any web project, create an empty C# web project.

New project

I'm going for a windows hosting so also install the nuget package Microsoft.Owin.Host.SystemWeb. The next thing we are going to do is add a second project, this time a FSharp library. Update the Library.fs file so it looks something like this:

namespace Web2Empty.Web
open Owin

type OwinAppSetup() =
    member this.Configuration (app:IAppBuilder) = 
        app.Run(fun c -> c.Response.WriteAsync("Hello FSharp World!"))

That is going to be your OWIN entry point. To get this to compile you need to add Owin nuget package to the FSharp project. When it compiles you add a line like this to your Assembly.cs file in the C# project and add a project reference to the FSharp project:

[assembly: OwinStartup(typeof(OwinAppSetup))]

This will tell the OWIN host to use the OwinAppSetup class which is defined in the FSharp class library to run the web application. If you run the application now you should get some output on the screen.

FSharp and Simple.Web

The last couple of months I've come to like Simple.Web more and more, so I wanted to try to get that to work together with FSharp. To really put it to a test I wanted to use discriminated unions as well.

Adding Simple.Web

Adding Simple.Web with json support is quite easy, just install the Simple.Web.JsonNet package in the FSharp project and you'll get the dependencies you packages. When it is installed you need to update Newtonsoft.Json to the latest version to get the FSharp support.

The sample data

The data structure I'll use in my requests look like this

type Life =
    | Great of reason: string
    | Sucks of reason: string
    | Complex of Life * Life

type LifeContainer = {Life: Life}

For some reason I need the container to be able to serialize the request when using Simple.Web, it doesn't bother me that much so I can live with it.

Creating the handlers

Simple.Web is using a handler/class per route per request model, so to handle this I need to create one FSharp type per route per request. To support a GET request you need something like this:

[<UriTemplate("/")>]
type RootGetEndPoint() = 
    interface IGet with
        member this.Get() = Status.OK
    interface IOutput<LifeContainer> with
        member this.Output = {Life=Complex(Great("FSharp"), Sucks("Inheritance"))}

Here we first have the UriTemplateAttribute that defines which url this handler should handle. After that it is straight forward implementation of the interfaces we need to answer a request.

For the POST endpoint we don't need to use the container for the input but we still need it for the output:

[<UriTemplate("/")>]
type RootPostEndPoint() = 
    let mutable life:Option<Life> = None
    interface IPost with
        member this.Post() = Status.OK    
    interface IInput<Life> with
        member this.Input with set(value) = life <- Some(value)
    interface IOutput<LifeContainer> with
        member this.Output = {Life = life.Value}

This handler takes the input and returns it with a 200 response.

The last part is the update of the OwinAppSetup:

type OwinAppSetup() =
    static member enforcedRefs = [typeof<Simple.Web.JsonNet.JsonMediaTypeHandler>] 
    member this.Configuration (app:IAppBuilder) = 
        JsonConvert.DefaultSettings <- fun () -> 
            let settings = new JsonSerializerSettings()
            settings.TypeNameHandling <- TypeNameHandling.Objects
            settings
        app.Run(fun c -> 
            Application.App(
                fun _ -> 
                    c.Response.WriteAsync("Hello FSharp world!")).Invoke(c.Environment))

The first static part is to load the media type handler, I don't know a better way of doing it at the moment for Simple.Web and I don't think it exist a better way at the momement. The next part is the configuration part where I first configure Json.NET and then setting up the application. The application is using Application.App from Simple.Web to configure Simple.Web but we still run the next step if we didn't found a route. So if you go anywhere except for the root, "/", you'll see "Hello FSharp world!".

Witht that finished it is now possible to post discriminated unions like:

{
    "case":"Complex",
    "fields":[
        {"case":"Great","fields":["FSharp"]},
        {"case":"Sucks","fields":["What"]}
    ]
}

This will return in a 200 and you'll get a container back with that Life.

The full code for the fsharp file looks like this:

namespace Web2Empty.Web
open System.Text
open Owin
open Simple.Web
open Simple.Web.Behaviors
open Simple.Web.MediaTypeHandling
open Newtonsoft.Json

type Life =
    | Great of reason: string
    | Sucks of reason: string
    | Complex of Life * Life

type LifeContainer = {Life: Life}

[<UriTemplate("/")>]
type RootGetEndPoint() = 
    interface IGet with
        member this.Get() = Status.OK
    interface IOutput<LifeContainer> with
        member this.Output = {Life=Complex(Great("FSharp"), Sucks("Inheritance"))}

[<UriTemplate("/")>]
type RootPostEndPoint() = 
    let mutable life:Option<Life> = None
    interface IPost with
        member this.Post() = Status.OK    
    interface IInput<Life> with
        member this.Input with set(value) = life <- Some(value)
    interface IOutput<LifeContainer> with
        member this.Output = {Life = life.Value}

type OwinAppSetup() =
    static member enforcedRefs = [typeof<Simple.Web.JsonNet.JsonMediaTypeHandler>] 
    member this.Configuration (app:IAppBuilder) = 
        JsonConvert.DefaultSettings <- fun () -> 
            let settings = new JsonSerializerSettings()
            settings.TypeNameHandling <- TypeNameHandling.Objects
            settings
        app.Run(fun c -> 
            Application.App(
                fun _ -> 
                    c.Response.WriteAsync("Hello FSharp world!")).Invoke(c.Environment))

Ending note

I did not work much on structuring the code into separate files etc. since it was not my goal, I just wanted it to work. Also, I'm not sure if this is the recommended way of doing things, but it works. Of course it would be better if you just needed one project instead of two, but this is a nice work around until that time. I'm not sure, but you might be able to manipulatae the .fsproj file to get it working without the extra project, but that is just a wild guess.

As you can see, there is nothing stopping us from doing all the code on the server in FSharp, we do miss some code generation support from Visual Studio with scaffolding, but that is something I want miss since I haven't found it useful for "real" business applications.


Comments powered by Disqus