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:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: |
|
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):
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: |
|
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:
1: 2: 3: 4: 5: |
|
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):
1: 2: 3: 4: 5: 6: 7: 8: 9: |
|
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!
.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: |
|
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).
- 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)
{English: string;
Other: string;}
Full name: fswebtoolsintroaspx.SearchResult
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = System.String
Full name: Microsoft.FSharp.Core.string
inherit obj
val mutable txtInput: obj
val mutable ctlOutput: obj
Full name: fswebtoolsintroaspx.Suggest
Full name: Microsoft.FSharp.Core.Operators.truncate
Full name: Microsoft.FSharp.Collections.ResizeArray<_>
type ReflectedDefinitionAttribute =
inherit Attribute
new : unit -> ReflectedDefinitionAttribute
new : includeValue:bool -> ReflectedDefinitionAttribute
member IncludeValue : bool
Full name: Microsoft.FSharp.Core.ReflectedDefinitionAttribute
--------------------
new : unit -> ReflectedDefinitionAttribute
new : includeValue:bool -> ReflectedDefinitionAttribute
Full name: Microsoft.FSharp.Core.array<_>
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Array.iter
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#