Building a Visual Search Service for IE8 with WCF - Part IV

Well I have to hurry up a bit as I initially planned to have this series finished even a while ago and now I'm even struggling to finish it until the end of the year. Anyway here is the next part and as promised this time I will focus on the search providers which feed the aggregator component with their content. As already mentioned since there are not many video portals that publish APIs I limited the search and implemented two providers which is however probably enough for demonstration purposes.

In order to live a bit of separation I first created a real simple interface which of course also describes the what the implementing search controllers can do. Since there is only one specific task the search controllers have to do is to fill a Section object which simply contains an array of objects. The interface itself is quite simple and contains only one method signature DoSearch(object obj) which takes an object as a parameter. Although in all cases the object that is filled is a Section object I needed to parameterize it with object because I use a threaded model for executing the searches with a ParameterizedThreadStart which unfortunately doesn't allow to pass on data to the thread procedure in a type-safe way. Alternatively I could have encapsulated the threaded calls to DoSearch() into a helper object which I would have initialized with the Section object to be filled by this thread however I didn't design the service in such a fine granular type-safe manner although it would have been relatively easy. If anybody follows the section and does his own implementation or variation the mentioned variant is described here. In any way here is the interface definition for the search controllers:

public interface ISearchController {
void DoSearch(object obj);
}

And here is the Section class which contains the results of the search:

[System.Serializable]
public class Section {
[XmlElement] public object[] items { get; set; }
}

Now that I created the interface I was finally able to start with the implementation of the SearchControllers for the different providers.

  • YouTubeSearchController

    For the YouTube search I used the .Net Client Library which is a library that exposes the YouTube data API through a handy .Net object model which of course makes it really easy to perform searches and retrieve relevant content. Above that the API allows for some tweaking. For example I set the YouTubeQuery.NumberToRetrieve property to 5 as we don't need more because the number of results displayed by the VisualSearch window is limited and we want to display results from the other sources as well. So in order to create as little overhead as possible it is probably very sensible to limit the number of results.
    In order to be able to use the API you need a Google account need to register for a developer key and need to specify a name for your application in which you use the API.

    The search specific code in my YouTubeSearchController is outlined here:

    publicvoid DoSearch(object youTubeSection)
    {
       try
       {
    ...
          this.youTubeSection = (Section )youTubeSection;
          YouTubeService service = newYouTubeService("<Your Application Name>" );
          YouTubeQuery query = newYouTubeQuery(YouTubeQuery .DefaultVideoUri);
    query.OrderBy = "viewCount" ;
    query.Racy = "exclude" ;
    query.VQ = searchquery;
    query.NumberToRetrieve = 5;
          YouTubeFeed videoFeed = service.Query(query);
          this.youTubeSection.items = newobject [videoFeed.Entries.Count + 1];
          Separator youtubeSeparator = newSeparator ();
    youtubeSeparator.title = "Videos On YouTube" ;
          this .youTubeSection.items[0] = youtubeSeparator;
          foreach (YouTubeEntry entry in videoFeed.Entries)
    {
             SectionItem secI = newSectionItem ();
             if (!String .IsNullOrEmpty(entry.Title.Text))
    {
    secI.title = entry.Title.Text;
    }
             else
             {
    secI.title = "Title not available" ;
    }

             if (!String .IsNullOrEmpty(entry.Media.Description.Value))
    {
                if (entry.Media.Description.Value.Length > 50)
    {
    secI.description = entry.Media.Description.Value.Substring(0, 50);
                }
                else
                {
    secI.description = entry.Media.Description.Value;
    }
    }
             else
             {
    secI.description = "Description not available" ;
    }
             if (!String .IsNullOrEmpty(entry.AlternateUri.Content))
    {
    secI.url = entry.AlternateUri.Content;
    }
             else
             {
    secI.url = "Media URI not available" ;
    }

            ThumbnailCollection thumbs = entry.Media.Thumbnails;
            if (thumbs != null && thumbs.Count > 1)
    {
               Thumbnail thumb = newThumbnail ();
    thumb.source = thumbs[0].Url;
    thumb.alternateText = secI.title;
    thumb.height = "35" ;
    thumb.width = "35" ;
    secI.image = thumb;
    }
             else
             {
    secI.image = newThumbnail ();
    }
             this .youTubeSection.items[counter] = secI;
    ...
          }
          catch
          {
             thrownewException ();
    }
    }

  • Metacafe

    Metacafe also does provide an API however they offer a RSS API and return an XML feed for the search results. So the implementation for the MetaCafeSearchController looks a lot different.

    publicvoid DoSearch(object metaCafeSection)
    {
       try
       {
          ...
          this.metaCafeSection = (Section )metaCafeSection;
          string uri = "https://www.metacafe.com/api/videos?vq=" + searchquery + "&max-results=5" ;
          Uri searchUri = newUri (uri);
          WebRequest req = WebRequest .Create(searchUri);
          WebClient client = newWebClient ();
          Stream resultData = client.OpenRead(searchUri);
          StreamReader sR = newStreamReader (resultData);
          XmlReader reader = XmlReader .Create(resultData);
          XmlDocument doc = newXmlDocument ();
    doc.Load(reader);
          XmlNamespaceManager nSmgr = newXmlNamespaceManager (doc.NameTable);
    nSmgr.AddNamespace( "media", https://search.yahoo.com/mrss/ );
          XmlNodeList itemList = doc.SelectNodes("/rss/channel/item" );
          this.metaCafeSection.items = newobject [itemList.Count + 1];
          Separator metacafeSeparator = newSeparator ();
    metacafeSeparator.title = "Videos on metacafe" ;
          this .metaCafeSection.items[0] = metacafeSeparator;
          foreach (XmlNode node in itemList)
    {
             SectionItem item = newSectionItem ();
             XmlNodeList childNodes = node.ChildNodes;
             foreach (XmlNode child in childNodes)
    {
                if (child.Name.Equals("title" ))
    {
                   if (!string .IsNullOrEmpty(child.InnerText))
    {
    item.title = child.InnerText;
    }
                   else
                   {
    item.title = "Title not available" ;
    }
    }
                elseif (child.Name.Equals("media:description" ))
    {
                   if (!string .IsNullOrEmpty(child.InnerText))
    {
                      if (child.InnerText.Length > 50)
    {
    item.description = child.InnerText.Substring(0, 50);
    }
                      else
                      {
    item.description = child.InnerText;
    }
    }
                   else
                   {
    item.description = "Description not available" ;
    }
    }
                elseif (child.Name.Equals("link" ))
    {
                   if (!string .IsNullOrEmpty(child.InnerText))
    {
    item.url = child.InnerText;
    }
                   else
                   {
    item.url = "Link not available" ;
    }
    }
                elseif (child.Name.Equals("media:thumbnail" ))
    {
                   Thumbnail thumb = newThumbnail ();
                   if (!String .IsNullOrEmpty(child.Attributes[0].Value))
    {
    thumb.source = child.Attributes[0].Value;
    thumb.alternateText = item.title;
    thumb.height = "35" ;
    thumb.width = "35" ;
    item.image = thumb;
    }
                   else
                   {
    item.image = newThumbnail ();
    }
    }
    }  
    this
    .metaCafeSection.items[counter] = item;
    counter++;
    ...
        }
        catch
        { thrownewArgumentNullException("searchquery not set"
    );
    }
    }

So now with these two search implementations I get two Section objects which then again are aggregated into one single SearchItem object. Which then is serialized into the service response and feeds the VisualSearch client in Internet Explorer 8. This process was already mentioned in the last part.

So that's it for part four. The next part is probably going to be the last part and deals about the accompanying features of the service like threading, caching and configuration in a little bit more detail.

Reference:

DeliciousBookmark this on Delicious Share