REALLY small unzip utility for Silverlight

UPDATE: See this updated blogpost.

There are quite a few libraries out there that adds zip decompression/compression to Silverlight. However, common to them all is that they add significantly to the size of the resulting .xap.

It turns out that Silverlight 2.0 already has zip decompression built-in. It uses this to uncompress the .xap files which really just are zip files with a different file extension.

There are several blog posts out there that will tell you how to dynamically load a XAP file and load it. It turns out that if you use the same approach with almost any other zip file, you can actually do the same thing, even though this is not a Silverlight XAP. I don’t think this was the original intent. but its still really neat! Here’s how to accomplish that, based on a zip file stream:

public static Stream GetFileStream(string filename, Stream stream)
{
    Uri fileUri = new Uri(filename, UriKind.Relative);
    StreamResourceInfo info = new StreamResourceInfo(stream, null);
    StreamResourceInfo streamInfo = System.Windows.Application.GetResourceStream(info, fileUri);
    if (streamInfo != null)
        return streamInfo.Stream;
    return null; //Filename not found or invalid ZIP stream
}

However, the problem is that this requires you to know before-hand what the names of the files are inside the zip file, and Silverlight doesn’t give you any way of getting that information (Silverlight uses the manifest file for reading the .xap).

Luckily getting filenames from the zip is the easy part of the ZIP specification to understand. This enabled us to create a generic ZIP file extractor in very few lines of code. Below is a small class utility class I created that wraps this all nicely for you.

public class UnZipper
{
    private Stream stream;
    public UnZipper(Stream zipFileStream)
    {
        this.stream = zipFileStream;
    }
    public Stream GetFileStream(string filename)
    {
        Uri fileUri = new Uri(filename, UriKind.Relative);
        StreamResourceInfo info = new StreamResourceInfo(this.stream, null);
        StreamResourceInfo stream = System.Windows.Application.GetResourceStream(info, fileUri);
        if(stream!=null)
            return stream.Stream;
        return null;
    }
    public IEnumerable<string> GetFileNamesInZip()
    {
        BinaryReader reader = new BinaryReader(stream);
        stream.Seek(0, SeekOrigin.Begin);
        string name = null;
        while (ParseFileHeader(reader, out name))
        {
            yield return name;
        }
    }
    private static bool ParseFileHeader(BinaryReader reader, out string filename)
    {
        filename = null;
        if (reader.BaseStream.Position < reader.BaseStream.Length)
        {
            int headerSignature = reader.ReadInt32();
            if (headerSignature == 67324752) //PKZIP
            {
                reader.BaseStream.Seek(14, SeekOrigin.Current); //ignore unneeded values
                int compressedSize = reader.ReadInt32();
                int unCompressedSize = reader.ReadInt32();
                short fileNameLenght = reader.ReadInt16();
                short extraFieldLenght = reader.ReadInt16();
                filename = new string(reader.ReadChars(fileNameLenght));
                if (string.IsNullOrEmpty(filename))
                    return false;
                //Seek to the next file header
                reader.BaseStream.Seek(extraFieldLenght + compressedSize, SeekOrigin.Current);
                if (unCompressedSize == 0) //Directory or not supported. Skip it
                    return ParseFileHeader(reader, out filename);
                else
                    return true;
            }
        }
        return false;
    }
}

Basically you create a new instance of the UnZipper parsing in the stream to the zip file. The method “GetFileNamesInZip” will provide you with a list of the file names available inside the file, that you can use to reference the file using “GetFileStream”.

Below is a simple example of using this. The contents of each file will be shown in a message box:

private void LoadZipfile()
{
    WebClient c = new WebClient();
    c.OpenReadCompleted += new OpenReadCompletedEventHandler(openReadCompleted);
    c.OpenReadAsync(new Uri("http://www.mydomain.com/myZipFile.zip"));
}
 
private void openReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
    UnZipper unzip = new UnZipper(e.Result);
    foreach (string filename in unzip.GetFileNamesInZip())
    {
        Stream stream = unzip.GetFileStream(filename);
        StreamReader reader = new StreamReader(stream);
        string contents = reader.ReadToEnd();
        MessageBox.Show(contents);
    }
}

Note that some ZIP files which doesn't report file size before the file content is not supported by Silverlight, and is therefore also ignored by this class. This is sometimes the case when the zip file is created through a stream where the resulting file size is written after the compressed data. If you are dealing with those kind of zip files (seems fairly rare to me), you will need to use a 3rd party zip library that supports this.
UPDATE: See this updated blogpost.

This class will hardly add much to your resulting .xap, and I think it will cover 95% of the use cases when working with zip files.

Download class file: UnZipper.zip (1.25 kb)

Comments (15) -

  • Nice example Morten, I really should get started on this silverlight/moonlight thing Smile
  • Great clear example Morten, keeping the size of your xap to a min is clearly good for everyone.
    Did you catch the news about the new binary serialization of WCF transmissions to Silverlight3?

    Binary XML support is introduced which enables Silverlight to communicate with Windows Communication (WCF) services using the Binary XML data format in addition to regular text XML.  The use of the Binary XML format results in smaller message sizes and better performance in the messaging with the service.

    Havn't seen more then this.
    John.
  • Hey Morten, good stuff once again!
    Does this mean you'll be enhancing your ShapeFile converter so it imports directly into silverlight (without going through sql server) ?

    For example, HurricaneMapping.com produces hurricane bulletins as a collection of zipped shape files.  Would be nice to be able to pull these in directly.
    www.hurricanemapping.com/data/Katrina22_2005.zip

    Regards, Kirk
  • John: I'm aware of this new feature, but I'm skeptic to whether this would give any benefit over HttpCompressed JSON responses. It also makes debugging what's going over the wire much harder.

    Kirk: While that would be totally possible, no I currently don't have any plans like that.
  • Hmmm, the shapefile reader sourcecode I found relies on System.Data.OleDB (for the dbf files) ... would you happen to know of a silverlight equivalent to OleDB?  Or a way to read dbf files without oledb ?

    Thanks, Kirk
  • See that's exactly why it isn't a straightforward task. However, DBF files is not THAT hard to read manually, but it does require you to read the xbase specification and understand how to read binary files.
  • It's seems like a great technique...I just can't get it work.
    Which zip utility did you use? I've tried various without any luck. I am able to read an XAP file though but would obviously like to be able to compress my own files.
  • I use the built in zip read capability. As the topic says this is for UNzipping.
  • Heh yeah I know what the article is about.

    I have some dynamic content sent from an ASP.NET application to a silverlight control. I'd like to compress that data on the fly and then uncompress it on the Silverlight side. I have tried various libraries but it seems the actual zip format used is not compatible. That's why I asked which utility you used for the actual zipping part...
  • I used both Windows's built in zipping, WinZip and TotalCommander. None of which you can use with ASP.NET.
  • I get string was not in the correct format when I just try to get the filenames with .zip and .xap files.
    Any idea what could be wrong?
  • sorry, it was my fault I had a [1} in my format string instead of {1}. Now it works but does not show Chinese correctly, but I do not think that a problem with the code or is it?
  • cannot get content that is Chinese either.  Must be something in the spec about this... but it is to deep for me.
  • Bydia: Make sure you use the correct Encoding for parsing the stream. The StreamReader and its default settings might not be good enough for what you need.
  • Thanks Morten... I made sure my content file was saved as unicode... however, not sure I can control the filenames.

Pingbacks and trackbacks (11)+

Comments are closed