ASP.NET MVC with NHaml - F# Edition

As part of some of my adventures with F#, I've seen a lot of interesting things coming from others with regards to SharePoint, ASP.NET and other technologies.  This had me thinking of any possibilities and ramifications of using F# with ASP.NET MVC.  Was it possible, and better question, what might make someone use this over their existing toolsets.  Those are some of the questions to explore.  But, in the mean time, let's take the journey of F# and ASP.NET MVC.

 

Getting Started

First, let's cover what it takes to get F# to work with ASP.NET MVC.  The required downloads are:

Side by side, I think it's easier to first create a sample C# ASP.NET MVC project, so it's easy to cut and paste the configuration file information.  What works better is to open the NHamlViewEngine sample from MVCContrib.  Also, create a standard F# library, and in my case, I called it MvcFSharp. 

I then add references to the following assemblies:

  • Microsoft.Web.Mvc.dll
  • MvcContrib.dll
  • MvcContrib.NHamlViewEngine.dll
  • System.Web.dll
  • System.Web.Abstractions.dll
  • System.Web.Extensions.dll
  • System.Web.Mvc.dll
  • System.Web.Routing.dll

Since the F# projects do not support creating folders, this next step requires some Visual Notepad support.

 

Modifying the Project File

First, create the Models, Controllers, Content and Views directories through Windows Explorer.  Create dummy files in each folder is probably the easiest thing to do.  When you are done, your project file contain this:

  <ItemGroup>
<Compile Include="Models\ListViewData.fs" />
<Compile Include="Controllers\HomeController.fs" />
<Compile Include="Default.aspx.fs">
<DependentUpon>Default.aspx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Global.asax.fs">
<DependentUpon>Global.asax</DependentUpon>
</Compile>
<Content Include="Default.aspx" />
<Content Include="Global.asax" />
<Content Include="Content\Site.css" />
<Content Include="Content\MicrosoftAjax.js" />
<Content Include="Content\MicrosoftAjax.debug.js" />
<Content Include="Content\MicrosoftMvcAjax.js" />
<Content Include="Content\MicrosoftMvcAjax.debug.js" />
<Content Include="Views\Home\About.haml" />
<Content Include="Views\Home\Index.haml" />
<Content Include="Views\Home\Numbers.haml" />
<Content Include="Views\Masters\Application.haml" />
<Content Include="Web.config" />
</ItemGroup>
 

In this actual case, these are the real files listed for our first F# ASP.NET MVC application.  Once you reload the project file from Visual Studio, you should be ready to go.  One thing to keep in mind with your project compilation is that in F#, order of files does matter.  Any further work where order may matter could force you to change the project file configuration with notepad once again.  Once the project file has been modified, let's move onto modifying the web.config to reflect using F# as a compiler.

 

Modifying the Configuration

There are several items we need to add in order to get both NHaml and F# to work as one inside our web.config.  As I said earlier, it's probably easiest to copy/paste some basic information from the NHamlViewEngine sample from MVCContrib to save yourself from having to reference additional things.  First, let's add NHaml support to our project file.  We need to modify the configSections area to add nhamlViewEngine support.  Add the following text to your configSections:

<configSections>
<section name="nhamlViewEngine"
type="MvcContrib.NHamlViewEngine.Configuration.NHamlViewEngineSection,
MvcContrib.NHamlViewEngine,
Version=0.0.1.159,
Culture=neutral,
PublicKeyToken=null
" />
 

Now, add the nhamlViewEngine information in as follows.  Note that I'm adding some references to F#.  The reason being is that should I return a type that is F# specific such as a list, seq or otherwise, NHaml will not be able to reference properly without access to the FSharp.Core.dll.

<nhamlViewEngine production="false"> 
<views>
<assemblies>
<add assembly="FSharp.Core,
Version=1.9.6.2,
Culture=neutral,
PublicKeyToken=a19089b1c74d0809
"/>
</assemblies>
<namespaces>
<add namespace="Microsoft.FSharp.Core" />
</namespaces>
</views>
</nhamlViewEngine>
 

In order for us to compile our default.aspx.fs and global.asax.fs file, we need to add support for the F# compiler in our web.config.  Add the following section of XML to your compilers section, right next to your C# compiler registration.

<compiler language="F#;f#;fs;fsharp" 
extension=".fs"
warningLevel="4"
type="Microsoft.FSharp.Compiler.CodeDom.FSharpAspNetCodeProvider,
FSharp.Compiler.CodeDom, Version=1.9.6.2,
Culture=neutral,
PublicKeyToken=a19089b1c74d0809
">
<providerOption name="CompilerVersion" value="v3.5" />
<providerOption name="WarnAsError" value="false" />
</compiler>
 

We now have the F# ASP.NET Code Provider installed in our web.config, so our focus now shifts to proper registration in our global.asax.fs and default.aspx.fs.

 

Modifying the Defaults

Now we turn our attention to the two defaults for our application, the global.asax and the default.aspx.  First, modify the global.asax to indicate the following:

<%@ Application CodeBehind="Global.asax.fs" Inherits="MvcFSharp.MvcApplication" Language="F#" %>
 

If you followed the project structure from above, open the global.asax.fs file and modify it to look like this.

#light

namespace MvcFSharp

open System.Web.Mvc
open System.Web.Routing
open MvcContrib.ControllerFactories
open MvcContrib.NHamlViewEngine

type MvcConstraint2 = { action:string; id:string }
type MvcConstraint3 = { controller:string; action:string; id:string }

type MvcApplication() =
  inherit System.Web.HttpApplication()
 
  static member RegisterRoutes(routes:RouteCollection) =
    routes.Add(
      new Route("{controller}.mvc/{action}/{id}"
        new MvcRouteHandler()
        Defaults = new RouteValueDictionary({ new MvcConstraint2 with action = "index" and id = "" })))
    
    routes.Add(
      new Route("Default.aspx"
        new MvcRouteHandler()
        Defaults = new RouteValueDictionary({ controller = "Home"; action = "index"; id = "" })))
 
  member x.Application_Start() =
    MvcApplication.RegisterRoutes RouteTable.Routes
    ViewEngines.Engines.Add(new NHamlViewFactory())
 

As you can see from above, I had to add two record types, called MvcConstraint2 and MvcConstraint3.  The reason being is that F# does not do anonymous types as C# does.  Instead, you define a simple record type to hold the data as needed.  Since there are two different needs, one with two fields and one with three, there is a need to define two separate instances.  Much as you would in the C# code, the registration should not look all that different.  But, I kept the RegisterRoutes function so that I can test my routes in a nice TDD fashion.

Moving onto the default.aspx file, modify the default.aspx to look like the following:

<%@ Page Language="F#" AutoEventWireup="true" CodeBehind="Default.aspx.fs" Inherits="MvcFSharp._Default" %>

Once that is complete, move onto the default.aspx.fs file.  It should look like the following:

#light

namespace MvcFSharp

open System
open System.Web.UI

type _Default() =
  inherit Page()
  
  member x.Page_Load(sender:obj, e:EventArgs) =
    x.Response.Redirect("~/Home.mvc/Index")
 

We now have a basic setup in which to build upon for our application.  Now we can concentrate on the models, controllers and views.

 

Creating the Model

I want just a basic model to show that creating concise and compact models is relatively simple using F#.  As I did for the MvcConstraint record types above, I can easily apply to my model.  Sometimes, our models may be nothing more than just a write once operation, so simple immutable record types suffice.  Other times, we may need to make some of the fields mutable.  But, that's the beauty of F#, is that it allows us to do both.

Let's create one to hold just some numbers to display on the screen.  If following the above project structure, your ListViewData.fs should look like the following:

#light

namespace MvcFSharp.Models

type ListViewData = { Numbers: seq<int> }
 

Done!  Now that was easy!  Moving onto the controller...

 

Creating the Controller

We have the models now defined, so let's move onto the controllers.  I only want one controller during this example, in this case the HomeController.fs.  Let's say I have three views I want to work with, the Index, About and Data.  Defining such a controller is quite simple.  It should look something like this:

#light

namespace MvcFSharp.Controllers

open MvcFSharp.Models
open System.Web.Mvc

[<HandleError>]
type HomeController() =
  inherit Controller()
  
  member x.Index() =
    x.View("Index")
    
  member x.About() =
    x.View("About")
    
  member x.Numbers() =
    let viewData = { Numbers = {1..10} }
    x.View("Numbers", viewData)
 

In this example, I did nothing more than just tell the system to render the view.  Each time, it's best that you cast it to the appropriate return type much as I did above.  In the case of the Numbers function, I wanted to create a set of numbers to render to the screen, so I create my new ListViewData with my numbers set.  Then, I pass that to the view to render.

 

Creating the Views

As I've stated before, I'm interested in following the example from MVCContrib for the NHamlViewEngine as much as possible.  So, the views look 100% like they do from the project, except for my numbers.haml file.  Let's look at the views that matter.  First, the index.haml file.

%h2 Welcome to my ASP.NET MVC Application using NHaml!
= Html.ActionLink("About Us", "About", "Home")
= Html.ActionLink("Data", "Numbers", "Home")
 

Let's move onto our Numbers.haml file which will use the data that I populated from our HomeController.  It should look like the following:

%h2 Data from Controller
%ul 
  - foreach (var n in ViewData.Model.Numbers)  
    %li =n

The rest should stay the same much as before.  As I said, I only wanted to try out a few features before going into a full fledged application.

 

Trying it Out

Once the application is built, we can then create the virtual directory in IIS to host our application.  Once that is complete, launching the browser will give us this for our Index view.

nhaml_index

Our about page will look like the following:

nhaml_about

And lastly, our numbers page will display the numbers from 1-10 in an unordered list:

nhaml_numbers

So, as you can see, we now display our data from our F# controller and F# models.  But, is that all of our story to tell?  Of course not?  I think it's important to emphasize TDD with this approach.  This works no different than it would in C#, quite frankly.

 

Test Driving our Solution

Much like when you create a new ASP.NET MVC project, it will by default help you create a set of unit tests using the xUnit framework of your hoice.  In this case, my default is xUnit.net.  Let's talk about testing here once again.  As I've stated before, I created an overall project called FsTest which creates a DSL over the assertion syntax.  This allows me to more naturally test using functional programming strengths.  Using this, I'm able to test all of my code much as you would in your C# solution.

Let's first start with our MvcApplication tests.  Let's go through one of the tests that I did earlier in regards to my Numbers function.

#light

namespace MvcFSharp.Tests.Controllers

open FsxUnit.Syntax
open MvcFSharp.Controllers
open System.Web.Mvc

module NumbersFacts =
  
  [<Concern>]
  let sets_model() =
    // Arrange
    let controller = new HomeController()
    
    // Act
    let result = controller.Numbers()
    
    // Assert
    result.ViewData.Model |> should be NonNull
 

This is just one in the number of unit tests that I defined for this application.  As you can see, it's quite easy to use FsxUnit in using the AAA syntax.

 

Wrapping it Up

As you can see, getting F# to work with ASP.NET MVC wasn't absolutely trivial.  But once the overall project skeleton is defined, modifying it to fit your application is easier.  But, the question comes up, why bother doing this?  I know I'm going to get that question a lot.  Well, first off, it was a challenge to myself.  But, secondly, I'm able to use the concise F# syntax to express controllers and models quite easily without much pomp and circumstance.  Maybe a hybrid approach may work better?  Maybe F# as a view engine may yield better results?  Anyhow, feel free to pick through this sample and let me know your thoughts. 

 

I've made the project available here.



kick it on DotNetKicks.com

7 Comments

  • Tried to get this going but keep getting the following error:

    Method 'FindPartialView' in type 'MvcContrib.NHamlViewEngine.NHamlViewFactory' from assembly 'MvcContrib.NHamlViewEngine, Version=0.0.1.159, Culture=neutral, PublicKeyToken=null' does not have an implementation.

    MVC works fine using c# on my machine but would really like to get the f# build going

  • @Graham

    This was a while ago and since then the assemblies have changed. I haven't looked at it lately, so I'm not sure what has changed and what hasn't. Sorry about that.

    Matt

  • Looks like the zip file for the project is gone? Anyway I could get a copy? Thanks!

  • I've recently been smitten by F#, and always wanted to see a good MVC web framework for .NET. Wow, I can have my cake and eat it too! Thanks for saving me some time as I was considering embarking on this very path myself. I even bought a book on ASP.NET MVC with the express idea of figuring out how to configure the framework for F# development.

  • @Bob,

    You might also want to look at the Bistro project which is an MVC implementation in F#: http://www.russiantequila.com/wordpress/?p=76

    Matt

  • Interesting. So MS won't let you build a web app in F#? I get this when I try to deploy to IIS: 'F#' is not a supported language. Have you tried this lately?

  • Hi, I am a c# developer and just was wondering if I can create web applications using F#. I couldn't find any templates online. If someone knows about this please email me.

Comments have been disabled for this content.