Tutorial: Implementing a Chat Client Using Asynchronous Workflows
Applies to: Functional Programming
Authors: Tomas Petricek and Jon Skeet
Get this book in Print, PDF, ePub and Kindle at manning.com. Use code “MSDN37b” to save 37%.
Summary: This tutorial discusses how to use F# asynchronous workflows for implementing reactive user interfaces. This is demonstrated using an example of an online chat client that also communicates with the server side.
This topic contains the following sections.
- Designing an Online Chat Application
- Preparing the Server and a User Interface
- Implementing Reactive Interaction
- Summary
- Additional Resources
- See Also
This article is associated with Real World Functional Programming: With Examples in F# and C# by Tomas Petricek with Jon Skeet from Manning Publications (ISBN 9781933988924, copyright Manning Publications 2009, all rights reserved). No part of these chapters may be reproduced, stored in a retrieval system, or transmitted in any form or by any means—electronic, electrostatic, mechanical, photocopying, recording, or otherwise—without the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.
Designing an Online Chat Application
Most Silverlight applications are not just standalone client-side applications. They usually use a service running on the server side. The service may be used as a source of data, execute some computations that cannot be done on the client side, or allow multiple clients to communicate with each other.
This tutorial looks at a simple client/server application that implements a simple chat application. The server-side uses a simple agent-based HTTP server (that is described in detail in a separate tutorial). The client side of the chat is an F# control implemented in Silverlight. A screenshot of the running application is shown in Figure 1.
Figure 1. A Silverlight client/server chat application created in F#
The application's user interface is extremely simple, but the implementation demonstrates an interesting technique for programming reactive user interfaces. The sample uses F# asynchronous workflows to encode the application's control flow. This is a very natural way of thinking about user interaction and it fits extremely well with applications that communicate with a server.
Before discussing asynchronous workflows, we need to set up the server side and build a user interface.
Preparing the Server and a User Interface
The server-side part of the application is a very simple service that communicates using the HTTP protocol. The content of the chat room is returned via the /chat page. Sending a message to the room is done via the /post page, which takes the text as an HTTP POST input. The service can be implemented using F# agents and the HttpListener type. The implementation is discussed in the following tutorial. A link to the complete source code is provided at the end of this article:
The client-side code in this tutorial is part of a standalone F# Silverlight project. It uses a XAML file to define the user interface and it accesses the controls defined in XAML using the dynamic operator (?). Information about the project structure and the techniques used can be found in the following related articles:
To begin, create a new F# Script file for the server side (for example Server.fsx) and a new F# project in Visual Studio using the "F# Client-Side Application (Silverlight)" template from the "Online Templates" section.
Designing the User Interface
Once an F# Silverlight project is created, the next step is to add a new XAML file (e.g., Chat.xaml) that defines the user interface. The following snippet defines the layout from Figure 1:
<StackPanel Orientation="Vertical" Margin="40">
<StackPanel Orientation="Vertical" x:Name="OutputPanel" />
<StackPanel Orientation="Horizontal" Margin="10">
<TextBox Width="200" x:Name="InputBox" Margin="5" />
<Button Content="Send" x:Name="SubmitButton" Margin="5" />
</StackPanel>
</StackPanel>
The user interface contains two main elements. The StackPanel named OutputPanel is used for displaying messages. When loading the messages, the application creates a new TextBlock element for every message and adds it as a child to this StackPanel. Below this panel, there is another StackPanel that arranges elements from left to right. It contains a TextBox for entering new messages and a Button for sending messages to the server.
Note that all of the elements that need to be accessed from the F# code have the x:Name attribute. The next section uses these names and the dynamic operator (?) (defined in a How-To article mentioned above) to access the controls.
Creating a Chat Control
The next step of creating the chat client is to implement the main page of the Silverlight application. When creating the project using a template, the code can be placed into the existing Page.fs file. This section looks at the boilerplate code that loads the user interface from the XAML file. The snippet also creates local fields for all named GUI elements (a textbox and a button for sending messages and a panel for displaying the chat):
namespace ChatClient
open System
open System.Windows
open System.Windows.Media
open System.Windows.Controls
open FSharp.Silverlight
type Chat() as this =
inherit UserControl(Width = 800.0, Height = 600.0)
let uri = Uri("/ClientChat;component/Chat.xaml", UriKind.Relative)
do Application.LoadComponent(this, uri)
let btn : Button = this?SubmitButton
let inp : TextBox = this?InputBox
let out : StackPanel = this?OutputPanel
// TODO: Add user interaction handling here
The details of this snippet are discussed in the articles mentioned earlier. In short, the first few lines of the constructor create a Uri object that refers to the XAML file in the resources and construct the user interface using the LoadComponent method.
Then, the three let declarations use the dynamic operator (?) to get references to the user interface elements that the F# code works with. The dynamic operator enables you to use the member access syntax (with a question mark instead of a dot) to access properties that cannot be statically checked at compile time. The operator implementation that finds a XAML element by name comes from the FSharp.Silverlight namespace and is discussed in a How-To article mentioned earlier.
The project can now be compiled and executed. It should display a user interface similar to Figure 1. Before adding the chat functionality, the next section discusses how to get the chat service running on the server side.
Running the Server Side
If you've read these articles in the sequential order and are already familiar with the tutorial that discussed creating an agent-based chat server, you can follow the steps below to start the server. As an alternative, the source code with a complete solution and the example is available at the end of the article.
Go to the article Tutorial: Creating a Chat Server Using Mailboxes and copy the declarations of Agent, ChatMessage and ChatRoom to a new F# Script File. Then, create a new instance of ChatRoom. This object will keep the state of the chat room.
Copy the declaration of the HttpExtensions module (with helper functions for creating an HTTP server) and the HttpAgent type.
In the initialization code, set the root path to point to the bin subdirectory in the folder with the Silverlight project (i.e., <ClientSide>\bin). This way, the server can serve the TestPage.html that is generated by compiling the Silverlight application.
Add the .xap extension to the dictionary of allowed content types. The MIME type for the extension should be application/x-silverlight-app. This allows the server to handle requests to load the Silverlight component.
Copy the snippet that uses the HttpAgent to create a chat server that handles /post and /chat pages. When using the same URL as specified in the tutorial, the server can be tested by navigating to https://localhost:8080/TestPage.html (after compiling the client-side project).
At this point, the skeleton of the client-side application as well as the chat service running on the server side are ready. The next section looks at the interesting part that connects the two components using asynchronous workflows.
Implementing Reactive Interaction
While running, the chat application does two things. First, when the user clicks the "Send" button, the application creates a new HTTP request to send the current message to the server and then clears the textbox. Second, it downloads messages from the server every second.
Sending Messages
Handling the first operation is very simple. It doesn't need to wait until the request completes, so it can be written as an ordinary event handler:
btn.Click.Add(fun _ ->
let wc = new System.Net.WebClient()
let uri = new Uri("https://localhost:8080/post")
wc.UploadStringAsync(uri, inp.Text)
inp.Text <- "" )
In F#, events appear as values of the IEvent<'T> type. The interface has an Add method for registering handlers that takes an F# function. Unlike in C#, the function has only a single argument (derived from EventArgs). The body of the handler creates a new WebClient object and sends the current text (inp.Text) to the service using the /post URL. The UploadStringAsync method doesn't block the current thread, so the handler completes immediately while the request is being sent.
Receiving Messages
The second aspect of the application is more interesting. It needs to get content from the chat service repeatedly, but the application shouldn't send a new request before the previous request completes. If that were done, the application might get a reply to the second request before getting a reply to the first one (and so messages would appear and then disappear again). In addition, it is not desirable to overload the server unnecessarily.
A desirable behavior would be a loop that sends a request to download the messages, waits for the reply, waits one second, and then starts again. The Figure 2 demonstrates the idea.
Figure 2. The structure of a loop that repeatedly downloads chat messages
The loop is not an ordinary busy loop that performs some calculation. There are three things that make it special:
The loop runs during the entire application lifetime, so it will run forever. This may sound suspicious because infinite loops usually cause applications to hang.
Some parts of the code running in the loop need to update the user interface. This means that the body of the loop should run on the GUI thread to make these updates safe.
Most of the time, the code running in the loop is waiting (for the completion of a HTTP request or for a specified time).
The way to implement a loop like this in F# is to create an asynchronous workflow that runs on the main thread. This means that all work that runs in the workflow will be scheduled to run on the GUI thread. Thanks to the use of asynchronous workflows, waiting (including HTTP communication) will not block the application, so it will remain responsive:
let refreshLoop = async {
let wc = new System.Net.WebClient()
let uri = Uri("https://localhost:8080/chat")
while true do
let! html = wc.AsyncDownloadString(uri)
let nodes = XDocument.Parse(html)
out.Children.Clear()
for el in nodes.Element(XName.Get("ul")).
Elements(XName.Get("li")) do
out.Children.Add(TextBlock(Text = el.Value))
do! Async.Sleep(1000) }
do refreshLoop |> Async.StartImmediate
The snippet creates a workflow named refreshLoop. The snippet declares the workflow as a value of type Async<unit> (instead of a function). This is possible because workflows need to be explicitly started, which is done at the end of the snippet using the Async.StartImmediate method. The method starts the workflow on the current (GUI) thread and ensures that all of the code in the workflow also runs on the same thread.
The workflow first initializes a new WebClient object and then runs a loop. Inside the loop, it asynchronously gets the chat room's content. This is achieved by calling AsyncDownloadString (which is an extension added by the F# library) using the let! construct. The result is an HTML document with a root tag <ul> containing an <li> element for every message. The document is parsed using LINQ to XML, so the project needs a reference to the System.Xml.Linq.dll assembly. It is also necessary to open the System.Xml.Linq namespace to access the XDocument and XName types.
After iterating over all of the <li> elements, the snippet performs another asynchronous operation. The Async.Sleep call creates a primitive workflow that resumes the computation after 1 second. That workflow doesn't return any result, so it can be called using the do! construct.
Note
The loop that was implemented in the above example is a simple case of a state machine. A more general case, for example, is a wizard. Wizards have multiple pages (states) and transitions between them caused by some events (e.g., clicks or completion of some background task).
When the application contains a state with several possible transitions (e.g., a page with "Next" and "Previous" buttons), it needs to wait for the first of several events. This can be done using an overloaded AwaitObservable method. The method is not currently available in F# libraries, but it is available on the Real World Functional Programming book web site (see references below). Chapter 16 includes an example that uses this feature to implement an application for drawing rectangles.
The first step when running the application is to recompile the Silverlight project. If the project is started as it is (using F5), Microsoft® Visual Studio will open the generated TestPage.html locally. That wouldn't work because Silverlight applications can only send requests to the server from which they are loaded. To run the application correctly, navigate to https://localhost:8080/TestPage.html. This way, the Silverlight application is accessed via the chat server (that was started earlier in F# Interactive in this tutorial). Then, the client-side code can call the service (using the same URL).
Summary
This article implemented an online chat application. It focused on the user interface part, which was created as a Silverlight application. The client-side connects to an agent-based service running on the server. To make sure that client/server applications do not hang, Silverlight provides only asynchronous methods for calling the server. Thanks to F# asynchronous workflows, using this API was easy because it wasn't necessary to use events directly and write all of the code in event handlers. Instead, the sample called an asynchronous method using the let! construct.
After receiving messages from the server, the messages need to be displayed in the user interface. This can be done only from the main user interface thread, so the application started the workflow using Async.StartImmediate. This function guarantees that all of the workflow code runs on the thread that starts the workflow. This way, it is also possible to write code that uses asynchronous workflows to implement complex user interaction that involves several events such as wizards.
Additional Resources
This article demonstrated how to use asynchronous workflows for creating Silverlight user interfaces. As already mentioned, the tutorial used a chat service created in another article and a template as well as helper functions that are discussed in the following articles:
An alternative approach to handling events is to use higher-order functions working with events. This makes it easy to filter event occurrences or merge events caused by different controls. The following articles give the overviews and comparison of the two approaches and provide more details about events:
To download the code snippets shown in this article, go to https://code.msdn.microsoft.com/Chapter-3-Reactive-Client-8a458f7d
See Also
This article is based on Real World Functional Programming: With Examples in F# and C#. Book chapters related to the content of this article are:
Book Chapter 9: “Turning values into F# object types with members” explains how to use object-oriented features of the F# language. This is an important topic for client-side development because it explains how to mix the object-oriented Silverlight design with the functional programming style.
Book Chapter 13: “Asynchronous and data-driven programming” explains how asynchronous workflows work and uses them to download data from the Internet.
Book Chapter 16: “Developing reactive functional programs” discusses how to write reactive user interfaces using asynchronous workflows and events. It also includes a basic introduction to the Reactive Framework (Rx).
The following MSDN documents are related to the topic of this article:
Events (F#) provides more details on F# first-class events.
Asynchronous Workflows (F#) explains how to use F# asynchronous workflows for writing long-running computations that do not block threads.
Previous article: Tutorial: Reactive Programming Using Events
Next article: How to: Design Silverlight User Interfaces