Language

ASP.NET MVC Facebook Birthday App

By Rick Anderson, Troy Dai and Yao Huang Lin|

The tutorial download was written with Visual Studio 2013. This tutorial was originally written with the MVC 4 template in Visual Studio 2012. After this tutorial was published, the Facebook API was changed, and the Google shopping API no longer exists. A revised version of the tutorial is planned. Until that is published, this tutorial still provides some valid guidance but many of the steps don't work as shown.

Start by installing and running Visual Studio Express 2013 for Web or Visual Studio 2013.  Install Visual Studio 2013 Update 2 or higher.

The Facebook template include with Visual Studio 2013 will be removed in the future and made available as an extension.

The Facebook App template includes a new library to take care of all the plumbing involved in building a Facebook app, so you can focus on building the business logic in your application. The Facebook app -- called a Canvas app -- that you can build with this new template is hosted on the web and is displayed inside the Facebook chrome via an iframe. The functionality included in the template will help you with authentication, permissions, and accessing Facebook data. The Facebook functionality also has an extensibility model so that you can build your own storage providers and more.

A Visual Studio project with C# source code is available to accompany this tutorial. Download the project.

The following changes need to be made to this tutorial.

  1. Use the new Facebook API. See Updating the MVC Facebook API..
  2. The Google shopping API was replaced with the ShopStyle API.

To run the app download.

  1. Create a new Facebook app and add appid, namespace, appsecret and add to the web.config file.
  2. Create a ShopSense account and add the uid to the web.config file.

What You'll Build

You'll implement a simple but fun and useful Facebook application that lists a person's Facebook friends who have upcoming birthdays and makes suggestions for birthday presents based on each friend's Facebook Likes. Here are screenshots, one that shows the list of friends with upcoming birthdays and one with recommended birthday presents which appears when you click a friend:

Facebook app home page

Facebook app presents page

Creating the project

  1. Run Visual Studio 2012 or Visual Studio Express for Web 2012.

  2. From the File menu, click New Project.

  3. In the New Project dialog box, expand C# and then click Web under Templates.

  4. Click ASP.NET MVC 4 Web Application.

  5. Leave the .NET Framework version set to .NET 4.5; this is the version that the Facebook template requires.

  6. Enter SampleFacebookApp for the project name, and then click OK.

    Note: You must use SampleFacebookApp for the project name so you can copy files from the download project without having to change the namespace.

    New Project dialog box

  7. In the New ASP.NET MVC 4 Project dialog box, click Facebook Application, and then click OK.

    (The Facebook template is available only for the Razor view engine.)

    Choose the Facebook App template

Setting up SSL in the Project

To connect to Facebook, you will need to set up IIS-Express to use SSL.

  1. In Solution Explorer, click the SampleFacebookApp project.
  2. Enter the F4 key to show the project properties. Alternatively, from the View menu you can select Properties Window.
  3. Change SSL Enabled to True.


  4. Copy the SSL URL.
  5. In Solution Explorer, right click the SampleFacebookApp and select Properties.
  6. Select the Web tab.


  7. Paste the SSL URL into the Project Url box, then click Create Virtual Directory. You will need this URL to configure Facebook.

Creating the app in Facebook and connecting the app to the project

Right out of the box, the Facebook template provides boilerplate code to help you connect your application to Facebook and retrieve Facebook properties such as Likes, photos, and email. All you have to do to get a simple application up and running is copy to your project some settings from an application that you create in Facebook.

  1. In your browser, navigate to https://developers.facebook.com/apps and log in by entering your Facebook credentials.

  2. If you aren't already registered as a Facebook developer, click Register as a Developer and follow the directions to register.

  3. Click Create New App.

    Create new app

  4. Enter an App Name and App Namespace, and then click Continue.

    These must be unique across Facebook. The App Namespace is the part of the URL that people will use to access your Facebook application (for example, https://apps.facebook.com/{App Namespace}). If you don't specify an App Namespace, the App ID will be used for the URL. The App ID is a long system-generated number that you will see in the next step.

    Create New App dialog

  5. On the Basic Settings page for the app, set the Sandbox Mode to Enabled. This ensures that you have exclusive access to the app until it's ready to be made available to everyone.

    You'll use the App ID, App Secret, and Namespace settings in the next step.

    Basic App Settings

  6. In Visual Studio, open the application Web.config file that is located in the root folder of your project.

  7. Copy and paste the AppId, App Secret, and App Namespace values into the corresponding key elements in the appSettings collection in the Web.config file.

    App settings in Web.config

  8. In Solution Explorer, right-click the project and click Properties, and then click the Web tab.

  9. Copy the Project URL from the Web tab and paste it into the Canvas URL and Secure Canvas URL fields in the Facebook Basic Settings page.


  10. Change Canvas Width to Fluid.

    The canvas width is the width of the window inside of a Facebook page in which your application will run. Setting it to Fluid enables your application's width to change depending on the user's browser window size.

  11. Click Save Changes.

  12. Press CTRL+F5 to run the application.

When you run the application in Internet Explorer (IE), Firefox, or Page Inspector, you get a security certificate warning. This is because the certificates used by IIS Express are not trusted by browsers. You can dismiss these warnings on your development machine. When you deploy the application to Windows Azure, the SSL certificates are trusted and you won't get any warnings. In IE, click Continue to this website (not recommended). In Chrome, click Proceed anyway.



 

The browser opens up on the Facebook login page if you aren't already logged in. With IE, you will get the following warning after you login to Facebook.



Click Show content.

After you log in, Facebook will display the following dialog box which asks whether you want to load the application. Although the birthday application doesn't write on your timeline, you can change the default restriction from Public to Friends, Only Me or Custom. Click Go to App.

You now see the application running inside a Facebook page. The first page that it displays is a request for permissions to access Facebook data for the logged-on user.

Template App first page

If you get an error similar to following illustration, make sure that you set the correct Canvas URL and set all of the the other parameters as described earlier.

When you click Authorize this application, Facebook asks you to authorize the application to access your email address and photos. Click Allow.

Authorize email and photos

You now have a simple working Facebook application.

Template app with pictures

Examining the template code

This section of the tutorial walks you through the code that was created by the Facebook Application project template. If you prefer to just get started building the birthday application, you can skip to the next section.

You saw two pages that were displayed by the template application: a request for authorization and the main page that displays photos and email address. Both of these pages are displayed by the Home controller: the Permissions action method displays the authorization request, and the Index action method displays the main page.

public class HomeController : Controller
{
    [FacebookAuthorize("email", "user_photos")]
    public async Task<ActionResult> Index(FacebookContext context)
    {
        if (ModelState.IsValid)
        {
            var user = await context.Client.GetCurrentUserAsync<MyAppUser>();
            return View(user);
        }

        return View("Error");
    }

    // This action will handle the redirects from FacebookAuthorizeFilter when 
    // the app doesn't have all the required permissions specified in the FacebookAuthorizeAttribute.
    // The path to this action is defined under appSettings (in Web.config) with the key 'Facebook:AuthorizationRedirectPath'.
    public ActionResult Permissions(FacebookRedirectContext context)
    {
        if (ModelState.IsValid)
        {
            return View(context);
        }

        return View("Error");
    }
}

Notice that the Index action method is an asynchronous method. Because this method calls a web service to get Facebook data, there will be some latency. Making the method asynchronous enables the server to process high traffic loads more efficiently. For more information about asynchronous methods in ASP.NET MVC, see Using Asynchronous Methods in ASP.NET MVC 4.

The FacebookAuthorize attribute on the Index method is what causes the Permissions page to be displayed first when your application runs and the user hasn't given it permission yet. You use this attribute to specify the Facebook data that your application needs permission to retrieve. The Web.config file has a setting that specifies the URL to use when the application doesn't have the required permissions:

  <appSettings>
    <!-- Other settings removed for clarity -->
    <add key="Facebook:AuthorizationRedirectPath" value="~/Home/Permissions" />
  </appSettings>

The MVC model binder provides the Permissions method with a FacebookRedirectContext object that encapsulates information about the request, including the requested permissions:

public class FacebookRedirectContext
{
    public FacebookRedirectContext();
    
    public FacebookConfiguration Configuration { get; set; }
    public string OriginUrl { get; set; }
    public string RedirectUrl { get; set; }
    public string[] RequiredPermissions { get; set; }
}

The Views\Home\Permissions.cshtml view then displays the requested permissions:

@using Microsoft.AspNet.Mvc.Facebook
@model FacebookRedirectContext
@{
    ViewBag.Title = "Required Permissions";
}

@if (Model.RequiredPermissions.Length > 0)
{
    <h3>You need to grant the following permission(s) on Facebook to view this page:</h3>
    <ul>
        @foreach (var permission in Model.RequiredPermissions)
        {
            <li>@permission</li>
        }
    </ul>
    <a class="buttonLink" href="@Html.Raw(Model.RedirectUrl)" target="_top">Authorize this application</a>
}

For the Index method that displays the main application page, the MVC model binder provides a FacebookContext object that encapsulates information about the request:

public class FacebookContext
{
    public FacebookContext();

    public string AccessToken { get; set; }
    public FacebookClient Client { get; set; }
    public FacebookConfiguration Configuration { get; set; }
    [Dynamic]
    public dynamic SignedRequest { get; set; }
    public string UserId { get; set; }
}

The FacebookClient object that is included in the context object provides methods you can use to get Facebook data about the user. The template code in the Index method specifies that it wants a MyAppUser object when it calls FacebookClient.GetCurrentUserAsync.

 var user = await context.Client.GetCurrentUserAsync<MyAppUser>();

The MyAppUser class specifies the data to retrieve for the user of the application:

public class MyAppUser
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }

    [JsonProperty("picture")] // This renames the property to picture.
    [FacebookFieldModifier("type(large)")] // This sets the picture size to large.
    public FacebookConnection<FacebookPicture> ProfilePicture { get; set; }

    [FacebookFieldModifier("limit(8)")] // This sets the size of the friend list to 8, remove it to get all friends.
    public FacebookGroupConnection<MyAppUserFriend> Friends { get; set; }

    [FacebookFieldModifier("limit(16)")] // This sets the size of the photo list to 16, remove it to get all photos.
    public FacebookGroupConnection<FacebookPhoto> Photos { get; set; }
}

The Index view displays this information:

@using SampleFacebookApp.Models
@using Microsoft.AspNet.Mvc.Facebook.Models
@model MyAppUser
@{
    ViewBag.Title = "Home Page";
}

<article class="intro">
    <span id="profilePicture">
        @if (Model.ProfilePicture != null && Model.ProfilePicture.Data != null)
        {
            <img src="@Model.ProfilePicture.Data.Url" />
        }
    </span>
    <h3>Welcome @Model.Name</h3>
    <label>Email: @Model.Email</label>
    <p>
        To learn more about building Facebook apps using ASP.NET MVC visit
        <a href="http://asp.net/mvc/facebook" title="ASP.NET MVC Website">http://asp.net/mvc/facebook</a>.
        The page features videos, tutorials, and samples to help you get the most from ASP.NET MVC.
        If you have any questions about ASP.NET MVC visit
        <a href="http://forums.asp.net/1146.aspx/1?MVC" title="ASP.NET MVC Forum">our forums</a>.
    </p>
</article>

<article id="content">
    <div class="left">
        <h4>Friends</h4>
        @if (Model.Friends != null && Model.Friends.Data != null && Model.Friends.Data.Count > 0)
        {
            foreach (var myFriend in @Model.Friends.Data)
            {
            <a href="@myFriend.Link" target="_blank">
                <div class="photoTile">
                    <label>@myFriend.Name</label>
                    @if (myFriend.Picture != null && myFriend.Picture.Data != null)
                    {
                        <img src="@myFriend.Picture.Data.Url" />
                    }
                </div>
            </a>
            }
        }
        else
        {
            <p>No friends found.</p>
        }
    </div>
    <div class="right">
        <h4>Photos</h4>
        @if (Model.Photos != null && Model.Photos.Data != null && Model.Photos.Data.Count > 0)
        {
            foreach (var photo in @Model.Photos.Data)
            {
            <a href="@photo.Link" target="_blank">
                <div class="photoTile">
                    <img src="@photo.ThumbnailUrl" />
                </div>
            </a>
            }
        }
        else
        {
            <p>No photo available.</p>
        }
    </div>
</article>

The model passed to this view is a MyAppUser object. The "intro" <article> element displays the user's name, email address, and picture. The "content" <article> element displays the user's friends in the left div and the user's photos in the right div.

Creating the Facebook birthday app

This tutorial will show the steps for creating the birthday application by using files from the completed project that you can download by using the link at the top of the page. If you haven't already downloaded the project, download it now before continuing with the tutorial.

Note: If you just want to run the downloaded application without going through the following steps to build it in your own project, you have to configure the Facebook application settings as shown in the previous section, and configure the Google Shopping API key as shown later in this tutorial. You may also need to enable NuGet package restore. To do this, click Library Package Manager from the Tools menu, and then click Manage NuGet Packages for Solution. In the Manage NuGet Packages dialog, click Restore. (If you have already enabled NuGet package restore, you won't see the yellow bar with the Restore button.)

Install Bootstrap

You'll begin by installing the Bootstrap NuGet package. Bootstrap is a popular and powerful front-end framework than enables faster and easier web development. We used it in this application because it allows you to easily create cool layouts that work great on desktops, tablets, and smartphones without having to learn or fight CSS. See the Bootstrap Getting Started guide for more information.

  1. From the Tools menu, click Library Package Manager, and then click Manage NuGet Packages for Solution.



  2. In the left tab of the Manage NuGet Packages dialog box, click Online.

  3. In the search box at the top right, enter bootstrap, and then press Enter.

  4. Install the Bootstrap package. The Bootstrap NuGet package installs the following files:
    • bootstrap-responsive.css and bootstrap.css (and the minified versions).
    • bootstrap.js (and the minified version).



  5. Add the Bootstrap CSS and JavaScript files to the App_Start\BundleConfig.cs file. Comment out the jQuery CSS files, we are not using them in this tutorial. The completed BundleConfig.cs file looks like this:
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                    "~/Scripts/jquery-{version}.js"));
    
        bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
                    "~/Scripts/jquery-ui-{version}.js"));
    
        bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                    "~/Scripts/jquery.unobtrusive*",
                    "~/Scripts/jquery.validate*"));
    
        // Use the development version of Modernizr to develop with and learn from. Then, when you're
        // ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
        bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                    "~/Scripts/modernizr-*"));
    
        bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css",
            "~/Content/bootstrap.css", "~/Content/bootstrap-responsive.css"));
    
        //bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
        //            "~/Content/themes/base/jquery.ui.core.css",
        //            "~/Content/themes/base/jquery.ui.resizable.css",
        //            "~/Content/themes/base/jquery.ui.selectable.css",
        //            "~/Content/themes/base/jquery.ui.accordion.css",
        //            "~/Content/themes/base/jquery.ui.autocomplete.css",
        //            "~/Content/themes/base/jquery.ui.button.css",
        //            "~/Content/themes/base/jquery.ui.dialog.css",
        //            "~/Content/themes/base/jquery.ui.slider.css",
        //            "~/Content/themes/base/jquery.ui.tabs.css",
        //            "~/Content/themes/base/jquery.ui.datepicker.css",
        //            "~/Content/themes/base/jquery.ui.progressbar.css",
        //            "~/Content/themes/base/jquery.ui.theme.css"));
    
        bundles.Add(new ScriptBundle("~/bundles/app").Include(
                    "~/Scripts/bootstrap.js" ));
    }
  6. Delete the Views\Shared\_Layout.cshtml file and add in its place the same file from the downloaded project. To add the file, right-click the Views\Shared folder and click Add Existing Item, and then navigate to the downloaded version of the Views\Shared\_Layout.cshtml file.

    The updated layout file is shown below, with the changes highlighted:
    @using Microsoft.AspNet.Mvc.Facebook
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="utf-8" />
      <title>@ViewBag.Title - My ASP.NET MVC Application</title>
      @Styles.Render("~/Content/css")
      @Scripts.Render("~/bundles/modernizr")
      @Scripts.Render("~/bundles/jquery")
      @Scripts.Render("~/bundles/app")
    </head>
    <body>
      <script>
          window.fbAsyncInit = function () {
              FB.init({
                  appId: '@GlobalFacebookConfiguration.Configuration.AppId', // App ID
              status: true, // check login status
              cookie: true, // enable cookies to allow the server to access the session
              xfbml: true  // parse XFBML
          });
        };
    
      // 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));
      </script>
    
    <div id="wrapper">  
       <div class="navbar">
          <div class="navbar-inner">
            <div class="container">
              <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
              </a>
              <a class="brand" target="_top" href="@GlobalFacebookConfiguration.Configuration.AppUrl@Url.Action("Index", "Home")">Birthday App</a>
              <div class="nav-collapse collapse">
                <ul class="nav">
                  <li><a target="_top" href="@GlobalFacebookConfiguration.Configuration.AppUrl@Url.Action("Index", "Home")">Home</a></li>
                  <li><a href="@Url.Action("About", "Home")" >About</a></li>
                </ul>
                <form class="navbar-form pull-right" action="@Url.Action("Search", "Home")" method="get">
                  <input class="span2" type="text" name="friendName" placeholder="Friend's name" />
                  <button type="submit" class="btn">Search</button>
                </form>
              </div>
            </div>
          </div>
        </div>
        <header id="topHeader">
          <h1>Birthday App</h1>
          <h2>Time to buy your friend a birthday present.</h2>
        </header>
        @RenderBody()
        <footer>
          <p>&copy; @DateTime.Now.Year - My ASP.NET MVC Application</p>
        </footer>
      </div>
      @RenderSection("scripts", required: false)
    </body>
    </html>
    The markup inside the wrapper div uses Bootstrap to create a navigation bar at the top of the page that enables users to go to different application pages or search for specific friends. For more information about this use of Bootstrap, see Twitter Bootstrap 101: The Navbar and the Bootstrap navbar help pages.

  7. Open Content\Site.css and remove the margin line from the h1 definition.

         header h1 {
            font-size: 80px;
            color: #004C66;
            /*margin: 0;*/
            padding-top: 20px;
            font-weight: normal;
         }

    This change prevents the h1 and h2 headings from overlapping.

  8. Run the application by pressing CTRL+F5.

    You see the new navigation bar and the new title text. If you resize the window and make it narrower, the links and Search box are replaced by a button that you can click to display them in a column on the left, making the application mobile browser friendly.

    Main page after Bootstrap

    The button that displays the page links and search text box in a column on the left is shown in the red square.

    Links and Search box on the left

Change the way the app user and friends are displayed

The MyAppUser class determines what information is gathered from Facebook about the application user, and the MyAppUserFriend class determines what information is gathered about the user's friends. In this section of the tutorial you change those classes and then change the Index view that displays the user and friend info.

  1. Open the Models\MyAppUser.cs file.

  2. Comment out the FacebookFieldModifier attribute on the ProfilePicture field. The template uses this attribute to set the profile picture to large size, but the user of your application doesn't need to see a large picture of himself or herself.

  3. Comment out the FacebookFieldModifier attribute on the Friends field. The template uses this attribute to limit the number of friends displayed to 8, but you'll retrieve all of the friends. (After you sort them by birthday, you'll limit the display to the first 100 friends to prevent the list from getting too long to display).

  4. Comment out the Photos property and its FacebookFieldModifier attribute. The birthday application doesn't need to display the user's photos.

    When you're done, the code will look like the following example :
    public class MyAppUser
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
    
        [JsonProperty("picture")] // This renames the property to picture.
        //[FacebookFieldModifier("type(large)")] // This sets the picture size to large.
        public FacebookConnection<FacebookPicture> ProfilePicture { get; set; }
    
        //[FacebookFieldModifier("limit(8)")] // This sets the size of the friend list to 8.
        public FacebookGroupConnection<MyAppUserFriend> Friends { get; set; }
    
        //[FacebookFieldModifier("limit(16)")] // This sets the size of the photo list to 16.
        //public FacebookGroupConnection<FacebookPhoto> Photos { get; set; }
    }
  5. Delete the Models\MyAppUserFriend.cs file, and add the same file from the downloaded project.

    The MyAppUserFriend.cs file contains three classes. The MyAppUserFriendSimple class encapsulates information about one of the application user's Facebook friends. It is used for the initial list of all of the user's friends. It doesn't get the user's Facebook Likes because that isn't necessary for the list of friends, and getting that data for all friends could slow down the display. The MyAppUserFriend class is used when the user selects a particular friend to get birthday recommendations for, so this class needs to gather more data about the friend. The FacebookLike class encapsulates information about a friend's Likes. Here is the code for the three classes:
        public class MyAppUserFriendSimple
        {
            public string Id { get; set; }
            public string Gender { get; set; }
            public string Name { get; set; }
            public string Link { get; set; }
            public string Birthday { get; set; }
    
            [FacebookFieldModifier("height(100).width(100)")] // This sets the picture height and width to 100px.
            public FacebookConnection<FacebookPicture> Picture { get; set; }
        }
    
        public class MyAppUserFriend
        {
            public string Id { get; set; }
            public string Name { get; set; }
    
            public FacebookGroupConnection<FacebookLike> Likes { get; set; }
        }
    
        public class FacebookLike
        {
            public string Category { get; set; }
            public string Name { get; set; }
        }
    
  6. Delete the Views\Home\Index.cshtml file and replace it with the same file from the downloaded project. The new Index view is shown here:
    @using SampleFacebookApp.Models
    @using Microsoft.AspNet.Mvc.Facebook.Models
    @using Microsoft.AspNet.Mvc.Facebook
    @model MyAppUser
    @{
      ViewBag.Title = "Home Page";
    }
    
    <article class="intro">
      <span id="profilePicture">
        @if (Model.ProfilePicture != null && Model.ProfilePicture.Data != null)
        {
          <img class="img-polaroid" src="@Model.ProfilePicture.Data.Url" />
        }
      </span>
      <h3>Welcome @Model.Name</h3>
      <label>Email: @Model.Email</label>
    </article>
    
    <article id="content">
      <h4>Friends with upcoming birthdays</h4>
      @Html.DisplayFor(m => m.Friends.Data, "Friends")
    </article>
    The view displays the user's picture (if available), then displays each of the user's friends by using a Friends.cshtml display template, which you will create next.

  7. Add a DisplayTemplates folder to the Views\Home folder.

  8. Copy the Views\Home\DisplayTemplates\Friends.cshtml file from the downloaded project to your project.

    The Friends template displays the MyAppUserFriendSimple model and is used in the Index view and the Search view. The Friends.cshtml display template is shown here:
    @using SampleFacebookApp.Models
    @using Microsoft.AspNet.Mvc.Facebook.Models
    @using Microsoft.AspNet.Mvc.Facebook
    @model IList<MyAppUserFriendSimple>
    
    <table class="table">
      @foreach (var friend in Model)
      {
        <tr>
          <td>
            <a target="_blank" href="@friend.Link">
              <div class="photoTile">
                <label>@friend.Name</label>
                @if (friend.Picture != null && friend.Picture.Data != null)
                {
                  <img class="img-polaroid" src="@friend.Picture.Data.Url" />
                }
              </div>
            </a>
          </td>
          <td>
            <label>@friend.Birthday</label>
          </td>
          <td>
            <a target="_top" href="@GlobalFacebookConfiguration.Configuration.AppUrl@Url.Action("RecommendGifts", new { friendId = friend.Id })" role="button" class="btn btn-success">
              @if (friend.Gender == "male")
              {
                <span>Buy him a present</span>
              }
              else
              {
                <span>Buy her a present</span>
              }
            </a>
          </td>
        </tr>
      }
    </table>
    For each of the application user's friends, this template displays a picture of the friend (if one is available) and provides a link to suggested gifts that you can purchase.

  9. Now that you're using a Friends template that requires MyAppUserFriendSimple, open MyAppUser.cs and change MyAppUser to specify that its friends collection uses the MyAppUserFriendSimple class.
    public FacebookGroupConnection<MyAppUserFriendSimple> Friends { get; set; }
  10. Run the application to see the changes.

    Main page with new friends list


Modify the app to support birthday gift suggestions

  1. Replace the Controllers\HomeController.cs file in your project with the same file from the downloaded project.

    The new HomeController class is shown below:
        public class HomeController : Controller
        {
            [FacebookAuthorize("email", "friends_birthday")]
            public async Task<ActionResult> Index(FacebookContext context)
            {
                if (ModelState.IsValid)
                {
                    var user = await context.Client.GetCurrentUserAsync<MyAppUser>();
                    var friendsWithUpcomingBirthdays = user.Friends.Data.OrderBy(friend =>
                    {
                        try
                        {
                            string friendBirthDayString = friend.Birthday;
                            if (String.IsNullOrEmpty(friendBirthDayString))
                            {
                                return int.MaxValue;
                            }
    
                            var birthDate = DateTime.Parse(friendBirthDayString);
                            friend.Birthday = birthDate.ToString("MMMM d"); // normalize birthday formats
                            return BirthdayCalculator.GetDaysBeforeBirthday(birthDate);
                        }
                        catch
                        {
                            return int.MaxValue;
                        }
                    }).Take(100);
                    user.Friends.Data = friendsWithUpcomingBirthdays.ToList();
                    return View(user);
                }
    
                return View("Error");
            }
    
            [FacebookAuthorize("friends_birthday")]
            public async Task<ActionResult> Search(string friendName, FacebookContext context)
            {
                var userFriends = await context.Client.GetCurrentUserFriendsAsync<MyAppUserFriendSimple>();
                var friendsFound = String.IsNullOrEmpty(friendName) ?
                    userFriends.ToList() :
                    userFriends.Where(f => f.Name.ToLowerInvariant().Contains(friendName.ToLowerInvariant())).ToList();
                friendsFound.ForEach(f => f.Birthday = !String.IsNullOrEmpty(f.Birthday) ? DateTime.Parse(f.Birthday).ToString("MMMM d") : "");
                return View(friendsFound);
            }
    
            [FacebookAuthorize("friends_likes")]
            public async Task<ActionResult> RecommendGifts(string friendId, FacebookContext context)
            {
                if (!String.IsNullOrEmpty(friendId))
                {
                    var friend = await context.Client.GetFacebookObjectAsync<MyAppUserFriend>(friendId);
                    if (friend != null)
                    {
                        var products = await RecommendationEngine.RecommendProductAsync(friend);
                        ViewBag.FriendName = friend.Name;
                        return View(products);
                    }
                }
    
                return View("Error");
            }
    
            [FacebookAuthorize]
            public ActionResult About()
            {
                return View();
            }
    
            // This action will handle the redirects from FacebookAuthorizeFilter when
            // the app doesn't have all the required permissions specified in the FacebookAuthorizeAttribute.
            // The path to this action is defined under appSettings (in Web.config) with the key 'Facebook:AuthorizationRedirectPath'.
            public ActionResult Permissions(FacebookRedirectContext context)
            {
                if (ModelState.IsValid)
                {
                    return View(context);
                }
    
                return View("Error");
            }
        }
    

    The asynchronous Index action method gets the current Facebook logged on user and populates the MyAppUser model, which contains the user's ID, name, email, profile picture and list of friends. Then it sorts the list of friends by calling the OrderBy extension method. For each friend, it passes to the sort method the number of days until the person's birthday. To calculate the number of days it converts the birthdate to a string that has only the month and the day and then passes that value to a GetDaysBeforeBirthday helper method that you'll create later in the tutorial. If the birthday field doesn't have a valid date (typically because the birthday isn't public), it passes int.MaxValue to the sort method. The result is that all friends with known birthdays appear in birthday order starting with the birthday closest to the current date. Those that haven't made their birthdays public appear at the end of the list.

    The Search action method is called when the user clicks the Search button to find a specific person without having to page through all friends. It gets the value of the string entered in the Search text box, calls the Facebook API to get all of the application user's friends, and then excludes from the list any friends whose names do not contain the search string. If no search string is entered, the entire list of all friends is returned. You'll create the view that displays this list of friends in the next step.

    The RecommendGifts method is called when the user clicks Buy him a present or Buy her a present. It gets the ID of the selected friend, calls the Facebook API to get that friend's information including the person's Likes, and calls a RecommendProductAsync helper method to get a list of products that contain keywords from the friend's Likes. You'll create the helper method later in the tutorial, and you'll create the view that displays the products in the next step.

    Most of the action methods in the home controller are asynchronous. Asynchronous methods can make your web server more efficient when applications make web service calls (like the Facebook API calls in this application). For more information about using asynchronous methods in ASP.NET MVC, see Using Asynchronous Methods in ASP.NET MVC 4.

  2. Add the Search.cshtml, RecommendGifts.cshtml, and About.cshtml files from the Views\Home folder in the downloaded project to your project's Views\Home folder.

    The Search view displays the list of friends that is returned by the Search action method:
    @using System.Collections.Generic
    @using SampleFacebookApp.Models
    @{
      ViewBag.Title = "Search";
    }
    @model IList<MyAppUserFriendSimple>
    
    <article class="intro">
      <h3>Search results</h3>
    </article>
    @Html.DisplayFor(m => Model, "Friends")
    The RecommendGifts view displays the list of products that is returned by the RecommendGifts action method:
    @model List<SampleFacebookApp.Models.RecommendedItem>
    
    @{
      ViewBag.Title = "Recommend Presents";
      string friendName = ViewBag.FriendName;
    }
    
    <article class="intro">
      <h3>Recommended birthday presents for @friendName</h3>
    </article>
    @if (Model.Count > 0)
    {
      <div class="container-fluid">
        @foreach (var present in Model)
        {
          <div class="row-fluid">
            <div class="span3">
              @if (@present.Product.Images != null && @present.Product.Images.Length > 0)
              {
                <img src="@present.Product.Images[0].link" />
              }
              else
              {
                <p>Picture not available</p>
              }
            </div>
            <div class="span9">
              <dl>
                <dt>@present.Product.Title</dt>
                <dd>@present.Product.Description</dd>
                <dt>Price</dt>
                @if (@present.Product.Inventories != null && @present.Product.Inventories.Length > 0)
                {
                  <dd class="price">@present.Product.Inventories[0].Price</dd>
                }
              </dl>
              <a class="btn btn-success" target="_blank" href="@present.Product.Link">
                <i class="icon-shopping-cart"></i>
                Buy it
              </a>
              <p>
                <small>Recommended because @friendName @present.Reason</small>
              </p>
            </div>
          </div>
        }
      </div>
    }
    else
    {
      <p>We don't know what @friendName likes. No recommendation can be given at the moment.</p>
    }0
    The CSS classes in this code are defined in the Bootstrap CSS files.

    The About view credits the author of the application:
    @{
      ViewBag.Title = "About";
    }
    
    <article class="intro">
      <h3>About</h3>
    </article>
    <div>
      <h4>This is an app created from MVC Facebook Template.</h4>
      <b>Author: <a target="_blank" href="https://www.facebook.com/yaohl">Yao Huang Lin</a></b>
    </div>
  3. Add the RecommendedItem.cs and SearchResultModel.cs files from the Models folder of the downloaded project to your Models folder.

    The RecommendedItem class contains the data about a suggested birthday present that is provided to the RecommendGifts view:
    namespace SampleFacebookApp.Models
    {
        public class RecommendedItem
        {
            public Product Product { get; set; } 
            public string Reason { get; set; }
        }
    }
    The classes in the SearchResultModel.cs file contain the information that is returned by the Google API:
    namespace SampleFacebookApp.Models
    {
        public class SearchResult
        {
            public Item[] items { get; set; }
        }
    
        public class Item
        {
            public Product product { get; set; }
        }
    
        public class Product
        {
            public string Title { get; set; }
            public string Description { get; set; }
            public string Link { get; set; }
            public ProductImage[] Images { get; set; }
            public ProductInventory[] Inventories { get; set; }
        }
    
        public class ProductImage
        {
            public string link { get; set; }
        }
    
        public class ProductInventory
        {
            public double Price { get; set; }
        }
    }
    This hierarchy of classes corresponds to the response format of the Google Search API for Shopping. The API returns much more data than is specified here; only the properties that you are actually going to use are defined. The Product object from this data graph is provided to the RecommendGifts view in the RecommendedItem object.
  4. In Solution Explorer, right click the SampleFacebookApp project, click Add, and then click New Folder.



    Name the folder Helpers.

  5. Right click the Helpers folder and choose Add, and then click Existing Item.

  6. Navigate to the downloaded project and add the three files from the Helpers folder.

    • The BirthdayCalculator helper calculates the number of days before each friend's birthday:
          public static class BirthdayCalculator
          {
              public static int GetDaysBeforeBirthday(DateTime birthDate)
              {
                  var today = DateTime.Now;
                  var nextBirthday = new DateTime(today.Year, birthDate.Month, birthDate.Day);
                  TimeSpan difference = nextBirthday - DateTime.Now;
                  if (difference.Days < 0)
                  {
                      nextBirthday = new DateTime(today.Year + 1, birthDate.Month, birthDate.Day);
                      difference = nextBirthday - DateTime.Now;
                  }
                  return difference.Days;
              }
      }
      The code subtracts the current date from the current year birthday to get the number of days until the birthday. If that number is negative, the birthday in the current year is before the current date, and the number of days is recalculated using the birthday in the next year.

    • The ShoppingSearchClient helper uses the Google Search API for Shopping to search for products that match a user's friend's Likes:
          public static class ShoppingSearchClient
          {
              private const string SearchApiTemplate = "https://www.googleapis.com/shopping/search/v1/public/products?key={0}&country=US&q={1}&alt=json";
              private static HttpClient client = new HttpClient();
      
              public static string AppKey = ConfigurationManager.AppSettings["Search:AppKey"];
      
              public static Task<SearchResult> GetProductsAsync(string query)
              {
                  if (String.IsNullOrEmpty(AppKey))
                  {
                      throw new InvalidOperationException("Search:AppKey cannot be empty. Make sure you set it in the configuration file.");
                  }
      
                  query = query.Replace(" ", "+");
                  string searchQuery = String.Format(SearchApiTemplate, AppKey, query);
                  var response = client.GetAsync(searchQuery).Result.EnsureSuccessStatusCode();
                  return response.Content.ReadAsAsync<SearchResult>();
              }
          }
      
      The ReadAsAsync<SearchResult> method gets the JSON response from the Google Search for Shopping API and uses it to populate a SearchResult object. You'll set up the key you need in order to call this API in the next section of the tutorial.

    • The RecommendationEngine helper uses the ShoppingSearchClient helper to get a list of suggested birthday gifts for a selected friend:
          public static class RecommendationEngine
          {
              private static string[] categoriesToConsider =
              {
                  "Entertainment",
                  "Tv show",
                  "Movie",
                  "Book",
                  "Musician/band",
                  "Product/service",
                  "Clothing",
                  "Food/beverages",
                  "Shopping/retail",
                  "Games/toys"
              };
      
              public static async Task<List<RecommendedItem>> RecommendProductAsync(MyAppUserFriend friend)
              {
                  List<RecommendedItem> recommendedItem = new List<RecommendedItem>();
                  if (friend.Likes != null)
                  {
                      var friendLikes = friend.Likes.Data;
                      var friendLikesFilteredByCategory = friendLikes.Where(like => categoriesToConsider.Contains(like.Category));
      
                      foreach (var item in friendLikesFilteredByCategory.Take(10))
                      {
                          var result = await ShoppingSearchClient.GetProductsAsync(item.Name);
                          if (result.items == null)
                          {
                              // skip empty results
                              continue;
                          }
                          var products = result.items.Select(i => i.product).Take(2).Where(p => p != null);
                          foreach (var product in products)
                          {
                              recommendedItem.Add(new RecommendedItem
                              {
                                  Product = product,
                                  Reason = "likes " + item.Name
                              });
                          }
                      }
                  }
      
                  return recommendedItem;
              }
      }
      The categoriesToConsider list specifies a list of popular product categories to filter the selected friend's Likes.

      The RecommendProductAsync method takes a MyAppUserFriend parameter containing the selected friend's name and list of Likes. It filters the Likes by removing any Likes that are not in one of the specified categories. Then for each Like, up to a maximum of 10, it searches for products that match the name associated with the Like. For each search it selects the first two of the products returned by the search and adds them to the list of recommended items. Then it returns the list.

      The RecommendationEngine class uses a simple selection and search algorithm in order to keep the code easy to follow and understand; you can plug in a more sophisticated selection engine if you prefer. Note that the product search is not guaranteed to return the actual products that the friend "Liked." For example, if the friend liked the television show "Heroes," the Google API would be called to search for the keyword "Heroes." However, it might return a "Hogan's Heroes" DVD or something completely unrelated in a different product category. This is a limitation of the Facebook API: for the Likes you get only a category name and an item name, not a link to the actual web page where the user clicked the Like button.

Create a Google API shopping Key.

The application is now complete except for one thing: a key for the Google shopping API. In order to search for birthday presents the application uses the Google shopping API, and you need a key provided by Google to call that API.

  1. Browse to https://code.google.com/apis/console and log in.

    Note: This requires a Google account.
  2. Click Create project.


  3. Click the Services tab, and then scroll down and click the on/off button to enable Search API for Shopping.



  4. Click the API Access tab, and then copy the API key:



  5. Open the Web.config file and add a new key element to the appSettings collection:
      <appSettings>
        <!-- Other settings. -->
        <add key="Search:AppKey" value="[yourkeyvaluehere]" />
      </appSettings>

Run the application and you see that friends are now in birthday order. You can try the Search page by entering a search string and clicking Search, and you can see a list of recommended gifts by clicking a Buy him a present or Buy her a present link.

Note: If the selected friend has never Liked anything, the application can't make any suggestions.

Deploying the app to Windows Azure

So far your application has been running locally in IIS Express on your development computer. To make it available for other people to use, you have to deploy it to a web hosting provider. In this section of the tutorial you'll deploy it to a Windows Azure Web Site.

Get a Windows Azure account

You'll need a Windows Azure account. If you don't already have one, you can create a free trial account in just a couple of minutes. For details, see Windows Azure Free Trial. If you already have a Windows Azure account, enable the Windows Azure Web Site preview; see Enable Windows Azure preview features.

Create a Windows Azure Web Site

To create a Windows Azure Web Site, follow the directions in the Create a web site section of the Deploying an ASP.NET Web Application to a Windows Azure Web Site tutorial.

Tell Facebook about your Windows Azure Web Site

Next, you have to tell Facebook to use your Windows Azure URL instead of the localhost URL when it runs your application.

  1. In your browser, go to the Windows Azure Management Portal, click the Web Sites tab, and then click the web site that you created for your Facebook app.

  2. Click the Dashboard tab, and then copy the URL from Site URL. It's on the right under the Quick Glance section. The URL will look like this: http://yoursitename.azurewebsites.net.

    Quick glance section of dashboard

  3. Go to https://developers.facebook.com/apps, click your app, and then click Edit App to go back to the Basic Settings page.

  4. On the Facebook Basic Settings page for your app, paste the Windows Azure Web Site URL in the Canvas URL field. Add a slash (/) to the end of the URL.

  5. Do the same for the Secure Canvas URL field but change http to https.

  6. Click Save Changes at the bottom of the page.

    Basic Settings with URLs

Note: These instructions explain how to set up the application to use the default azurewebsites.net domain. When you use SSL with a URL in this domain, you are using an SSL certificate that is shared by all URLs in the same domain. If you want to use your own SSL certificate and your own domain, see Configuring a custom domain name for a Windows Azure web site and Configuring an SSL certificate for a Windows Azure web site on the WindowsAzure.com site.

Deploy the Facebook Application project to the Windows Azure Web Site

Next, deploy your Facebook application to the Windows Azure Web Site by following the directions in the Deploy the application to Windows Azure section of the same tutorial.

When you finish publishing the project, Visual Studio automatically opens a browser to the URL of your Windows Azure Web Site, and your application automatically redirects to the Facebook site. Your application is now running in the Facebook page as before, but now it is being served from your Windows Azure Web Site instead of from the local computer.

Facebook app home page

All you have to do to make it available to anyone on Facebook is disable sandbox mode on the Facebook Basic Settings page.

Next steps

Yao Huang Lin wrote this Facebook Birthday application, and his blog post, The new Facebook application template and library for ASP.NET MVC, contains an excellent overview of the ASP.NET MVC Facebook library.

You can enable your Facebook application to get real-time updates from Facebook by using the UserRealtimeUpdateController controller. For more information, see Realtime Update in the ASP.NET MVC Facebook Template on Troy Dai's blog.

Acknowledgements

  • Yao Huang Lin: Yao is the principal developer for the ASP.NET MVC Facebook library and templates. Yao wrote the sample used in this tutorial.
  • Tom Dykstra: Tom co-authored this tutorial and is a senior programming writer on the Microsoft Web Platform & Tools Content Team.
  • Rick Anderson: (twitter @RickAndMSFT ) Rick co-authored this tutorial and is a senior programming writer for Microsoft focusing on Azure and MVC.
  • Troy Dai: (twitter: @troy_dai ) Troy is a Software Design Engineer in Test at Microsoft.

Author Information

Rick Anderson

Rick Anderson – Rick Anderson works as a programmer writer for Microsoft, focusing on ASP.NET MVC, Windows Azure and Entity Framework. You can follow him on twitter via @RickAndMSFT.

Troy Dai

Troy Dai – Troy Dai is a software engineer at Microsoft.

Yao Huang Lin

Yao Huang Lin – Yao Huang Lin is a software engineer at Microsoft.