Silverlight: Downloading Zipped files with the WebClient (stand Silverlight 2 beta 1)
Introduction
In the "old" alpha edition of Silverlight, you could use the Downloader class to download a zip file containing one or many packed media elements (images, videos). The Downloader provided a progress report (to update a progress bar, for example). After the download was completed, you could use the Image.SetSource or MediaElement.SetSource method to directly unpack one file from the downloaded zip file.
In Silverlight 2 beta, the interfaces changed, but you can still do the same operations. Let's see how:
Creating
-
Create a new Silverlight 2 Beta project in Visual Studio. Also create a test web site. In my example, the Silverlight application is named GalaSoft.SL.WebClientTest and the test web site is named GalaSoft.SL.WebClientTest.Web.
-
In the newly created project, delete Default.aspx and GalaSoft.SL.WebClientTestTestPage.aspx.
-
Rename GalaSoft.SL.WebClientTestTestPage.html to index.html.
-
Using a utility of your choice, create a Zip file containing multiple media files. Alternatively, you can download one from here.
-
Create a XML file named "content.xml" with the media files information, for example:
<?xml version="1.0" encoding="utf-8" ?>
<mediafiles>
<mediafile type="video"
name="mov2008021202.wmv"/>
<mediafile type="video"
name="mov2008021203.wmv"/>
<mediafile type="image"
name="el2008021001.jpg"/>
<mediafile type="image"
name="el2008021101.jpg"/>
<mediafile type="image"
name="el2008021103.jpg"/>
<mediafile type="image"
name="el2008021501.jpg"/>
</mediafiles>
-
Add the file "content.xml" to the zip file, in the root.
-
Add the zip file (renamed Media.zip) to the project GalaSoft.SL.WebClientTest.Web.
Designing the XAML UI
-
Open the file Page.xaml and copy the following XAML code:
<UserControl x:Class="SilverlightApplication1.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400"
Height="300">
<Grid x:Name="LayoutRoot"
Background="White">
<Image x:Name="MyImage" />
<MediaElement x:Name="MyVideo" />
<TextBlock x:Name="ProgressTextBlock"
Text="Init"
Margin="5,0,0,0" />
<StackPanel HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Opacity="0.5"
Orientation="Horizontal"
Margin="0,0,0,5"
MouseEnter="StackPanel_MouseEnter"
MouseLeave="StackPanel_MouseLeave">
<Button Content="Prev"
x:Name="PrevButton"
Click="PreviousButton_Click"
Height="30"
Margin="0,0,5,0"
Width="80"
Opacity="1"
Cursor="Hand"
IsEnabled="False" />
<Button Click="NextButton_Click"
x:Name="NextButton"
Height="30"
Margin="0,0,5,0"
Width="80"
Opacity="1"
Content="Next"
Cursor="Hand"
IsEnabled="False" />
</StackPanel>
</Grid>
</UserControl>
This XAML code contains an Image and a MediaElement, used to display images and videos. It has a TextBlock to display the download progress. And two buttons in a StackPanel, to display the Previous and Next media.
Declaring attributes
-
Open the filePage.xaml.cs.
-
Add 3 private attributes:
private List<MediaInfo> _mediaInfos = null;
private StreamResourceInfo _zipInfo = null;
private int _mediaIndex = 0;
The attribute _mediaInfos will contain the list of media information read from the XML file. The attribute _zipInfo stores the StreamResourceInfo read from the Zip file. Finally, the _mediaIndex stores the index of the media file currently displayed by the application.
Starting the download
-
Add an event handler to the "Loaded" event for the Page:
public Page()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Page_Loaded);
}
-
In the Page_Loaded event handler, start the download. Note: The URL of the Zip file must be entered as relative to the "xap" file, which is located in the ClientBin folder.
void Page_Loaded(object sender, RoutedEventArgs e)
{
WebClient webClient = new WebClient();
webClient.DownloadProgressChanged
+= new DownloadProgressChangedEventHandler(webClient_DownloadProgressChanged);
webClient.OpenReadCompleted
+= new OpenReadCompletedEventHandler(webClient_OpenReadCompleted);
webClient.OpenReadAsync(new Uri("../Media.zip", UriKind.Relative));
}
-
Handle the DownloadProgressChanged event. This simply updates the ProgressTextBlock, showing how many percents of the Zip file was downloaded.
void webClient_DownloadProgressChanged(object sender,
DownloadProgressChangedEventArgs e)
{
ProgressTextBlock.Text = "Downloading "
+ e.ProgressPercentage + "%";
}
Reading the XML file
-
The most complex method is the one called when the Zip file is finished downloading. Let's copy the code first, and then we will review it:
void webClient_OpenReadCompleted(object sender,
OpenReadCompletedEventArgs e)
{
if (e.Error != null)
{
ProgressTextBlock.Text = "Error: "
+ e.Error.Message;
return;
}
ProgressTextBlock.Text = "Loading";
_zipInfo = new StreamResourceInfo(e.Result, null);
// Read manifest from zip file
StreamResourceInfo manifestInfo
= Application.GetResourceStream(_zipInfo,
new Uri("content.xml", UriKind.Relative));
StreamReader reader = new StreamReader(manifestInfo.Stream);
XDocument document = XDocument.Load(reader);
var mediaFiles = from m in document.Descendants("mediafile")
select new MediaInfo
{
Type = (MediaType) Enum.Parse(typeof(MediaType),
m.Attribute("type").Value, true),
Name = m.Attribute("name").Value
};
_mediaInfos = new List<MediaInfo>();
_mediaInfos.AddRange(mediaFiles);
ProgressTextBlock.Visibility = Visibility.Collapsed;
PrevButton.IsEnabled = true;
NextButton.IsEnabled = true;
DisplayMedia(_mediaInfos[0]);
}
-
First, we check if there was an error downloading the file. if it's the case, the e.Error is not null, and contains information about the error.
-
Then, we convert the stream returned by the web client in the e.Result property into a StreamResourceInfo. This class is the center of the whole process. We store this reference in a private attribute, because we will need it every time that we want to display a new media file. We do not unzip the downloaded file!! We just keep it in memory.
-
Then, we can start reading from the Zip file. The first thing we read is the XML file. We use another StreamResourceInfo instance to get the XML file's stream. We then pass the Stream to a StreamReader, and eventually we construct a XDocument with this reader. The XDocument class is allowing us to use LINQ to XML to load the XML elements easily.
Important: To use the XML to LINQ library, you must add the DLL System.Xml.Linq to the project's references.
-
The next block of code uses LINQ to create a collection of MediaInfo instances. This class is declared after the Page class (see below). One MediaInfo instance simply reflects the XML file's content: Which type does the media file have (video or image), and the file name. After the collection has been loaded with LINQ, we initialize a List with this information, so that we can enumerate the files easily.
Displaying the media
-
Once done, we are ready to roll: We simply hide the progress TextBlock, enable the buttons and display the first media.
private void DisplayMedia(MediaInfo mediaInfo)
{
StreamResourceInfo mediaStreamInfo
= Application.GetResourceStream(_zipInfo,
new Uri(mediaInfo.Name, UriKind.Relative));
switch (mediaInfo.Type)
{
case MediaType.Image:
MyImage.Visibility = Visibility.Visible;
MyVideo.Visibility = Visibility.Collapsed;
BitmapImage image = new BitmapImage();
image.SetSource(mediaStreamInfo.Stream);
MyImage.Source = image;
break;
case MediaType.Video:
MyImage.Visibility = Visibility.Collapsed;
MyVideo.Visibility = Visibility.Visible;
MyVideo.SetSource(mediaStreamInfo.Stream);
MyVideo.Play();
break;
}
}
-
The method DisplayMedia above displays the media file corresponding to the index. First it uses the information passed in the MediaInfo parameter to get the media file's stream from the Zip file. If the media is a video, you can simply use the method MediaElement.SetSource and "feed" the stream to that method. If the media is an image, it's a bit more complex, and you have to construct a BitmapImage first, and then assign it to the Source property of the Image control.
-
Note that we use a different control to display image or video, so we must take care of hiding the non-used control.
-
The rest of the methods are here to control the application: The buttons Previous and Next simply decrement or increment the index and call DisplayMedia. The method StopVideo stops the currently playing video. Finally, the event handler StackPanel_MouseEnter and StackPanel_MouseLeave change the StackPanel's opacity when the mouse enters it, so that the buttons don't block the media currently displayed.
Additional Enum and Class
-
In the end of the file, you'll find an enum for the media type, and the MediaInfo class:
public enum MediaType
{
Video = 0,
Image = 1,
}
public class MediaInfo
{
public MediaType Type
{
get;
internal set;
}
public string Name
{
get;
internal set;
}
}
Conclusion
This code demonstrates how easy it is to pass a Zip file containing various media files to a Silverlight application. In a few lines of code, we have a full blown downloader (including download progress indication), and a UI displaying mixed videos and images. The beauty of it is that all the information needed is contained in a XML file, so you can change the content of the Zip file without recompiling the application. It also shows how to use LINQ to XML to load a XML file in a very concise and easy way.
| Date |
Version |
Description
|
| 23.03.2008 |
V1.0.0 |
First published (stand Silverlight 2 beta 1).
|