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.
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:
Since the F# projects do not support creating folders, this next step requires some Visual Notepad support.
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.
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.
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.
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:
We now have a basic setup in which to build upon for our application. Now we can concentrate on the models, controllers and views.
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:
Done! Now that was easy! Moving onto 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:
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.
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.
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:
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.
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.
Our about page will look like the following:
And lastly, our numbers page will display the numbers from 1-10 in an unordered list:
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.
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.
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.
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.