Build a Simple Twitter client using Silverlight 2
In this article I will show you how to build a simple Twitter client in Silverlight. We will also learn the shortcomings of WebClient Class in Silverlight. ScottGu’s Silverlight 2 End to End Tutorial: Building a Digg Search Client is a wonderful article to start learning about Silverlight and its various controls. If you are new to Silverlight, I recommend you to read that article before you start building the Twitter Client shown here. For this article, I assume you have some experience in creating Silverlight projects and have worked with WebServices.
Here’s how the Twitter Client will look once it is made. Let us see the steps to get started with it.
Step 1: Launch Visual Studio 2008 and choose File > New project > Silverlight > Silverlight Application.
Step 2: Choose location and name for your application and click OK. Click OK again keeping the defaults in the next window. Now your solution explorer should look like the one shown below:
Step 3: Open ‘Page.xaml’ and add the following code
<Grid x:Name="LayoutRoot" ShowGridLines="False">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="7" ShowGridLines="false">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="140"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="140"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Username :" Grid.Column="0" Foreground="Black" HorizontalAlignment="Right" VerticalAlignment="Center"></TextBlock>
<TextBox x:Name="txtUsername" Grid.Column="1" Width="140"></TextBox>
<TextBlock Text="Password :" Grid.Column="2" Foreground="Black" HorizontalAlignment="Right" VerticalAlignment="Center"></TextBlock>
<PasswordBox x:Name="txtPassword" Grid.Column="3" Width="140" ></PasswordBox>
<Button x:Name="btnLogin" Grid.Column="4" Content="Login" Width="100" Click="btnLogin_Click" ></Button>
</Grid>
<data:DataGrid Name="FriendsTimeLine" Grid.Row="1"></data:DataGrid>
</Grid>
In the markup shown above, we have added one TextBox, PasswordBox and Button. We have also done some formatting to align our controls. ‘PasswordBox’is a new control in Silverlight 2. You can read more about it here. If you want to learn more about aligning controls, read ScottGu’s article.
Step 4: Before we move on to next step, let’s talk about the sample Digg app which is in ScottGu’s tutorial. If you have read the article you will see that Scott has used the WebClient Class to retrieve data from Digg.com. It’s actually very easy to request data using WebClient class, however when you are using WebClient in Silverlight, you will notice that some headers like ‘Authorization’ are classified as “Restricted” for Silverlight’s HttpWebRequest. It does not appear in Intellisense as well. But requesting friends timeline from Twitter requires Authentication!
One way to overcome this restriction is by adding a webservice to your application which can make the authentication for us. I have used this excellent example by Peter from www.eggheadcafe.com with his permission.
Step 5: Add a new webservice to your project by Right Click -> Add New Item. Add the following code to your webservice code file. (Refer Peter’s example for more information and how this works)
C#
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Services;
namespace GenericRequest
{
///<summary>
/// Generic Request Service for Silverlight Apps
///</summary>
[WebService(Namespace = "http://genericrequest.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class Service1 : System.Web.Services.WebService
{
///<summary>
/// Makes an HttpRequest.
///</summary>
///<param name="targetUrl">The target URL.</param>
///<param name="headerNames">The header names.</param>
///<param name="headerValues">The header values.</param>
///<param name="method">The method.</param>
///<param name="postValueNames">The post value names.</param>
///<param name="postValues">The post values.</param>
///<param name="userName">username.</param>
///<param name="password">password.</param>
///<returns>string (xml, html, etc</returns>
[WebMethod]
public string MakeRequest(string targetUrl, string[] headerNames, string[] headerValues,
string method, string[] postValueNames, string[] postValues, string userName, string password)
{
string returnString = "Bad Request";
// This is a GET request with no form values and optional Basic auth
if (method.ToUpper() == "GET")
{
using (WebClient wc = new WebClient())
{
wc.Proxy = null;
if (userName != null && password != null)
wc.Credentials = new NetworkCredential(userName, password);
if (headerNames != null && headerNames.Length > 0)
{
for (int i = 0; i < headerNames.Length; i++)
{
wc.Headers.Add(headerNames[i], headerValues[i]);
}
}
returnString = wc.DownloadString(targetUrl);
}
}
// This is a POST request with form values and optional Basic auth
if (method.ToUpper() == "POST" && postValueNames != null)
{
using (WebClient wc = new WebClient())
{
wc.Proxy = null;
if (userName != null && password != null)
wc.Credentials = new NetworkCredential(userName, password);
if (headerNames != null && headerValues != null)
{
for (int i = 0; i < headerNames.Length; i++)
{
wc.Headers.Add(headerNames[i], headerValues[i]);
}
}
var data = new NameValueCollection();
for (int i = 0; i < postValueNames.Length; i++)
{
data.Add(postValueNames[i], postValues[i]);
}
byte[] result = wc.UploadValues(targetUrl, "POST", data);
returnString = System.Text.Encoding.UTF8.GetString(result);
}
}
return returnString;
}
}
}
VB.NET
Imports System
Imports System.Collections.Generic
Imports System.Collections.Specialized
Imports System.Linq
Imports System.Net
Imports System.Web
Imports System.Web.Services
Namespace GenericRequest
''' <summary>
''' Generic Request Service for Silverlight Apps
''' </summary>
<WebService(Namespace:="http://genericrequest.org/"), WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1), System.ComponentModel.ToolboxItem(False)> _
Public Class Service1
Inherits System.Web.Services.WebService
''' <summary>
''' Makes an HttpRequest.
''' </summary>
''' <param name="targetUrl">The target URL.</param>
''' <param name="headerNames">The header names.</param>
''' <param name="headerValues">The header values.</param>
''' <param name="method">The method.</param>
''' <param name="postValueNames">The post value names.</param>
''' <param name="postValues">The post values.</param>
''' <param name="userName">username.</param>
''' <param name="password">password.</param>
''' <returns>string (xml, html, etc</returns>
<WebMethod()> _
Public Function MakeRequest(ByVal targetUrl As String, ByVal headerNames() As String, ByVal headerValues() As String, ByVal method As String, ByVal postValueNames() As String, ByVal postValues() As String, ByVal userName As String, ByVal password As String) As String
Dim returnString As String = "Bad Request"
' This is a GET request with no form values and optional Basic auth
If method.ToUpper() = "GET" Then
Using wc As New WebClient()
wc.Proxy = Nothing
If userName IsNot Nothing AndAlso password IsNot Nothing Then
wc.Credentials = New NetworkCredential(userName, password)
End If
If headerNames IsNot Nothing AndAlso headerNames.Length > 0 Then
For i As Integer = 0 To headerNames.Length - 1
wc.Headers.Add(headerNames(i), headerValues(i))
Next i
End If
returnString = wc.DownloadString(targetUrl)
End Using
End If
' This is a POST request with form values and optional Basic auth
If method.ToUpper() = "POST" AndAlso postValueNames IsNot Nothing Then
Using wc As New WebClient()
wc.Proxy = Nothing
If userName IsNot Nothing AndAlso password IsNot Nothing Then
wc.Credentials = New NetworkCredential(userName, password)
End If
If headerNames IsNot Nothing AndAlso headerValues IsNot Nothing Then
For i As Integer = 0 To headerNames.Length - 1
wc.Headers.Add(headerNames(i), headerValues(i))
Next i
End If
Dim data = New NameValueCollection()
For i As Integer = 0 To postValueNames.Length - 1
data.Add(postValueNames(i), postValues(i))
Next i
Dim result() As Byte = wc.UploadValues(targetUrl, "POST", data)
returnString = System.Text.Encoding.UTF8.GetString(result)
End Using
End If
Return returnString
End Function
End Class
End Namespace
The web service shown above is a generic web service, that could make any kind of WebRequest call we wanted using the full Framework (without the "restrictions"), and return the appropriate results to our Silverlight apps. We could give it the target url (including querystring), the method ("GET" or "POST"), username and password if authentication is required, the names and values of any request headers we want, and the names and values of any form POST values we needed.
Step 6: Right click the project > Add Service Reference to add a Service reference to the new webservice shown above.
Step 7: Open Page.xaml.vb and add the following code to your button’s click event.
C#
ServiceReference1.Service1SoapClient client = new ServiceReference1.Service1SoapClient();
string strURL = null;
strURL = "http://twitter.com/statuses/friends_timeline.xml";
client.MakeRequestCompleted += downloadComplete;
ArrayOfString headerNames = null;
headerNames = new ArrayOfString();
ArrayOfString headerValues = null;
headerValues = new ArrayOfString();
headerNames.Add("Cache-Control");
headerValues.Add("no-cache");
ArrayOfString formValueNames = null;
formValueNames = new ArrayOfString();
ArrayOfString formValues = null;
formValues = new ArrayOfString();
client.MakeRequestAsync(strURL, headerNames, headerValues, "GET", formValueNames, formValues, txtUsername.Text, txtPassword.Password);
VB.NET
Dim client As New ServiceReference1.Service1SoapClient
Dim strURL As String
strURL = "http://twitter.com/statuses/friends_timeline.xml"
AddHandler client.MakeRequestCompleted, AddressOf downloadComplete
Dim headerNames As ArrayOfString
headerNames = New ArrayOfString()
Dim headerValues As ArrayOfString
headerValues = New ArrayOfString()
headerNames.Add("Cache-Control")
headerValues.Add("no-cache")
Dim formValueNames As ArrayOfString
formValueNames = New ArrayOfString()
Dim formValues As ArrayOfString
formValues = New ArrayOfString()
client.MakeRequestAsync(strURL, headerNames, headerValues, "GET", formValueNames, formValues, txtUsername.Text, txtPassword.Password)
The code shown above will make a request to the webservice using ‘MakeRequestAsync’ along with the username and password. We also attach an event handler ‘downloadComplete’ which will be called as soon as the download is comeplete.
Step 8: Add a new class file to the project by Right Click - > Add new item. And add the following code to the class file
C# (2.0)
using System;
namespace TwitterApp
{
public class Twitter
{
private string privateID;
public string Id
{
get
{
return privateID;
}
set
{
privateID = value;
}
}
private string privateMessage;
public string Message
{
get
{
return privateMessage;
}
set
{
privateMessage = value;
}
}
}
}
VB.NET
Imports Microsoft.VisualBasic
Imports System
Namespace TwitterApp
Public Class Twitter
Private privateID As String
Public Property Id() As String
Get
Return privateID
End Get
Set(ByVal value As String)
privateID = value
End Set
End Property
Private privateMessage As String
Public Property Message() As String
Get
Return privateMessage
End Get
Set(ByVal value As String)
privateMessage = value
End Set
End Property
End Class
End Namespace
We have just defined a new class that has properties which can be used to map the XML content from Twitter.
Step 9: Finally add the following code to Page.xaml.cs or Page.xaml.vb
C#
private void downloadComplete(object sender, MakeRequestCompletedEventArgs e)
{
string strResult = null;
strResult = e.Result;
XDocument xmlStories = XDocument.Parse(strResult);
var stories =
from story in xmlStories.Descendants("status")
where story.Element("text") != null
select new Twitter {Message = (System.Convert.ToString(story.Element("text"))).Trim(), Id = (System.Convert.ToString(story.Element("user").Element["name"])).Trim()};
FriendsTimeLine.ItemsSource = stories;
}
VB.NET
Private Sub downloadComplete(ByVal sender As System.Object, ByVal e As MakeRequestCompletedEventArgs)
Dim strResult As String
strResult = e.Result
Dim xmlStories As XDocument = XDocument.Parse(strResult)
Dim stories = From story In xmlStories.Descendants("status") _
Where story.Element("text") IsNot Nothing _
Select New Twitter With {.Message = (CStr(story.Element("text"))).Trim(), .Id = (CStr(story.Element("user").Element("name"))).Trim()}
FriendsTimeLine.ItemsSource = stories
End Sub
Once the download is complete, we use ‘XDocument.Parse’ to parse the XML message from Twitter. Then we run through all the nodes and add the values to the properties if the “text” element is not empty. ‘text’ element is the element containing your friend’s message. You can check out the Twitter API wiki to know more about Twitter API and how to use it.
Now run your project and see Twitter client in action. The source code of this article can be downloaded from here.
Useful Links:
Shoban Kumar, is a 23 year old techie from Trivandrum mainly working on ASP, ASP.NET, VB, VB.NET and PHP. You can also find him blogging at www.crankup.net and www.codegeeks.net
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
ShobanKumar is an ex-Microsoft MVP in SharePoint who currently works as a SharePoint Consultant. You can read more about his projects at
http://shobankumar.com. You can also follow him on twitter @
shobankr