Silverlight 2 Beta 2 - Doing Data Part IV: Browser / Cookie Interaction

As you travel in your Silverlight oddysey, you will discover that there is often a need for persisting simple state and interacting with the underlying browser DOM. Fortunately, Silverlight's System.Windows.Browser namespace makes this easy. We create a Silverlight Twitter Search widget using the Twitter API. We'll save searches in a cookie, navigate to selected Tweets using the browser, and when the Back button is pressed, we'll recapture the last search via the cookie and redisplay it.

First of all, I'm a big proponent of "not reinventing the wheel". There's some excellent code put out by Yavor Georgiev of the Connected Framework team that I was able to use for much of the basis of this sample app.

This app uses the Twitter REST API to perform a search and return RSS like so: 

http://search.twitter.com/search.rss?lang=en&rpp=100&q=searchterm

If you check  http://search.twitter.com/crossdomain.xml you'll see that our kind friends at Twitter are enabling cross-domain access for us.

When the Silverlight Page class initializes, I check for a cookie called "search". If it isn't null, I execute my asynchronous MakeRequest call which then databinds the ListBox that is used to show the results. When you click on a result, we grab the Url and use the System.Windows.Browser.HtmlPage.Window.Navigate method to make the browser - not the Silverlight app - request the selected page. Having saved the search in a cookie, when the user clicks their browser's Back button, our Silverlight app reads the cookie, and re-issues the request, redisplaying the results of the last search. If we wanted, we could actually use the retrieved cookie value to load the previous results from IsolatedStorage, a technique which I've covered in a previous article.  Since all of the business logic is included in the Page.Xaml.cs class, I present it here:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.ServiceModel.Syndication;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Xml;

namespace SLTwitterSearch
{
    public partial class Page : UserControl
    {
            SynchronizationContext uiThread;
            string searchUrl = "http://search.twitter.com/search.rss?lang=en&rpp=100&q=";

            public Page()
            {
                InitializeComponent();
                // Grab a reference to the UI thread
                uiThread = SynchronizationContext.Current;
                string cookval = GetCookie("search");
                if(cookval!=null)
                {
                    this.feedAddress.Text = cookval;
                    MakeRequest();

                }
            }

            private string GetCookie(string key)
            {
                string[] cookies = HtmlPage.Document.Cookies.Split(';');
                foreach (string cookie in cookies)
                {
                    string[] keyValue = cookie.Split('=');
                    if (keyValue.Length == 2)
                    {
                        if (keyValue[0].ToString() == key)
                            return keyValue[1];
                    }
                }
                return null;
            }

            private void Button_Click(object sender, RoutedEventArgs e)
            {
                itemsList.Visibility = Visibility.Collapsed;
                MakeRequest();
            }


        void MakeRequest ()
        {
             // save this search in a cookie for later retrieval
            this.SetCookie("search", feedAddress.Text);
            // Begin HTTP request to get feed
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(new Uri(searchUrl + feedAddress.Text));
            request.BeginGetResponse(new AsyncCallback(responseHandler), request);
        }

            void responseHandler(IAsyncResult asyncResult)
            {
                // Get HTTP response
                HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
                HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult);
                // Load feed into SyndicationFeed object
                XmlReader reader = XmlReader.Create(response.GetResponseStream());
                SyndicationFeed feed = SyndicationFeed.Load(reader);
                // invoke the databinding method on the UI thread
                uiThread.Post(setItemsListDataContext, feed);
            }

            void setItemsListDataContext(object feed)
            {
                // Set up databinding
                itemsList.DataContext = (feed as SyndicationFeed).Items;
                itemsList.Visibility = Visibility.Visible;
            }

            private void itemsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
            {
                SyndicationItem itm = (sender as ListBox).SelectedItem as SyndicationItem;
               // Twitter puts the Url to the Tweet in the "Id" field:
System.Windows.Browser.HtmlPage.Window.Navigate(new Uri(itm.Id.ToString())); } private void SetCookie(string key, string value) { // Expire in 7 days DateTime expireDate = DateTime.Now + TimeSpan.FromDays(7); string newCookie = key + "=" + value + ";expires=" + expireDate.ToString("R"); HtmlPage.Document.SetProperty("cookie", newCookie); } } // Helper classes to clean up and shape received data for UI databinding public class HtmlSanitizer : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { // Remove HTML tags and empty newlines and spaces string returnString = Regex.Replace(value as string, "<.*?>", ""); returnString = Regex.Replace(returnString, @"\n+\s+", "\n\n"); // Decode HTML entities returnString = HttpUtility.HtmlDecode(returnString); return returnString; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } public class LinkFormatter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { // Get the first link - that's the link to the post return ((Collection)value)[0].Uri; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } }
This is a pretty simple implementation; if you are interested in Twitter you can find some very interesting folks to follow on Twitter by using this little search app. You can download the complete Visual Studio 2008 Silverlight Application and web here.

NOTE: Some Twitter API's require HTTP BASIC authentication. Unfortunately the "Authorization" HttpWebRequest header in Silverlight is on the "restricted" list, so if you want to use those methods, you'll need to implement a page or service on your hosting web server application to accept the username:password combo on the querystring (encrypted if you like) or as POST form parameters, then have your server-side proxy make the HttpWebRequest call using the full Framework (which is not "restricted") and return the results to your client-side Silverlight app. See here for a detailed list. You will note that the help topic states that "many of these headers are set by the hosting browser". Unfortunately, if you use the browser-based HttpRequest object, it isn't smart enough to read the crossdomain.xml policy file on Twitter, so it becomes, effectively, useless.
By Peter Bromberg   Popularity  (2323 Views)