Welcome to irritatedVowel.com Sign in | Help

POKE 53280,0: Pete Brown's Blog

Silverlight, WPF, Woodworking, .NET Programming, CNC, Nature, and other topics.
Pete is an MVP for Silverlight Click here to view the MVP profile.

A Microsoft Silverlight MVP and INETA Speaker, Pete Brown writes on a number of topics including Silverlight, WPF, .NET, woodworking and working as a consultant in the DC area. read more

Subscribe

Subscribe to my feed
Add to Technorati Favorites

Community Events



Applied Information Sciences - My Employer

who's online

AddThis Social Bookmark Button

Silverlight Assembly Preloader in Managed Code

The Silverlight preloader example on the silverlight.net quickstarts site shows how to use unmanaged code to swap in a new page over an old one once the content is loaded. In the carbon calculator, that was the approach I took, as it was the only one out there. I never liked it as it was pretty hokey, seemed brittle, and required more javascript than I wanted in what was otherwise an all-c# application.

Since then, I have found ways to do this in 100% managed code. I thought I hit a dead end on it until I got some help from the great folks participating in the Silverlight.net forums. This thread was the key to solving the problem. Thanks again to everyone who wrote in on that.

Managed Code Preloader Overview

I wrote a very simple and quick managed code preloader, for illustrative purposes (in other words, this is a sample, not production-ready code). There are lots of different ways to manage preloading content and assemblies, but I decided to follow a simple discovery and interface-based approach to loading a single DLL from the server. The assumption is that you will have a shim in your main Page.xaml that does little more than keep the user occupied while it downloads assets and the other assemblies. You want that shim to be as light as possible so it loads quickly. Once all the downloading is complete, you will display the content on your main page, using a model similar to the UserControls as Screens pattern I previously posted about.

The main SIlverlight app never needs a compile-time reference to the downloadable assembly. In fact, you won't want to have any reference to it as (if you use code from that DLL in your code) it will cause the DLL to get downloaded alongside your primary assembly - defeating the purpose.

How it works

The preloader takes a fully qualified assembly name as well as a URL from which to download the assembly. Under the hood, it uses the downloader object to grab the assembly and reflection to load the assembly into memory, iterate its contents, and return back references to all IPlugin-implementing classes in the assembly. The calling code then calls IPlugin.RootElement to get the instance of the root element (a usercontrol) from the plugin assembly which it then adds to a canvas on the main page. While I confine the plugin to a small part of the main canvas, you could, and likely would, have it completely overtake the canvas area.

The interface I use looks like this.

    public interface IPlugin
    {
        FrameworkElement RootElement { get; }

        // you can put in lots of other things here, like an Activate or Start method, 
        // functions to provide access to a common in-memory data store etc.
    }

Here is the code that shows how to instantiate the assembly that was downloaded.

    // load the assembly into our app domain
    Assembly pluginAssembly = Assembly.Load(assembly.AssemblyName);

    Type[] types = pluginAssembly.GetTypes();

    // enumerate all types in the assembly and pull out all the ones that are of type IPlugin
    // this allows for more than one IPlugin per assembly.
    foreach (Type type in types)
    {
        if (type.GetInterface(typeof(IPlugin).FullName, false) != null)
        {
            // instantiate and load the plugin
            IPlugin instance = (IPlugin)Activator.CreateInstance(type);

            _plugins.Add(instance);
        }
    }

The code that calls the preloader from the shim looks like this:

    statusText.Text = "About to load plugins.";

    // this event will fire when all plugins have been loaded and instantiated
    _pluginManager.PluginsLoaded += new EventHandler(OnPluginsLoaded);
    _pluginManager.PluginsDownloadFailed += new EventHandler(OnPluginsDownloadFailed);
    _pluginManager.PluginError += new EventHandler(OnPluginError);

    // background download and instantiate all plugins
    _pluginManager.DownloadAndLoadAllPlugins(
        new PluginAssembly("PeteBrown.SilverlightAppDownload.LibOne, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", 
        new Uri("PeteBrown.SilverlightAppDownload.LibOne.dll", UriKind.Relative)));

    statusText.Text = "Call to load plugins completed.";

(The reliance on statusText was to allow me to better debug what was going on when I ran it from my web server.)

Finally, the plugin's visual element needs to be added to the tree. That's a pretty simple task in Silverlight (pluginContainer is a canvas):

    FrameworkElement element = plugin.RootElement;

    element.SetValue(Canvas.LeftProperty, 0);
    element.SetValue(Canvas.TopProperty, 0);
    element.Width = pluginContainer.Width;
    element.Height = pluginContainer.Height;
    element.Visibility = Visibility.Visible;

    // add the plugin to our visual tree
    pluginContainer.Children.Add(element);
    statusText.Text = "Element added to tree";

 

Notes

The downloader will throw an exception if you run the code from the file system. For that reason, you need to set up a Silverlight project that runs on a web site (local or remote), or comment out the downloader code (temporarily) and just pretend that part works

This code doesn't download any other referenced assemblies, and I haven't tested to see how that functions, or if it functions at all (for example, to see if the runtime is smart enough to do the download for us when the assembly is loaded). Right now, I assume you will need to use the Downloader object to download other assemblies, and use my code to download the plugin assembly.

You can run the sample here. The source code is available here.

If you clear your cache and load up Fiddler, you can see exactly when the assemblies are downloaded to your machine. Make sure you clear your cache first, though, or you won't see much of anything :) 

Like all my other Silverlight samples, I expect this will change in later versions of Silverlight 1.1. In the mean time, I hope this helps you work out preloading code and assets in your Silverlight 1.1 applications.

 

  Add to Technorati Favorites
Posted: Friday, November 09, 2007 11:16 PM by Pete.Brown
Filed under: ,

Comments

Christopher Steen said:

ASP.NET The REST-Like Aspect Of ASP.NET MVC [Via: Haacked ] WPF Routed Event Viewer [Via: Karl Shifflett...
# November 11, 2007 12:23 AM

WynApse said:

Silverlight Cream for November 11, 2007 -- #123
# November 11, 2007 7:28 PM

Community Blogs said:

Pete Brown shows how to write an assembly preloader in managed code and Michael Washington reproduced
# November 11, 2007 8:02 PM

Arne Claassen said:

I thought the purpose of using javascript to handle the preloading was to avoid the start-up penalty of the CLR. Have you compared how fast your managed preloader comes up compared to the javascript one? I'm curious because your method is oh so much more appealing than going javascript.
# November 12, 2007 3:37 PM

Pete.Brown said:

Hi Arne

The startup penalty is almost nothing. I don't consider it to be an issue at all, especially when compared to the download penalty.

I have used both approaches: the unmanaged version in the carbon calculator (hated it and it felt brittle) and the managed version here. Both perform well enough to eliminate any real concerns.

Pete

# November 12, 2007 3:47 PM

Arne Claassen said:

Pete, Thanks for information. I just tried your sample out and, yes, it comes up immediately, so the execution time to get the CLR going certainly is not a concern. I know this isn't a datapoint of significance, but the javascript payload for a downloader is 2k, vs. 23k for your base DLLs. Now, just to void my whole point about download size, taking your downloader code and building a very simple, lightweight Inversion of Control Container on top of it could be an amazingly useful foundation for Silverlight apps.
# November 12, 2007 5:16 PM

POKE 53280,0: Pete Brown's Blog said:

In a break from some of my previous Silverlight talks, today at the NoVA Code Camp, I did a mostly slideless
# November 17, 2007 6:22 PM

POKE 53280,0: Pete Brown's Blog said:

Assem in this thread on Silverlight.net pointed out that the downloader object isn't required to
# November 18, 2007 8:04 PM

Jeff Wilcox said:

You'll receive a FileNotFoundException error when trying to use an assembly that references another assembly that isn't loaded in the appdomain. For instance, if you have a plugin called A.dll, and it contains a reference for B.dll... and a method call or property in A.dll needs to use a B.dll type, then the error will pop up before the CLR completes that call. To work around this, you can manually download the assembly part and make sure to call AssemblyPart.Load(...) on B.dll -before- it is needed by A.dll. No trouble then. Hope this makes sense to anyone running into issues with dependencies that weren't needed by the calling application, but were required by the plugin/floating DLL. -Jeff
# July 1, 2008 7:31 PM

MIchael Sync said:

>>type.GetInterface Seems like it doesn't support in SL2. What would be the similar thing for type.GetInterface in SL2?
# December 17, 2008 1:03 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 

Enter the text you see in the image:

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS