Creating a Silverlight 2 CLR object for Photobucket's API

Over the past few months, I have been working on a Silverlight kit for Photobucket's API.  I have used this sample to demonstrate a few things:

  1. How to build an API kit for a REST Library
  2. How to adapt existing .Net code to Silverlight (OAuth Library)
  3. How to build a unit test application for a Silverlight API Kit: see https://xmldocs.net/sketchbook/test.html for the current test suite.
  4. How to integrate Flash Video with Silverlight Applications: see https://xmldocs.net/sketchbook, search for a video and drag that onto the yellow canvas.

Now, I'd like to show how to create a CLR object that can expose the API so that designers can start using the API in Expression Blend without touching code.  To see the end results, take a look at this Sketchbook demo and see the listbox that shows a list of images, in this case using the Photobucket Get Featured Media API.

  1. Create a new class derived from INotifyPropertyChanged
  2. Add an enumerable property of type IEnumerable<>
  3. In the getter function of the property, implement the asynchronous call to the web service if the field backing the property is null
  4. In the setter function of the property, implement the property changed notification.
  5. Build the project so the class is in the assembly.
  6. For a ListBox, if you don't specify the DisplayMemberPath, the listbox will call toString() for each item.  To make things easier for the designer, I implemented toString() for my collection item (Media) to help the designer hook up the correct data binding.  I wrote it in a generic fashion so that it would be easy to reuse.  Please steal my GetProperties() function and use it for your generic toString() implementation for your data classes.

Here's what my classes looks like:

PhotobucketLibrary

 using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;

namespace Photobucket
{
    /// <summary>
    /// Photobucket library for CLR Binding
    /// </summary>
    public class PhotobucketLibrary : INotifyPropertyChanged
    {
        #region Member Data
        IEnumerable<Media> m_featuredMedia;
        #endregion

        #region Properties
        /// <summary>
        /// Get/set the featured media
        /// </summary>
        [Category("Media")]
        public IEnumerable<Media> FeaturedMedia
        {
            get
            {
                if (m_featuredMedia == null)
                {
                    API api = new API();

                    api.GetFeaturedMedia(new EventHandler<Photobucket.SearchCompletedEventArgs>(GotFeaturedMedia));
                }

                return m_featuredMedia;
            }
            set
            {
                m_featuredMedia = value;

                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("FeaturedMedia"));
                }
            }
        }
        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Event triggered when properties change
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Implementation
        void GotFeaturedMedia(object sender, SearchCompletedEventArgs args)
        {
            FeaturedMedia = args.PrimaryMedia;
        }
        #endregion
    }
}

Media

 using System;
using System.Text;

namespace Photobucket
{
    /// <summary>
    /// Photobucket Media
    /// </summary>
    public class Media
    {
        /// <summary>
        /// Media Description ID
        /// </summary>
        public string DescriptionId { get; set; }
        /// <summary>
        /// Media Name
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// Public
        /// </summary>
        public int Public { get; set; }
        /// <summary>
        /// Media Type
        /// </summary>
        public string MediaType { get; set; }
        /// <summary>
        /// User Name
        /// </summary>
        public string UserName { get; set; }
        /// <summary>
        /// Browse URL
        /// </summary>
        public Uri BrowseUrl { get; set; }
        /// <summary>
        /// Image URL
        /// </summary>
        public Uri Url { get; set; }
        /// <summary>
        /// Album URL
        /// </summary>
        public Uri AlbumUrl { get; set; }
        /// <summary>
        /// Thumbnail image
        /// </summary>
        public Uri Thumb { get; set; }
        /// <summary>
        /// Description
        /// </summary>
        public string Description { get; set; }
        /// <summary>
        /// Media Title
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// Upload date
        /// </summary>
        public DateTime UploadDate { get; set; }

        /// <summary>
        /// Output the property names and values
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return GetProperties(this);
        }

        static string GetProperties(object data)
        {
            var type = data.GetType();

            System.Reflection.PropertyInfo[] properties = type.GetProperties();

            StringBuilder builder = new StringBuilder(type.FullName);

            foreach (var property in properties)
            {
                builder.AppendLine();
                builder.AppendFormat(System.Globalization.CultureInfo.CurrentCulture, "{0}: {1}", property.Name, property.GetValue(data, null));
            }

            return builder.ToString();
        }
    }
}

Now that I have a class that exposes the property, I'd want to use it in Expression Blend 2.5 to add to a Silverlight 2 project. The class that I created is now part of the Silverlight assembly, so I can use it as a CLR Data Source:

  1. Add a ListBox to the page

  2. In the Project Tab, Data section, press the +CLR Object button

  3. Select the class created previously in Visual Studio (I selected PhotobucketLibrary) , give the data source a name (I typed Photobucket) and press OK.

    image

  4. Select the Listbox, and click to the properties tab

  5. In the Common Properties section, click the small box to the right of the ItemsSource property and select Data Binding...

    image

  6. Click on the data source you created in Step 3 in the Data Sources list, then expand the PhotobucketLibrary on the right to select the name of the property you want to bind to.  For my Library, it is FeaturedMedia.  Press Finish once you've selected that.

    image

  7. Now when I run the application, I will see the listbox filled with Media elements, each one using the default toString() output which is informative but not designed:

    image

  8. Now what I would want to do as a designer is create a data template and bind the appropriate properties of the Media class using an ItemTemplate for the ListBox.  The easiest way to do this is to find the ListBox in the Object and Timeline tree control and right click on it to drill down to the Item Template:

    image

  9. You then create a template for the ListBox item and add data binding for the properties in the data element.  I already had one that I was using for my search results so I selected it:

    image

From this demonstration, hopefully I was able to show a workflow for how a designer and a developer could work together using Blend and Visual Studio: in my case those two roles occupy the two opposite sides of my brain. 

Next Steps