Live Twitter messages on Silverlight 4 through WCF Push

In this post I’ll share a small fun application that fetches live messages from Twitter and displays it in Silverlight using WCF Push technology. A topic I’ll discuss in particular in this topic is the WCF Duplex model. The twitter library Im using is called Yedda. Thanks to the creators of Yedda. Its very simple and easy to use. You can visit their site at http://yedda.com/ . We’ll be downloading their Twitter Wrapper later, If you’re in too much of a hurry go download it right now. So, lets get coding tweeps…

1. Create a Simple Silverlight Form

First Im going to create a New Silverlight Project called FunWithTwitter. Visual Studio 2010 is going to ask us if we want to create a Web project along with it. Yes, we do want it to create a web project. First we’ll make a very simple interface to show the text messages. We’ll only use a text block. No need for any fancy XAML here. We’re more interested in the service side right now.

<UserControl x:Class="FunWithTwitter.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    <Grid x:Name="LayoutRoot">
        <Border Margin="20" BorderBrush="#B8343434" CornerRadius="10" BorderThickness="1">
            <Border.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="White" Offset="0" />
                    <GradientStop Color="#FFDEDEDE" Offset="1" />
                </LinearGradientBrush>
            </Border.Background>
                <Grid Margin="25">
                <Grid.RowDefinitions>
                    <RowDefinition Height="30" />
                    <RowDefinition Height="1*" />
                </Grid.RowDefinitions>
                <StackPanel Grid.Row="0" Orientation="Horizontal">
                    <Button Content="Invoke" Width="100" HorizontalAlignment="Left" VerticalAlignment="Stretch" Margin="0,0,10,0" />
                    <TextBlock x:Name="TxtStatus" HorizontalAlignment="Left" Text="Application starting..." VerticalAlignment="Center" />
                </StackPanel>
                <ScrollViewer Grid.Row="1">
                    <TextBlock x:Name="TxtTweets" />
                </ScrollViewer>
            </Grid>
        </Border>
    </Grid>
</UserControl>

In the above form, the big TextBlock with the scroll bar (TxtTweets) is where we will populate the tweets. The TextBlock at the top (TxtStatus) will show a timestamp that displays the time the last message was received. The Button is used to invode the service.

2. Add Twitter API

Now is the time we download the Yedda library from http://devblog.yedda.com/wp-content/uploads/2007/05/yeddatwitter-v01.zip. You’ll be downloading a standard visual studio project. Copy that project to the same folder where your current project FunWithTwitter is located. (If required create a folder Yedda here). In visual studio > Solution (context menu) > Add existing project – select the .csproj file from the Yedda folder that you just copied the files into. This will be the third project to your solution. You’ll not be touching this project again, so you can collapse it and forget it.

Now on your FunWithTwitter.Web project add a reference to the Yedda Project. You’re all set to start consuming RSS from Twitter.

3. Write the WCF Service

Now this is the biggest part of the project. Do this with a bit of patience and read your reference material carefully. I’ve often found myself skipping straight to the sample code and not reading the description and then ending up googling to get the answer back in the same place I started off with. So… We’ll first add a new Silverlight Enabled WCF Service to our FunWithTwitter.Web project. I’ll name my file TwitterService.svc. Here we’ll be doing 3 things – Adding Library Reference, Creating the service, Configuring the service.

Add the additional references

Right click on your references folder in FunWithTwitter.Web project. In the .NET tab check if you have a reference for:

System.Linq;System.ServiceModel.PollingDuplex

If you don’t, go to the ‘Browse’ tab and the the DLL at the following location:

C:\Program Files\Microsoft SDKs\Silverlight\v4.0\Libraries\Server\System.ServiceModel.PollingDuplex.dll

Now you should have the required libraries to run the WCF Service. (We’ll be adding a reference like this to the Silverlight project later)

Create the service

Creating the service is quite easy actually. Just copy and paste the code below 🙂 [A detailed explanation of this is at the end of the post]

using System;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.Threading;
using System.Xml.Linq;

namespace FunWithTwitter.Web
{
    [ServiceContract(Namespace = "", CallbackContract = typeof(ISessionClient))]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class TwitterService
    {
        ITwitterClient client;

        [OperationContract(IsOneWay = true)]
        public void Startup()
        {
            client = OperationContext.Current.GetCallbackChannel<ITwitterClient>();

            // A timer that calls the SendRandomString function once every 10 seconds
            Timer t = new Timer(new TimerCallback(StreamTweets), new AutoResetEvent(false), 0, 10000);
        }

        // A function that gets the friends timeline, converts it to plaintext and sends it to the client
        private void StreamTweets(object State)
        {
             string ClientMessage = "";

            Yedda.Twitter t = new Yedda.Twitter();

            XDocument xDoc = XDocument.Parse(t.GetFriendsTimeline("shishir305", "*****", Yedda.Twitter.OutputFormatType.RSS));

            foreach (XElement item in xDoc.Element("rss").Element("channel").Elements("item"))
            {
                ClientMessage += item.Element("description").Value + "\r\r";
            }

            // The actual client callback
            client.RecentTweets(ClientMessage);
        }
    }

    [ServiceContract]
    public interface ITwitterClient
    {
        [OperationContract(IsOneWay = true)]
        void RecentTweets(string tweets);
    }
}

First, you should notice the CallbackContract = typeof(ITwitterClient) in the [ServiceContract]. This tell the WCF service that call back contract is defined by the ITwitterClientInterface.
Second, you should notice the IsOneWay = true statements in the [OperationContract]. This will tell the service that this is a one way operation. i.e., data will be passed continuously from one endpoint of the connection to the other. (There are three modes in WCF / Silverlight – OneTime(default), OneWay, TwoWay).
Third, you should notice the client = OperationContext.Current.GetCallbackChannel<ITwitterClient>(); statement. This is the place where the service comes to know who the client is and stores a reference to the client in the client object. This is generally the toughest part in a Duplex connection. Getting the service to know who the client is. Thanks to System.ServiceModel.PollingDuplex, this has been reduced to one line for us.

Once you have understood the three things we have done, you are almost done with Duplex connections. The next few lines are just some flashy timers that call the StreamTweets function to run over and over, there by sending a stream of data to the client. You will soon notice that the client is simply sitting at one place and displaying data whenever the service sends some.

Fourth, Notice the client.RecentTweets(ClientMessage); in the StreamTweets function. This is where the server sends the data to the client.

Configure the service

You should be very careful when you are configuring the service. This is the configuration that goes in your web.config or your app.config. The code below shows the configuration that should go into this. You can just copy and replace your settings in your config file.

Note the following changes:
1. We added pollingDuplexHttpBinding as a binding extension. For this you need to make sure

    <system.serviceModel>

      <extensions>
        <bindingExtensions>
          <add name="pollingDuplexHttpBinding"
   type="System.ServiceModel.Configuration.PollingDuplexHttpBindingCollectionElement, System.ServiceModel.PollingDuplex, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        </bindingExtensions>
      </extensions>

      <behaviors>
        <serviceBehaviors>
          <behavior name="">
            <serviceMetadata httpGetEnabled="true" />
            <serviceDebug includeExceptionDetailInFaults="false" />
          </behavior>
        </serviceBehaviors>
      </behaviors>

      <bindings>
        <pollingDuplexHttpBinding />
      </bindings>

      <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />

      <services>
        <service name="FunWithTwitter.Web.TwitterService" >
          <endpoint address="" binding="pollingDuplexHttpBinding" contract="FunWithTwitter.Web.TwitterService" />
          <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        </service>
      </services>

    </system.serviceModel>

Once you have the service configured, build the application and make sure its working.

4. Consume the service

Finally we create the client:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using FunWithTwitter.TwitterServiceProxy;
using System.ServiceModel;

namespace FunWithTwitter
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            PollingDuplexHttpBinding binding = new PollingDuplexHttpBinding();
            EndpointAddress remoteAddress = new EndpointAddress("http://localhost:54643/TwitterService.svc");

            TwitterServiceClient proxy = new TwitterServiceClient(binding, remoteAddress);

            proxy.RecentTweetsReceived += new EventHandler<RecentTweetsReceivedEventArgs>(proxy_RecentTweetsReceived);

            proxy.StartupCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(proxy_StartupCompleted);
            proxy.StartupAsync();
        }

        void proxy_StartupCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                TxtStatus.Text = "Error initializing application";
            }
            else
            {
                TxtStatus.Text = "Application successfully initialized";
            }
        }

        void proxy_RecentTweetsReceived(object sender, RecentTweetsReceivedEventArgs e)
        {
            if (e.Error != null)
            {
                TxtStatus.Text = "Error receiving tweets";
            }
            else
            {
                TxtStatus.Text = "Updates received @ " + DateTime.Now.ToString("T");
                TxtTweets.Text = e.tweets;
            }
        }
    }
}