Thursday, July 30, 2009

Preloading and Caching Images in Silverlight

One thing I found with our image-intensive Silverlight application was that the screens sometimes painted a little slowly due to our decision to store images externally. Instead of having a bloated XAP file, we decided to map a path to the server with the images. Because the application is enterprise and therefore behind SSL, this can sometimes result in a noticeable load time for the images.

After doing some research, I found that Silverlight actually uses the browser's own image caching mechanisms. In other words, when you point to an external image on the web, the browser will cache it just like an ordinary image stored in a web page. The first time, it will pull down, but after that it should be fast.

Knowing this, it was simple to make a little image helper to help me preload images so they are ready before the user is. The class looks like this:

public static class ImageHelper
{
   public static void PreLoad(string imagePath)
   {
      const string JS = "var img = new Image(); img.src='{0}';";
      HtmlPage.Window.Eval(string.Format(JS, AppUriHelper.GetRelativeUri(imagePath)));
   }
}

That's it ... in my App.cs I can now preload images easily, by doing this:

...
ImageHelper.PreLoad("Images/mycachedimage.jpg");
...

The application hooks into the JavaScript of the DOM for the browser page, then creates an image object that triggers a load into your browser of the image, even if you haven't shown it within the Silverlight application yet.

Oh, what is AppUriHelper? It's just a simple utility to help me build uris that follow the application wherever it is installed:

public static class AppUriHelper
{        
   private static readonly Uri _baseUri;
   private static readonly string _uriRoot;

   static AppUriHelper()
   {
      _uriRoot = System.Windows.Browser.HtmlPage.Document.DocumentUri.AbsoluteUri;
      int lastSlash = _uriRoot.LastIndexOf("/");

      // we do this twice to get the "second to last" slash - leave this out if
      // your app is hosted in the root, this assumes /subdir/mypage.aspx
      _uriRoot = _uriRoot.Substring(0, lastSlash);
      lastSlash = _uriRoot.LastIndexOf("/");
      _uriRoot = _uriRoot.Substring(0, lastSlash + 1);

      _baseUri = new Uri(_uriRoot, UriKind.Absolute);
   }

   public static Uri GetBaseUri()
   {
      return _baseUri; 
   }

   public static Uri GetRelativeUri(string relativePath)
   {
      string newUri = string.Format("{0}{1}", _uriRoot, relativePath);
      return new Uri(newUri, UriKind.Absolute);
   }
}

Of course, GetRelativeUri is probably not named well because it returns an absolute Uri ... it really is "Get Absolute Uri from Relative Path".

Jeremy Likness