F# Web Tools: "Ajax" applications made simple

I started thinking about working on "Ajax" framework quite a long time ago - the key thing I really wanted from the beginning was using the same language for writing both client and server side code and the integration between these two sides, so you could write an event handler and specify if it should be executed on the client or on the server side. About a year ago I visited Cambridge (thanks to the MVP program) and I had a chance to talk with Don Syme [^]. Don showed me a few things in F# and suggested using F# for this project, so when I was later selected to do an internship at MSR, this was one of the projects that I wanted to work on.

The original reason for using F# was its support for meta-programming ([8], which makes it extremely easy to translate part of the page code-behind code to JavaScript. During my internship, the F# team was also working on a feature called computational expressions, which proved to be extremely useful for the F# Web Tools as well - I bet you'll hear a lot about this from Don soon, so I'll describe only the aspects that are important for this project. Aside from these two key features that F# has, I also quite enjoyed programming in F# itself - I already used it for a few things during the last year, but I could finally work on a large project in F# (and discuss the solution with the real experts!) and I don't believe I would be able to finish the project of similar complexity during less than three months in any other language (but this is a different topic, which deserves separate blog post).

What makes "Ajax" difficult?

Traditional "Ajax" application consists of the server-side code and the client-side part written in JavaScript (the more dynamicity you want, the larger JS files you have to write), which exchanges some data with the server-side code using XmlHttpRequest, typically in JSON format. I think this approach has 3 main problems, which we tried to solve in F# Web Tools. There are a few projects that try to solve some of them already - the most interesting projects are Volta from Microsoft [1], Links language [3] from the University of Edinburgh and Google Web Toolkit [2], but none of the projects solve all three problems at once.

1. Limited client-side environment

First of the problems with "Ajax" style applications is that significant part of the application runs on the client-side (in a web browser). Currently majority of the web applications use JavaScript to execute code in the browser, so in the F# Web Tools we wanted to use JavaScript as well, however in the future, when installation of Silverlight [4] becomes more common, we would like to allow using Silverlight as an alternative.

F# Web Tools allows you to write client-side code in F# (so if you don't know JavaScript, you don't have to learn it!) and also use your existing knowledge of .NET and F# classes and functions (so you can use some of the .NET and F# types when writing client-side code). The code you write in F# is of course executed in JavaScript, so it runs in any browser that supports JavaScript - the current implementation is tested with IE and Firefox, but it could be easily tested with other browsers.

2. Discontinuity between server and client side

The second major problem with "Ajax" applications is that the web application has to be written as two separate parts - client-side part (when written in JavaScript) consists of several JS files and the server-side part (for example in ASP.NET) is written as a set of ASPX and C# or VB files. Also when using JavaScript, both sides use different formats to store the data, so bridging this gap is difficult. In Silverlight [4] (or in GWT [2]), the gap is still there, even though both parts are written using the same technology - the client-side part is usually even a separate project.

In F# Web Tools we wanted to make this discontinuity as small as possible - You can write both server and client-side code in a same file (as a code-behind code). You can also call server-side functions from the client-side code and you can use certain data-types (including your own) in both sides and you can send them as an arguments from one side to the other. What's also important is that these calls are done without blocking the browser, but without the usual cumbersome programming style (calling a function, setting a callback and writing the rest of the code in the callback).

3. Components in web frameworks are only server-side

The third key problem appears once we tightly integrate client and server side code, because there is one more step that has to be done - most of the web frameworks have some way for composing web site from smaller pieces (in ASP.NET this is done using controls) and by defining the interaction between these pieces, however they allow defining the interaction only for the server-side, which is rather problematic in "Ajax" applications, where most of the interaction between components is done on the client-side.

Since F# Web Tools is built using ASP.NET, we wanted to allow same compositionality as ASP.NET - to achieve this, controls written using F# Web Tools can wrap both server and client side functionality. Controls than expose both server and client side properties and events that can be used by the page to implement server-side, respectively client-side interaction between components.

Example - "Ajax" dictionary

Ajax dictionary

I will demonstrate some of the F# Web Tools features using an "Ajax" dictionary application (see screenshot on the right side) which displays possible matching words as you type the word you want to find. This is one of the typical "Ajax" tasks, so it's a good example to start with. First, we need to define the code-behind file for the ASP.NET page - the page itself is quite simple, it contains just two controls - textbox for entering word that you're looking for (txtInput) and generic element for displaying results (ctlOutput), so let's look at the code-behind:

// F# record type, which will be used for sending lookup 
// results from the server-side to the client-side
type SearchResult = { English:string; Other:string; }

// Code-behind type for the ASP.NET page
[<MixedSide>]
type Suggest = 
  inherit ClientPage as base
  
  // Controls for entering text and displaying result
  val mutable txtInput : TextBox
  val mutable ctlOutput : Element

  (* .. interaction of components will go here .. *)

Aside from the code-behind class, we also defined a type (SearchResult), which will be used for returning loaded results from server to the client side - it is just a F# record containing two strings (word in English and word in the language we're translating to). Now let's look at the methods that define the interaction logic. The first method that we will look at is a method running on the server-side that takes entered text as an argument and returns a collection of results (ResizeArray<SearchResult>) - it is a static method, so it can't modify anything else on the page (I will write about non-static methods in one of the next articles):

// Searches the dictionary for specified prefix
static member LoadSuggestions(prefix) = 
  server
    { let db = DictionaryDb(connectionString)
      let res = 
        SQL <@ { for p in §db.Dictionary
                 when p.English.StartsWith(§prefix) 
                 -> {English=p.English; Other=p.Other} }
                 |> truncate 10 @> 
      return (new ResizeArray<_>(res)) }

The method is all wrapped in server computational expression (this is one of the new F# features - it will be in details described in the Expert F# [6] book, but I'll definitely write a few lines about it as well) - for now you can read it as (almost) ordinary F# code wrapped in a block that specifies how the code should be executed. The server block is executed as ordinary F# code, but wrapping the code in this block allows us to call it from the client side later. The server block also changes the type of the method, so it doesn't return ResizeArray<SearchInfo>, but Server<ResizeArray<SearchInfo>>, where the Server type helps to ensure that the code will be executed only on the correct side.

Now, let's look at the members that define client-side interaction. The entry point on the client-side is a method called Client_Load, which is executed when the page is loaded in the browser:

/// Initialization on the client side - attach event handlers    
[<ReflectedDefinition>]
member this.Client_Load(sender, e) =
  client
    { do this.txtInput.ClientKeyUp.AddClient(this.UpdateSuggestions) }

In this case we just register an event handler that will be called whenever user types something to the txtInput textbox. It is important to note that this code will be executed in JavaScript - even though when writing it, it is easy to forget about this! You can see the method that is used as an event handler in the next code sample (this whole method will be executed in JS as well):

[<ReflectedDefinition>]
member this.UpdateSuggestions(sender, e) =
  client
    { do! asyncExecute
           (client_async 
             { let  sprefix = this.txtInput.Text
               let! sugs = serverExecute(Suggest.LoadSuggestions(sprefix))
               do!  this.DisplayResponse(sugs) }) }            

If you look at the method, you can see that it contains call to the asyncExecute function and the argument given to the function is entire block of F# code marked using client_async computational expression. The asyncExecute function executes the given block asynchronously, which means that it doesn't block the calling function (and it doesn't block browser GUI) - you can look at it as if it created new thread (but it's actually using one trick from functional programming called continuation passing style, because JavaScript doesn't support threads). In the client_async block, we first read the value from the textbox and then call the LoadSuggestions static method to get collection with matching words. The call to the server-side functions is done using serverExecute function (which is a special function, because it bridges the gap between client and the server automatically). Once we get the result we can call the DisplayResponse method to display the results. You can see that we used a let! and do! instead of let and do here - this is because we're calling a methods that are written using F# computational expressions (server or client blocks). In general when you're calling any ordinary code, you can use the operators without the exclamation mark, but when calling a code wrapped in a block you have to use let! or do!.

[<ReflectedDefinition>]
member this.DisplayResponse (sugs:ResizeArray<Result>) =
  client
    { let sb = StringBuilder()
      let array = sugs.ToArray()
      do  Array.iter (fun (res) -> 
            sb.Append("<li><strong>" + r.Source + 
                      "</strong> - " + r.Target + "</li>") |> ignore)
      do   this.ctlOutput.InnerHtml <- "<ul>" + (sb.ToString()) + "<ul>" }

In this last sample code, we just generate HTML code from the data we received from the server and display them. It is interesting to note, that the code is running on the client-side (it's wrapped in the client block), but you can still use some F#/.NET types and functions in the code (we're using ResizeArray, StringBuilder classes and Array.iter function). This is possible because F# Web Tools re-implements subset of the F#/.NET functionality for the client-side code. And that's all - I described slightly simplified version of one of the F# Web Tools demos, but you can get the full source code if you check out our CodePlex project. You can also look at the live Dictionary sample [^].

More information

I think this example gives you a general idea what is the F# Web Tools and why it is interesting. I will definitely write more about it in the future, because I didn't describe all important features in this example. If you're interested in this project, you can also read the Paper we submitted to the ML Workshop or slides from the presentation I did at the end of my internship in MSR Cambridge (see below). This project is also available to the community at CodePlex, so you can look at the source code (including two more samples).

Related projects and links

Discuss on twitter, .
Send corrections via GitHub pull requests.