Build Facebook Applications with Silverlight 3 and Silverlight 4 - part 3

[Infragistics] Mihail Mateev / Sunday, April 18, 2010

Part 3 of this article describes how to build a Silverlight application with Facebook API for Silverlight from the Facebook Developer Toolkit

To create a Facebook Application with Silverlight API you can follow these steps:

  1. Create a Silverlight application, hosted in an ASP.Net application.
  2. Add the Facebook Connect Components to the ASP.Net application.
  3. Set up the Silverlight project to reference the assemblies from  the Facebook Developer Toolkit.
  4.  Set the static port for our Web Application.
  5. Create a new Facebook application.
  6. Add APIKey and Secret to Web.config of your Web Application.
  7. Instantiating the Facebook Developer Toolkit BrowserSession object and Authenticating the user
  8. Use an Asynchronous API Requests with  Facebook Developer Toolkit
    1. Instantiating the Facebook Developer Toolkit BrowserSession object and Authenticating the user
    2. Get user details with Silverlight API
    3. Maintain user albums with Silverlight API
  • Create a Silverlight Application:


Host the Silverlight application in a new Web site:

 A new Silverlight application is created

 

 

  •  Configure a Silverlight Application

Adding the Facebook Connect Components to the  Application

Download and extract the Facebook Developer Toolkit(FDT) from the CodePlex project home

In this application I will use 3.1 Beta version of the Facebook Developer Toolkit because of issue with receiving of the users info  in earlier versions (v. 3.0.2).

More information you can find in:

http://facebooktoolkit.codeplex.com/WorkItem/View.aspx?WorkItemId=17036

It is possible to fix the issue in the source and rebuild the libraries or download the latest version 3.1 Beta.

Set up the host Web site application for communication with Facebook:

Add the following script references to the Web page which will host the Silverlight .xap (i.e. default.aspx):

<head runat="server">

            <script

                 src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php"

                 type="text/javascript">

            </script>

            <script type="text/javascript" src="fblogin.js"></script>

            <!-- Other header info -->

      </head>

Add a new Jscript file to your root of the web application named "blogin.js"

Open fblogin.js and replace the entire file with the content with this:

 

// Verify this variable matches the Silverlight plugin ID
var silverlightPluginId = 'Silverlight1';
 
function facebook_init(appid) {
    FB.init(appid, "/xd_receiver.htm");
}
 
function isUserConnected() {
    FB.ensureInit(function () {
        FB.Connect.get_status().waitUntilReady(function (status) {
            var plugin = document.getElementById(silverlightPluginId);
});
    });
}
 
function facebook_login() {
    FB.ensureInit(function () {
        FB.Connect.requireSession(facebook_getSession, true);
    });
}
 
function facebook_logout() {
    FB.Connect.logout(facebook_onlogout);
}
 
function facebook_getSession() {
 
    FB.Facebook.get_sessionState().waitUntilReady(function () {
        var session = FB.Facebook.apiClient.get_session();
        var plugin = document.getElementById(silverlightPluginId);
        plugin.Content.FacebookLoginControl.LoggedIn(session.session_key, session.secret, session.expires, session.uid);
    });
}
 
function facebook_onlogout() {
    var plugin = document.getElementById(silverlightPluginId);
    plugin.Content.FacebookLoginControl.LoggedOut();
}
 
function facebook_onpermission(accepted) {
    var plugin = document.getElementById(silverlightPluginId);
    plugin.Content.FacebookLoginControl.PermissionCallback(accepted);
}
 
function facebook_prompt_permission(permission) {
    FB.ensureInit(function () {
        FB.Connect.showPermissionDialog(permission, facebook_onpermission);
    });
}
Edit the fblogin.js' silverlightPluginId variable value to match the ID of the Silverlight plug-in in your hosting Web page.
This variable is used to locate the plug-in during authentication.

 

  • Set up the Silverlight project to reference the assemblies from  the Facebook Developer Toolkit

 In Visual Studio, add a reference to the Facebook.Silverlight.dll assembly into the Silverlight project (by browsing to the location to which the Toolkit was extracted).

 

 

  • Set the static port for our Web Application.

  • Create a new Facebook application.

 

  1. Copy APIKey and Secret from Basic tab to use it your application.
  2. Set a local application URL in Canvas tab -> Canvas Callback URL
  3. Set a canvas page  URL in Canvas tab -> Canvas Page URL
  4. Set IFrame Size to be "Resizable"
  5. Save the Facebook Application.

 

  • Instantiating the Facebook Developer Toolkit BrowserSession object and Authenticating the user

In your Silverlight application's main page code behind, add the following private members to the class:
NOTE: Storing the API Secret within Silverlight code (which runs on the client) is NOT recommended as this code can be viewed by third party tools.
The BrowserSession API only requires the API Key as shown in the code below.

 

#region Private Members
private Api _fb;
readonly BrowserSession _browserSession;
private const string ApplicationKey = "d7cb11a21b91bcd7840dd3e40122992a";
private const string ApplicationSecret = "8a8cc0cf8e66211ced18a6fe37a90acd";
endregion Private Members

 

  

 

Add the following code to the constructor of the class:

public MainPage()
{
    InitializeComponent();
     _browserSession = new BrowserSession(ApplicationKey);
    _browserSession.LoginCompleted += BrowserSession_LoginCompleted;
    _browserSession.LogoutCompleted += BrowserSession_LogoutCompleted;
}

Add a login button to the XAML markup and attach a click event handler. In the handler, issue a login call to the Facebook Developer Toolkit  BrowserSession object.
When the user clicks the button the BrowserSession will launch the Facebook authentication popup window:

private void Login_Click(object sender, RoutedEventArgs e)
 {
     _browserSession.Login();
 
 }

 

 Add a logout button to the XAML markup and attach a click event handler.

 private void Logout_Click(object sender, RoutedEventArgs e)

{
    _browserSession.Logout();
}

 

In the BrowserSession_LoginCompleted event handler referenced in the constructor, add code to handle a completed user authentication.
Once the _fb object is assigned, the application has everything needed to access and integrate with Facebook data:

 private void BrowserSession_LoginCompleted(object sender, AsyncCompletedEventArgs e)

{
    _fb = new Api(_browserSession);
    ….
}

 

·         Using an Asynchronous API Requests with  Facebook Developer Toolkit

Silverlight applications are required to issue all service requests in an asynchronous manner. It prevents service calls from locking the user interface.
Due to this Silverlight standard the Facebook Developer Toolkit's Silverlight version (Facebook.Silverlight.dll) exposes only asynchronous API methods.

Common used methods in the Silverlight API from the Facebook Developer Toolkit

The code below shows a simple implementation of an asynchronous call to Facebook and display of the result in a user control

Authentication and Initiating a Session

BrowserSesson.Login() method:

You need to have a BrowserSesson object that is initialized in the ctor:

 

readonly BrowserSession _browserSession;
 
private const string ApplicationKey = "d7cb11a21b91bcd7840dd3e40122992a";
 
 
AppKey from your Facebook application is required to initialize a Browser Session:
 
public MainPage()
{
    InitializeComponent();
    _browserSession = new BrowserSession(ApplicationKey);
    _browserSession.LoginCompleted += BrowserSession_LoginCompleted;
}

Clicking the button will bring up the Facebook Authentication screen:

private void Login_Click(object sender, RoutedEventArgs e)
{
    _browserSession.Login();
}

Run and authenticate your application: 

 Enter the password for your Facebook account and press "Connect".

More information what is Facebook Connect you can find here:

http://msdn.microsoft.com/en-us/windows/ee702803.aspx

After a successful authentication will be called LoginCompleted event handler:

You must to initialize Api object:

 

private void BrowserSession_LoginCompleted(object sender, AsyncCompletedEventArgs e)
{
   _fb = new Api(_browserSession);
….
 
    this.RefreshInfo();
}
 
private void RefreshInfo()
{
     _fb.Users.GetInfoAsync(new Users.GetInfoCallback(GetUserInfoCompleted), null);
     _fb.Friends.GetUserObjectsAsync(new Users.GetInfoCallback(GetFriendsInfoCompleted), null);
 
….
 
}

GetInfoAsync calls asynchronously GetInfoCallback delegate.

Get user details with Silverlight API from Facebook Developer Toolkit

You can get details for which you have permission in this delegate:

private void GetUserInfoCompleted(IList<user> users, Object state, FacebookException e)
{
   if (e == null)
   {
      _currentUser = users.First();
      if (_currentUser.pic != null)
      {
 
          Uri uri = new Uri(_currentUser.pic);
          Dispatcher.BeginInvoke(() =>
          {
             ProfilePhoto.Source = new BitmapImage(uri);
             ProfileStatus.Text = _currentUser.status.message;
             ProfileName.Text = _currentUser.first_name + " " + _currentUser.last_name + " Birthday:" + _currentUser.birthday;
            });
        }
      }
      else
      {
          Dispatcher.BeginInvoke(() => MessageBox.Show("Error: " + e));
      }
 }

 

GetUserObjectsAsync calls asynchronously GetInfoCallback delegate that gets the list of friends of the current user.

Info for each friend is stored in Facebook.Schema.user class.

private void GetFriendsInfoCompleted(IList<user> users, Object state, FacebookException e)
{
     if (e == null)
     {
          Dispatcher.BeginInvoke(() => ListFriends.ItemsSource = users);
     }
     else
     {
          Dispatcher.BeginInvoke(() => MessageBox.Show("Error: " + e.Message));
     }
}

 In versions 3.0.1 and 3.0.2 there is a bug when trying to get list of friends via GetUserObjectAsync.
It could not deserialize data returned from server. Facebook changed something in the way their hs_info is passed down and the C# API is not expecting nullable values.

More information and solution how to fix it you can find here: http://facebooktoolkit.codeplex.com/WorkItem/View.aspx?WorkItemId=17036

You can apply the fix in the source code of the Facebook Development Toolkit and recompile it.
Other solution is to use a Facebook Development Toolkit version 3.1 Beta where this bug is fixed.
I tried both approaches and it works fine.
You can show part of data, stored in user objects .

There is a part of MainPage.xaml file in my demo application:

 

<ListBox x:Name="ListFriends" Grid.Row="1" Background="LightGray"    HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,5,0,0">
   <ListBox.ItemTemplate>
      <DataTemplate>
         <Grid Background="Transparent" Margin="0, 0, 0, 10">
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                 <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Image x:Name="coverImage" Width="150" Source="{Binding pic}" Stretch="Uniform" Tag="pic"/>
                  </Grid>
                  <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                     <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                     </Grid.RowDefinitions>
<TextBox x:Name="firstName" Grid.Row="0" Background="Transparent" BorderThickness="0" Text="{Binding first_name}"/>
<TextBox x:Name="lastName" Grid.Row="1" Background="Transparent" BorderThickness="0" Text="{Binding last_name}"/>
                  </Grid>
                </StackPanel>
         </Grid>
       </DataTemplate>
     </ListBox.ItemTemplate>
</ListBox>

 

The demo application layout after user has logged in.

  

Often used methods from Silverlight API from Facebook Developer Toolkit

Update user status:

 

Api.Status.SetAsync( string status, Status.SetCallback callback, object state);
 
_fb.Status.SetAsync(TxtStatus.Text, SetStatusCompleted, null);

Status.SetCallback delegate can maintain the result and Exceptions when updating of the status is completed:

private void SetStatusCompleted(bool result, Object state, FacebookException e)
{
   if (e == null)
   {
         if (result == false)
         {
             Dispatcher.BeginInvoke(() => MessageBox.Show("call failed"));
         }
    }
    else
    {
         Dispatcher.BeginInvoke(() => MessageBox.Show("Error: " + e.Message));
    }
}

Receiving the user photo albums and photos:

private void GetAlbumsByUser(long userId)
{
    // Issue async request for user albums via the Facebook Developer Toolkit
    _fb.Photos.GetAlbumsAsync(userId, GetUserAlbumsCompleted, UserAlbums);
}
 
private ObservableCollection<album> _userAlbums;
 
public ObservableCollection<album> UserAlbums
{
     get
     {
         return _userAlbums;
     }
     set
     {
         _userAlbums = value;
         NotifyPropertyChanged("UserAlbums");
     }
}
 
 
private void GetUserAlbumsCompleted(IList<album> albums, object state, FacebookException exception)
{
    // Marshall back to UI thread
    Dispatcher.BeginInvoke(() =>
    {
         // Verify albums returned
         if (albums == null) return;
 
         // If existing collection is null, new up collection ObservableCollection<album>
         UserAlbums = state as ObservableCollection<album> ?? new ObservableCollection<album>();
 
         // Iterate result set
         foreach (var a in albums)
         {
             if (!UserAlbums.Contains(a))
             {
                 UserAlbums.Add(a);
              }
          }
    });
}

Bind the collection to UI:

 

<ListBox Width="200" Height="270" x:Name="ListAlbums" ItemsSource="{Binding UserAlbums, ElementName=MainWindow}" SelectionChanged="AlbumList_SelectionChanged">
     <ListBox.ItemTemplate>
         <DataTemplate>
            <StackPanel Orientation="Horizontal">
              <TextBlock Text="{Binding Path=name}"></TextBlock>
            </StackPanel>
         </DataTemplate>
      </ListBox.ItemTemplate>                              
</ListBox>
 
private void AlbumList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    // Clear any album photo captions
    AlbumPhotoCaption.Text = string.Empty;
    AlbumPhotoCacheCaption.Text = string.Empty;
    this.CurrentAlbum = e.AddedItems[0] as album;
 
    this.UserAlbumCaption.Text = string.Format("User Albums: {0}", UserAlbums.Count);
 
    GetAlbumPhotos();
 
    if (AlbumPhotos != null)
    {
        // Set cache loaded and async loaded album photo counts
        AlbumPhotoCacheCaption.Text = string.Format("Original Load Album Photos: {0}", AlbumPhotos.Count);
 
        AlbumPhotos.CollectionChanged += delegate
        {
            // Set album photo total count
            AlbumPhotoCaption.Text =
                string.Format("Total Album Photos: {0}", AlbumPhotos.Count);
        };
    }
 
}
private void GetAlbumPhotos()
{
    // Issue async request for all photos in the current album
    if (CurrentAlbum == null || CurrentAlbum.aid == null) return;
    _fb.Photos.GetAsync(null, CurrentAlbum.aid, null, GetAlbumPhotosCompleted, AlbumPhotos);
 
}
private void GetAlbumPhotosCompleted(IList<photo> photos, object state, FacebookException exception)
{
 
    // Marshall back to UI thread
    Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
 
        // Verify photos returned
        if (photos == null) return;
 
        // If stateful (existing) collection is null, new up collection
        // if you want to add new photos to list without lost the old photos use:
        // ObservableCollection<photo> statefulPhotos = state as ObservableCollection<photo> ?? new ObservableCollection<photo>();
        ObservableCollection<photo> statefulPhotos = new ObservableCollection<photo>();
 
        // Iterate result set
        foreach (var p in photos)
        {
            // Set flag to determine existence
            var photoExistsInCollection = false;
 
            // Iterate existing photo cache
            foreach (var existingPhoto in statefulPhotos)
            {
                // Check for matching photo IDs
                if (existingPhoto.pid == p.pid)
                {
                    // This is a duplicate, ignore and break
                    photoExistsInCollection = true;
                    break;
                }
            }
 
            // Check if photo does not exist in cache
            if (!photoExistsInCollection)
            {
                // Add to photo collection
                statefulPhotos.Add(p);
            }
        }
 
        this.AlbumPhotos = statefulPhotos;
    });
}
#endregion //GetAlbumPhotosCompleted

 

AlbumPhotos collection is used to store collection of photos from selected photo album:

private ObservableCollection<photo> _currentAlbumPhotos;
….
public ObservableCollection<photo> AlbumPhotos
{
    get
    {
        return _currentAlbumPhotos;
    }
    set
    {
        _currentAlbumPhotos = value;
        NotifyPropertyChanged("AlbumPhotos");
    }
}
 

Binding AlbumPhotos to UI:

<DataTemplate x:Key="photoItemsTemplate">
    <Grid Background="Transparent" Margin="0, 0, 0, 10">
        <StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
            <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                <Image x:Name="coverImage" Width="180" Source="{Binding src}" Stretch="Uniform" Tag="src"/>
            </Grid>
        </StackPanel>
    </Grid>
</DataTemplate>
 
 
<ScrollViewer BorderThickness="0" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Hidden"
Height="300" Width="190" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
    <ItemsControl x:Name="ScrollPhoto"  VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
ItemTemplate="{StaticResource photoItemsTemplate}" ItemsSource="{Binding AlbumPhotos, ElementName=MainWindow}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</ScrollViewer>

 

Upload a new photo to album, created from the facebook application:

private void UploadButton_Click(object sender, RoutedEventArgs e)
{
    OpenFileDialog openfile = new OpenFileDialog { Multiselect = false };
    openfile.ShowDialog();
 
    System.IO.Stream fileStream = openfile.File.OpenRead();
    byte[] data;
    using (BinaryReader reader = new BinaryReader(fileStream))
    {
        TxtUploadStatus.Text = "Uploading...";
        data = reader.ReadBytes((int)fileStream.Length);
    }
    fileStream.Close();
 
    _fb.Photos.UploadAsync(null, "Myphoto", data, "image/jpeg", OnUploadPhoto, null);
 
}

You can download Demo sample there:

 Part 4 of this article will demonstrate some specific issues when building out-of-browser applications with the Facebook API for Silverlight from the Facebook Developer Kit.