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
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) =
{ let db = DictionaryDb(connectionString)
let res =
SQL <@@ { for p in
when p.English.StartsWith(
-> {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) =
{ 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):
/// When text changes, trigger server call to update suggestions
member this.UpdateSuggestions(sender, e) =
{ 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!.
/// Display newly received array with results
member this.DisplayResponse (sugs:ResizeArray<Result>) =
{ 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 [^].
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).
F# Web Tools: Rich client/server web applications in F# - Research paper (unpublished draft)
Ajax-style Client/Server Programming with F# (Slides, PDF) - Final Internship Presentation
F# Web Tools at CodePlex [^]- Project & Samples source code
Related projects and links
[1] Erik Meijer: Volta - Wrapping the Cloud with .NET - Part 1 [^] - Erik Meijer, Microsoft
[2] Google Web Toolkit [^] - Google
[3] Links: Linking Theory to Practice for the Web [^] - Ezra Cooper, Sam Lindley, Philip Wadler, Jeremy Yallop
[4] Microsoft Silverlight [^] - Microsoft
[5] Script# [^] - Nikhil Kothari
[6] Book: Expert F# [^] - Don Syme, Adam Granicz, Antonio Cisternino
[7] Book: Foundations of F# [^] - Robert Pickering
[8] Leveraging .NET Meta-programming Components from F# (PDF)
type SearchResult =
{English: string;
Other: string;}
Full name: fswebtoolsintroaspx.SearchResult
SearchResult.English: string
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
type string = System.String
Full name: Microsoft.FSharp.Core.string
SearchResult.Other: string
inherit obj
val mutable txtInput: obj
val mutable ctlOutput: obj
Full name: fswebtoolsintroaspx.Suggest
Suggest.txtInput: obj
Suggest.ctlOutput: obj
val truncate : value:'T -> 'T (requires member Truncate)
Full name: Microsoft.FSharp.Core.Operators.truncate
type ResizeArray<'T> = System.Collections.Generic.List<'T>
Full name: Microsoft.FSharp.Collections.ResizeArray<_>
type ReflectedDefinitionAttribute =
new : unit -> ReflectedDefinitionAttribute
new : includeValue:bool -> ReflectedDefinitionAttribute
member IncludeValue : bool
Full name: Microsoft.FSharp.Core.ReflectedDefinitionAttribute
type 'T array = 'T []
Full name: Microsoft.FSharp.Core.array<_>
module Array
from Microsoft.FSharp.Collections
val iter : action:('T -> unit) -> array:'T [] -> unit
Full name: Microsoft.FSharp.Collections.Array.iter
val ignore : value:'T -> unit
Full name: Microsoft.FSharp.Core.Operators.ignore
Published: Friday, 13 July 2007, 4:32 AM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: web, f#
September 2022 (1), April 2022 (1), October 2021 (1), April 2021 (1), October 2020 (1), July 2020 (1), April 2020 (2), December 2019 (1), February 2019 (1), November 2018 (1), October 2018 (1), May 2018 (1), September 2017 (1), June 2017 (1), April 2017 (1), March 2017 (2), January 2017 (1), October 2016 (1), September 2016 (2), August 2016 (1), July 2016 (1), May 2016 (2), April 2016 (1), December 2015 (2), November 2015 (1), September 2015 (3), July 2015 (1), June 2015 (1), May 2015 (2), April 2015 (3), March 2015 (2), February 2015 (1), January 2015 (2), December 2014 (1), May 2014 (3), April 2014 (2), March 2014 (1), January 2014 (2), December 2013 (1), November 2013 (1), October 2013 (1), September 2013 (1), August 2013 (2), May 2013 (1), April 2013 (1), March 2013 (1), February 2013 (1), January 2013 (1), December 2012 (2), October 2012 (1), August 2012 (3), June 2012 (2), April 2012 (1), March 2012 (4), February 2012 (5), January 2012 (2), November 2011 (5), August 2011 (3), July 2011 (2), June 2011 (2), May 2011 (2), March 2011 (4), December 2010 (1), November 2010 (6), October 2010 (6), September 2010 (4), July 2010 (3), June 2010 (2), May 2010 (1), February 2010 (2), January 2010 (3), December 2009 (3), July 2009 (1), June 2009 (3), May 2009 (2), April 2009 (1), March 2009 (2), February 2009 (1), December 2008 (1), November 2008 (5), October 2008 (1), September 2008 (1), June 2008 (1), March 2008 (3), February 2008 (1), December 2007 (2), November 2007 (6), October 2007 (1), September 2007 (1), August 2007 (1), July 2007 (2), April 2007 (2), March 2007 (2), February 2007 (3), January 2007 (2), November 2006 (1), October 2006 (3), August 2006 (2), July 2006 (1), June 2006 (3), May 2006 (2), April 2006 (2), December 2005 (1), July 2005 (4), June 2005 (5), May 2005 (1), April 2005 (3), March 2005 (3), January 2005 (1), December 2004 (3), November 2004 (2),
License and about
All articles on this site are licensed under Creative Commons Attribution Share Alike. All source code samples are licensed under the MIT License.
This site is hosted on GitHub and is generated using F# Formatting and DotLiquid. For more info, see the website source on GitHub.
Please submit issues & corrections on GitHub. Use pull requests for minor corrections only.
Twitter: @tomaspetricek
GitHub: @tpetricek
Email: tomas@tomasp.net