Silverlight: Handling Cross-Domain Images and Gifs

An easy way to get around cross-domain image urls and handle gif images for Silverlight.

In my travels working with Wally McClure and David Silverlight on the Silverlight 3 UI client for Wally's TimedTweet Azure service, I discovered that when we bring back a Twitter Timeline, all the users' profile images point to Amazon S3 storage. There is no crossdomain.xml or clientaccesspolicy.xml file there, so all the image url requests will be denied by Silverlight. Obviously, for better or worse, Silverlight has been trained to know that any image that doesn't come from the same domain it was served from is a baddie, unless there is an enabling policy file there. Now of course all this extra checking means extra HTTP Requests over the wire, but hey - that's progress.

Tim Heuer and others have some fancy workarounds involving DNS server settings, but I think the easiest solution for me is the simplest one. Just make a "ClientAccess.ashx" handler on the same server / domain that that Silverlight app is served from , and convert all the Amazon image urls to point to ClientAccess.ashx?url=amazonimageurl - with the original Amazon image url on the querystring. Your handler will then read the "url" item from the Request.QueryString collection, make the request for the image, and Response.BinaryWrite the image bytes out to the requesting Silverlight client. Since Silverlight sees this as a same-domain request, there are no security issues and your images will come back just fine.

There is an additional benefit of using this technique: Silverlight - even Silverlight 3 -- cannot display gif images natively. Some (but not all) of the Twitter user Profile images are indeed gifs. There is some code out in the wild that will decode gif images in Silverlight, but since I am already serving my images from my server with the ASHX handler, it just makes sense to convert gifs to jpegs there too. So all we need to do is have the ASHX handler check to see if ".gif" is in the url it's been given, and if so, we can create a .NET Bitmap object from the image bytes, convert it to a jpeg image, and rewrite out the new jpg byte array and serve it out. Silverlight will now happily consume the converted jpeg image and we've solved all our problems in one fell swoop. This approach to handling the delivery of off-domain images will work equally well in Silverlight 2.

Here is an example of how this all works; for this "demo" I'm just doing it with a single gif image from Amazon:

public MainPage()
        {
             InitializeComponent();
            // test Amazon s3 url to a Twitter User's Profile Image that is a gif:
            string amazonImageUrl =
                 "http://s3.amazonaws.com/twitter_production/profile_images/53709078/me2_bigger.gif";
            string imgUrl =Application.Current.Host.Source.Scheme + "://" + Application.Current.Host.Source.Host + ":" +
                Application.Current.Host.Source.Port + "/ClientAccess.ashx?url=" + amazonImageUrl;
           image1.Source = new BitmapImage(new Uri(imgUrl, UriKind.RelativeOrAbsolute));

          
        }

Here is the code for my ASHX handler:

using System;
using System.Collections;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Linq;
using System.Net;

namespace SLImageHandler.Web
{
  
  
     public class ClientAccess : IHttpHandler
    {

         public void ProcessRequest(HttpContext context)
         {
             string imageUrl = context.Request["url"].ToString();
            WebClient client = new WebClient();
             byte[] bytes = client.DownloadData(new Uri(imageUrl));

             // convert gif to jpg  here
            if(imageUrl.Contains(".gif"))
            {
                Bitmap bmp = (Bitmap)Bitmap.FromStream(new MemoryStream(bytes));
                bytes = GifConverter.ConvertGif(bmp);
             }
             context.Response.BinaryWrite(bytes);
         }

         public bool IsReusable
        {
             get
             {
                  return false;
            }
        }
    }

And finally, here is the code for my little "GifConverter" utility:

using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Web;

namespace SLImageHandler.Web
{
    public class GifConverter
    {
         public static byte[] ConvertGif(Bitmap Image)
        {

            MemoryStream objStream = new MemoryStream();
            ImageCodecInfo objImageCodecInfo = GetEncoderInfo("image/jpeg");
            EncoderParameters objEncoderParameters;
             try
            {
                 if (Image == null)
                     throw new Exception("ImageObject is not initialized.");
                objEncoderParameters = new EncoderParameters(3);
                objEncoderParameters.Param[0] = new EncoderParameter(Encoder.Compression,
                 (long)EncoderValue.CompressionLZW);
                objEncoderParameters.Param[1] = new EncoderParameter(Encoder.Quality, 100L);
                objEncoderParameters.Param[2] = new EncoderParameter(Encoder.ColorDepth, 24L);
                Image.Save(objStream, objImageCodecInfo, objEncoderParameters);
             }
             catch
            {
                 throw;
            }
            return objStream.ToArray();
        }

        private static ImageCodecInfo GetEncoderInfo(String mimeType)
         {
             int j;
            ImageCodecInfo[] encoders;
            encoders = ImageCodecInfo.GetImageEncoders();
             for (j = 0; j < encoders.Length; ++j)
             {
                 if (encoders[j].MimeType == mimeType)
                     return encoders[j];
             }
             return null;
        }
     }
}

You can download the Visual Studio 2008 Silverlight 3 Beta solution here.

By Peter Bromberg   Popularity  (8773 Views)