Monday, August 16, 2010

Messenger Connect JavaScript API – Listing Contacts

Roadmap

This post is part of a series of related posts on how to use Windows Live Messenger Connect.

1. Using Messenger Connect REST Explorer for exploring Windows Live resources.
2. (this post) Using the Messenger Connect JavaScript API for listing your contacts.
3. Getting contacts in three different ways (API and REST).
4. Messenger Connect - Initializing Controls

Overview
In the last post Windows Live REST Explorer – Messenger Connect, we discussed a new offering from Microsoft Windows Live called Windows Live Essentials. A part of this offering includes APIs and tools that developers can use. Good developer overviews are here: Windows Live for Developers blog and your complete resource is dev.live.com.

In this post and the previous we are not interested in the new versions of Messenger or Photo Gallery, rather, we will focus on one of the APIs that allows us to talk to the Windows Live cloud. In the last post, we talked about an browser-based tool (Windows Live REST Explorer - REX) that allows you to see your Windows Live data (after signing in). In this post, we'll take the next step and work with the JavaScript API that abstracts much of repetitive working in dealing with constructing the correct requests to access resources and parsing the responses. Instead, the JavaScript API provides a familiar programmatic construct where we deal with collections, array, and types. To this end, we'll show some simple JavaScript code in a web page displaying your contacts.

The approach we'll take is to first start by downloading the SDK and using it with a web application project we create to get a successful (programmatic) sign-in to Windows Live. Then, we'll create a custom page that displays contacts. You will need to have a Windows Live ID account (___@live.com or ____@hotmail.com for example).

1. Registering An Application
2. Getting Your Computer Ready
3. Creating a New Web Application
4. Download the SDK and Add Files to the Web Application
5. Testing SignIn
6. Creating a Page That Displays Your Contacts

Registering An Application


1. To start go to dev.live.com and download the SDK and register for the Beta. If you can't get Beta access or don't want to, you can still follow along and get a sense of how the JavaScript API works. You can also work with the Interactive SDK without joining the Beta.

2. Much like Facebook Connect, there is a registration process for get a unique ID (called a client ID) that identifies your application and a key (called a secret key) that is used in token exchanges. You will get these when you register and are accepted for the Beta and then follow further instructions on the registration site: manage.dev.live.com. Shown below are two screenshots for the basic part of registering your web application (the type we are interested in here).


Registering a Web Application


Application Keys

You don't have to worry about going to live with your application. Just getting to the point where you application is registered and you can get your client ID and secret key means you are good to go.

Getting Your Computer Ready

1. If you have IIS running and it is using port 8080 you can either shut it off temporarily, OR change the port used in this tutorial to something else when following the instructions below.

2. Make sure your computer can serve up a page on the domain you registered with manage.dev.live.com. You can do this by editing your hosts file and adding an entry for the domain.

Creating a New Web Application

1. Create a new web application. For this tutorial we'll assume it is named WindowsLiveWebApplication.

2. Set the web properties of the web application as shown below, substituting your domain name where appropriate. The web properties tell Visual Studio how to act when serving up the pages on the site.

Windows Live Messenger Connect Web Application Properties
Web Application Web Properties

3. In the global.asax.cs code-behind add a session flag in the Session_Start handler as shown below.


void Session_Start(object sender, EventArgs e)
{
/* This is to ensure you have a unique sessionId for the request in the case where you keep information
* in the session state either in the implementation IAuthStoreProvider or ISessionIdProvider.
*/
Session.Add("wl_Session_started", true);
}


4. Modify the web.config file to have the following keys.


<appSettings>
<!-- Copy Client ID from http://manage.dev.live.com below for your application -->
<add key="wl_wrap_client_id" value="***PUT YOUR CLIENT ID HERE***"/>

<!-- Copy Secret Key from http://manage.dev.live.com below for your application -->
<add key="wl_wrap_client_secret" value="***PUT YOUR SECRET KEY HERE***"/>

<!-- Callback Url for your application, Register this for your app in http://manage.dev.live.com. -->
<add key="wl_wrap_client_callback" value="http://***PUT YOUR DOMAIN NAME HERE***:8080/OAuthWrapCallback.ashx"/>

<!-- Channel Url for your application.-->
<add key="wl_wrap_channel_url" value="http://***PUT YOUR DOMAIN NAME HERE***:8080/channel.htm"/>
</appSettings>


5. Add a callback handler.


<system.web>
<compilation debug="true" targetFramework="4.0" />

<httpHandlers>
<add verb="*" path="OAuthWrapCallback.ashx" type="Microsoft.Live.OAuthWrapHandler, Microsoft.Live.AuthHandler"/>
</httpHandlers>
</system.web>


The plumbing here is to identify the OAuthWrapCallback.ashx page as the handler of the consent process. The page (which you don’t create but is associated with the Microsoft.Live.OAuthWrapHandler.dll) processes tokens when your site talks to the Windows Live consent service. You can create a custom callback page as described here. In that case you don’t need to reference the Microsoft.Live.OAuthWrapHandler.dll which is what you would do for non-ASP.NET sites.

Note that if you were to use IIS 7 you would have to add the handler to the system.webServer, handlers section of the web.config. But in this tutorial we are using Visual Studio to serve the page.

Download the SDK and Add Files to the Web Application

1. Go and get the SDK download. Once you download the SDK you will see that there are two files in it: Windows Live SDK\v4.1\API Toolkits\Microsoft.Live.AuthHandler.dll and Windows Live SDK 4.1\Windows Live SDK\v4.1\Channel\channel.htm.

2. In the web application you created make a reference to the AuthHandler.dll. The reference to the DLL makes the communication between your web application and the Windows Live cloud possible through the callback page.

3. Put a copy of the channel.htm file in the root of the web application you created. The channel.htm file which is used for cross domain communication when Flash isn't installed or a HTML5's cross domain post message is not available - as in older browsers.

4. In the site master page code-behind Site.Master/.cs add a reference to the AuthHandler.dll with a “using Microsoft.Live;” statement and define a property called SessionId that will be use declarative markup. Your Site.Master.cs should look like this:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.Live;

namespace WindowsLiveWebApplication
{
public partial class SiteMaster : System.Web.UI.MasterPage
{
protected string SessionId
{
get
{
SessionIdProvider oauth = new SessionIdProvider();
return "wl_session_id=" + oauth.GetSessionId(HttpContext.Current);
}
}

protected void Page_Load(object sender, EventArgs e)
{

}
}
}


The SessionID property provides a unique identifier that can be used when the callback page is getting consent. Since we are not using HTTPS we need this extra step for security. The SessionID will be used to help formulate the application tag shown below.

5. In the Site.Master page you need to add several things to make the JavaScript API magic happen. Let's look at the declarative markup first. The whole page is be shown after showing the individual pieces.

First, add a loader statement which loads the JavaScript library.


<script type="text/javascript" src="http://js.live.net/4.1/loader.js"></script>


Add the namespace declaration referencing the Windows Live namespace.


<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:wl="http://apis.live.net/js/2010">


Add an application tag that initializes the application as a Messenger Connect-enabled application.


<wl:app
channel-url="<%=WebConfigurationManager.AppSettings["wl_wrap_channel_url"]%>
callback-url="<%=WebConfigurationManager.AppSettings["wl_wrap_client_callback"]%>?<%=SessionId%>"
client-id="<%=WebConfigurationManager.AppSettings["wl_wrap_client_id"]%>"
scope="WL_Profiles.View, Messenger.SignIn, WL_Contacts.View"
onload="appLoaded">
</wl:app>


The first three attributes of the app tag read values out of the web.config file. The scopes attribute defines resource and access permission. The last attribute, onload, defines a handler to be executed once the application is loaded.

Add an import directive so the WebConfigurationManager class can be used above.


<%@ Import Namespace="System.Web.Configuration" %>


Add a script tag with the following JavaScript defining the appLoaded handler. In our example here it's just defines a shortcut for using the Microsoft.Live namespace and nothing else. But this is where you could put custom code that should run after the application is loaded.


<script type="text/javascript">
function appLoaded() {
Microsoft.Live.Core.Namespace.using("wl:Microsoft.Live");
}
</script>


Finally, add a wl:signin and a templated wl:userinfo control which together make signing in and showing a little information about who is signed in very easy. Also, add the wl:bar control in the footer div.

The Site.Master page so far should look like this:


<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="WindowsLiveWebApplication.SiteMaster" %>

<%@ Import Namespace="System.Web.Configuration" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:wl="http://apis.live.net/js/2010">
<head id="Head1" runat="server">
<script type="text/javascript" src="http://js.live.net/4.1/loader.js"></script>
<title></title>
<link href="~/Styles/Site.css" rel="stylesheet" type="text/css" />
<asp:ContentPlaceHolder ID="HeadContent" runat="server">
</asp:ContentPlaceHolder>
<script type="text/javascript">
function appLoaded() {
Microsoft.Live.Core.Namespace.using("wl:Microsoft.Live");
}
</script>
</head>
<body>
<wl:app
channel-url="<%=WebConfigurationManager.AppSettings["wl_wrap_channel_url"]%>"
callback-url="<%=WebConfigurationManager.AppSettings["wl_wrap_client_callback"]%>?<%=SessionId%>"
client-id="<%=WebConfigurationManager.AppSettings["wl_wrap_client_id"]%>"
scope="WL_Profiles.View, Messenger.SignIn, WL_Contacts.View"
onload="appLoaded">
</wl:app>

<form id="Form1" runat="server">
<div class="page">
<div class="header">
<div class="title">
<h1>
My Windows Live Web Application
</h1>
</div>
<div class="loginDisplay">
<wl:signin id="mysignin" style='float:right'></wl:signin>
<!-- Display user first name after signed in -->
<wl:userinfo>
<div id="userInfoTemplate" class="sys-template">
<div id='profileElement' class='LiveConnectProfile'>
<div class='LiveConnectProfileTextFrame' style='float:right; margin-top:-2px'>
<span id='profileName' class='LiveConnectProfileName' style='position:relative; color:white' sys:innertext="{binding profile.firstName, convert={{new Function('a'\, 'return a?\'Hello \'+a:\'\'')}} }">
</span>
</div>
</div>
</div>
</wl:userinfo>
</div>
<div class="clear hideSkiplink">
<asp:Menu ID="NavigationMenu" runat="server" CssClass="menu" EnableViewState="false"
IncludeStyleBlock="false" Orientation="Horizontal">
<Items>
<asp:MenuItem NavigateUrl="~/Default.aspx" Text="Home" />
<asp:MenuItem NavigateUrl="~/About.aspx" Text="About" />
</Items>
</asp:Menu>
</div>
</div>
<div class="main">
<asp:ContentPlaceHolder ID="MainContent" runat="server" />
</div>
<div class="clear">
</div>
</div>
<div class="footer">
<wl:bar></wl:bar>
</div>
</form>
</body>
</html>


Testing SignIn

To run the application press F5 or CTRL + F5 (no debug). The Visual Studio ASP.NET Development Server is used to serve up the page.

Windows Live Messenger Connect SignIn Page
SignIn Page Before Signing In

Notice the messenger bar widget at the bottom of the page. When you sign in it becomes active and provides useful Messenger features like chatting in the window. When you sign in the page should change UI to reflect your information.

Windows Live Messenger Connect - After Sign In
SignIn Page After Signing In

Creating a Page That Displays Your Contacts

In the previous sections, if all went well, you got a successful sign in to Windows Live. In this section, we show a simple approach for listing contacts of the user that signs in. This approach could be extended easily to include methods for importing and exporting contacts or a dashboard for managing contacts.

What we show here is similar to what is discussed in this video that shows working with contacts. The video walks through a sample project given at the http://code.msdn.microsoft.com/messengerconnect site.

1. Create a new web form (page) called contacts.aspx that derives from the Site master page. We are going to using the Microsoft Ajax preview library (to use the DataView control part of preview releases of Microsoft Ajax Library) and the jQuery library. Add the following to the header content div:


<script type="text/javascript" src="http://ajax.microsoft.com/ajax/beta/0911/start.debug.js"></script>
<script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.min.js" ></script>


2. The next step is think about the programming pattern a bit. The Messenger Connect JavaScript API is designed so that you structure your data requests as asynchronous operations in a sort of cascade-fashion. For example, the wl:signin control we put on the page will call a SignedInCallback handler on sign-in which kicks off a cascade of subsequent calls (the full code sample below contains all the code). The cascade of function calls looks like this:


function SignedInCallback() { contactsLoaded();}
function contactsLoaded() { populateTable(); }
function populateTable() { contactProfile(); //populate the table with basic contact info }
function contactProfile() { //get specific info for each contact}


What's tricky about this is getting the right information to the UI at the right time. In the contactProfile function the particular piece of data returns and must be assigned to a named UI element after the table has already been rendered. (There is probably a better way of structuring this code, but on first pass this code seems to be what the API suggests we do.)

3. In this example, we decided to try and stick to using all client script instead of resorting a server-side control. We loaded the Microsoft Ajax library previously to use a DataView control to display data. The markup for the DataView looks like this:


<tbody id="contactList" class="sys-template">
<tr>
<td>{{firstName}} {{lastName}}</td>
<td>{{location}}</td>
<td><div sys:if="{{cid}}">
<img sys:if="{{thumbnailImageLink}}" width="40px" sys:src="{{thumbnailImageLink}}" />
<div sys:if="{{cid}}">says </div>
<span sys:id="{{cid}}" class="emp"></span>
</div>
</td>
</tr>
</tbody>


The items in the {{ }} will be determined in the populateTable function.

4. Contacts (collection) are loaded using a View which helps show only a specified number of items from a collection at any given time. You can then advance or go back through the collection and the JavaScript API will handle to queries to Windows Live to get the data. It's an easy way to move through data.


function contactsLoaded(dataLoadCompletedEventArgs) {
contactsCollection = dataLoadCompletedEventArgs.get_data();
contactsView = contactsCollection.get_view(); // get the view
contactsView.set_pageSize(pageSize); // set the page size
populateTable(contactsView); // pass the view to the function that iterates through and displays contacts
}


5. Finally, when you put it all together and sign in you get something like the screen shot shown below.

Windows Live Messenger Connect Contacts

The full code sample is shown below.


<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Contacts.aspx.cs" Inherits="WindowsLiveWebApplication.Contacts" %>

<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">
<script type="text/javascript" src="http://ajax.microsoft.com/ajax/beta/0911/start.debug.js"></script>
<script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.min.js" ></script>
<script type="text/javascript">
var dvContactList;
var dataContext;
var contactsView;
var totalNumContacts;
var pageSize = 5;
jQuery.noConflict();

Microsoft.Live.Core.Loader.onReady(function () {
Sys.require([Sys.components.dataView], function () {
dvContactList = $create(Sys.UI.DataView, {}, {}, {}, $get('contactList'));
});
});

function SignedOutCallback() {
// Clear the DataView
dvContactList.set_data([]);
$get('contactTable').className = 'hidden';
$get('prevButton').className = 'hidden';
$get('nextButton').className = 'hidden';

}
function SignedInCallback(signInCompletedEventArgs) {
if (signInCompletedEventArgs.get_resultCode() !== Microsoft.Live.AsyncResultCode.success) {
alert("Failed to sign in: " + signInCompletedEventArgs.get_resultCode());
return;
}
dataContext = Microsoft.Live.App.get_dataContext();
dataContext.loadAll(Microsoft.Live.DataType.contacts, contactsLoaded);
}

function contactsLoaded(dataLoadCompletedEventArgs) {
if (dataLoadCompletedEventArgs.get_resultCode() !== Microsoft.Live.AsyncResultCode.success) {
alert("Contacts failed to load...!");
return;
}

contactsCollection = dataLoadCompletedEventArgs.get_data();
totalNumContacts = contactsCollection.items.length; // not used, but could be
contactsView = contactsCollection.get_view();
contactsView.set_pageSize(pageSize);

setButtons(contactsView);
$get('contactTable').className = 'visible';
$get('prevButton').className = 'visible';
$get('nextButton').className = 'visible';
populateTable(contactsView);
}

function setButtons(contactsView) {
contactsView.hasNextPage(function (hasNext) {
if (!hasNext) {
$get('nextButton').disabled = true;
}
else {
$get('nextButton').disabled = false;
}
});
if (!contactsView.hasPreviousPage()) {
$get('prevButton').disabled = true;
}
else {
$get('prevButton').disabled = false;
}
}

function populateTable(contactsView) {
var contactsJSON = [];
for (var i = 0; i < contactsView.get_data().length; i++) {
var contact = contactsView.get_data()[i];
var windowsLiveUser = (contact.get_isFriend() === undefined) ? false : true;
var cid = (contact.get_cid() === undefined) ? null : contact.get_cid();
var thumbnailImageLink = (contact.get_thumbnailImageLink() === undefined) ? null : contact.get_thumbnailImageLink();
contactsJSON.push({ firstName: contact.get_firstName(),
lastName: contact.get_lastName(),
location: (((contact.get_locations()[0]) === undefined) ? "" : (contact.get_locations()[0]).get_city()),
windowsLiveUser: windowsLiveUser,
cid: cid,
thumbnailImageLink: thumbnailImageLink
});
if (windowsLiveUser) {
contact.loadProfiles(contactProfile);
}

}
dvContactList.set_data(contactsJSON);
}

function contactProfile(dataLoadCompletedEventArgs) {
if (dataLoadCompletedEventArgs.get_resultCode() !== Microsoft.Live.AsyncResultCode.success) {
alert("Profile for contact failed to load...!");
return;
}
var profileCollection = dataLoadCompletedEventArgs.get_data();
var cid = profileCollection.get_profile().get_cid()
var statusMessageLink = profileCollection.items[0].get_statusMessageLink();
var statusText = "";
if (statusMessageLink !== undefined) {
var statusCollection = new Microsoft.Live.Services.StatusCollection(statusMessageLink);
statusCollection.load(function (dataLoadCompletedEventArgs) {
if (dataLoadCompletedEventArgs.get_resultCode() !== Microsoft.Live.AsyncResultCode.success) {
log("Couldn't load status.");
return;
}
else {
var statusCollection = dataLoadCompletedEventArgs.get_data();
var statusMessage = statusCollection.get_statusMessage();
if (statusMessage) {
statusText = statusMessage.get_statusText();
jQuery('#' + cid.toLowerCase()).text(statusText);
}
}
});
}
}

function getPage(direction) {
switch (direction) {
case 'next':
contactsView.nextPage(function (evtArgs) {
if (evtArgs.get_resultCode() === Microsoft.Live.AsyncResultCode.success) {
populateTable(contactsView);
}
});
break;
case 'prev':
if (contactsView.hasPreviousPage()) {
contactsView.previousPage();
populateTable(contactsView);
}
break;
}
setButtons(contactsView);
}

</script>
<style type="text/css">
table thead tr td
{
font-weight: bold;
}
table tr td { text-align: center; width: 250px; height: 40px;}
table.hidden, input.hidden
{
display: none;
}
table.visible, input.visible
{
display:inline;
}
span.emp { font-style: italic}
</style>

</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h1>
Contacts DataView</h1>
<wl:signin onsignin="SignedInCallback" onsignout="SignedOutCallback" signedouttext="Sign In To View Contacts">
</wl:signin>
<input id="prevButton" type="button" onclick="getPage('prev')" value="Prev Page" class="hidden" />
<input id="nextButton" type="button" onclick="getPage('next')" value="Next Page" class="hidden"/>


<table id="contactTable" class="hidden">
<thead>
<tr>
<td>First/Last Name</td>
<td>City</td>
<td>Windows Live User?</td>
</tr>
</thead>
<tbody id="contactList" class="sys-template">
<tr>
<td>{{firstName}} {{lastName}}</td>
<td>{{location}}</td>
<td><div sys:if="{{cid}}">
<img sys:if="{{thumbnailImageLink}}" width="40px" sys:src="{{thumbnailImageLink}}" />
<div sys:if="{{cid}}">says </div>
<span sys:id="{{cid}}" class="emp"></span>
</div>
</td>
</tr>
</tbody>
</table>

</asp:Content>

7 comments:

  1. Hello,
    I saw your interesting article, I'm using Windows Live ID on my site but sometimes for some account I have issues.
    Maybe you can help, 2 accounts and always the same when they use the sign in button and when the script calls profile.get_cid() returns undefined
    I'm not able to reproduce that with my windows different live ids.
    Any idea why .get_cid() could return undefined?
    I do not find any reasons

    ReplyDelete
  2. Is the account you are signing in with returning get_cid() undefined or another account this is a contact of the account you signed in with?

    Two possible troubleshooting steps:

    1. Go to http://rex.mslivelabs.com, sign in as you would on your test page and either check the profile (ProfilesLink) of the account you signed in with or check the contacts (ContactsLink) and cid values of contact. If you can see data in REX I think you can then see it in your application (if it hasn't changed since I looked at this).

    2. I'm not sure but there could be issues with permissions of the accounts you are having problems with. Log into home.live.com and find the permissions settings of the 2 accounts in question and see if they are overly restrictive. Perhaps the settings of those accounts are restrictive so that nothing is visible? Navigate to:
    Account Name > Options > Privacy > Advanced.

    Let me know.

    ReplyDelete
  3. Thanks a lot for you answer.
    I also think that's related to permissions settings.

    I will ask if those 2 persons can try http://rex.mslivelabs.com and see if they can sign there

    ReplyDelete
  4. hi TravelMarx ;
    How i can get or capture email address like "xxxx@live.com" for the signed user in javascript variable .. ??

    and in the example you mentioned "how i can display emails in contact Profile example in same DataView"

    thhank you

    ReplyDelete
  5. Go to this page: http://msdn.microsoft.com/en-us/library/ff748764.aspx and look at how the profile (of the logged in user) obtained. This is just one of several ways of doing it.

    Using what the link given above suggests, you can do something like this:

    1. In the SignedInCallback function. Put a statement like this:

    dataContext.loadAll(Microsoft.Live.DataType.profile, profilesLoaded);

    2. Create a profilesLoaded function like this (left out error handling):

    function profilesLoaded(dataLoadCompletedEventArgs) {
    var profileCollection = dataLoadCompletedEventArgs.get_data();
    var profile = profileCollection.get_item(0);
    if (profile) {
    alert(profile.get_emails()[0].get_address());
    }
    }

    We get the email through the profile data of the current logged in user. The profile type is the Microsoft.Live.Service.Profile type which is found here: http://msdn.microsoft.com/en-us/library/ff749610.aspx.

    Hope this helps.

    ReplyDelete
  6. Thanks TravelMarx for your fast response , when i am try to use
    profile.get_emails()[0].get_address()
    its didn't work ? Why ?

    ReplyDelete
  7. Are you signing in okay? Before you see your email you need to log in. The code I gave in the last comment worked for me.

    You can try these manual steps to verify data you can see:

    1. Go to http://rex.mslivelabs.com/
    2. Sign in.
    3. Click the ProfilesLink.
    4. On the next page, click the ProfilesLink again.
    5. You should end accessing this resource: http://apis.live.net/V4.1/cid-####/Profiles where #### is unique your sign in.
    6. You should your email info as part of the profile.

    ReplyDelete