Click here to Skip to main content
Click here to Skip to main content

SongBird - a Twitter Hybrid Smart Client

, 18 Jun 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
Using the WCF RESTful services to create a Twitter hybrid Smart Client.

Background

I've recently been using Microsoft's WCF REST starter kit to access information over the web, and have been very impressed with its ease of use and design, but I wanted more. I didn't want to evaluate the toolkit against a noddy little application that I'd thrown together. I wanted to test it against a busy system; one which was getting thousands of hits a second; one which would really test it to see if it was worth the free asking price.

In order to test out the functionality that Microsoft is providing, I decided to write an application against a source of data that provides a fairly full and feature rich RESTful API. To that end, I've developed a simple Twitter client that allows you to view your latest posts: the latest posts that have been made to Twitter, and the latest posts that your friends have made to Twitter. As well as doing this, it also allows you to upload new messages.

The resulting application is called SongBird, just because I think it's prettier for birds to sing than Tweet (sorry, I know it's a bad pun, but I liked it).

Why REST?

You may be wondering why there's a need for RESTful services. After all, there's a perfectly serviceable means to perform operations over HTTP called SOAP. To get a feel for this, it's important to understand some of the key principals of REST.

  1. Complex operations are represented via a URI.
  2. Representations can be cached.
  3. No state is maintained on the server.
  4. Does not require the consuming systems to be aware of any other architectures than XML and HTTP. In other words, you do not have to implement a full SOAP Web-Service architecture to use REST.
  5. REST is a lightweight format that uses standard HTTP Verbs (PUT, GET, POST, DELETE).

As you can see, if your application needs to perform simple operations, REST is a good choice - especially as you can test your commands in the browser address bar and email them to your friends as links. If your application needs to perform more complex operations, such as updating a master object with lots of sub objects, then REST is not going to be the best choice for this.

The services we are going to use to access Twitter are provided via a RESTful API, which are fairly well documented at http://apiwiki.twitter.com/Twitter-API-Documentation. The API supports a wide variety of MIME type encodings, so it's easy to find a format that suits our needs. It's very important that you read the API documentation for each API call to find out what these types are (known as Formats in Twitter), and to identify the HTTP operations that apply. For instance, if you want to search Twitter using ATOM, you would use http://search.twitter.com/search.atom. As always, the API documentation should also tell you what parameters need passing and how they should be formatted.

REST in SongBird

In order to write SongBird, I decided to use the WCF REST Starter Kit which is currently at Preview 2 status. For the purposes of SongBird, I was happy to use a library that hadn't officially been released yet, and which may undergo some changes before it is finally released. For the purposes of SongBird, it's unlikely that the elements of the component are likely to undergo any serious revisions.

Architecture

Not surprisingly, for those who know me, I have written SongBird using WPF. I have a long history of loving WPF, and use it in pretty much everything that I write now. The interface to SongBird was quickly pulled together using a combination of Visual Studio 2008 and Expression Blend 2. Internally, SongBird makes heavy use of two patterns: Model Model ViewModel (MVVM) and Mediator. I'm not going to go into MVVM in too much depth, except to show how it helped in writing SongBird - if you want to know more about it, please read Josh Smith's, Karl Shifflett's, and Sacha Barber's excellent articles on MVVM. The Mediator pattern is used to relay messages backwards and forwards between Views (well, strictly speaking, it's between ViewModels and not Views).

The themes in SongBird come from the WPFThemes project on CodePlex, and I've also made fairly heavy use of David Anson's excellent BackgroundTaskManager class to neaten up my background tasks.

The screens

When SongBird starts up, the user is presented with the following screen:

StartupScreen.png

The only thing the user can do is enter their account name and password, and minimize or close the window. The portion of the screen at the bottom (the status portion) is disabled, and the posts tabs are hidden until the user has been authenticated. When the user types in their account name and password, the Log in button becomes enabled. This is all accomplished through simple two way databinding and the judicious use of ICommand to wire up commands. The code for the login screen is found in ViewModel/LoginViewModel.cs in the solution.

When the user clicks Log in, SongBird kicks the login process off on a background thread, like so:

private void SignInExecute(object parameter)
{
    try
    {
        string accountName = AccountName;
        string password = Password;
        bool rememberMe = RememberMe;
        Mouse.OverrideCursor = Cursors.Wait;
        LoginMessage = string.Empty;
        BackgroundTaskManager.RunBackgroundTask(() => { return Login(); },
        (result) =>
        {
            Mouse.OverrideCursor = null;
            if (result == null)
            {
                Mediator.Instance.NotifyColleagues(
                   ViewModelMessages.UserAuthenticated, false);
                LoginMessage = "Unfortunately, Twitter did not recognise" + 
                               " this username and " +
                               "password. Please try again";
            }
            else
            {
                LoginResult(result as XElement);
            }
        }
        );
    }
    catch
    {
        LoginMessage = "We are sorry, SongBird was unable" + 
                       " to connect to Twitter. " +
                       "Please check yournetwork connection and try again.";
    }
}

private XElement Login()
{
    try
    {
        return new LoginManagement().Login(AccountName, Password);
    }
    catch (ArgumentOutOfRangeException ex)
    {
        LoginMessage = "We are sorry, SongBird was unable" + 
                       " to connect to Twitter. " +
                       "Please check your network connection and try again.";
    }
    return null;
}

private void LoginResult(XElement root)
{
    SaveDetails("AccountName", AccountName);
    SaveDetails("Password", Password);
    SaveDetails("RememberMe", RememberMe);
    LoginMessage = "You were successfully logged in.";
    Mediator.Instance.NotifyColleagues(ViewModelMessages.UserAuthenticated, true);
}
private void SaveDetails(string key, object value)
{
    App.Current.Properties.Remove(key);
    App.Current.Properties.Add(key, value);
}

The SignInExecute method is triggered by the command associated with the Log in button. It starts off by changing the mouse cursor to the wait cursor, before it creates a background login task. If the log in completes successfully, then the user is informed of this and the Mediator notifies all of the interested ViewModels (I'll shorten this to VMs from now on in) that the user has been successfully authenticated. Note that we save the account name, password, and the remember me flag to the application properties bag so that we can use them in other VMs with ease. So, how does the login actually work?

If you remember, we said earlier that REST uses the URL to communicate. There is a little bit more to this, and this is where the WCF REST starts to provide us with that little bit more support than writing our own code would. As you can imagine, it would not be secure to pass the account name and password as a querystring parameter. Instead, we need to set up some credentials that get passed with the REST request. What we need to do is create an instance of an HttpClient object and give it some credentials -the account name and password that the user has supplied. HttpClient is a utility class designed to manipulate the request and response objects in a much easier, more natural way. We're only going to scratch the surface of HttpClient here, as it provides a lot more power than we need to communicate with the Twitter API. The actual login object looks like the following:

private const string BASEADDRESS = "http://twitter.com/account/";
private const string VERIFYCREDENTIALS = "verify_credentials.xml";
public XElement Login(string accountName, string password)
{
    using (HttpClient client = new HttpClient(BASEADDRESS))
    {
        client.TransportSettings.Credentials = new NetworkCredential(accountName, 
            password);
        using (HttpResponseMessage response = client.Get(VERIFYCREDENTIALS))
        {
            response.EnsureStatusIsSuccessful();
            return response.Content.ReadAsXElement();
        }
    }
}

When we create the HttpClient object, we need to give it a base address for our operations. Again, the Twitter API comes to our rescue, and we find that user verification is present at http://twitter.com/account/verify_credentials.xml. (If we wanted to use a different encoding (such as JSON), we could, but we are going to use XLinq to query the information returned by this request.) The account name and password are supplied as credentials to the HttpClient TransportSettings.

Our code is going to attempt the authentication using the GET verb. HttpClient makes this simple by providing the Get method, and we provide the location of the operation we want to perform here as a parameter to the method. Cunningly enough, other Verbs are named in a similar meaningful fashion, so a PUT is Put, and a POST is Post.

A feature of the WCF REST implementation that I really like is its provision of the EnsureStatusIsSuccessful method, and I heartily recommend that you include this in all of your requests. Basically, this method ensures that the application receives a HTTP Status 200, and throws an exception if it doesn't. This is a quick and convenient way to ensure that the application successfully connects to the target.

Finally, we retrieve the content from the response (inside a HttpResponseMessage) and return it as an XElement object.

At this point, assuming that the user has successfully been authenticated, the mediator has notified interested VMs that the UserAuthenticated is set to true. This causes the posts tabs to be displayed, and the update status area to be enabled.

FriendsScreen.png

The images have been blurred to preserve the anonymity of the posters.

The code to read the posts again runs on a background thread. This VM provides a bit more functionality because it sets SongBird to reread posts every 5 minutes, or when the users update their status. Again, the VMs don't need to know about each other's existence because the Mediator will notify this code that it needs to reread the posts - it's a simple but very powerful mechanism, and one that I recommend that you take a good look at.

protected override void Initialize()
{
    base.Initialize();
    Mediator.Instance.Register((object o) => { 
        ReadTwitter();
        _timer = new Timer(300000); // 5 minute refresh interval
        _timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
        _timer.Enabled = true;
        _timer.Start();
    }, ViewModelMessages.UserAuthenticated);
    Mediator.Instance.Register((object o) =>
    {
        _timer.Stop();
        ReadTwitter();
        _timer.Start();
    }, ViewModelMessages.StatusUpdated);
}

The code to actually read the posts is:

private void ReadTwitter()

{
    try
    {
        _accountName = App.Current.Properties["AccountName"].ToString();
        _password = App.Current.Properties["Password"].ToString();
        Mouse.OverrideCursor = Cursors.Wait;
        BackgroundTaskManager.RunBackgroundTask(() => { return DoReadTwitter(); },
          (posts) =>
          {
            Mouse.OverrideCursor = null;
            List<PostItemModel> postItems = new List<PostItemModel>();
            if (posts != null && posts.Count() > 0)
            {
              // Read through the posts and update them...
              foreach (TwitterPost post in posts)
              {
                postItems.Add(PostItemModel.Fill(post));
              }
              Model.Clear();
              Model.AddRange(postItems);
            }
          }
        );
    }
    catch (Exception)
    {
    }
}

The method to read Twitter is set up in a similar way to the method that verifies the user credentials. What quickly becomes apparent, is how well thought out the design actually is, and how easy the consumption of REST services actually is:

private static List<TwitterPost> RetrieveStatuses(string accountName, 
        string password, string location)
{
    List<TwitterPost> posts = new List<TwitterPost>();
    using (HttpClient client = new HttpClient(BASEADDRESS))
    {
        client.TransportSettings.Credentials = 
          new NetworkCredential(accountName, password);
        using (HttpResponseMessage response = client.Get(location))
        {
            response.EnsureStatusIsSuccessful();
            XElement root = response.Content.ReadAsXElement();
            var statuses = root.Descendants("status");
            foreach (XElement status in statuses)
            {
                posts.Add(TwitterPost.Fill(status));
            }
        }
    }
    return posts;
}

When the posts are being read and added to our collection for display with the aid of our ViewModel, we don't want to reread the same image over and over. The image information is returned as part of the XML from the status as a profile URL. We use this URL to retrieve the image using the following helper class:

public static class ImageManager
{
    private static Dictionary<string, BitmapImage> 
    _profileImages = new Dictionary<string,BitmapImage>();
    
    public static BitmapImage GetImage(string key)
    {
        return _profileImages[key];
    }
    
    public static void AddImage(string key)
    {
        if (!_profileImages.ContainsKey(key))
        {
            _profileImages.Add(key, new BitmapImage(new Uri(key)));
        }
    }
}

With the aid of this, our posts bind to the image by retrieving the BitmapImage that maps to the relevant profile URL.

Finally, we have the update status code. Again, this executes on a background thread, and it raises the StatusUpdated notification from the Mediator, informing the posts VMs that they need to update.

The interesting thing behind the HttpClient scenes here is the provision of a handy method to encode the status text for passing as a parameter. In our code, we wrap the status text post with Uri.EscapeUriString to encode the text.

Points of interest

SongBird makes use of a couple of helpers that I've blogged about in the past. There's an implementation of a bulk loadable ObservableCollection, and a databound PasswordBox attached property that you are free to use as you see fit.

Compilation note

You may receive the exception "Error 5 The tag 'VisualStateManager.VisualStateGroups' does not exist in XML namespace 'clr-namespace:System.Windows;assembly=WPFToolkit'. Line 67 Position 38. Theme.xaml 67 38 SongBird" when you compile SongBird. This is a problem with the WPFToolKit DLL and you can safely ignore it. SongBird will still compile safely, and Visual Studio seems to ignore this error.

Finally

SongBird started off as a simple intellectual exercise aimed at testing how the WCF REST functionality stands up to higher load sites, and has impressed me with the ease of development it provides. The question has become - am I finished with SongBird?

Currently, SongBird does the things I do with Twitter, but it could do so much more. If there is sufficient interest in it, if people think it has potential as a Twitter client, then I would look to formalise the development of SongBird and implement some of the features such as friends management and turn SongBird into a fully featured Twitter client.

What do you think? It's up to you.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Pete O'Hanlon
CEO
United Kingdom United Kingdom
A developer for over 30 years, I've been lucky enough to write articles and applications for Code Project as well as the Intel Ultimate Coder - Going Perceptual challenge. I live in the North East of England with 2 wonderful daughters and a wonderful wife.
 
I am not the Stig, but I do wish I had Lotus Tuned Suspension.
Follow on   Twitter   Google+

Comments and Discussions

 
AnswerRe: typo PinmvpLuc Pattyn8-Mar-10 3:14 
GeneralCongratulation PinmvpAbhijit Jana14-Jul-09 12:30 
GeneralRe: Congratulation PinmvpPete O'Hanlon14-Jul-09 22:35 
Thanks mate - I shall wander over and have a read immediately.
 

"WPF has many lovers. It's a veritable porn star!" - Josh Smith

As Braveheart once said, "You can take our freedom but you'll never take our Hobnobs!" - Martin Hughes.

My blog | My articles | MoXAML PowerToys | Onyx



GeneralRe: Congratulation Pinmembermbaocha25-Aug-09 12:44 
GeneralCongratulations PinmemberRama Krishna Vavilala14-Jul-09 0:50 
GeneralRe: Congratulations PinmvpPete O'Hanlon14-Jul-09 1:12 
GeneralGreat PinmemberDr.Luiji9-Jul-09 21:54 
GeneralRe: Great PinmvpPete O'Hanlon9-Jul-09 22:27 
GeneralGreat Article Pinmembericetea9422-Jun-09 22:02 
GeneralRe: Great Article PinmvpPete O'Hanlon22-Jun-09 22:53 
GeneralGreat Stuff Pete! Pinmemberma1achai22-Jun-09 19:32 
GeneralRe: Great Stuff Pete! PinmvpPete O'Hanlon22-Jun-09 22:52 
GeneralGreat Pete PinmemberDaniel Vaughan20-Jun-09 0:44 
GeneralRe: Great Pete PinmvpPete O'Hanlon20-Jun-09 0:56 
GeneralRe: Great Pete PinmemberDaniel Vaughan20-Jun-09 1:01 
GeneralWow. There is a lot that can be learned from this article/sample... Pinmemberjaime rodriguez19-Jun-09 10:38 
GeneralRe: Wow. There is a lot that can be learned from this article/sample... PinmvpPete O'Hanlon19-Jun-09 10:46 
GeneralExcelent Work PinmvpAbhijit Jana19-Jun-09 9:57 
GeneralRe: Excelent Work PinmvpPete O'Hanlon19-Jun-09 10:08 
GeneralGreat job PinmemberWilliam E. Kempf19-Jun-09 6:02 
GeneralRe: Great job PinmvpPete O'Hanlon19-Jun-09 6:06 
QuestionPerformance Pinmemberkamarchand19-Jun-09 3:32 
AnswerRe: Performance PinmvpKarl Shifflett19-Jun-09 3:56 
AnswerRe: Performance PinmvpPete O'Hanlon19-Jun-09 4:47 
GeneralProblem with one of your graphics. PinmemberDave Lamb18-Jun-09 23:16 
GeneralRe: Problem with one of your graphics. PinmvpPete O'Hanlon18-Jun-09 23:19 
GeneralCannot Compile Project [modified] Pinmembersam.hill18-Jun-09 20:49 
GeneralRe: Cannot Compile Project PinmvpPete O'Hanlon18-Jun-09 21:29 
GeneralRe: Cannot Compile Project Pinmemberdbmeyer22-Nov-09 9:04 
GeneralRe: Cannot Compile Project PinmvpPete O'Hanlon18-Jun-09 23:04 
GeneralRe: Cannot Compile Project Pinmembersam.hill19-Jun-09 16:25 
GeneralRe: Cannot Compile Project PinmvpPete O'Hanlon21-Jun-09 11:17 
GeneralCool Pete! PinmvpKarl Shifflett18-Jun-09 18:48 
GeneralRe: Cool Pete! PinmvpSacha Barber18-Jun-09 21:47 
GeneralRe: Cool Pete! PinmvpPete O'Hanlon18-Jun-09 22:27 
GeneralRe: Cool Pete! PinmvpSacha Barber18-Jun-09 23:15 
GeneralRe: Cool Pete! PinmvpPete O'Hanlon18-Jun-09 23:17 
GeneralRe: Cool Pete! PinmvpSacha Barber19-Jun-09 0:42 
GeneralRe: Cool Pete! PinmemberJammer18-Jun-09 23:24 
GeneralRe: Cool Pete! PinmvpPete O'Hanlon18-Jun-09 23:32 
GeneralRe: Cool Pete! PinmvpKarl Shifflett19-Jun-09 3:55 
GeneralRe: Cool Pete! PinmvpPete O'Hanlon18-Jun-09 22:55 
GeneralHave a 5! PinmemberJammer18-Jun-09 14:17 
GeneralRe: Have a 5! PinmvpPete O'Hanlon18-Jun-09 14:18 
GeneralRe: Have a 5! PinmemberJammer18-Jun-09 14:26 
GeneralRe: Have a 5! PinmvpPete O'Hanlon19-Jun-09 2:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141216.1 | Last Updated 19 Jun 2009
Article Copyright 2009 by Pete O'Hanlon
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid