Introducing Nini, a micro web-framework for DNX written in F#

First of all, I'd like to apologize for the the delay in this blog post. Any of you who follow me on twitter will know that it was supposed to come out a day or two ago, but unfortunately my blog sort of ... imploded. So, yeah, for any of you who are reading this, don't try to run ghost in Azure webpages. Probably, don't run node servers in Azure web pages at all. Note, I'm not complaining about Azure here, Azure has mostly been great. It's just node has ... issues, on Windows. In general. I've seen hiccups with everything from building my node projects on AppVeyor, to hosting my blog on Azure. Some things doesn't just play well. And it's sort of weird bugs, that randomly manifest itself. Simply rerunning the build, without changing anything at all fixes it.

Anyways, I'm not here to complain about node or Azure or Windows. My hope is that in time, all of these problems will be moot, as we move to container technologies like docker, but only time will tell I guess. What I'm going to write about today though, is my latest development with DNX and F#. So let's start with that bit.

YoloDev.Dnx.FSharp

Unfortunately, the old F# support for (what was then called) k broke completely. I had a really funky setup to allow building the F# support with the F# support (so I could write it in F# - how meta is that?). The problem with this setup was that when things broke (and they did break), I had a bootstrapping setup that did magic using the DNX to figure out dependencies and such, and list them in a file, so I could do a traditional compilation of the source (using the normal F# compiler), and then use that bootstrapped F# support to compile my actual F# support. You can read more about this in eating your own dogfood.

The problem was that when this bootstrapping support itself broke, because of changes to... well, everything, I was in a giant clusterfuck of a mess and un-clusterfucking it turned out to be ... rather annoying. Also, there were things I wasn't happy about how worked, so I rewrote the whole thing from scratch. Enter YoloDev.Dnx.FSharp. As opposed to the previous F# support, this one is written in C#. This will probably be changed though, once DNX reaches a stable language integration API. But for now, it stays in C#.

So how do you get started with using this new F# support? Well, there are are a few changes to how things work. First of all, the changes you have to make to your project.json have changed. To set up a project to use F#, you need to add the following to your project.json file:

  "compiler": {
    "name": "F#",
    "compilerAssembly": "YoloDev.Dnx.FSharp",
    "compilerType": "YoloDev.Dnx.FSharp.FSharpProjectCompiler"
  }

You also need to add YoloDev.Dnx.FSharp to your dependencies. It's recommended to add it as a build dependency like so:

"YoloDev.Dnx.FSharp": { "type": "build", "version": "1.0.0-*" }

You might also want to add a dependency on FSharp.Core. This is not required to build, but you might end up with runtime errors without it. Worse yet, this might not be discovered in unit tests or similar, but might only manifest itself if any non-F# project depends on your project. I hope to make it so that the F# support can add this automatically, but it might not be doable. For now at least, to be save, add the following dependency to your project.json:

"FSharp.Core": "3.1.2.1"

The last thing you need to do is add a project.fsharp.json file to your project. This file is where F# specific compilation settings go. Though I say compilation settings, currently only two values are supported, and I invented one of them ^^. As of date, the two supported values are files, and autoDiscoverFsi. files is a simple array of F# files, whereas autoDiscoverFsi (which defaults to true) tells the compiler whether or not to look for .fsi files with the same names as the .fs files. Here is the sample project.fsharp.json file I use for Nini for instance:

{
  "files": [
    "utils.fs",
    "types.fs",
    "http.fs",
    "middleware.fs",
    "builder-extensions.fs"
  ]
}

This file was needed because F# deals with source files quite differently than C# does. For instance, C# does not care about file order, while F# does. This means that the glob system used in the normal project.json was unsuited for using with F# files. For a good while I had to name my files 00-utils.fs and 01-types.fs so that they would get ordered correctly. One of the problems caused by this however, is that the generated symbols package does not contain any sources, but I think I have a solution for that, and will hopefully have it fixed shortly.

Now, let's talk a bit about the autoDiscoverFsi property. As said, it's by default enabled, and if it is enabled, it will automatically look for .fsi files for any .fs it finds, and adds them before the .fs files in the list. This means, that if I have .fsi files for the types.fs and http.fs files, my list will end up looking like this:

{
  "files": [
    "utils.fs",
    "types.fsi",
    "types.fs",
    "http.fsi",
    "http.fs",
    "middleware.fs",
    "builder-extensions.fs"
  ]
}

The only thing to keep in mind with this is that you should not add your .fsi files to the files array if you have this turned on.

Now that we have that covered let's move on the the main event.

Nini

Now for the fun part. I titled this post "Introducing Nini", but all I've been doing is talking about other things ^^. Well, fear not, here is the information you've been waiting for:

Nini is a small web framework written entirely in F#. It's highly (like copy-paste level) inspired by Suave.io with some things changed because of personal preferences. Nini is (like Suave) based on WebParts, which are functions that takes a web context, and either return None (meaning it wasn't handled), or Some WebContext meaning it was handled. The WebContext returned contains the response to send to the client.

Above that, it provides a number of combinators and operators that take one or more WebParts, and transform them in some way. A simple example of a WebPart that simply returns a message to the client is the following:

ok "Hello, World!"  

To enable DNX to run this as a web application, we need to add a normal Startup class, and register the Nini middleware. The end-result is the obligatory "hello world" application, and looks like this:

namespace MyApp

open Microsoft.AspNet.Builder  
open Nini.Http.Successful

type Startup() =  
  let handle = ok "Hello, World!"

  member x.Configure (app: IApplicationBuilder) =
    app.UseNini handle |> ignore

And there we have it. A simple hello world application. A few thing to notice here though. First, if you know F#, it's quite obvious that I didn't need to make the WebPart into a separate variable, and then call UseNini with it. I could have just run app.UseNini (ok "Hello, World!"), but I prefer having it as a separate variable. Secondly, the ignore after UseNini is important. This is important because Asp.Net 5 requires Configure to return void. So if you have problem getting it up and running, try putting a printfn in your Configure method to make sure that it's actually run.

The project.json file looks like this:

{
  "version": "1.0.0-beta-*",

  "dependencies": {
    "YoloDev.Dnx.FSharp": { "type": "build", "version": "1.0.0-*" },
    "Nini": "1.0.0-*",
    "Kestrel": "1.0.0-*"
  },

  "frameworks": {
    "dnx451": {
      "frameworkAssemblies": {
        "System.Runtime": "",
        "System.Threading.Tasks": ""
      }
    }
  },

  "compiler": {
    "name": "F#",
    "compilerAssembly": "YoloDev.Dnx.FSharp",
    "compilerType": "YoloDev.Dnx.FSharp.FSharpProjectCompiler"
  },

  "commands": {
    "web": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000"
  }
}

And the project.fsharp.json is simply the following (given that you named your file startup.fs):

{
  "files": [
    "startup.fs"
  ]
}

Run dnx . web in the current directory, and presto:

hello world

One thing to note though, is that you need the YoloDev myget feed either in your global nuget config, or in a NuGet.Config file sitting in your project folder. The address to this feed is https://www.myget.org/F/yolodev/api/v2/. For those that haven't use it before, here is a sample NuGet.Config file that I often use:

<?xml version="1.0" encoding="utf-8"?>
<configuration>  
  <packageSources>
    <add key="AspNetVNext" value="https://www.myget.org/F/aspnetvnext/api/v2" />
    <add key="XUnit" value="https://www.myget.org/F/xunit/api/v2/" />
    <add key="YoloDev" value="https://www.myget.org/F/yolodev/api/v2/" />
    <add key="NuGet Central" value="https://nuget.org/api/v2/" />
  </packageSources>
</configuration>  

Now, Nini is still very much a work in progress. It was made in a couple of days by myself alone, mostly stealing code from Suave. I would gladly welcome both suggestions and contributions. You can find it on GitHub.

If you have any questions (or just want to hang out), you can often find me at https://jabbr.net/#/rooms/YoloDev.