Silverlight 3 Bing API: A Related Links Widget

A custom Silverlight UserControl to use the search engine referer to create a Bing site-specific search on the query and display "related items" from the site.


At we're working on a Silverlight Makeover for the site, and this gives us the opportunity to experiment with various concepts and ideas along the way. Recently I found the "BingSharp" Bing API Wrapper at Codeplex, and decided to port it to Silverlight. The port was extremely easy, all I had to do was create a Silverlight Class Library project and bring in all the .cs code files. One minor change to a signature, and I was done. Well, almost: BingSharp was not written with Silverlight's asynchronous-only http calls in mind. However, with some minor modifications, all the code is eminently usable. Here is an example using the "Web" search query, which is the one I needed:

private void GetBingResults()
            SearchRequest searchRequest = new SearchRequest { AppId = API_KEY, Query = App.SearchTerm, Market = "en-US" };
            Bing.WebRequest webRequest = new Bing.WebRequest();
            webRequest.Count = 50;  // Required by the search that's all they give you!
            string requestString = API.Web(searchRequest, webRequest);
            WebClient wc = new WebClient();
            wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
             wc.DownloadStringAsync(new Uri(requestString));

        void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
             string response = e.Result;
// XElement.Load wants a stream:
            MemoryStream ms = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(response));
            XElement element = XElement.Load(ms);
            Bing.WebResponse wr = Bing.WebResponse.ParseElement(element);
             this.itemsList.DataContext = wr.Results;

The above code uses the API Wrapper's API.Web method, which won't work for Silverlight, so I modified it as follows:

public static string Web(SearchRequest searchRequest, WebRequest request)
            searchRequest.Sources = new[] { ApiHelper.SourceType.Web };
            searchRequest.Version = "2.1";
            string requestString = searchRequest.ToQueryString() + request.ToQueryString();
            return requestString;
          //  XElement element = XElement.Load(requestString);
           // return WebResponse.ParseElement(element);

The return type was originally "WebResponse", but now you would handle that in the callback. If you decide to use this API wrapper library, you will probably find you need to make similar adjustments for all the other methods. 

To check for a referer from a search engine link  and grab the search term, I use the following code in App.Xaml.cs:

private void Application_Startup(object sender, StartupEventArgs e)
            string referer =  Convert.ToString(HtmlPage.Document.GetProperty("referrer"));
            // if referer is empty or is coming from our site, don't want it.
            if (referer == "" || referer.ToLower().IndexOf("eggheadcafe") > -1) return;
            System.Uri uri = new System.Uri(referer);
             string query = uri.Query;
             // If there is no search term, bail
            if (String.IsNullOrEmpty(query))
            query = query.TrimStart('?');
            int startPos = query.IndexOf("q=") + 2;
            query = query.Substring(startPos);
             int endPos = query.IndexOf("&");
            if (endPos > 0)
                query = query.Substring(0, endPos);
            query = query.Replace("\"", "").Replace("+", " ").Replace("%22", "").Replace("%20", " ");
            SearchTerm = query;
             this.RootVisual = new MainPage();

Then, in the control proper, the GetBingResults method uses the App.SearchTerm to create the actual Bing Search. You would probaly want to abort the search if there is no search term so that nothing is displayed.

Finally, the XAML for the actual display in the control, showing the databinding:

<UserControl x:Class="BingSearchWidget.BingControl"
    Width="210" Height="450" BorderThickness="1" Background="Bisque"  BorderBrush="DarkBlue" UseLayoutRounding="True">  
    <Grid x:Name="LayoutRoot" Background="White" Width="210"  >
            <RowDefinition Height="25"   />
             <RowDefinition  Height="*"  />
        <TextBlock Text="Related Items We found:" VerticalAlignment="Top" HorizontalAlignment="Center" Margin="0" Grid.Row="0" />
        <ListBox x:Name="itemsList" ItemsSource="{Binding}" VerticalAlignment="Bottom" Grid.Row="1" ScrollViewer.HorizontalScrollBarVisibility="Hidden" >
                <DataTemplate >
                    <StackPanel >
                     <HyperlinkButton   Width="200"  
                            NavigateUri="{Binding Link}" >                    
                    <TextBlock Text="{Binding Title}"  Foreground="Navy"  FontSize="11" TextWrapping="Wrap" Width="195"/>                            
                        <TextBlock Text="{Binding Description}"  Foreground="SteelBlue"  FontSize="9" TextWrapping="Wrap" HorizontalAlignment="Stretch"  Width="195"/>

Obviously, the XAML could use some nice styling, but I'm just not a Blend kind of guy. I'll probably ask David Silverlight to help me with that, cause he really groks Blend. (After all, with a surname like Silverlight...). UPDATE: I did get David to style it. He changed it to a StackPanel with a nice "flyin" rendering animation, and added a nice feature to display the details on mouse-enter.

And this is what David's styling  makeover looks  like in action:


I have modified the control to operate when there is either a querystring item "q=xxx" or the same with the http referer. There are a few parameters needed  in the Silverlight page <OBJECT -- tag. Here they are:

<div id="silverlightControlHost" align="left" style="width:210px">
        <object id="sl1" data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="210px" height="500px">
          <param name="source" value="ClientBin/BingSearchWidget.xap"/>
          <param name="onError" value="onSilverlightError" />
          <param name="background" value="white" />
          <param name="minRuntimeVersion" value="3.0.40624.0" />
          <param name="autoUpgrade" value="true" />
          <param name="enablehtmlaccess" value="true"/>
            <param name="initparams" value="API_KEY=YOUR_BING_API_KEY,test=true"/>
          <a href="" style="text-decoration:none">
               <img src="" alt="Get Microsoft Silverlight" style="border-style:none"/>
        </object><iframe id="_sl_historyFrame" style="visibility:hidden;height:0px;width:0px;border:0px"></iframe>

Note that if you do not replace the "YOUR_BING_API_KEY" with a valid API key, you'll get a MessageBox warning. The boolean  "test" InitParam is for bypassing the same-host origination of the page. The "enablehtmlAccess" parameter is required to be able to get the referer or querystring from the page itself. Otherwise, you're free to do whatever you like with the control and the code.

You can download the working Silverlight 3 RTW  Solution here. You'll need a Bing API key, which you can easily get here.

By Peter Bromberg   Popularity  (5103 Views)