Wednesday, June 03, 2009

Facebook Connect With ASP.NET

What is Facebook Connect

Facebook Connect, first released in May 2008, is the latest Facebook Platform for development Facebook applications. It is basically a series of libraries to enable any website to allow a user to log into Facebook, and retrieve the user’s data such as profile and friends from Facebook. Two major functions provided by Facebook Connect are delegated authentication and data sharing for authenticated users.

In this article I will examine how Facebook Connect works and create some demo pages to connect Facebook.

Under the Hood of Facebook Connect: Secure Cross Domain Communication

By design, web browsers isolate pages in different domains to prevent them from peeking at each other for security consideration. This isolation model ensures websites only accessing data from their own servers. This becomes a barrier for advanced web applications that need to aggregate data from multiple domains.

Windows Live development team in Microsoft first presented the concept of Secure Cross Domain Communication using secure channel in nested iframes (http://msdn.microsoft.com/en-us/library/bb735305.aspx). The idea is that the application page (http://www.application.com/CrossDomainLogin.htm) includes an iframe (A) to connect third-party page in different domain like Facebook authentication page (http://www.facebook.com/extern/login.php), and embeds an extra iframe (B) inside iframe (A) where iframe B is in the same application domain. The result from third-party page (iframe A) can be passed to iframe A as a hash in URL, like iframeB.src=”http://www.application.com/Receiver.htm#ResultData”. Within Receiver.htm, onload JavaScript can send result data back to parent’s (iframe A) parent (CrossDomainLogin.htm) because they are in the same domain. Here the iframe B is the channel and bridge for cross domain communication. The approach is soon adopted by many developers and vendors. Facebook Connect uses the same mechanism in its implementation, refer to http://wiki.developers.facebook.com/index.php/Cross_Domain_Communication and http://wiki.developers.facebook.com/index.php/How_Connect_Authentication_Works for details. Instruction of building a Facebook Connect application is available in http://wiki.developers.facebook.com/index.php/Trying_Out_Facebook_Connect.

Register Application with Facebook Developer

A new Facebook Application is required for building any Facebook Connect website. To setup the Facebook Application, first thing is to login to Facebook and Facebook Developer Application (http://www.facebook.com/developers/), and allow the Developer access:


Type Application Name “MyFacebookConnect” and click Save and the Edit Application page will show up. The Application ID, API Key and Secret on the page base section are important and they are required for website to connect to Facebook.

Select the Connect section of the edit page, type url that you will use to connect Facebook, “http://myfacebookconnect.com” in our case. Also we input the myfacebookconnect.com as the Base Domain.

Click Save Changes, and then a new Facebook Application named “myfacebookconnect” is created, and summary page comes up showing the related data and links of the application. Now we are ready to build our website that will use Facebook Connect to login and pull out data from Facebook.

Note that in order to make the application work in development environment; we need to modify hosts entry so that the myfacebookconnect.com is pointing to local host. Add following to the hosts file located in “C:\WINDOWS\system32\drivers\etc\hosts”:
127.0.0.1 localhost
127.0.0.1 myfacebookconnect.com
Also the web application can only run in IIS, and the cross domain communication won’t work properly in Visual Studio embedded web server where a dynamic port number is used.

Simple Page Connect to Facebook

As we mentioned before, a channel page is required for cross domain communication. We create an html page named xd_receiver.htm under the ASP.NET solution:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.js"
type="text/javascript"></script>
</body>
</html>
The page only references a JavaScript from Facebook without any other html tags. The JavaScript is the way Facebook Connect applications talk to Facebook servers, no matter the applications are using PHP, Java or .NET.

For a simple test purpose, we create a facebooktest.htm page that shows login status:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php"
type="text/javascript"></script>
<form>
<div>
<fb:login-button onlogin="window.location.reload()">
</fb:login-button> <div id="status"></div>
</div>
</form>

<script type="text/javascript">
function showLogin() {
document.getElementById("status").innerHTML = "You are logged in";
}
function showLogout() {
document.getElementById("status").innerHTML = "You are logged out";
}
FB.init("f81522f787ba594e567ec85424d513b5", "xd_receiver.htm",
{ "ifUserConnected": showLogin, "ifUserNotConnected": showLogout });
</script>
</body>
</html>
The Facebook Connect JavaScript library is loaded before the Form elements are created. The <fb:login-button> is the login button control for logging in to Facebook. The JavaScript call FB.init is invoked at the end of page. The init method takes three parameters, first is the Facebook Appplication API Key we saw previously during the Facebook Application setup, the second parameter is the cross domain channel file we just created, the last parameter is anonymous method (call back function) to invoke showLogin and showLogout status based on the return value of init call.

The page shows “You are logged out” when it’s first loaded. The Facebook login page pops up when the Facebook Connect button is clicked:


Type a valid email and password, and click Connect button, the popup will be closed and the facebooktest.htm page is refreshed. Now the status is changed to being logged in:


Retrieve Facebook Friend List

After a user logging in the Facebook Connect, the application has the capability to retrieve user’s profile and other information.

In this example, we create an ASP.NET application to display Facebook friend list with login and logout functions. To make the work simpler, we use open source .NET Facebook Developer Toolkit (http://www.codeplex.com/FacebookToolkit) to pull out data from Facebook instead of using Facebook API (JavaScript) for data retrieval. The usage of Facebook Developer Toolkit is simple; just download the facebook.dll and Microsoft.Xml.Schema.Linq.dll from the website, save them to the application bin folder, and add them to the application references.

First we put the Facebook API key and Secret in web.config:
<appSettings>
<add key="facebook.APIKey" value="f81522f787ba594e567ec85424d513b5"/>
<add key="facebook.Secret" value="1426a4ce7cb9be28b6cc927109ef2afa"/>
</appSettings>
We then create an ASP.NET page named FacebookTest.aspx as following:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="FacebookTest.aspx.cs" Inherits="Web.FacebookTest" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml">
<head id="Head1" runat="server">
<title>Facebook Connect Test</title>
<style type="text/css">
.friendlist li
{
display: inline;
float: left;
margin-left: 5px;
margin-bottom: 5px;
}
</style>
</head>
<body>
<script src=http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php
type="text/javascript"></script>

<form id="form1" runat="server">
<div>
<p><asp:Label ID="lblLoginStatus" runat="server"></asp:Label></p>
<p>
<div runat="server" id="divLogin">
<fb:login-button onlogin="window.location.reload()"></fb:login-button>
</div>

<div runat="server" id="divLogout">
<a href="#" onclick="FB.Connect.logoutAndRedirect('FacebookTest.aspx');">
<img src="http://static.ak.fbcdn.net/images/fbconnect/logout-buttons/logout_small.gif" />
</a>
</div>
</p>
<asp:Label ID="lblFriendCount" runat="server"></asp:Label>
<asp:ListView ID="ListView1" runat="server">
<LayoutTemplate>
<ul class="friendlist">
<asp:PlaceHolder ID="itemPlaceholder" runat="server" />
</ul>
</LayoutTemplate>
<ItemTemplate>
<li><img src='<%#Eval("imgURL") %>' width="75" height="75" /> <br/> <%#Eval("name") %></li>
</ItemTemplate>
</asp:ListView>
</div>
</form>

<script type="text/javascript">
FB.init("ecf025f742453d4253eb40b074049b7a", "xd_receiver.htm");
</script>
</body>
</html>
The XML namespace
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml">
is to tell Visual Studio the valid control names from Facebook such as <fb:login-button>. Here we also add a logout button so that user can log out the Facebook session. The Facebook JavaScript and FB initialization (FB.init) are included exactly as the regular html page we tested in previous section. A ListView control is used to display the friends’ image and name.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Configuration;
using System.Xml.Linq;
using facebook;

namespace Web
{
public partial class FacebookTest : System.Web.UI.Page
{
string FacebookAPIKey = ConfigurationManager.AppSettings["facebook.APIKey"];
string FacebookSecret = ConfigurationManager.AppSettings["facebook.Secret"];

private string GetFacebookCookie(string cookieName)
{
string cookieValue = null;
string fullCookieName = FacebookAPIKey + "_" + cookieName;

if (HttpContext.Current.Request.Cookies[fullCookieName] != null)
{
cookieValue = HttpContext.Current.Request.Cookies[fullCookieName].Value;
}

return cookieValue;
}

protected void Page_Load(object sender, EventArgs e)
{
string sessionKey = GetFacebookCookie("session_key");
int userID = -1;
int.TryParse(GetFacebookCookie("user"), out userID);
bool isConnected = false;
if (int.TryParse(GetFacebookCookie("user"), out userID)
&& userID != -1 && !string.IsNullOrEmpty(sessionKey))
{
isConnected = true;
}
if (isConnected)
{
divLogin.Style["display"] = "none";
divLogout.Style["display"] = "block";

// Setup Facebook Toolkit API
API api = new API();
api.ApplicationKey = FacebookAPIKey;
api.Secret = FacebookSecret;
api.SessionKey = sessionKey;
api.uid = userID;

// Get User Info
facebook.Schema.user user = api.users.getInfo();
string userName = (user == null) ? string.Empty : user.name;

// Get User Friend List
IList<facebook.Schema.user> friends = api.friends.getUserObjects();
List<FBUserInfo> friendList = new List<FBUserInfo>();
foreach (facebook.Schema.user friend in friends)
{
FBUserInfo info = new FBUserInfo()
{
id = friend.uid.HasValue ? friend.uid.Value.ToString() : "",
name = friend.name,
imgURL = friend.pic
};
if (string.IsNullOrEmpty(info.imgURL))
info.imgURL = "http://static.ak.fbcdn.net/pics/q_silhouette.gif";
friendList.Add(info);
}
ListView1.DataSource = friendList;
ListView1.DataBind();

lblLoginStatus.Text = "You have logged in as " + userName;
lblFriendCount.Text = "You have " + friendList.Count.ToString() + " friends.";

}
else // Logged out
{
divLogin.Style["display"] = "block";
divLogout.Style["display"] = "none";
lblLoginStatus.Text = "You have not logged in Facebook.";
}
}

public class FBUserInfo
{
public string id { get; set; }
public string name { get; set; }
public string imgURL { get; set; }
}
}
}
The application first checks the login status by examining the cookie set by Facebook call-back via channel page (xd_receiver.htm). If the cookie is valid, a Facebook Developer Toolkit API object is created and its properties are populated by Facebook Connect setting and user’s login session stored in cookie. We then can use the API object to get user’s name and user’s friend list.

The user’s friends in Facebook will be listed on the page when the user successfully logs in Facebook Connect. Below is the screen-shot of the page:


Filter Friends by Social Network

The friend objects returned by Facebook Connect include a list of affiliations objects that identify the social networks. Filtering friends by affiliation group make the friend selection easier if the user has a large number of friends.

Following code snippet shows how to populate affiliation info in friend list using Facebook Developer toolkit:
// Get User Friend List
IList<facebook.Schema.user> friends = api.friends.getUserObjects();
Dictionary<long, string> affDictionary = new Dictionary<long, string>();
List<FBUserInfo> friendList = new List<FBUserInfo>();
foreach (facebook.Schema.user friend in friends)
{
FBUserInfo info = new FBUserInfo()
{
ID = friend.uid.HasValue ? friend.uid.Value.ToString() : "",
Name = friend.name,
ImageURL = friend.pic
};
if (string.IsNullOrEmpty(info.ImageURL))
info.ImageURL =
"http://static.ak.fbcdn.net/pics/q_silhouette.gif";

// Add social networks
IList<facebook.Schema.affiliation> affList = friend.affiliations.affiliation;
foreach (facebook.Schema.affiliation aff in affList)
{
if (!affDictionary.Keys.Contains(aff.nid))
affDictionary.Add(aff.nid, aff.name);
info.AffiliationIDs += aff.nid.ToString() + ",";
}
friendList.Add(info);
}

ListView1.DataSource = friendList;
ListView1.DataBind();

ddlAffiliation.Visible = friendList.Count > 0;
lblLoginStatus.Text = "You have logged in as " + userName;
lblFriendCount.Text = "You have total " + friendList.Count.ToString() + " friends.";

ddlAffiliation.DataSource = affDictionary;
ddlAffiliation.DataTextField = "Value";
ddlAffiliation.DataValueField = "Key";
ddlAffiliation.DataBind();
ddlAffiliation.Items.Insert(0, new ListItem("All", "0"));
The AffiliationIDs for each friend are stored in a hidden field so the client side JavaScript can set a friend’s visibility based on the selected affiliation ID:
<asp:ListView ID="ListView1" runat="server" EnableViewState="false">
<LayoutTemplate>
<ul class="friendlist">
<asp:PlaceHolder ID="itemPlaceholder" runat="server" />
</ul>
</LayoutTemplate>
<ItemTemplate>
<li>
<img src='<%#Eval("ImageURL") %>' width="75" height="75" />
<br/> <%#Eval("Name") %>
<input id="hidID" type="hidden" value='<%#Eval("ID") %>'/>
<input id="hidAffIDs" type="hidden" value='<%#Eval("AffiliationIDs") %>'/>
</li>
</ItemTemplate>
</asp:ListView>
jQuery and JavaScript and jQuery update the page when affiliation dropdown is changed. All filtering occurs in client side without any postback and AJAX call:
<script type="text/javascript">
$(document).ready(function() {
$("#<%= ddlAffiliation.ClientID %>").change(affiliationChanged);
});

function affiliationChanged() {
var affiliationID = $("#<%= ddlAffiliation.ClientID %> :selected").val();
var affiliationCount = 0;
$(".friendlist").find("li").each(function(i) {
var ids = $(this).find("#hidAffIDs").val();
$(this).show();
if (affiliationID != "0" && !hasIDInlist(ids, affiliationID))
$(this).hide();
else
affiliationCount++;
});
var affMsg = "There are " + affiliationCount + " friends in " +
$("#<%= ddlAffiliation.ClientID %> :selected").text() + " network.";
$("#<%=lblAffiliationFilter.ClientID %>").html(affMsg);
}

function hasIDInlist(ids, selectedID) {
if (ids) {
var idSplit = ids.split(',');
for (i = 0; i < idSplit.length; i++) {
if (idSplit[i] == selectedID) {
return true;
}
}
}
return false;
}
</script>
When user changes the social network filter, a JavaScript handler will check all friends’ affiliation IDs, and set the friend image invisible if the selected ID is not on the list. Below is screen-shot of the page with affiliation filter:


Sending Message to Selected Friends

For security reason Facebook Connect does not provider email address to other party, and there is no simple way to access user or user’s friends email addresses directly. The detail has been described in http://www.facebook.com/help.php?page=888. However a signed in user can send a Facebook notification to another user (by user’s ID) using Facebook Connect library. In Facebook Developer Toolkit, we can use API’s notifications.send method to send a notification to another user:
api.notifications.send(selectedFriend.id, "This is a message from Facebook Connect Test!");
The message will go to receiver’s Inbox Notification tab:

Some speical characters and html tags inside notification message will be filtered out by Facebook. The allowed tags for email notificaiton are listed in http://wiki.developers.facebook.com/index.php/Allowed_FBML_and_HTML_Tags#Notifications:_Allowed_Tags.

conclusion

With Facebook Connect, we can easily share the Facebook data and use them in our application.