In Part 1, we created a Facebook application using ASP.NET. Now, in Part 2, we’ll cover how to add Silverlight into the mix. I’ll assume you have the Part 1 solution working. If not, go to that blog post and download the project files linked at the end of the post.
Install the Silverlight 2 Developer Tools
If you haven’t done so already, install the Silverlight 2 Developer tools from Silverlight.net
Add the Silverlight Project
Add a new Silverlight Application project to the solution.
When requested about how to link and test the project, be sure to choose the option to link the Silverlight control to an existing web site. Select the option to make it the test page, but deselect the option to make it the start page. The reason we do that is to make it easy to copy and paste the object instantiation code from the test page to Default.aspx
You should now have a solution that looks like this:
Copy the asp:Silverlight Control to Default.aspx
Find the ASP.NET test page in your web project and open up the markup. Copy the Register Assembly directive:
<%@ Register Assembly="System.Web.Silverlight"
Namespace="System.Web.UI.SilverlightControls"
TagPrefix="asp" %>
and the ScriptManager and Silverlight control:
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<div style="height:100%;">
<asp:Silverlight ID="Xaml1" runat="server"
Source="~/ClientBin/GeekSpeakDemo02.xap"
MinimumVersion="2.0.31005.0"
Width="100%" Height="100%" />
</div>
over to your Default.aspx, so it looks like this:
<%@ Page Language="C#" MasterPageFile="~/Site.master"
AutoEventWireup="true"
CodeFile="Default.aspx.cs" Debug="true"
Inherits="_Default" Title="Untitled" %>
<%@ Register Assembly="System.Web.Silverlight"
Namespace="System.Web.UI.SilverlightControls"
TagPrefix="asp" %>
<%@ Register Assembly="facebook.web"
Namespace="facebook.web" TagPrefix="cc1" %>
<asp:Content ID="Content1"
ContentPlaceHolderID="MainContentPlaceHolder"
runat="Server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<div style="height:100%;">
<asp:Silverlight ID="Xaml1" runat="server"
Source="~/ClientBin/GeekSpeakDemo02.xap"
MinimumVersion="2.0.31005.0"
Width="100%" Height="100%" />
</div>
</asp:Content>
Open Default.aspx.cs and remove the code from inside the Page_Load !IsPostBack block. We won’t be needing it anymore.
protected void Page_Load(object sender, EventArgs e)
{
facebookAPI = ((CanvasIFrameMasterPage)Master).API;
if (!IsPostBack)
{
}
}
Test Your Application
In Page.xaml, inside the grid, add a textblock:
<TextBlock Text="Hello Facebook, from Silverlight!" />
We’re going to use that just to prove that we’re viewing Silverlight content.
Run the application, and you should see this:
We’re halfway there. We now have Silverlight content running inside of Facebook. However, the content isn’t actually interacting with Facebook or using any Facebook data. We’ll fix that in the rest of this post.
A Dilemma – Silverlight is an Island
Remember, a Silverlight app is basically a little desktop application running inside the browser on the client machine. It’s likely not doing postbacks or sharing server-side session state.
Now we have a little dilemma. Facebook is providing some important information to Default.aspx.cs, specifically the session key. That key represents a unique session within Facebook, and is what we need, along with the API key and secret, in order to make any calls to the Facebook API.
Once Silverlight is on the client, any calls it makes back to the web server will not share this same contextual information. Therefore we need to pass these tidbits down to Silverlight so that it can send them back up on the web service calls.
Pass Down the Session Key and Current User
There are a number of ways to pass information down to Silverlight. You could use hidden controls or javascript and access via the HTML bridge; you could include parameters on the querystring; or you could do like I’m going to do and use the initParams. For write-once properties, I find initParams to be pretty easy and reliable.
In the Default.aspx.cs page in the web site, change Page_Load to look like this:
protected void Page_Load(object sender, EventArgs e)
{
facebookAPI = ((CanvasIFrameMasterPage)Master).API;
if (!IsPostBack)
{
Xaml1.InitParameters =
string.Format("uid={0},sessionkey={1}",
facebookAPI.uid,
facebookAPI.SessionKey
);
}
}
Xaml1 is the name of the asp:Silverlight control instance. The format for the InitParameters is the one recognized by silverlight: name-value pairs separated by commas.
Pick the Key Up
Back to the Silverlight project, we’re going to put in just a little code to read the values back. But first, we need some common place to store these values.
First add a class called “FacebookSession”. This class encapsulates the client-side information that makes up a facebook session. We’re going to move this around later, but it will sit here for now.
namespace GeekSpeakDemo02
{
public class FacebookSession
{
public int UserID { get; set; }
public string SessionKey { get; set; }
}
}
Add a new class called “ApplicationState”. This is a singleton class which will contain the values we’re interested in. While we could have made FacebookSession a singleton and just used that, I use the ApplicationState class for a lot more in my own projects, and don’t like the idea of having a bunch of free-floating singletons I need to keep track of.
The class itself is a regular old singleton with a property to hold the FacebookSession information.
namespace GeekSpeakDemo02
{
public class ApplicationState
{
private static ApplicationState _currentInstance;
private FacebookSession _facebookSession;
private ApplicationState() {}
public static ApplicationState Current
{
get
{
if (_currentInstance == null)
{
_currentInstance = new ApplicationState();
}
return _currentInstance;
}
}
public FacebookSession CurrentFacebookSession
{
get
{
if (_facebookSession == null)
_facebookSession = new FacebookSession();
return _facebookSession;
}
}
}
}
Next, we need to add the code to App.xaml.cs to pull the initparam values. The initparams are made available to Silverlight in the Application_Startup in much the same way that command-line arguments are made available to console applications in the constructor (or at least they used to be back when I wrote DOS programs in C …)
In your Silverlight project, in App.xaml.cs, change the Application_Startup event handler to look like this.
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new Page();
if (e.InitParams != null && e.InitParams.Count != 0)
{
ApplicationState.Current.CurrentFacebookSession.SessionKey =
e.InitParams["sessionkey"];
ApplicationState.Current.CurrentFacebookSession.UserID =
int.Parse(e.InitParams["uid"]);
}
}
The code here isn’t forgiving of missing or misspelled InitParams. In a real app, you’ll want to take some appropriate action if they are missing. What the action is depends on what your app does and what you want to do.
Create the Web Service
Now that our Silverlight application has the required session information, we need to provide a server-side interface to get at Facebook. In theory, you could also pass down the apikey and secret (but that would not be very secure) and call the Facebook API directly from Silverlight. I haven’t tried that myself as I don’t think it is a good model to follow in most cases.
Add a Silverlight-enabled WCF service to your web site, and call it FacebookService.svc. I like to put services in a separate folder if they aren’t in a separate domain. In this case, I just stuck it un a “Services” folder.
You’ll end up with FacebookService.cs in your App_Code folder, FacebookService.svc in your Services folder, and some changes to your web.config. The entry for the service in web.config will specify basicHttpBinding, which means a SOAP 1.1 web service. Silverlight cannot currently talk to other variants of SOAP, or to other types of WCF bindings (other than duplex and REST)
At this point, I realized this may be slightly confusing for folks new to Silverlight or Facebook development, so let me summarize what we’re doing in this sequence diagram:
It’s not 100% architecturally correct as I mince the Silverlight Client with the asp:Silverlight control, but you get the idea. We’ve already handled everything up to “Get Some Data”, so let’s get that piece wrapped up now.
We’re going to do this as simply as possible, with all the code inside the service method itself. In real projects I do, web services are rarely anything more than a wrapper around other code. In MSDN East Coast News, for example, I have a FacebookInterface class that wraps calls to the Facebook API (and actually implements a generic interface so we can plug in other social platforms), and a server-side ApplicationData class that caches data as appropriate.
First, remember that FacebookSession class we stuck in the Silverlight client? Well, turns out we’ll need that server-side as well. So just drag it into the App_Code folder and remove the namespace and the System.Windows.* usings. Finally, tag the fields as WCF serializable:
using System;
using System.Runtime.Serialization;
[DataContract]
public class FacebookSession
{
[DataMember]
public int UserID { get; set; }
[DataMember]
public string SessionKey { get; set; }
}
Remove the old FacebookSession class from the Silverlight client.
The end result should be a FacebookSession server-side, with none client-side. One thing I love about Silverlight development is I can easily refactor my code this way because we’re using the same language and tools on both the client and server. I did this a couple times in MSDN East Coast News, primarily around RSS parsing and feed grabbing. Originally that was all client-side, then I moved it to the server without any code changes. Sweet!
Next we need to add a class to hold the information we’re going to return from our service method. In this case, the service method is simply going to return some information from the profile of the currently logged-in user.
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
[DataContract]
public class FacebookProfileInformation
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
[DataMember]
public string HomeTown { get; set; }
[DataMember]
public Uri PictureUrl { get; set; }
[DataMember]
public List<string> FriendNames { get; set; }
}
At this point, your App_Code folder should look like this:
Finally we’re set to add the service method itself.
using System;
using System.Configuration;
using System.ServiceModel;
using System.ServiceModel.Activation;
using facebook;
using facebook.Schema;
[ServiceContract(Namespace = "uri:com.irritatedvowel.facebook.demo")]
[AspNetCompatibilityRequirements(RequirementsMode =
AspNetCompatibilityRequirementsMode.Required)]
public class FacebookService
{
[OperationContract]
public FacebookProfileInformation GetProfileInformation(
FacebookSession session)
{
API api = new API();
// get our app's keys from the web.config shared with the site
api.ApplicationKey = ConfigurationManager.AppSettings["appkey"];
api.Secret = ConfigurationManager.AppSettings["secret"];
// provide the information sent up from Silverlight
api.uid = session.UserID;
api.SessionKey = session.SessionKey;
// get information about the current user
user userInfo = api.users.getInfo(session.UserID);
if (userInfo != null)
{
FacebookProfileInformation profileInfo =
new FacebookProfileInformation();
profileInfo.LastName = userInfo.last_name;
profileInfo.FirstName = userInfo.first_name;
profileInfo.HomeTown = userInfo.hometown_location.city;
profileInfo.PictureUrl = new Uri(userInfo.pic);
return profileInfo;
}
else
{
return null;
}
}
}
The method simply provides a few key bits of information about the currently logged-in user. We’ll skip the friend list for the moment.
Wire Up the Silverlight Client
Right-click the Silverlight project and add a service reference to our FacebookService on the web site. Rename the namespace to Services:
You’ll see a new file added to the client: ServiceReferences.ClientConfig. This is the file you’ll need to change when you move from development to test or production, as it has the URL to our service (on localhost) in the config.
If all went well, you should have only one error in your client: it can’t resolve the FacebookSession inside ApplicationState.cs. To remedy that, simply add the following to ApplicationState.cs:
using GeekSpeakDemo02.Services;
If you run the app, it still won’t do anything interesting, as we neither call nor display the results from the service.
Build the UI
We’re going to stay simple with the UI. Styles have been well-covered by other folks.
Open up Page.xaml and modify it to have the following contents:
<UserControl x:Class="GeekSpeakDemo02.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image x:Name="ProfilePicture" Grid.Column="0"
Stretch="None"
VerticalAlignment="Top"
/>
<StackPanel Grid.Column="1" Orientation="Vertical">
<TextBlock Text="Last Name" FontSize="10" />
<TextBlock Text="{Binding LastName}" FontSize="14"
Margin="0 0 0 10" />
<TextBlock Text="First Name" FontSize="10" />
<TextBlock Text="{Binding FirstName}" FontSize="14"
Margin="0 0 0 10" />
<TextBlock Text="Home Town" FontSize="10" />
<TextBlock Text="{Binding HomeTown}" FontSize="14"
Margin="0 0 0 10" />
<Button x:Name="LoadData" Content="Load Data"
Width="75"
HorizontalAlignment="Left"/>
</StackPanel>
</Grid>
</UserControl>
Call the Service and Display the Results
The next part is typical Silverlight service wireup and UI binding. The code from Page.xaml.cs is below.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using GeekSpeakDemo02.Services;
namespace GeekSpeakDemo02
{
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
LoadData.Click += new RoutedEventHandler(LoadData_Click);
}
void LoadData_Click(object sender, RoutedEventArgs e)
{
FacebookServiceClient client = new FacebookServiceClient();
client.GetProfileInformationCompleted +=
new EventHandler<GetProfileInformationCompletedEventArgs>(
client_GetProfileInformationCompleted);
client.GetProfileInformationAsync(
ApplicationState.Current.CurrentFacebookSession);
}
void client_GetProfileInformationCompleted(object sender,
GetProfileInformationCompletedEventArgs e)
{
if (e.Result != null)
{
LayoutRoot.DataContext = e.Result;
// have to do this because Images don't
// bind well in Silverlight 2
ProfilePicture.Source =
new BitmapImage(e.Result.PictureUrl);
}
else
{
// here's where you'd display some sort of message
// saying the call failed. Check e.Error
}
}
}
}
If you run it at this point, and click the “Load Data” button, you’ll see something like this (only with your profile):
Pretty cool. Now your app is pulling data from Facebook. It’s pretty easy to see how you can expand on this, but I’ll take us one step further before we call this sample quits.
Modify the Service to Return Your Facebook Friends
Update the service method to add the following (gray text is for context):
profileInfo.HomeTown = userInfo.hometown_location.city;
profileInfo.PictureUrl = new Uri(userInfo.pic);
// get the friends
IList<user> friends = api.friends.getUserObjects();
if (friends != null)
{
profileInfo.FriendNames = new List<string>();
foreach (user friend in friends)
{
profileInfo.FriendNames.Add(
string.Format("{0}, {1}",
friend.last_name, friend.first_name));
}
}
return profileInfo;
That code simply calls another Facebook API method to get the friend objects for all the friends of the currently logged-in user. Remember it knows who is logged in because we set the session key and user id earlier in the code.
Update the UI
We need a place to put the friends, so we’ll add a simple listbox. Add this in the Page.xaml file right before the LoadData button:
<ListBox ItemsSource="{Binding FriendNames}"
Height="250"/>
At the same time, in the xaml, set the control height to be 500, or else the listbox will get clipped by the bounds of the Page. We could also use a grid for layout, but the root issue is the size of the control and the size of the html div. In the current facebook profile, you can have a Silverlight control that is about 760 x 600 without running into clipping by Facebook (the height can be almost anything, but 600 works well on most displays)
Another thing you need to do to avoid clipping is to modify the application settings for sizing. I’ve found the smart-size to be a bit buggy in trying to figure out the size of your iframe, especially if you resize your page while your app is up. For that reason, I suggest changing it to the less interesting but functional Resizable option
Open the Facebook developer application and select “Edit Settings” for your app:
Now scroll down to “Default IFrame Canvas Size Option” and change it from Smart size to Resizable and save your application.
Finally, go into your Default.aspx and change the div height from 100% to 500px (or whatever you set your Silverlight page to be):
<div style="height:500px;">
<asp:Silverlight ID="Xaml1" runat="server"
Source="~/ClientBin/GeekSpeakDemo02.xap"
MinimumVersion="2.0.31005.0"
Width="100%" Height="100%" />
</div>
In theory, you shouldn’t have to do that. However, more browsers have problems with Height:100% than not. FireFox always had issues with that, and IE8 does as well.
Now when you run the app, you’ll see your full Facebook application. Note that Resizable is not perfect either, so be sure to test your app out at multiple resolutions. Note that unless you run Silverlight in Windowless mode (which can take a significant performance hit) your app will overlay other things on the canvas page such as the Facebook toolbar at the bottom. You can see a little of that in the screenshot below.
Conclusion
There are lots more things a Facebook application can do. Some of those things (like posting updates to all users, updating all profile FBML etc) require background jobs and purpose-built databases. Expect to do a fair amount of server-side coding separate from your custom Silverlight experience.
I’m looking forward to a great crop of Silverlight apps on Facebook. If you create one, or know of a good one, drop me a note in the comments to this post.
Further Reading and Related Links
Source code for this walkthrough may be found here.