Friday, April 25, 2008

Using the WebClient to Download Content On-Demand

Source (Beta 2): The Beta 2 source is available here.


In this post I'm going to describe how you can use the WebClient class in Silverlight 2 to download content on-demand.

Firstly why would you want to download something on-demand? Say your application is displaying images, video, lots of XAML etc. Now if you allow the appplication to download all of this initially to the client, the user may get impatient because of the long waiting period. You may lose your user even before he has had a first look at your application.

So it makes sense to only include in the initial download, what is absolutely essential for the application to load. Once the application is loaded, you can start downloading other parts of the application behind the scenes. This is where downloading content on-demand comes into picture.

We'll look at three scenarios.

  1. Downloading a loose image file on-demand.

  2. Downloading a zip file on demand and retrieving an image file.

  3. Downloading an assembly on demand, and thereafter using classes within the assembly


The source for the sample application I created to illustrate this is available here.



Downloading a loose image file on-demand




In Silverlight 1.0, the class used for downloading content on-demand was the Downloader class. In Silverlight 2, we use the WebClient class.

private void Button_Click_1(object sender, RoutedEventArgs e)

{

WebClient wc = new WebClient();

wc.OpenReadCompleted += new OpenReadCompletedEventHandler(wc_OpenReadCompleted1);

wc.OpenReadAsync(new Uri("bird.jpg", UriKind.Relative));

}


void wc_OpenReadCompleted1(object sender, OpenReadCompletedEventArgs e)

{

if (e.Error == null)

{

StreamResourceInfo sri = new StreamResourceInfo(e.Result as Stream, null);

BitmapImage imgsrc = new BitmapImage();

imgsrc.SetSource(sri.Stream);

ImgToFill.Source = imgsrc;

}

}


This code is quite self-explanatory. You need to call the OpenReadAsync method ofthe WebClient, and then retrieve the image in the OpenReadCompleted event.


Downloading a package (zip file) on-demand and extracting an image file

In this case the code is similar.

To download a package
  1. Copy zip next to xap file
  2. Download as stream using WebClient
  3. Use StreamResourceInfo class to retrieve the desired part from the package stream
private void Button_Click_2(object sender, RoutedEventArgs e)

{

WebClient wc = new WebClient();

wc.OpenReadCompleted += new OpenReadCompletedEventHandler(wc_OpenReadCompleted2);

wc.OpenReadAsync(new Uri("mediaassets.zip", UriKind.Relative), "Garden.jpg");

}


void wc_OpenReadCompleted2(object sender, OpenReadCompletedEventArgs e)

{

if (e.Error == null)

{

StreamResourceInfo sri = new StreamResourceInfo(e.Result as Stream, null);

String sURI = e.UserState as String;

StreamResourceInfo sri2 = Application.GetResourceStream(sri, new Uri(sURI, UriKind.Relative));

BitmapImage imgsrc = new BitmapImage();

imgsrc.SetSource(sri2.Stream);

ImgToFill.Source = imgsrc;

}

}

Notice that after downloading the zip package, we did not have to unzip it explicitly. The StreamResourceInfo class took care of that.


Downloading an assembly on-demand and extracting an image file

What do we mean by downloading an assembly on-demand? Say you a large assembly that contains content like user controls, XAML etc. that you want to use in the application, but not in the first page that the application loads.

In this case you you can download the assembly on-demand instead of loading it at the beginning when the application is loaded by the client.

One question I had when I read of this concept was, wouldn't you have to reference the assembly in the application at design time itself to compile code that makes of its classes? And if you reference the assembly, won't it become part of the application's initial download to the client?

Yes, it is true that you need to reference the assembly, but you set the private attribute of the csproj file for that assembly to false. In this case, the assembly will not become part of the xap package, but you can still call classes of this assembly in the code base.

<ProjectReference Include="..\LargeSizeAssembly\LargeSizeAssembly.csproj">

<Project>{51238BB3-5EC5-4B01-BABD-779DBF69F623}Project>

<Name>LargeSizeAssemblyName>

<Private>FalsePrivate>

ProjectReference>

Summarizing the steps, this is what you need to do:
  1. Add reference to the assembly
  2. In the csproj file:set the 'Private' tag for that assembly to False
  3. Copy assembly next to xap file
  4. Download as stream using WebClient
  5. Use AssemblyPart class to load assembly to current AppDomain
  6. Now start using classes in the assembly

private void Button_Click_3(object sender, RoutedEventArgs e)

{

// Download assembly "on-demand"

WebClient wc = new WebClient();

wc.OpenReadCompleted += new OpenReadCompletedEventHandler(wc_OpenReadCompleted3);

wc.OpenReadAsync(new Uri("LargeSizeAssembly.dll", UriKind.Relative));

}


void wc_OpenReadCompleted3(object sender, OpenReadCompletedEventArgs e)

{

if (e.Error == null)

{

//Uncommenting the line below will throw an exception because the assembly

//in which Page2 resides has not been loaded into current AppDomain as yet

//Page2 page2 = new Page2();


// Convert the downloaded stream into an assembly that is

// loaded into current AppDomain

AssemblyPart assemblyPart = new AssemblyPart();

assemblyPart.Load(e.Result);

//now access classes in the downloaded assembly

DisplayPageFromLibraryAssembly();

}

}


private void DisplayPageFromLibraryAssembly()

{

// Instantiate type from assembly

Page2 page2 = new Page2();

// display it

LayoutRoot.Children.Add(page2);

}


You can see how powerful this option of downloading on-demand is. The source for the sample application I created to illustrate this is available here.

17 comments:

Sajiv said...

Great job. An excellent post!!

Sylvie said...

Hi Jim,

I was looking for a post like yours. Thanks a lot for taking time to explain it so well to us ;)
I do have however a question: You took the example of an Image resource assigned as None. What about taking a video. I suppose the code will change a little bit, since it´s not a BitmapImage but an Uri. How will then looks the function wc_OpenReadCompleted(...,...) like?
Thanks in advance for answering!

Sylvie (a SL fan ;))

Jim Mangaly said...

Hi Sylvie,

If you want to use a video, you should use the MediaElement to display the video. The MediaElement has a SetSource method that can take the stream.

void wc_OpenReadCompleted1(object sender, OpenReadCompletedEventArgs e)
{
if (e.Error == null)
{
StreamResourceInfo sri = new StreamResourceInfo(e.Result as Stream, null);

myMediaElement.SetSource(sri.Stream);
}
}

Hope this helps,
Jim

Sylvie said...

Hi Jim,
Thanks for your reply. It did help indeed!
Now I wrote this from your code:
ImgToFill.Source = imgsrc;
And I got the error message that ImgToFill doesn´t exist and I am asked if I miss an Assembly. Where did you declare this Variable or which class is it?
Thanks again,

Sylvie

Sylvie said...

Oh I know, it´s the name of an Image Element declared in the page.xaml.
thanks anyway... ;) Have a nice day!

achu said...

excellent post. i was looking for a solution to dynamically load and use a user control, your post helped a lot.

achu.
alwaye.

Michael Washington said...

This was a really post. Thanks!

Michael Washington said...

I made an adaption (I allow you to inject any assembly) I placed it here: http://www.adefwebserver.com/DotNetNukeHELP/Misc/Silverlight/SilverlightDesktop/

Jim Mangaly said...

Nice! I like the draggable window you used in your sample. Thanks Michael!

Anonymous said...

Thanks for the sample. Though what about combining examples 2 & 3. Download a zip that contains many assemblies, then iterate through them loading them into the AppDomain. And maybe also saving some of them into IsolatedStorage.

sandman said...

Nice example, but the dll download fails in FireFox 3.0; It keeps saying transfering data , but never completes

JUNAID said...

Great work man

BritneySpears said...

Excellent article. It proved very usefull

Tamer said...

i cant imagine britney spears coding lol..

Cialis said...

Thank you for this tutorial!

w hotel in los angeles said...

looks cool. I am grateful for the information given. Thanks for giving us this useful information.

Bishal Santra said...

Thanks helped me a lot...