Bay Bridge

The big news a couple of weeks ago was the opening up of the Facebook Places API and launching a Deals Service as well. The Places API is pretty straightforward to work with so I’ve updated the Sample MVC App to show an example of calling it, specifically getting a user’s checkins and search for places given a search term, latitude and longitude, and distance. I’ve also had a few requests to show a few other calls, so I’ve included a sample for displaying a user’s wall (feed) and how to use offline_access to retrieve a user’s wall even when they aren’t logged in. Additionally, I’ve refactored some of the code a bit to be a little cleaner, but I’ve only written samples using the official C# SDk (I assume thats what everyone’s using at this point).

Setting the proper extended permissions

We’re going to be using two new extended permissions here, checkins and offline_access, so the first change we need to make is in Home/Index.aspx.

	<fb:login-button autologoutlink='true' onlogin='window.location.reload()'
	perms='read_stream, publish_stream, read_friendlists, user_activities,
	user_checkins, offline_access'></fb:login-button>

The first time you log in after making these changes you should get prompted with a confirmation dialog from facebook.
Extended permissions: Offline Access and Checkins

Places and checkins

The code to get a user’s checkins unsurprisingly makes a call to the Facebook Checkin service

	public ActionResult GetCheckins()
	{
		JSONObject checkins = helper.Get("/me/checkins");
		if (checkins != null)
		{
			var data = checkins.Dictionary["data"];
			List<JSONObject> checkinData = data.Array.ToList<JSONObject>();
			ViewData["Checkins"] = checkinData;
		}

		ViewData["Name"] = Session[Constants.FacebookNameKey];
		ViewData["isConnected"] = helper.IsConnected;

		return View("Index");
	}

The structure of the returned data is as follows:

{
   "data": [
      {
         "id": "CheckinID",
         "from": {
            "name": "UserName",
            "id": "UserId"
         },
         "place": {
            "id": "PlaceId",
            "name": "Giants World Series Victory Parade Route",
            "location": {
               "latitude": 37.78627395,
               "longitude": -122.40441083333
            }
         },
         "application": null,
         "created_time": "2010-11-03T19:45:31+0000"
      }
   ]
}

Facebook Checkin sample

Note that the data set returned from a call to the checkins api will be paged. I’ve only ever checked in once, hence the single item in mu list.

The search call is somewhat different since you need to have 4 parameters set, the query term, the latitude and longitude of the center of the lcoation, and the distance from the center to use (the 5th parameter, ‘type’ is just set to ‘place’ in the code below). I’ve provided some sample data (which is just the sample data used in the Facebook example), which can be loaded by pressing the ‘Load Test Data’ button. If you want to test a different address and need to find a valid lat/long setting, you can use http://geocoder.us/ to convert an address.

	public ActionResult SearchPlaces(FormCollection form)
	{
		Dictionary<string,string> parameters = new Dictionary<string,string>();
		parameters.Add("q", form["q"]);
		parameters.Add("center", String.Format("{0},{1}",form["lat"], form["long"]));
		parameters.Add("type", "place");
		parameters.Add("distance", form["distance"]);

		JSONObject searchResults = helper.Get("/search", parameters);
		if (searchResults != null)
		{
			var data = searchResults.Dictionary["data"];
			List<JSONObject> places = data.Array.ToList<JSONObject>();
			ViewData["Places"] = places;
		}

		ViewData["Name"] = Session[Constants.FacebookNameKey];
		ViewData["isConnected"] = helper.IsConnected;

		return View("Index");
	}

Nothing fancy about the returned data either, it’s pretty similar to the data for the checkin. One thing to note though is that the location item may not have an address associated with it, so you need to account for that when working with the data (the sample just checks for the existence of the ‘street’ key in the location array).

{
   "data": [
      {
         "name": "Philz Coffee",
         "category": "Local business",
         "location": {
            "street": "4023 18th St",
            "city": "San Francisco",
            "state": "CA",
            "zip": "94114-2501",
            "latitude": 37.760863,
            "longitude": -122.433326
         },
         "id": "151116474914629"
      },
      {
         "name": "Ritual Coffee Roasters",
         "category": "Local business",
         "location": {
            "latitude": 37.75659,
            "longitude": -122.4211
         },
         "id": "119185971453428"
      },
	  ...
	 ]
}

Facebook Places search sample

Retrieving a user’s wall

The previous iteration of the sample including an example on how to post to a user’s wall, so getting a user’s wall is a natural corollary. Getting a user’s feed is accomplished by making a call to ‘me/feed’

	public ActionResult GetWall()
	{
		JSONObject wallData = helper.Get("/me/feed");
		if (wallData != null)
		{
			var data = wallData.Dictionary["data"];
			List<JSONObject> wallPosts = data.Array.ToList<JSONObject>();
			ViewData["Wall"] = wallPosts;
		}

		ViewData["Name"] = Session[Constants.FacebookNameKey];
		ViewData["isConnected"] = helper.IsConnected;

		return View("Index");
	}

The returned data can differ depending on the type of the wall post, so when working with the data you just need to check for the presence of the appropriate dictionary keys before trying to use it. For example:

	<%
		foreach (Facebook.JSONObject wallItem in wallPosts)
		{
			string wallItemType = wallItem.Dictionary["type"].String;
			if (wallItemType == "photo" || wallItemType == "video")
			{
	%>
		<li><% if (wallItem.Dictionary.ContainsKey("name")) {  %><%= wallItem.Dictionary["name"].String%> <% } %> (<%= wallItemType %>) 			<br /> <a href='<%= wallItem.Dictionary["link"].String %>'><%= wallItem.Dictionary["link"].String %></a></li>
	 <% }
		else if (wallItemType == "status") {  %>
			<li><%= wallItem.Dictionary["from"].Dictionary["name"].String%> <br /> <%= wallItem.Dictionary["message"].String%></li>
	 <% } %>

Note that a picture or video may or may not have a name so you’ll need to check for that as well.

Facebook Wall sample

Offline Access

The offline_access extended permission is pretty cool, it basically extends the life of the access_token you receive after logging in via OAuth. The example I wrote just stores this in a cookie (named FacebookAccessToken) the first time you log in with your account. Normally you’d want to store this in a database or some other more permanent store.

	public ActionResult GetOfflineWall()
	{
		if (HttpContext.Request.Cookies[Constants.TokenCookieName] == null ||
			String.IsNullOrEmpty(HttpContext.Request.Cookies[Constants.TokenCookieName].Value))
		{
			return View("Index");
		}

		JSONObject wallData = helper.GetWhileOffline("/me/feed");
		if (wallData != null)
		{
			var data = wallData.Dictionary["data"];
			List<JSONObject> wallPosts = data.Array.ToList<JSONObject>();
			ViewData["OfflineWall"] = wallPosts;
		}

		return View("Index");
	}

The GetWhileOffline method beind used there just takes the access_token out of the FacebookAccessToken cookie and uses that when instantiating the FacebookAPI object…(in FacebookHelper.cs)

	public JSONObject GetWhileOffline(string apiCall)
	{
		JSONObject jsonObject = null;

		if (HttpContext.Current.Request.Cookies[Constants.TokenCookieName] != null ||
			!String.IsNullOrEmpty(HttpContext.Current.Request.Cookies[Constants.TokenCookieName].Value))
		{ 

			string token = HttpContext.Current.Request.Cookies[Constants.TokenCookieName].Value;
			token = HttpUtility.UrlDecode(token);

			Facebook.FacebookAPI api = new Facebook.FacebookAPI(token);
			jsonObject = api.Get(apiCall);
		}

		return jsonObject;
	}

Facebook Offline Wall Sample

Thats really all there is to it, as long as you have the extended permission ‘offline_access’ and an access_token that was created when a user logs in after granting this permission, you should be able to make offline calls.

Download the latest version here