A Real Time Rich Internet Application (RTRIA) Example

03 Nov 2009

I've just had an article published in the latest UK MSDN Flash newsletter on How to consume real-time data in a Silverlight RIA. As part of writing up the article I developed a sample Real-Time Rich Internet Application (RTRIA) that consumes real-time data from the Twitter real-time data feed. I also put together my first ever screencast. So, you can start by getting hold of the code or watching the screencast.

The Code

First, and this is Important:

To get the sample application to stream real-time data from the Twitter real-time feed you will need to use Fiddler to trick Silverlight into allowing a crossdomain Web Request.

Now that you are aware of that, you will also need the Silverlight development environment. You can get everything you need via the Silverlight Getting Started page.

You've now got everything you need to run the RTRIA example. To run the sample application you should set the MSDNFlashRTRIAExample.Web project as the startup project and the MSDNFlashRTRIAExampleTestPage.html page as the startup page.

[caption id="attachment_488" align="alignnone" width="335" caption="Setting up the solution to run the application"]Setting up the solution to run the application[/caption]

If you'd like to find out a bit more about the code then read on. If you'd rather jump straight into the code you can download it from the TweetStreamer Google Code project.

The streaming connection

The following extracts of code may be slightly modified but that has been done to be able to explain what the code does in general a bit better.

The following extract is used to establish a connection to the Twitter real-time data stream using a HttpWebRequest. The important thing to note is the use of request.AllowReadStreamBuffering = false; which is required since we are requesting a streaming feed. Without setting the AllowReadStreamBuffering property to false the ConnectionResponseCallback callback will not be invoked because the response will be continuously buffering.

Since the Twitter real-time data stream requires authentication, and we can't set Credentials on the HttpWebRequest in Silverlight, the browser will prompt the user for a username and password.
[csharp]
private const string SPRITZER_URL = "http://stream.twitter.com/1/statuses/sample.json";

/// <summary>
/// Starts the connection to the Twitter real-time data stream.
/// </summary>
public void Connect()
{
this.InternalConnectionStatus = ConnectionStatus.Connecting;

try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri(SPRITZER_URL));
request.AllowReadStreamBuffering = false;
request.BeginGetResponse(ConnectionResponseCallback, request);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
this.InternalConnectionStatus = ConnectionStatus.Disconnected;
}
}
[/csharp]

Within the callback method we ensure that we are connected and then call the ReadResponseStream method which will not return until we call Disconnect().

[csharp]
/// <summary>
/// Called when the initial connection has been established.
/// </summary>
private void ConnectionResponseCallback(IAsyncResult asynchronousResult)
{
try
{
HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;
using (HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult))
{
if (response.StatusCode == HttpStatusCode.OK)
{
this.InternalConnectionStatus = ConnectionStatus.Connected;

this.ReadResponseStream(request, response);

if (this.InternalConnectionStatus != ConnectionStatus.Disconnecting)
{
// unexpected status
Debug.WriteLine("unexpected connection status: " + this.InternalConnectionStatus);
}
}
else
{
Debug.WriteLine("unexpected status code: " + response.StatusCode);
}
}

this.InternalConnectionStatus = ConnectionStatus.Disconnected;
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
[/csharp]

In the ReadResponseStream method we continually read the stream until we are interrupted by the user setting the connection status to a value other than ConnectionStatus.Connected, by calling the Disconnect() method, or an exception is thrown whilst reading from the stream. For each read from the stream we parse the data to get the tweets from the real-time data feed in JSON format.

[csharp]
/// <summary>
/// Reads the information received from the Twitter real-time data stream.
/// </summary>
private void ReadResponseStream(HttpWebRequest request, HttpWebResponse response)
{
byte[] buffer = new byte[65536];
using (Stream stream = response.GetResponseStream())
{
while (this.InternalConnectionStatus == ConnectionStatus.Connected)
{
int read = stream.Read(buffer, 0, buffer.Length);
UTF8Encoding encoding = new UTF8Encoding();
string data = encoding.GetString(buffer, 0, read);
ParseResponseChunk(data);
}

// need to call request.Abort or the the thread will block at the end of
// the using block.
request.Abort();
}
}
[/csharp]
The ParseResponseChunk checks the data it's passed and ensures that the data contains at least one full status message (tweet). I'll not go into the details of that here since it's just a matter of string parsing.

I chose to use the JSON format simply because the content passed over the wire is smaller than the XML feed. This should mean that the application has to do less work to read all the data. What we really should also do is benchmark the deserialisation of JSON against the deserialisation of XML to see which performs best within a Silverlight application.

Deserialising the JSON

The following JavaScript JSON snipped shows an example of a single Tweet that we get back from the Twitter real-time data feed.
[javascript]
{
"in_reply_to_status_id":9999999,
"in_reply_to_user_id":00000000,
"favorited":false,
"in_reply_to_screen_name":"leggetter",
"text":"@leggetter Wow! A Real-Time Rich Internet Application (RTRIA)",
"id":2820354600,
"created_at":"Fri Nov 4 09:39:33 +0000 2009",
"truncated":false,
"source":"<a href=\"http:\/\/tweetdeck.com\/\">TweetDeck<\/a>"
}
[/javascript]

The JSON can be deserialised as an instance of a C# class using the DataContract attribute on the class and the DataMember attributes on properties.
[csharp]
using System;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.ComponentModel;

namespace TweetStreamer
{
[DataContract]
public class StatusMessage : IStatusMessage, INotifyPropertyChanged
{
[DataMember(Name = "text")]
public string Text {get;set}
}
}
[/csharp]

A single Tweet, or in TweetStreamer a StatusMessage, can be deserialised using an instance of the DataContractJsonSerializer.
[csharp]
/// <summary>
/// Creates a single message from json string.
/// </summary>
/// <param name="messageData">The message data.</param>
/// <returns></returns>
private static IStatusMessage CreateMessageFromJsonString(string messageData)
{
Debug.WriteLine(String.Format("Creating StatusMessage for: {0}", messageData));

IStatusMessage message = null;

using (MemoryStream stream = new MemoryStream(UTF8Encoding.UTF8.GetBytes(messageData)))
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(StatusMessage));
message = ser.ReadObject(stream) as StatusMessage;
}

if (string.IsNullOrEmpty(message.Id))
{
message = null;
Debug.WriteLine("message had no ID. Assuming to be a delete message so nulling message object");
}

return message;
}
[/csharp]

Binding the data to the grid

To bind the data to a DataGrid the grid needs to be defined in XAML. In addition we can specify the properties on the StatusMessage that we want to bind to columns. In the XAML below we are binding the CreatedAtString property to a Time column, a User.ScreenName to a User column, and a Text property to a Message column. Notice the cool binding of User.ScreenName. The StatusMessage.User property returns an instance of another class and we are actually binding to a property on the returned class.
[xml]
<data:DataGrid Grid.Row="0" x:Name="Tweets" AutoGenerateColumns="False">
<data:DataGrid.Columns>
<data:DataGridTextColumn Header="Time"
Binding="{Binding CreatedAtString}" />
<data:DataGridTextColumn Header="User"
Binding="{Binding User.ScreenName}" />
<data:DataGridTextColumn Header="Message"
Binding="{Binding Text}" />
</data:DataGrid.Columns>
</data:DataGrid>
[/xml]
Next, the code to set up the binding and registering for StatusMessage updates using the StatusMessageReceived event. As you many have noticed, the StatusMessage object implements the INotifyPropertyChanged interface. This was used so that we could add each StatusMessage to an ObservableCollection<T> and then bind it to a DataGrid to display the Tweets in real-time.
[csharp]
Connection _twitterConnection;
ObservableCollection<IStatusMessage> _messages = new ObservableCollection<IStatusMessage>();
public MainPage()
{
InitializeComponent();

Tweets.ItemsSource = _messages;

_twitterConnection = new Connection();
_twitterConnection.StatusMessageReceived += new Connection.OnStatusMessageReceivedEventHandler
(twitterConnection_StatusMessageReceived);
_twitterConnection.Connect();
}
[/csharp]
Finally, whenever we get a StatusMessageReceived callback we need to add the new StatusMessage to the ObservableCollection</code> collection. This has to be done on the UI thread and via the Tweets.ItemsSource property or the UI will not update.
[csharp]
/// <summary>
/// Status message received event handler.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
void twitterConnection_StatusMessageReceived(object sender, IStatusMessageReceivedEventArgs args)
{
Dispatcher.BeginInvoke(() =>
AddMessage(args.Message)
);
}</p>

/// <summary>
/// Adds a message to the observable message list which updates the UI.
/// </summary>
/// <param name="message"></param>
void AddMessage(IStatusMessage message)
{
ObservableCollection<IStatusMessage> messageList = ((ObservableCollection<IStatusMessage>)Tweets.ItemsSource);
messageList.Insert(0, message);
}
[/csharp]
Hopefully this will have helped you understand how the Twitter real-time data stream is consumed and an example of how you can use it within a Real-Time Rich Internet Application. Now, why now download the TweetStreamer library and example application and have a play.

Limitations: Although I've seen the sample application perform reasonably well I've also seen it perform quite poorly. How well it performs will depend on the machine running the application and the frequency of the updates from the real-time Twitter stream. In later posts I'll provide information on how to improve performance by making changes to the client code and I'll also go into what can be done on the server.

Download

You can download the source from the TweetStreamer Google Code project.

Screencast

This screencast was supposed to be short but ended up being just shy of 10 minutes. In it I provide some technical detail of how I built the application, show the basics of how Fiddler is used to give access to the Twitter real-time data stream, and give a demo of the application.
</embed>