Home Interviews Developers Our View Blogs We Like The List

This Blog

Syndication

Quick Apps

LiveSide - Developer Blog

November 2007 - Posts

  • An easy way to add friends - Invite2Messenger

    Today, Microsoft Netherlands released Invite2Messenger which allows new Windows Live Messenger users to send out invitations to their contacts from Hotmail, Outlook, Outlook Express, Yahoo! Mail and Gmail. Existing Messenger users can also use the website if they have a lot of contacts which aren’t yet added to their buddy list.

    The site with a couple of simple steps:

    1. First you can choose which type of contacts to load into the system. Hotmail contacts are loaded via the Contacts API, while you have to upload a CSV file to load Outlook, Yahoo! mail and Gmail contacts.
    2. Next, you can select the contacts you want to invite. Contacts which have been invited previously will be marked by a green check mark.
    3. Finally, you can preview the invitation e-mail before sending it out to the new contacts you want to invite.

    Hotmail currently contain a similar concept, however Hotmail allows you to import contacts to your Hotmail address book, while this system allows you to invite people to add you to Messenger. While Invite2Messenger is currently targeted at the Dutch market, we might see it emerge in other markets as well.

  • Windows Live Tools and Visual Studio 2008 RTM

    Kevin over at http://dev.live.com has posted about a slight tweak that's needed for using the Windows Live Tools with the recently released Visual Studio 2008.

    Known Issue: When using the current Windows Live Tools release with the Visual Studio 2008 final release,  you will need to make changes to the web.config file for any project built using the “ASP.NET Windows Live Web Site” template. Please be sure to update the version of the System.Data.DataSetExtensions assembly in web.config file to 3.5.0.0.

    Has anyone started using the Live Tools? If so, how are you finding it?

    Thanks to Ian for the tip.

    Update: Angus has posted that the Windows Live Tools has been refreshed for the Visual Studio 2008 RTM which should mean that the above tip will now be unnecessary. So make sure you go to connect and get the updated set of tools. 

    SL

  • Windows Live Spaces Photos API

    livespaces_logo Last week I mentioned about the Windows Live Tools for Visual Studio that came up for download on Connect, well this week, I will be mentioning the Windows Live Spaces Photos API (alpha), which has also just come up for download under the same Connection on Connect.

    Angus Logan briefly mentioned it on his blog last week in a video interview that he did, which certainly got my attention, and it appears it is now readily available.

    The download itself is simply a CHM file with all the documentation and code examples in it, so expect my normal "How To" guide on how to use it. So get cracking, download the documentation and let us know if you plan to use it.

    SL

  • Windows Live Quick Apps – Contoso Bicycle Club Part 3

    BikeClubFull

    Welcome back to the third part of our deep dive in the Windows Live Quick Apps Website featuring the Contoso Bicycle Club. In this part we’ll take apart one of the main JavaScript function calls that this web site uses, updatePage.

    In order to display the main panel you have a few of options. You can click on any of the links in the Events user control, any of the links in the Rides user control or use the links from the Rides or Events dropdown menu.

    All of these links make a call to a javascript function updatePage that we’ve touched on in previous parts of this deep dive. Lets take a closer look at this function to see exactly what it does :-

    function updatePage(title, feed, item, cid, album) {

    hideRideMenu();

    hideEventsMenu();

    hideDiv($('SlideShowPanel'));

    hideDiv($('DirectionsPanel'));

    hideDiv( $('MainPanel'));

    hideVideo();

    showDiv($('TextPanel'), 500);

    showDiv($('MenuPanel'), 500);

    showDiv($('MapPanel'), 99999);

    CID = cid;

    var titleDiv = $('Title');

    titleDiv.innerHTML = title;

    ALBUM = album;

    updateText(feed, item);

    // Load the collection

    loadCollection(cid);

    if(album == "")

    disableAnchor($('PhotosFromRideLink'), true);

    else

    {

    if($('PhotosFromRideLink').disabled)

    disableAnchor($('PhotosFromRideLink'), false);

    }

    if(cid == "")

    {

    disableAnchor($('MapLink'), true);

    disableAnchor($('DirectionsLink'), true);

    disableAnchor($('ViewRouteLink'), true);

    disableAnchor($('BikeCamLink'), true);

    }

    else

    {

    if($('MapLink').disabled)

    disableAnchor($('MapLink'), false);

    if($('DirectionsLink').disabled)

    disableAnchor($('DirectionsLink'), false);

    if($('ViewRouteLink').disabled)

    disableAnchor($('ViewRouteLink'), false);

    if($('BikeCamLink').disabled)

    disableAnchor($('BikeCamLink'), false);

    }

    // Hard coded link for London Bike Cam demo

    switch(title.toLowerCase())

    {

    case "london river thames":

    if($('BikeCamLink').disabled)

    disableAnchor($('BikeCamLink'), false);

    break;

    default:

    disableAnchor($('BikeCamLink'), true);

    break;

    }

    }

    The first few function calls simply cleans up the screen a bit should it need to. The first two function calls hide the divs that contain the drop down menus for Rides and Events found at the top of the screen. The next three statements also hide regions defined in the default.aspx for for display of various media.

    If you’re not used to Asp.Net Ajax, the function call may look a little strange as it uses some shorthand notation :-

    hideDiv($('DirectionsPanel'));

    The $(‘element name’) notation is just shorthand for document.getElementById(‘element name’);

    The next function call hides the video panel should it be open :-

    function hideVideo() {

    var div = $('VideoWrapper');

    if (div.style.visibility == 'visible') {

    document.getElementById('VideoControl').content.findName('VideoWindow').pause();

    PAUSED = true;

    hideDiv(div);

    deleteCyclist();

    }

    }

    As you can see, if the panel is displayed, first off it pauses the video, then hides the panel. Finally it calls a function that deletes the cyclist icon from the map. The cyclist icon is in reality just a custom pushpin. We’ll take a closer look at this later.

    Back to the updatePage function. Next we put in some calls to display various panels, the main map panel, a text panel that will hold the blog description and a menu panel giving the user some options to choose from.

    Next we set the Title of the main portion of the page to the Title of the blog (see the previous article in this deep dive serious for an explanation of the input parameters to the updatePage function call).

    TitleDiv

    The next function call that we make displays the description from our blog into the text panel that we’ve just displayed :-

    function updateText(feed, item)

    {

    var textPanel = $('TextPanel');

    loadHTML( 'Item.aspx?feed=' + feed + '&item=' + item, textPanel)

    }

    As you may recall, the parameter feed is the URL to the RSS feed of the blog post and item is a counter to basically give each post its own ID. In this context it tells the updateText routine which blog post we want from the RSS feed.

    The first statement simply gets a reference to the TextPanel div itself. The next function call is slightly more involved. It places a call to the loadHTML function which is found in the ajah.js file in the /js subdirectory of the web site :-

    function loadHTML( url, div) {

    var xmlHttpRequest = createXMLHttpRequest();

    var handler = function() {

    if (xmlHttpRequest.readyState==4) {

    if (xmlHttpRequest.status==200) {

    div.innerHTML = xmlHttpRequest.responseText;

    } else {

    alert('Error - LoadHTML failed.');

    }

    }

    }

    xmlHttpRequest.onreadystatechange = handler;

    xmlHttpRequest.open("GET", url, true);

    xmlHttpRequest.send(null);

    }

    We pass in the url of the html we wish to access and the panel that it should be displayed in.

    First off here we create an xmlHttpRequest option. Due to different browsers handling this in different ways a function has been written to get the appropriate xmlHttpRequest option for the browser that the user is currently using :-

    function createXMLHttpRequest() {

    try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}

    try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}

    try { return new XMLHttpRequest(); } catch (e) {}

    alert("This browser does not support Ajax");

    return null;

    }

    Next we create a prototype javascript function that will be called whenever the xmlHttpRequest object changes it’s state.

    As you no doubt know, the xmlHttpRequest object allows you to place out of band asynchronous server side calls. Because of the asynchronous nature of it, you have to give it a method that it returns (think of this like a delegate in standard .Net development). What we’re saying here is that every time the state has changed in the ajax call, return to and run the handler prototype that we’ve setup.

    The readyState can have one of 5 values :-

    0 (Uninitialized)

     

     

    The object has been created, but not initialized (the open method has not been called).

     

     

    1 (Open)

     

     

    The object has been created, but the send method has not been called.

     

     

    2 (Sent)

     

     

    The send method has been called, but the status and headers are not yet available.

     

     

    3 (Receiving)

     

     

    Some data has been received.

     

     

    4 (Loaded)

     

     

    All the data has been received, and is available.

     

     

    All the data has been received, and is available.

    What we’re interested in is when readystate returns the value 4, which basically means the ajax call has completed.

    Next we check that status value, this is basically the standard html status codes :-

    Number

     

     

    Description

     

     

    100

     

     

    Continue

     

     

    101

     

     

    Switching protocols

     

     

    200

     

     

    OK

     

     

    201

     

     

    Created

     

     

    202

     

     

    Accepted

     

     

    203

     

     

    Non-Authoritative Information

     

     

    204

     

     

    No Content

     

     

    205

     

     

    Reset Content

     

     

    206

     

     

    Partial Content

     

     

    300

     

     

    Multiple Choices

     

     

    301

     

     

    Moved Permanently

     

     

    302

     

     

    Found

     

     

    303

     

     

    See Other

     

     

    304

     

     

    Not Modified

     

     

    305

     

     

    Use Proxy

     

     

    307

     

     

    Temporary Redirect

     

     

    400

     

     

    Bad Request

     

     

    401

     

     

    Unauthorized

     

     

    402

     

     

    Payment Required

     

     

    403

     

     

    Forbidden

     

     

    404

     

     

    Not Found

     

     

    405

     

     

    Method Not Allowed

     

     

    406

     

     

    Not Acceptable

     

     

    407

     

     

    Proxy Authentication Required

     

     

    408

     

     

    Request Timeout

     

     

    409

     

     

    Conflict

     

     

    410

     

     

    Gone

     

     

    411

     

     

    Length Required

     

     

    412

     

     

    Precondition Failed

     

     

    413

     

     

    Request Entity Too Large

     

     

    414

     

     

    Request-URI Too Long

     

     

    415

     

     

    Unsupported Media Type

     

     

    416

     

     

    Requested Range Not Suitable

     

     

    417

     

     

    Expectation Failed

     

     

    500

     

     

    Internal Server Error

     

     

    501

     

     

    Not Implemented

     

     

    502

     

     

    Bad Gateway

     

     

    503

     

     

    Service Unavailable

     

     

    504

     

     

    Gateway Timeout

     

     

    505

     

     

    HTTP Version Not Supported

     

     

    As you can see, we’re checking for the value 200 which means that everything is OK and no errors were encountered.

    Finally in our prototype we fill the div (panel) that we passed into the main function with the value returned by our ajax call.

    The final three lines of the main function call just place the ajax call.

    It is interesting to note here that this is a manual Ajax call and it doesn’t use the Asp.Net Ajax framework to place the call by using the createCallback and createDelegate functions built into Asp.Net Ajax.

    All of the above, as mentioned, takes our blog entry and pastes the html into the text panel that we showed on the main page.

    TextPanel

    Back in our updatePage method, the next call we place is to load the map collection that we previously stored (see prevous article for explanation of map collections).

    // Load the collection

    loadCollection(cid);

    We pass across the collection id that is unique to our map collection and passed into the updatePage method.

    function loadCollection(cid)

    {

    // Delete any pre existing shapes from the layer

    COLLECTION_LAYER.DeleteAllShapes();

    // Create the new layer with the Collection

    var veLayerSpec = new VEShapeSourceSpecification(VEDataType.VECollection, cid, COLLECTION_LAYER);

    // Import the layer

    MAP.ImportShapeLayerData(veLayerSpec, onFeedLoad, true);

    }

    The first statement uses a virtual earth collection layer. This is defined further up in the javascript :-

    // The layer used for the maps.live.com collection

    var COLLECTION_LAYER = new VEShapeLayer();

    You can think of a VEShapeLayer as a layer in a paint program. It allows you to draw on it without affecting the original underneath. In the case of Virtual Earth maps, it allows you to add polylines, polygons and pushpins to the underlying map.

    The first thing we do here however is call one of the built in methods to erase any shapes that may have been previously created on this layer.

    Next we define the layer to hold the map collection that we want. We’re telling it that we want collection, the unique id of the collection that we want to define as a layer and the actual layer itself to which this data should be added.

    Finally we merge our map collection data layer with the map itself. Once the data has been retrieved and merged we make a call to the onFeedLoad function :-

    function onFeedLoad(feed)

    {

    var count = COLLECTION_LAYER.GetShapeCount();

    for(var i=0; i < count; ++i)

    {

    var shape = COLLECTION_LAYER.GetShapeByIndex(i);

    shape.SetCustomIcon("<img src='images/directions_pin.png'/>");

    }

    getDirections();

    }

    MapRoute

    The layer that we’re returning here is basically a collection of pushpin objects. In the onFeedLoad function we’re iterating through all the pushpins and swapping out the default pushpin icon with our own one.

    Finally we place a call to the getDirections function :-

    function getDirections()

    {

    // Get the directional panel

    var directionsPanel = $('DirectionsPanel');

    // define the HTML string

    var directionsHtml = '';

    // Setup the distance

    var totalDistance = 0.0;

    // Fetch the shape cound.

    var count = COLLECTION_LAYER.GetShapeCount();

    var MAP_ITEM_TITLE = 'Click to see point on map.';

    // Enumerate the shapes

    for (var i = 0 ; i < count ; i++) {

    // Get the shape

    var shape = COLLECTION_LAYER.GetShapeByIndex(i);

    // Setup distance variable

    var distance;

    // If the first item

    if(i == 0)

    distance = 0.0;

    else

    distance = CalculateDistance(COLLECTION_LAYER.GetShapeByIndex(i-1).Latitude,COLLECTION_LAYER.GetShapeByIndex(i-1).Longitude,shape.Latitude, shape.Longitude);

    // Add to the total distance

    totalDistance = totalDistance + distance;

    // build the directions HTML (is persisted throughout the enumeration)

    directionsHtml += '<div class="direction">' +

    ' <div class="distance">' + distance.toFixed(2) + ' miles</div>' +

    ' <a title="' + MAP_ITEM_TITLE + '" href="BLOCKED SCRIPTzoom(' + shape.Latitude + ',' + shape.Longitude + ')">' + shape.GetTitle() + '</a>' +

    '</div>';

    }

    // Add the totals

    directionsHtml += '<div class="DirectionsTotal direction heavy">' +

    ' <div class="distance heavy">' + totalDistance.toFixed(2) + ' miles</div>' +

    ' Total Distance' +

    '</div>';

    // Add the header and add the distance to the header

    directionsHtml = '<h2>Directions (' + totalDistance.toFixed(2) + ' miles)</h2>' + directionsHtml;

    // Output

    directionsPanel.innerHTML = directionsHtml;

    }

    First off we get a reference to the directions panel (div) defined in the default.aspx page then we define a couple of variables, one that will hold the actual directions and the next to hold the total distance between each pushpin in our layer.

    Next we make a function call to get the total number of pushpins that we have added to the map layer and we round out the setting up of this function call by defining a title that we can display.

    The next thing we do here is to iterate through all of pushpins we have defined in our map layer one by one.

    Wihtin the loop, we get a reference to the actual pushpin itself :-

    var shape = COLLECTION_LAYER.GetShapeByIndex(i);

    then we call a function call that calculates the distance between this pushpin and the previous one :-

    function CalculateDistance(startLatitude, startLongitude, endLatitude, endLongitude)

    {

    var R = 3959; // miles

    // Get the Latitude differential

    var dLat = (endLatitude-startLatitude).toRad();

    // Get the Longitude differential

    var dLon = (endLongitude-startLongitude).toRad();

    ///TODO - add comments.

    var a = Math.sin(dLat/2) * Math.sin(dLat/2) +

    Math.cos(startLatitude.toRad()) * Math.cos(endLatitude.toRad()) *

    Math.sin(dLon/2) * Math.sin(dLon/2);

    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

    var d = R * c;

    return d;

    }

    Because Virtual Earth deals with latitudes and longitudes and we deal with miles, all this function does is convert the difference between the two points into miles. It’s worth taking note of this routine as it can be very handy in your applications.

    We take this value returned and add it to our total miles variable.

    Our next series of statements basically just builds a link for the current pushpin that we’re iterating against. If you click on a pushpin the map will zoom in to that point allowing you to see more detail.

    Once we iterate through each individual pushpin, we add another link to show the whole map layer. Basically this is a “zoom out” if you’ve zoomed in by clicking on an individual pushpin.

    MapRouteZoomed

    Finally we load all of this into our Directions panel.

    We are now back in our main updatePage function :-

    if(album == "")

    disableAnchor($('PhotosFromRideLink'), true);

    else

    {

    if($('PhotosFromRideLink').disabled)

    disableAnchor($('PhotosFromRideLink'), false);

    }

    if(cid == "")

    {

    disableAnchor($('MapLink'), true);

    disableAnchor($('DirectionsLink'), true);

    disableAnchor($('ViewRouteLink'), true);

    disableAnchor($('BikeCamLink'), true);

    }

    else

    {

    if($('MapLink').disabled)

    disableAnchor($('MapLink'), false);

    if($('DirectionsLink').disabled)

    disableAnchor($('DirectionsLink'), false);

    if($('ViewRouteLink').disabled)

    disableAnchor($('ViewRouteLink'), false);

    if($('BikeCamLink').disabled)

    disableAnchor($('BikeCamLink'), false);

    }

    // Hard coded link for London Bike Cam demo

    switch(title.toLowerCase())

    {

    case "london river thames":

    if($('BikeCamLink').disabled)

    disableAnchor($('BikeCamLink'), false);

    break;

    default:

    disableAnchor($('BikeCamLink'), true);

    break;

    }

    Here we are dealing with the MenuPanel and we’re simply going over which of the 6 options that are displayed in the panel.

    MenuPanel

    Firstly we’re checking to see if there’s a photo album associated with the blog post. If there is then activate the “Photos from Ride” menu button, otherwise disable it.

    Next we check whether there is an actual map collection associated with the blog post. The reason for this is that some items listed in the Events user control (which links to the Events RSS feed) may not have been defined yet and therefore will not have a map associated with them. If a map does indeed exist then enable all the buttons that pertain to the map (View Route, Map, Directions and Bike Cam) otherwise disable these buttons.

    One of the rides (London River Thames) has a video associated it. The next series of statements checks to see if we are displaying the details for this blog post, if so then we enable the Bike CAM button, otherwise disable it.

    And that’s all there is to it. The updatePage javascript function is basically the main routine for this application which sets up and displays the main portion of the default.aspx but as you’ve seen, it’s fairly straight forward.

     

  • Windows Live Quick Apps – Contoso Bicycle Club Part 2

    LatestRides

    Welcome back to the second part of our deep dive in the Windows Live Quick Apps Website featuring the Contoso Bicycle Club. In this part we’ll take a look at another custom user control found on the home page, the Latest Rides feed.

    If you followed the first part of this deep dive then you will find a lot of things very similar with this control :-

    <%@ Control Language="C#" ClassName="LatestRidesControl" %>

    <div id="latestRidesContent">

    <script runat="server">

    int n = 1;

    </script>

    <!-- setup the XML Datasource - this will query the RSS feed directly -->

    <asp:XmlDataSource ID="latestRidesDataSource" runat="server" EnableCaching="false"

    DataFile="<%$ AppSettings:LatestRidesFeed %>" XPath="/rss/channel/item"></asp:XmlDataSource>

    <table>

    <asp:DataList ID="latestRidesDataList" runat="server" DataSourceID="latestRidesDataSource">

    <ItemTemplate>

    <tr class="latestRideItem">

    <td valign="top" class="left">

    <a href="BLOCKED SCRIPTshowDiv($('contentPanel'), 100);updatePage('<%# XPath("title") %>','<%= latestRidesDataSource.DataFile %>', <%= n%> ,'<%# HtmlProcessor.ExtractMapCid(XPath("description").ToString()) %>','<%# HtmlProcessor.ExtractPhotoAlbumFeed(XPath("description").ToString()) %>')">

    <img width="50" height="50" alt="<%# XPath("title") %>" src="<%# HtmlProcessor.ExtractImageUrl(XPath("description").ToString()) %>" />

    </a>

    </td>

    <td class="right">

    <div class="title">

    <a href="BLOCKED SCRIPTshowDiv($('contentPanel'), 100);updatePage('<%# XPath("title") %>','<%= latestRidesDataSource.DataFile %>', <%= n++ %> ,'<%# HtmlProcessor.ExtractMapCid(XPath("description").ToString()) %>','<%# HtmlProcessor.ExtractPhotoAlbumFeed(XPath("description").ToString()) %>')">

    <%# XPath("title") %>

    </a>

    </div>

    <div class="description">

    <%# HtmlProcessor.FirstLine(XPath("description").ToString()) %></div>

    </td>

    </tr>

    </ItemTemplate>

    </asp:DataList>

    </table>

    </div>

    So let’s start at the top. The first thing you’ll see is a counter has been setup and initialized to 1. This serves the same purpose as the counter found in Events Control and simply gives each entry its own unique ID.

    Next we set up the XML DataSource. This time however we’re point to a different entry in the web config file :-

    <add key="LatestRidesFeed" value="http://contosobicycleclub.spaces.live.com/category/rides/feed.rss"/>

    In the Event Control we pointed to the RSS feed gained from the Events category in the CBC Windows Live Spaces site, this time we’re pointing to the Rides feed. This feed will be used in more than one place in the sample web site but we’ll talk about it again when we get to that point in our deep dive. The XPath expression for our DataSource is the same as the one used the DataSource control in the Events Control. This is because the feed that is being distributed is based on a standard. You will find the same xml markup tags in every feed produced by Windows Live Spaces. This makes our jobs as developers easier.

    XPath="/rss/channel/item"

    Here we are saying, from the root of the received feed, we are only interested in the xml that is encapsulated in the Item tag. The Item tag is found within the Channel tag which itself is found in the RSS tag under the root. The easy way to think about this is a folder hierarchy as you might find on your computer. An equivalent would be C:/Windows/System32. Here we’re looking for the contents of the System32 folder which is found under the Windows folder which is found under the root of the C: drive.

    XPath expressions were designed this way so as to be easy to use and understand.

    Next we start a table. This is being used purely for layout purposes as this control is slightly more complex than the Event Control and displays more data.

    Within the table tag we start our repeating section using the DataList control. The DataList control gets its data from the XmlDataSource that we defined above.

    We define a table row and a couple of table cells. As mentioned this is purely for presentation purposes. Within the first table cell we display an image.

    <a href=" BLOCKED SCRIPTshowDiv($('contentPanel'), 100);updatePage('<%# XPath("title") %>','<%= latestRidesDataSource.DataFile %>', <%= n%> ,'<%# HtmlProcessor.ExtractMapCid(XPath("description").ToString()) %>','<%# HtmlProcessor.ExtractPhotoAlbumFeed(XPath("description").ToString()) %>')">

    <img width="50" height="50" alt="<%# XPath("title") %>" src="<%# HtmlProcessor.ExtractImageUrl(XPath("description").ToString()) %>" />

    </a>

    Don’t worry, it looks way more complicated than it actually is.

    First, we define a link and rather than the link taking us to new page or section we’re running some custom Javascript inside it. The first statement simply calls a Javascript function that un-hides a DIV section that is defined in the default.aspx page.

    function showDiv(div, zIndex) {

    div.style.visibility = 'visible';

    div.style.zIndex = zIndex;

    // Work around for Bugzilla Bug 187435 for Firefox on Mac

    if(div.id.toLowerCase() == "directionspanel" || div.id.toLowerCase() == "textpanel" || div.id.toLowerCase() == "mainpanel")

    {

    div.style.overflow = "auto";

    }

    }

    As you can see from this javascript function, we simply take a reference to the div and show it with a z-index that is also passed in.

    The only thing slightly unusual about this is the call to this function :-

    BLOCKED SCRIPTshowDiv($('contentPanel'), 100);

    The javascript function showDiv is expecting a reference to the actual Div, not the name of the name of the Div as you may guess from contentPanel being enclosed in quotes. This is actually using a short cut notation that is found in Asp.Net Ajax. The actual expression is “$(‘contentPanel’)”. This is equivalent of saying :-

    Document.getElementById(‘contentPanel’)

    Which would be a reference to the Div itself and not just the name of the Div.

    Next we have a call to another JavaScript function. This function call is slightly more complex as it is a generic function for use by virtually every control on the page and therefore there are some parameters to it that are essentially optional, depending on where the function is being called from. So lets go through this step by step.

    The first parameter in the updatePage function call is title of the blog post :-

    <%# XPath("title") %>

    Remember that we have already extracted the xml down the item level. Each Item is a blog post itself. So here we’re simply saying, give me the contents of what’s found in the Title Xml element in the Item.

    The next parameter is the whole RSS feed itself :-

    '<%= latestRidesDataSource.DataFile %>'

    Next we pass in the value of the counter that was defined at the top of the user control. This essentially gives each repetition its own ID.

    The next call is to static member of a class file found in the Classes folder of the website :-

    <%# HtmlProcessor.ExtractMapCid(XPath("description").ToString()) %>

    We make a call to ExtractMapCid method, this returns an id for a virtual earth map collection that is taken from within the description of the blog post. What is this?

    If you open up http://maps.live.com, in the top right of the page you’ll see a dropdown for Collections. This was a feature added to virtual earth V5. You can create your own collections or browse other peoples collections that they have made public. Each collection has its own unique ID.

    Here is the code that we are calling :-

    public static string ExtractMapCid(string html)

    {

    // Create the RegEx (conditions are find anything (including URLs) which have cid={THECIDVALUE}

    Regex regex = new Regex(";cid=(.*?)&amp");

    // Execute a search/match against the regex

    Match match = regex.Match(html);

    if (match.Groups.Count > 1)

    return match.Groups[1].Value;

    else return "";

    }

    What we’re doing here is parsing through the contents of the Description XML tag looking for anything that matches “;cid=” and the cid value. This cid value is the unique ID given to collections within the maps.live.com application and extracting that value out to be passed into our updatePage javascript function.

    The final parameter for out updatePage function call makes a call to another static method found in the HtmlProcessor class file :-

    <%# HtmlProcessor.ExtractPhotoAlbumFeed(XPath("description").ToString()) %>

    This time (as the function name explains) we are looking for photoalbums.

    public static string ExtractPhotoAlbumFeed(string html)

    {

    // Create a regex to find a spaces photoalbum url.

    Regex regex = new Regex("http://(.*?).spaces.live.com/.*?PhotoAlbum.*?(cns!.*?)&");

    Match match = regex.Match(html);

    if (match.Groups.Count > 2)

    return string.Format("http://{0}.spaces.live.com/photos/{1}/feed.rss", match.Groups[1].Value, match.Groups[2].Value);

    else return "";

    }

    This is a snippet from one of the blog posts :-

    Finished ride near some really good places to eat at London Bridge.<br></span> </div>

    <div>

    <p><a href="http://maps.live.com/default.aspx?v=2&amp;cid=2BACE20A0AB578FB!180&amp;encType=1">Map</a>

    <p><a href="http://contosobicycleclub.spaces.live.com/?_c11_PhotoAlbum_spaHandler=TWljcm9zb2Z0LlNwYWNlcy5XZWIuUGFydHMuUGhvdG9BbGJ1bS5GdWxsTW9kZUNvbnRyb2xsZXI$&amp;_c11_PhotoAlbum_spaFolderID=cns!19C180FDFB1C7EFF!138&amp;_c11_PhotoAlbum_startingImageIndex=0&amp;_c11_PhotoAlbum_commentsExpand=0&amp;_c11_PhotoAlbum_addCommentExpand=0&amp;_c11_PhotoAlbum_addCommentFocus=0&amp;_c=PhotoAlbum">Photos</a></div><div>

    As you can see it’s basically HTML markup. Within here you will notice there is a link tag pointing to a photo album that matches our regular expression :-

    http://contosobicycleclub.spaces.live.com/?_c11_PhotoAlbum_spaHandler=TWljcm9zb2Z0LlNwYWNlcy5XZWIuUGFydHMuUGhvdG9BbGJ1bS5GdWxsTW9kZUNvbnRyb2xsZXI$&amp;_c11_PhotoAlbum_spaFolderID=cns!19C180FDFB1C7EFF!138&

    The regular expression splits this down into 3 separate groups. The first group is the whole string above, the second is initial value found after http:// as defined by :-

    http://(.*?)

    And the third group is the cns number as defined in the regular expression by :-

    (cns!.*?)

    What we are really looking for is the second and third values. These are plugged into the return string :-

    string.Format("http://{0}.spaces.live.com/photos/{1}/feed.rss",match.Groups[1].Value, match.Groups[2].Value);

    And so what you actually end up with is this url :-

    http://contosobicycleclub.spaces.live.com/photos/cns!19C180FDFB1C7EFF!138/feed.rss

    In the middle of the link (anchor tag) an image is placed :-

    <img width="50" height="50" alt="<%# XPath("title") %>" src="<%# HtmlProcessor.ExtractImageUrl(XPath("description").ToString()) %>" />

    Following the same logic we have thus far, the alt parameter is defined as the title of blog and src parameter point to what is returned from the call to ExtractImageUrl, another static function call found in the HtmlProcessor class :-

    public static string ExtractImageUrl(string html)

    {

    // Create a RegEx to find the Image

    Regex regex = new Regex("<img.*?src.*?=.*?\"(.*?)\"", RegexOptions.IgnoreCase);

    // Execute the search/match.

    Match match = regex.Match(html);

    if (match.Groups.Count > 1)

    return match.Groups[1].Value;

    else return "";

    }

    Once again we are using regular expression pattern matching to look for a specific value.

    If you take a look at the source for the RSS feed, look towards the end of the first <item> tag, just before all the comments and you’ll see the following (even if you just look at the rss feed itself in IE7, you’ll see a picture attached to the end of each post) :-

    img src="http://storage.live.com&amp;#47;items&amp;#47;19C180FDFB1C7EFF&amp;#33;131&amp;#58;thumbnail

    This is what we’re extracting out, the value for the source of this image tag.

    The contents of the second table cell are exactly the same as the content for the Event Control (see previous article for details). The only difference between the two is that the second and third parameters for the updatePage function call actually have values this time (as explained above for the first table cell).

    And there you have it, the Latest Rides user control from the Contoso web site. It’s almost identical to the Events user control, but this time we looked at a different feed and extracted out slightly more information from the description tag to enable us to display images (and also information that will be used by other controls on the page but isn’t really apparent in the display of the Latest Rides control).

    So let’s extract what we’ve learned here and build upon our very basic page from the previous article.

    Open up the website you created from the previous article and open the default.aspx page.

    What we’ll be doing is simply adding an image next to the title for each blog post and display the first image we find that is contained within the blog in this spot.

    So here is the default page from yesterday :-

    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

    <html xmlns="http://www.w3.org/1999/xhtml" >

    <head runat="server">

    <title>Untitled Page</title>

    </head>

    <body>

    <form id="form1" runat="server">

    <div>

    <asp:TextBox ID="Feed" runat="server" Columns="60" />

    <br />

    <asp:Button ID="btnSubmit" runat="server" OnClick="btnSubmit_Click" Text="Get Feed" />

    <br />

    <br />

    <asp:XmlDataSource ID="source" runat="server" XPath="/rss/channel/item" DataFile="http://msnwindowslive.spaces.live.com/feed.rss" />

    <asp:Repeater DataSourceID="source" ID="DisplayFeed" runat="server">

    <ItemTemplate>

    <div>

    <a href='<%# XPath("link") %>'>

    <%# XPath("title") %>

    </a>

    <hr />

    <%# GetFirstXCharacters(XPath("description").ToString(), 900)%>

    <br />

    <br />

    </div>

    </ItemTemplate>

    </asp:Repeater>

    </div>

    </form>

    </body>

    </html>

    Now all that we need to do is insert the following image tag just before the <%# XPath("title") %> that is found in the link :-

    <img width="50" height="50" alt="<%# XPath("title") %>" src="<%# ExtractPics(XPath("description").ToString()) %>" />

    Now go into your code behind and add the ExtractPics static method as so :-

    public static string ExtractPics(string description)

    {

    Regex regex = new Regex("<img.*?src.*?=.*?\"(.*?)\"", RegexOptions.IgnoreCase);

    Match match = regex.Match(description);

    if (match.Groups.Count > 1)

    return match.Groups[1].Value;

    else return "";

    }

    This is basically the same pattern matching expression that we discussed above in the article. And that’s all there is to it.

    In the next part of this deep dive we’ll be taking a look at the main body of the page which is really where the fun starts.