While working on a current project — which is very much Facebook integrated, but not so much Facebook centric — the decision was made to not use the Facebook template, but instead use the standard MVC template, with the Facebook JS SDK. Here are a few things about the application you should understand before we continue:
For the landing page, I started out by simply adding an action to an existing controller.
|
public ActionResult Facebook() { ViewBag.ReturnUrl = ""; // Did the user request a specific game? string redirectId = Request.QueryString["game"]; // Make sure they go there when they're done. if (!string.IsNullOrEmpty(redirectId)) { ViewBag.ReturnUrl = "/Game/Play?game=" + redirectId; } return View(); } |
There really is not much going on there, it just preps everything before rendering the view. The view then does all of the work. This landing page is configured as the Canvas Url in the Properties on the Facebook Application Dashboard. Note the “game” being pulled from the QueryString. This is to support the scenario where a user has accepted a challenge and is being brought in to play the exact same game as their friend. Facebook has some outstanding documentation on how to post a challenge to the user’s wall using the Feed Dialog. I just augment the link that I’m sending into the dialog to include the ?game=xxx GET parameters.
The view for this landing page is some very basic HTML wrapping the more important JavaScript so that it can handle everything that Facebook may throw at it.
First, I had to set it up to handle executing an external login using the existing /Account/ExternalLogin action (part of the default Account Controller). So to accomplish this, I simply set up a form with all hidden components. When the time is right it will be submitted via JavaScript.
|
<div id="fb-root"></div> @using(Html.BeginForm("ExternalLogin", "Account", FormMethod.Post, new { id = "MVCLogin" })) { @Html.AntiForgeryToken() @Html.Hidden("Provider", "Facebook") @Html.Hidden("ReturnUrl", (string)ViewBag.ReturnUrl) } |
Note that I’m passing the ReturnUrl from my controller so once the user is logged in, they proceed to the appropriate action. Also notice the <div id=”fb-root”></div>. This is important for Facebook, but they made their script smart enough to write it in if you leave it out.
Next I added the magic Facebook Script. This too is well documented in the Facebook developer portal. It breaks up into 3 parts. The code offered on the documentation (linked above) covers parts 1 and 3, it’s up to you as the developer to create the middle part.
Part 1 is the call to initialize the Facebook SDK
|
// Additional JS functions here window.fbAsyncInit = function () { FB.init({ appId: '@System.Configuration.ConfigurationManager .AppSettings.Get("FacebookAppId")', channelUrl: '@System.Configuration.ConfigurationManager .AppSettings.Get("ChannelUrl")', status: true, // check login status cookie: true, // enable cookies to allow the server to access the session xfbml: true // parse XFBML }); |
Part 3 is the bit that actually loads up the SDK itself
|
// Load the SDK asynchronously (function (d) { var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0]; if (d.getElementById(id)) { return; } js = d.createElement('script'); js.id = id; js.async = true; js.src = "//connect.facebook.net/en_US/all.js"; ref.parentNode.insertBefore(js, ref); }(document)); |
The middle part however, is where it gets fun. This is where the meat of the Facebook integration comes into play. Part two also, in my case, included an additional method in my controller that takes an HTTP Post and persists the user’s Facebook information to session state. While this article will not make use of it, you will see a call to it in the code below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
// Here we subscribe to the auth.authResponseChange JavaScript event. This event // is fired for any authentication related change, such as login, logout or // session refresh. This means that whenever someone who was previously logged out // tries to log in again, the correct case below will be handled. FB.Event.subscribe('auth.authResponseChange', function (response) { // Here we specify what we do with the response anytime this event occurs. if (response.status === 'connected') { // The response object is returned with a status field that lets the // app know the current login status of the person. In this case, we're // handling the situation where they have logged in to the app. var uid = response.authResponse.userID; var accessToken = response.authResponse.accessToken; // Use a bootstrap modal and tell the user we are talking to facebook. $("#dialog").modal({keyboard: false, show: true}); // Call the authorize page with the user id and access token we got from // the authentication flow var params = { "uid": uid, "accessToken": accessToken }; $.ajax({ url: "/Game/FacebookAuthorize", dataType: 'json', data: JSON.stringify(params), type: 'POST', contentType: 'application/json; charset=utf-8', traditional: true }) |
First I set up the subscription for the auth.authResponseChange event. When this connects successfully, I get the user’s uid and access token fed back to me. Then I pop up a friendly little bootstrap modal to tell the user I’m connecting them to Facebook, just before authorizing their information back to my server.
At this point, if everything is OK then we simply handle the complete promise for the ajax call in the snippet above. Notice that I am using the ReturnUrl that we stuffed into the ViewBag earlier in this code as well.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
// Handle the complete promise .complete(function () { var isServerAuthenticated = @User.Identity.IsAuthenticated.ToString().ToLower(); if(!isServerAuthenticated){ // Authenticate $("#MVCLogin").submit(); } else { // Send them on to the return URL, everythin is good here. var returnUrl = '@ViewBag.ReturnUrl' if(returnUrl != "") { console.log("Redirecting to " + returnUrl); window.location.href = returnUrl; } else{ // If there is no return url, then just hide the dialog. $("#dialog").modal("hide"); } } }) |
Also notice that I’m handling the scenario where the user may have already been signed into my game, but not authorized via Facebook before. I did this by nestling the value of User.Identity.IsAuthenticated as a lower case string into a JavaScript variable. Next I simply check that value, and if they are not authenticated, I submit my form – The one I mentioned in the beginning of the article.
If they are authenticated, then we should be golden. Redirect them if they asked to be, and if not, then simply hide the “Connecting to Facebook” popup.
This code only shows the handling of the “complete” promise, but of course we handle the failure and error promises too. For these I simply hide the dialog (if it’s not already hidden) and post an alert for the user. I do not show this in the code samples, but it’s as simple as it sounds.
Posting the Challenge
I’ve also included my code for posting the challenge to a user’s Facebook wall. Note your application has to be configured to support the correct permissions for this activity. The page that is posting the message also has parts 1 and 2 of the Facebook JS SDK setup. The only other code it requires is a single method call.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
$("#facebookPost").click(function () { // This function exists in the layout page. facebookPostChallenge(); }); function faceBookCallback(response) { console.log(response); } function facebookPostChallenge() { var score = "@Model.GameScore"; var id = "@Model.GameId"; var title = "@Model.GameTitle"; var name = "I"; FB.api('/me', function (response) { name = response.first_name; }); // call the API var obj = { method: 'feed', link: 'https:@System.Configuration.ConfigurationManager .AppSettings.Get("FacebookLandingUrl")' + '?game=' + id, picture: 'https:@System.Configuration.ConfigurationManager .AppSettings.Get("LargeIconUrl")', name: '', caption: '', actions: [ { 'name': 'Beat My Score', 'link': 'https:@System.Configuration.ConfigurationManager .AppSettings.Get("FacebookLandingUrl")' + '?game=' + id } ], description: 'I just scored ' + score + ' on ' + title + ' while playing . Do you think you can beat me?' }; FB.ui(obj, faceBookCallback); } |
Summary
It took a little time and some trial and error to get it all figured out, but integrating a web app with the JavaScript SDK is pretty easy. Facebook already provides you with big chunks of code. Using those — along with some creative utilization of the tools afforded to you by MVC — you should find that working through a non-facebook-template site can still be tacked in pretty effectively.