Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Silverlight Assembly Preloader in Managed Code

Pete Brown - 09 November 2007

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.

 

   
posted by Pete Brown on Friday, November 9, 2007
filed under:    

5 comments for “Silverlight Assembly Preloader in Managed Code”

  1. Arne Claassensays:
    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.
  2. Pete Brownsays:
    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
  3. Arne Claassensays:
    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.
  4. Jeff Wilcoxsays:
    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

Comment on this Post

Remember me

5 trackbacks for “Silverlight Assembly Preloader in Managed Code”

  1. Christopher Steensays:
    ASP.NET The REST-Like Aspect Of ASP.NET MVC [Via: Haacked ] WPF Routed Event Viewer [Via: Karl Shifflett...
  2. WynApsesays:
    Silverlight Cream for November 11, 2007 -- #123
  3. Community Blogssays:
    Pete Brown shows how to write an assembly preloader in managed code and Michael Washington reproduced
  4. POKE 53280,0: Pete Brown's Blogsays:
    In a break from some of my previous Silverlight talks, today at the NoVA Code Camp, I did a mostly slideless
  5. POKE 53280,0: Pete Brown's Blogsays:
    Assem in this thread on Silverlight.net pointed out that the downloader object isn't required to