One of my personal projects is a facebook application using the Facebook Developer’s Toolkit and ASP.NET WebForms. There’s a number of good guides for using the toolkit out there (Devtacular for example) and for the most part development was straightfoward. However, there were a few issues that popped up as a result of webforms particular model of execution. So, being a fan of new and shiny things, I thought it’d be interesting to try and integrate the Facebook Developer’s toolkit with the newly-released ASP.NET MVC.


The toolkit relies on inheritance for Facebook integration: an asp.net page (or your own base page) should inherit off CanvasFBMLBasePage or CanvasIFrameBasePage, depending on the mode the app is run in. These pages inherit off Page and use another class, BasePageHelper, to ensure a user logs into their facebook account. They also provide access to an API object that provides facebook user details. While this works fine in a standard web form app, this approach doesn’t quite work in an MVC application. Pages in MVC are strictly views and don’t even have a code behind file by default. You can add a code-behind manually of course but this seemed like adding too much functionality to what should be a ‘dumb’ view.

A more MVC’ish approach would be to implement the toolkit using Action Filters. Action Filters allow you to tap into an executing action or page, via an attribute. For example we could ensure a facebook user is logged in by just decorating a controller with an attribute. The code for this is based on the BasePageHelper in facebook.web:

   public class FacebookFilterAttribute : ActionFilterAttribute
    {
        #region Instance members and proeprties
        private const string FACEBOOK_CANVAS_PARAM = "&canvas";
        private const string FACEBOOK_LOGIN_URL = @"http://www.facebook.com/login.php?api_key=";
        private const string QUERY_AUTH_TOKEN = "auth_token";
        private const string REQUEST_IN_CANVAS = "fb_sig_in_canvas";
        private const string REQUEST_SESSION_KEY = "fb_sig_session_key";
        private const string REQUEST_USER_ID = "fb_sig_user";
        private const string SESSION_SESSION_KEY = "SessionKey";
        private const string SESSION_USER_ID = "UserId";

        public API FacebookAPI { get; set; }
        public string ApiKey { get; set; }
        public bool RequireLogin { get; set;}
        public string Secret { get; set; }
        #endregion

        #region Methods
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            string sessionKey = null;
            string userId = null;
            string inCanvas = filterContext.HttpContext.Request[REQUEST_IN_CANVAS];

            FacebookAPI = new API();

            if (string.IsNullOrEmpty(FacebookAPI.ApplicationKey) || string.IsNullOrEmpty(FacebookAPI.Secret))
            {
                GetKeyAndSecretFromWebConfig(FacebookAPI);
            }

            if (RequireLogin)
            {
                if (string.IsNullOrEmpty(FacebookAPI.SessionKey) || string.IsNullOrEmpty(FacebookAPI.uid.ToString()))
                {
                    sessionKey = filterContext.HttpContext.Request[REQUEST_SESSION_KEY];
                    userId = filterContext.HttpContext.Request[REQUEST_USER_ID];
                }
                else
                {
                    sessionKey = FacebookAPI.SessionKey;
                    userId = FacebookAPI.uid.ToString();
                }

                // When the user uses the facebook login page, the redirect back here will will have the auth_token in the query params
                string authToken = filterContext.HttpContext.Request.QueryString[QUERY_AUTH_TOKEN];

                // We have already established a session on behalf of this user
                if (!String.IsNullOrEmpty(sessionKey))
                {
                    FacebookAPI.SessionKey = sessionKey;
                    FacebookAPI.uid = long.Parse(userId);
                }
                //// This will be executed when facebook login redirects to our page
                else if (!String.IsNullOrEmpty(authToken))
                {
                    FacebookAPI.CreateSession(authToken);
                }
                else
                {
                    if (inCanvas == "1")
                    {
                        filterContext.HttpContext.Response.Write("");
                    }
                    else
                    {
                        filterContext.HttpContext.Response.Write("");
                    }

                    filterContext.HttpContext.Response.End();
                }
                filterContext.Controller.TempData["API"] = FacebookAPI;
            }
        }

        ///
        /// Converts the relative part of the URL being requested into a relative URL that can be called from the canvas page,
        /// taking into account whether or not the application is actually in a subdirectory of the web application.
        ///
        ///
        ///
        private static string GetCanvasURL(HttpRequestBase request)
        {
            string webApplicationSubdirectory = WebConfigurationManager.AppSettings["WebApplicationSubdirectory"];

            if (webApplicationSubdirectory == null)
            {
                webApplicationSubdirectory = string.Empty;
            }

            string relativeURL = request.AppRelativeCurrentExecutionFilePath.Substring(2);

            if (relativeURL.StartsWith(webApplicationSubdirectory))
            {
                relativeURL = relativeURL.Substring(webApplicationSubdirectory.Length);
            }

            string query = request.QueryString.ToString();
            if (!string.IsNullOrEmpty(query))
                relativeURL = System.Web.HttpUtility.UrlEncode(relativeURL + "?" + query);

            return relativeURL;
        }

        internal static void GetKeyAndSecretFromWebConfig(API FaceBookAPI)
        {
            // ApplicationKey and Secret are acquired when you sign up for
            // Get the values from the configuration file.
            var apiKey = WebConfigurationManager.AppSettings["APIKey"];
            var secret = WebConfigurationManager.AppSettings["Secret"];

            if (apiKey == null || secret == null)
                throw new Exception(
                    "You must specify values for both APIKey and Secret in the web config of your project.\r\n" +
                    "For example: \r\n" +
                    " \r\n " +
                    "   \r\n" +
                    "   \r\n" +
                    "\"");

            FaceBookAPI.ApplicationKey = apiKey;
            FaceBookAPI.Secret = secret;
        }
        #endregion
    }

Essentially all we had to do was tap into the OnActionExecuting event to ensure the user was authenticated via Facebook. To use this filer you can just add it to a controller:

    [FacebookFilter(RequireLogin = true)]
    public class HomeController : Controller

The end result is that a user will be prompted to log in to facebook when trying to view your page. Note that this is using FBML, so it assumes you’re running it as an application within facebook (and not an iframe). If you notice the last line in OnActionExecuting I’ve set the following:

filterContext.Controller.TempData["API"] = FacebookAPI;

This allows us to actually use the API object within our view. For example, the following page will greet the user with their name when viewed:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Facebook.Master" Inherits="System.Web.Mvc.ViewPage" %>
<%@ Import Namespace="facebook.web" %>
<%@ Import Namespace="facebook" %>

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
    <h2><%= Html.Encode(ViewData["Message"]) %></h2>
    <p>
        <% API facebookAPI = TempData["API"] as API; %>
        Hello, <%= facebookAPI.users.getInfo().name %>
    </p>
</asp:Content>