TP

F# Support for ASP.NET and Notes on Samples

More recent article available

This article is no longer up-to-date. Although the techniques described in the article might still work (with minor adjustments to the newer version of F#), it is now recommended to use a different approach. Also, the samples discussed in the article are not currently available. For more recent information see:

As I mentioned earlier, I spent three months as an intern in Microsoft Research in Cambridge last year and I was working with Don Syme and James Margetson from the F# team. Most of the time I was working on the F# Web Toolkit, which I introduced on the blog some time ago [1], but I also worked on a few additions that are now part of the F# release. Probably the most useful addition is a new implementation of the CodeDOM provider for the F# language which makes it possible to use ASP.NET smoothly from F# (but it can be used in some other scenarios as well) together with two ASP.NET sample applications that you can explore and use as a basis for your web sites. This was actually a part of the distribution for a few months now (I of course wanted to write this article much earlier...), so you may have already noticed, but anyway, I'd still like to write down a short description of these ASP.NET samples and also a few tips for those who're interested in writing web applications in F#.

F# and ASP.NET

Let's start by looking at the ASP.NET examples. You can find them in the samples directory in your F# installation under the Web/ASP.NET path. The directory also contains html files with description of the projects and a guide to configuring them, but I'll describe both of these topics in this post. The distribution contains two sample projects:

ASP.NET Introduction using F#

To start playing with ASP.NET you'll need to open the project (I recommend copying it to your working directory first). If you're using Visual Studio, you can select File - Open - Web Site... in the menu and select the directory with your project as demonstrated at Figure 1 below. The organization of ASP.NET projects is different than organization of ordinary F# projects - in ASP.NET the project is just a directory and it contains all the files in the directory (this is also the reason why you have to open it using a different command). The Figure 2 shows how the files of the ASP.NET Introduction project are organized in the Solution Explorer:

Open Web Site in Visual Studio
Figure 1: Open Web Site
Solution Explorer
Figure 2: Solution Explorer

As you can see, there are 6 files in the project. The Default.aspx.fs and Default.aspx together form one web page and the DataBinding.aspx.fs with DataBinding.aspx form the second web page. The App_Code directory contains application logic that can be used from other pages in the project and in our sample project it contains only one file (logic.fs). Finally, the web.config file contains configuration of the whole application.

Before we look at the pages you may want to check the web.config file, because it needs to contain the correct reference to the CodeDOM provider implementation including a version of the current F# installation. At the time of writing this article, the latest version is 1.9.3.14, but if you're not sure what version you are using, you can just start the fsi.exe from the F# installation which prints the version number. The web.config file is an xml file and it should contain the following content (with the right version number). The samples in the distribution should contain the correct version number, but the incorrect configuration is a common issue when working with ASP.NET in F#, so it is useful to know what the configuration should look like:

<?xml version="1.0"?>
<configuration><system.web>
<compilation debug="true">  
  <compilers>
    <compiler 
      language="F#;f#;fs;fsharp" extension=".fs" 
      type="Microsoft.FSharp.Compiler.CodeDom.FSharpAspNetCodeProvider, 
            FSharp.Compiler.CodeDom, Version=1.9.3.14, 
            Culture=neutral, PublicKeyToken=a19089b1c74d0809"/>
  </compilers>
</compilation></system.web>
</configuration>

Now, let's look at the Default.aspx and Default.aspx.fs files that together represent a simple page. The page contains one button and one label (a control that can display some text) and when the user clicks on the button, the result of some calculation is displayed in the label (the calculation is executed on the server-side). The following code is a (slightly simplified) content of the Default.aspx file, which defines the HTML markup together with the ASP.NET controls that are on the page:

<%@@ Page Language="F#" 
  CodeFile="Default.aspx.fs" Inherits="FSharpWeb.Default" %>
<html>
<body><form runat="server">
  <asp:Button 
    ID="btnTest" RunAt="server" Text="Click me!" 
    OnClick="ButtonClicked" /><br />
  <asp:Label ID="lblResult" RunAt="server" />
</form></body>
</html>

You can easily identify two server-side controls, because these are written using prefix asp and also contain the RunAt="server" attribute, which means that the control is processed on the server-side. You can use standard HTML notation for setting attributes of the controls. We set the ID attribute to both of the controls, which is important because it allows us to use them in the code-behind code. The OnClick attribute of the button sets an event handler - a member of the FSharpWeb.Default type from the code-behind file that will be called on the server-side, when a user clicks on the button (which submits the HTML form and causes a page reload, so the event can be processed on the server). The file also contains the first special line, which tells the ASP.NET engine that the page is written in F# and it also tells what source file contains the code-behind code and what is the name of type declared in that file. The code-behind file (Default.aspx.fs) has the following content:

#light
namespace FSharpWeb
open System
open System.Web
open System.Web.UI.WebControls

type Default() =
  inherit Page()
  
  [<DefaultValue>]
  val mutable btnTest : Button

  [<DefaultValue>]
  val mutable lblResult : Label

  member this.ButtonClicked(sender, e) =
    this.lblResult.Text <- 
      (sprintf "Factorial of 5 is: %d" 
               (FSharpWeb.Logic.factorial 5))

As we can see, the file contains an F# object type (named Default), inherited from the base ASP.NET Page type. Thanks to the recent improvements in F# it is possible to write the type using the new implicit class syntax (note the parentheses after the type name), which means that you can place additional initialization code to the type declaration directly (following the inherit clause and F# will treat this as a constructor.

The class contains a mutable field for every control declared in the declarative markup with the name same as the ID attribute in the markup file (btnTest and lblResult). The ASP.NET initializes these controls automatically when it creates the page (that's why the fields are marked using mutable), so we don't need to initialize these fields in our code - F# doesn't usually allow uninitialized fields, but if you place the DefaultValue before the field declaration, it will be initialized to the default value (null), which is correct, because the field will be later initialized by the ASP.NET runtime. Finally, the type contains a member called ButtonClicked, which is a same name we used in the markup for the OnClick attribute of the button control. This is an event handler that will be called when user clicks on the button, it performs some calculation (the factorial function is declared in the logic.fs file) and sets a property of the other control that we have on the page.

Personal Web Site in F#

Personal Web Site
Figure 3: Personal Web Site

The second sample ASP.NET project is a port of quite a complex ASP.NET web site which also uses MS SQL database. The database can be created manually as described in Welcome.html in the PersonalWebSite directory, but I also uploaded the database to my web, so you can just download it and attach it in the SQL server (this is described below and the files for download can be found at the end of the article). The PWS_AspNetDb is a database managed by ASP.NET that contains user information - the database below already contains the required role (called Administrators) and one user (admin with password admin123!). The second database file (PWS_WebPersonal) contains the application data, mainly information about photos and galleries (as you can see from the screenshot in Figure 3).

The application uses many advanced ASP.NET features including the following:

Configuring the databases

To configure the databases required by the demo, you can either follow the steps described in the Welcome.html file (which is part of the sample project), or if you already have some version of SQL Server installed on your machine, you can download the sample databases below (a bunch of MDF and LDF files) and attach them to the SQL Server. I'll explain how to do this using full version of SQL Server, but the steps needed with Express edition are very similar.

Once you downloaded the files, you'll need to launch SQL Server Management Studio and connect to the server instance. After doing that you should see an "Object Explorer" window, where you can right click on the "Databases" group and select "Attach..." from the pop-up menu. In the opened dialog window, you can add the databases (by selecting the MDF file) and click the "OK" button to attach the databases. Finally, you'll need to modify the web.config file in the Personal WebSite sample to include the following section:

<connectionStrings>
 <add name="Personal" providerName="System.Data.SqlClient" connectionString=
      "Data Source=.;Integrated Security=True;Database=PWS_WebPersonal" />
 <remove name="LocalSqlServer"/>
 <add name="LocalSqlServer" connectionString=
      "Data Source=.;Integrated Security=True;Database=PWS_AspNetDb" />
</connectionStrings>

The Data Source parameter in the connection string specifies the instance of the SQL Server, where the databases are attached. By default this is the name of your computer (which can be written shortly using dot "."). When using the SQL Express the default instance name is ".\SQLExpress". The Database parameter specifies the name of the database, which you can see in the "Object Explorer" in the SQL Server Management Studio. After following these steps, you should be able to run the demo application.

Data-access in F#

Most of the ASP.NET technologies used in the web site are used in almost the same way as in C#, so I will not discuss them in larger details, but the last item in the list - the use of ASP.NET data controls together with F# is I believe quite interesting, so I'll write a few notes about it. Let's look for example at the Photos.aspx file, which displays a list of photos in a gallery. The most important parts of the file related to data access are shown in the following code snippet:

<asp:DataList RunAt="server" DataSourceID="photoSource" EnableViewState="false">
<ItemTemplate>
  <!-- ... -->
  <a href='Details.aspx?AlbumID=<%# base.Eval("PhotoAlbumID") %>'>
    <img src="Handler.ashx?PhotoID=<%# base.Eval("PhotoID") %>&Size=S" 
      class="photo_198" style="border:4px solid white" 
      alt='Thumbnail of Photo Number <%# base.Eval("PhotoID") %>' />
  </a>
  <!-- ... -->
</ItemTemplate>
</asp:DataList>

<asp:ObjectDataSource ID="photoSource" RunAt="server" 
  TypeName="PersonalWebSite.PhotoManager" 
  SelectMethod="GetPhotos">
  <SelectParameters>
    <asp:QueryStringParameter Name="AlbumID" Type="Int32" 
      QueryStringField="albumID" DefaultValue="0"/>
  </SelectParameters>
</asp:ObjectDataSource>

The first ASP.NET control used in the code is asp:DataList, which is a control that simply displays a collection of some items and uses a specified ItemTemplate for displaying every single item. In our case we're working with collection of records and the record has several members (including PhotoID and PhotoAlbumID). To display a value of a record member in the ASP.NET page we have to use ASP.NET Eval method as you can see in the sample. The Eval method reads the specified member of the item in collection and writing the code in <%# ... %> tells ASP.NET that we want to render the result of the expression as a part of the item template. The record that we're using in this example is very simple and has the following structure:

type Photo = 
  { PhotoID:int; 
    PhotoAlbumID:int; 
    PhotoCaption:string; }

This is actually one of the places where the F# version is shorter, because in C# version you have to write this as a class with properties, which makes the code quite longer.

The second server-side control (asp:ObjectDataSource) in the first code snippet isn't visual, which means that it will not produce any HTML output. It serves just as a declarative data-source for the data list and the properties of this control specify how the content for the data list should be loaded. There are various other data-source controls in ASP.NET (e.g. asp:SqlDataSource which loads data directly from the database), but the one that we're using in the example uses a specified .NET/F# type and calls a method of the given object when it needs to retrieve the data. In F# this is even easier, because we can use a name of the module and the code that retrieves the data can be a function in the module.

As you can see the property TypeName is set to PersonalWebSite.PhotoManager, which can be treated as a module called PhotoManager in a namespace PersonalWebSite and the SelectMethod property, which specifies a name of the function that will be called when loading the data is set to GetPhotos. Finally, the SelectParameters specifies that the function expects one argument called AlbumID and that the value of this argument should be retrieved from the query string (that is from the URL of form photo.aspx?albumIdD=42).

Now it is pretty easy to implement the module that matches this specifications and can be used as a data source (we could of course use FLinq for loading the data, but I wanted to keep things simple for now, so we're using standard SqlDataReader object from .NET):

namespace PersonalWebSite
// ...

module PhotoManager = 
  // Function will be called by ASP.NET when loading data
  // for the asp:DataList control in the 'Photos.aspx' page
  let GetPhotos (albumID:int) =
    // Open connection & create command
    use conn = new SqlConnection(" ... ")
    use cmd  = new SqlCommand("GetPhotos", conn)
    
    // Can the user see 'private' photos?
    let usr = HttpContext.Current.User
    let filter = not (usr.IsInRole("Friends") || usr.IsInRole("Administrators"))
    cmd.CommandType <- CommandType.StoredProcedure)
    cmd.Parameters.Add(SqlParameter("@@AlbumID", albumID)) |> ignore
    command.Parameters.Add(SqlParameter("@@IsPublic", filter)) |> ignore
    conn.Open()
    
    // Read all photos into a .NET ResizeArray type
    let list = new ResizeArray<_>()
    use reader = command.ExecuteReader()
    while (reader.Read()) do
      list.Add({ PhotoID = unbox (reader.get_Item("PhotoID")) 
                 PhotoAlbumID = unbox (reader.get_Item("AlbumID")) 
                 PhotoCaption = unbox (reader.get_Item("Caption")) }) 
                 
    // Return the created collection
    list 

I hope this article explained some of the interesting things that you may find in the ASP.NET samples provided with the F# distribution and I think that the Personal WebSite Sample shows that F# can be used for writing quite complicated web applications as well. Of course, my initial motivation for writing web applications in F# is the possibility of using meta-programming to develop client-side code (the code that runs on the client as a JavaScript) in F# too, which makes development of modern "Ajax"-style applications easier, so if you're interested in this project you may want to look at F# Web Tools [1].

Downloads & References

Published: Saturday, 8 March 2008, 11:07 PM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: web, f#