Downloading Content on Demand

Microsoft Silverlight will reach end of support after October 2021. Learn more.

Microsoft Silverlight 4 provides a WebClient class, which supports a set of HTTP client features for a Silverlight plug-in. The WebClient class replaces the Downloader object that was used for this purpose in Silverlight 1.0. This topic describes how to use the WebClient class to download content on demand.

This topic contains the following sections.

  • Role of the WebClient Class
  • Creating and Initializing a WebClient Object
  • Related Topics

Role of the WebClient Class

The WebClient class provides the ability to download application data, such as XAML content, additional assemblies, or media assets such as images. The WebClient class can download content on demand in response to application needs. You can display or use downloaded content asynchronously. This means you do not have to refresh the HTML page that contains the Silverlight control, even if you replace most of the Silverlight content on the HTML page. The WebClient class provides features for initiating the download request, monitoring the progress of the request, and retrieving the downloaded content. For example, if your application plays videos from a video library as soon as the initial application loads, you could choose to start requesting each video in the library. This means that the videos are available to the browser cache and can be played locally after the progressive download rather than streaming the videos, which might result in buffering gaps that are visible to the user.

The WebClient class also provides the ability to upload application data and textual data to a server. In this topic, we focus on the download of application data, since this is the more common scenario.

Creating and Initializing a WebClient Object

In order to use the WebClient class in your Silverlight-based applications, you have to add a reference to the System.Net assembly (System.Net.dll) to your Silverlight application project, and add the following using statement to the source code for your Silverlight application.

using System.Net;

To create a WebClient object, call the WebClient constructor. You might typically create a WebClient as part of a page Loaded handler, and then retain the WebClient to process each request that is initiated by code on that page.

WebClient requests are asynchronous, so most of the interaction that your code has with the WebClient will be through event handlers. Typically this involves defining event-handlers for one or more of the following events:

When you initiate a request, you use different WebClient class methods depending on whether you are requesting a resource as a stream, or as a string.

To request to read a resource as a stream, you first call one of the following overloads of OpenReadAsync:

OpenReadAsync(Uri)

OpenReadAsync(Uri, Object)

Next, you handle the OpenReadCompleted event.

To request to download a resource as a string, you call one of the following overloads of DownloadStringAsync:

Next, you handle the DownloadStringCompleted event.

Important noteImportant Note:

You can only initiate one request at a time with WebClient class. If you make a second request before the first request raises its completed event or raises an asynchronous error (in other words, while IsBusy is true), the second request will cause a NotSupportedException.

Whether to download content as a stream or a string depends on what classes you intend to use with the content result after you download it, and how the content is used. In general, downloading as a stream will probably be more common, because streams are used for several dynamic UI scenarios, such as providing image sources, fonts, or MediaElement content on demand. Also, you can use streams to access parts of a downloaded package.

OpenReadCompleted Handler

The following is the signature of an OpenReadCompleted event handler.

void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e) {
...
}

The second parameter passed to the event handler is an instance of the OpenReadCompletedEventArgs class. The most important member is the Result property of the OpenReadCompletedEventArgs class. The Result property is of type Stream. You can use this stream directly to set properties or call methods that can take a stream, for example SetSource(Stream). Alternatively, you can use a StreamReader or other classes to access the stream.

An application should check the AsyncCompletedEventArgs.Error and AsyncCompletedEventArgs.Cancelled properties inherited by the OpenReadCompletedEventArgs class instance before using the data that is returned by the Result property. If the inherited AsyncCompletedEventArgs.Error property's value is an Exception object or the inherited AsyncCompletedEventArgs.Cancelled property's value is true, the asynchronous operation did not complete correctly and the Result property's value will not be valid. Otherwise, an exception will be thrown that should be handled when the Result property is accessed.

DownloadStringCompleted Handler

The following is the signature of an DownloadStringCompleted event handler.

void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) {
...
}

The second parameter passed to the event handler is an instance of the DownloadStringCompletedEventArgs class. The most important member is the Result property of the DownloadStringCompletedEventArgs class. The Result is of type String. You can use this string directly to set properties or call methods that can take a string, for example Load. Or you can perform your own processing of the string.

An application should check the AsyncCompletedEventArgs.Error and AsyncCompletedEventArgs.Cancelled properties inherited by the DownloadStringCompletedEventArgs class instance before using the data that is returned by the Result property. If the inherited AsyncCompletedEventArgs.Error property's value is an Exception object or the inherited AsyncCompletedEventArgs.Cancelled property's value is true, the asynchronous operation did not complete correctly and the Result property's value will not be valid. Otherwise, an exception will be thrown that should be handled when the Result property is accessed.

Download Packages

Silverlight provides the ability to download content as a package, which is a collection of independent files that contain XAML content, media assets, and other application data. If you are downloading a package, you call OpenReadAsync, so that the returned type is a Stream.

Obtaining named parts from the package is not done by the WebClient class directly. Instead, you use StreamResourceInfo to process the stream as a package, and then return one of the parts of the package as a new stream.

Using the SetSource Method

Several media types can have their source properties set asynchronously. You first get a specific stream that contains the source material as described previously. Then, call one of the methods that use the stream as the source for an object's media. The following is a list of methods that work this way:

The SetSource can be used as a common source for either Image.Source or ImageBrush.ImageSource. This enables asynchronous source-setting for either Image or ImageBrush.

You can also set asynchronous sources for a VideoBrush. You would first set the source for the MediaElement that is used as the video source with SetSource. Then, call SetSource.

For example, a package might contain a graphics file that is used for an Image source. In this case, a part reference is the file name of the individual content within the package.

The following C# example shows how to use the SetSource method to populate an ImageSource with a specific part stream from the downloaded content. Then, the ImageSource is used to used to set the Source property of an Image object.

The first code example establishes the WebClient for the request, adds the completed handler, and initiates the request. Note that it passes a token value imgPart. This will inform the handler of which part is desired from the overall package.

void DownloadImagePart(string imgPart)
{
    WebClient wc = new WebClient();
    wc.OpenReadCompleted += new OpenReadCompletedEventHandler(wc_OpenReadCompleted);
    wc.OpenReadAsync(new Uri("imgs.zip", UriKind.Relative), imgPart);
}

The next code example defines the completed handler referenced in the previous code. Within the completed handler, the initial stream is processed to get a specific part, and that part is used to set the image source. The part name itself was passed as a token, retrieved from e.UserState, and used to specify the URI within the stream/package. ImgToFill in this code example is a reference to an Image that was defined in XAML with x:Name of ImgToFill (not shown).

void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
    StreamResourceInfo sri = new StreamResourceInfo(e.Result as Stream, null);
    String sURI = e.UserState as String;
    StreamResourceInfo imageStream = Application.GetResourceStream(sri, new Uri(sURI, UriKind.Relative));
    BitmapSource imgsrc = new BitmapSource ();
    imgsrc.SetSource(imageStream.Stream);
    ImgToFill.Source = imgsrc;
}

To use the classes listed in the previous sample, you have to add the following using statements to your file.

using System.IO;
using System.Windows.Resources;

Also, if you are setting image sources from download parts, BitmapImage is in a separate namespace from the rest of the image and media classes, so also include the following namespace:

using System.Windows.Media.Imaging;

FontSource

You can also retrieve fonts from downloaded packages, which are added to the font source collection for text display elements, such as TextBox and TextBlock. The mechanism for getting fonts for a font source is slightly different. You request the package that contains the fonts as a stream (by using OpenReadAsync(Uri)). After handling OpenReadCompleted, retain a reference to the stream. Use the stream as the constructor parameter for FontSource (FontSource constructor). All font sources in the package are available and are added to the font collection in the FontSource. You do not have to specify a part by name. Constructing a FontSource turns the parts into collection items, with the font name / part name becoming the key for the font source of an individual font in the collection. Then you set FontSource properties, such as FontSource, with the FontSource object. You specify specific font names when you set the FontFamily properties on various types and the corresponding font is obtained from the font source collection.

NoteNote:

You can retrieve fonts from packages that contain non-font parts. Only the font source parts are used for the FontSource.

Handling the DownloadProgressChanged Event

The DownloadProgressChanged event is often used with a visual progress indicator, which displays the percentage of content that has been downloaded. A visual progress indicator can be a simple set of XAML content, such as Rectangle and TextBlock objects, as shown in the following XAML example.

<!-- Visual progress indicator -->
<Canvas Canvas.Top="70">
  <Rectangle
    Name="progressRectangle"
    Canvas.Left="20"
    Height="10" Width="0"
    Fill="Maroon" />
  <Rectangle
    Canvas.Top ="-1"
    Canvas.Left="19" Height="12"
    Width="202"
    StrokeThickness="1" Stroke="Black" />
  <TextBlock
    x:Name="progressText"
    Canvas.Top ="-4" Canvas.Left="230"
    Text="0%" FontSize="12" />
</Canvas>

To add the handler for progress, add it at the same time that you add either the OpenReadCompleted or DownloadStringCompleted handlers.

 void DownloadImagePart(string imgPart)
{
    WebClient wc = new WebClient();
    wc.OpenReadCompleted += new OpenReadCompletedEventHandler(wc_OpenReadCompleted);
    wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(wc_DownloadProgressChanged);
    wc.OpenReadAsync(new Uri("imgs.zip", UriKind.Relative), imgPart);
}

The following C# example shows how to define a DownloadProgressChanged event-handler function that updates the visual progress indicator shown in the previous XAML.

// Event handler for updating visual progress indicator
void wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
    // Calculate the downloaded percentage.
    // Update the Rectangle and TextBlock objects of the visual progress indicator.
    progressText.Text = e.ProgressPercentage.ToString();
    progressRectangle.Width = (double) e.ProgressPercentage;
}

The following illustration shows the visual progress indicator at the starting, middle, and ending position of the download.

Visual progress indicator

Progress indicator

A visual progress indicator can be constructed from a wide variety of XAML content, such as animated objects. The progress is reported by the ProgressPercentage property as a Double value between 0 and 100.

NoteNote:

If you are migrating Silverlight 1.0 code, the equivalent Downloader progress properties returned a double between 0 and 1, so you may have to eliminate any *100 logic you used to get a percentage number between 0 and 100.

Using the CancelAsync Method

You might call CancelAsync in response to your own timers, or you might provide a UI option so that the user can cancel any download. Before calling CancelAsync you should check IsBusy to make sure that you are not about to cancel a request that just completed. The following code shows how to perform this check.

void CancelButton_ClickHandler(object sender, RoutedEventArgs e)
{
     // Check if async download is in progress
     if (wc.IsBusy)
     {
            // Initiate cancel
            wc.CancelAsync();
     }
}

When you call the CancelAsync method, your application still receives the completion event associated with the operation. For example, if you call CancelAsync to cancel a DownloadStringAsync operation and you have specified an event handler for the DownloadStringCompleted event, your event handler receives notification that the operation has ended. To learn whether the operation completed successfully, check the AsyncCompletedEventArgs.Cancelled property of the System.ComponentModel.AsyncCompletedEventArgs inherited by the class (DownloadStringCompletedEventArgs, for example) for the relevant completed event handler. The AsyncCompletedEventArgs.Cancelled property inherited by the class is set to true if the true if the operation was cancelled.

If you cancel a string download operation and you check the Result property of the

DownloadStringCompletedEventArgs object, an exception will occur since the operation was cancelled.

URI Protocols and Schemes

The URI that you specify for the OpenReadAsync and DownloadStringAsync APIs will generally use relative URIs. The relative URI will then use the same URI scheme that is used to serve the HTML page, which is generally HTTP. The starting location for the relative reference is the HTML page that contains the current Silverlight plug-in, also known as the site of origin.

You can download cross-domain content using WebClient APIs from the client perspective. But the server that is serving cross-domain must have a client access policy that allows cross-domain access. For more information, see Network Security Access Restrictions in Silverlight.

WebClient does not support downloads through the FILE scheme. This may be an issue if you are testing Silverlight-based applications locally in the file system, instead of developing and then deploying your Web site to a test server or to the localhost.

You can use HTTPS as the implied protocol for downloads, but only if the HTML page that contains the current Silverlight plug-in is also HTTPS.

Backslashes (\) are not permitted in URIs; always use forward slashes (/).