A Smaller XAP Preloader for Silverlight

In many situations, it is important that your Silverlight appilcations load as quickly as possible.  For example, when building a Silverlight Advertisement, the user should not experience a significant load time.  The advertisement should load just as fast as the other page assets.  But how do you achieve this when your application includes heavy-weight resources, such as video or images?  Of course you could pull these individual assets from a server during runtime, but if your assets are packaged inside the XAP package, you need a better solution.

The most common solution I have seen is to build a small, light-weight Silverlight application that will behave as a preloader for the main application.  The preloader application will load first to provide some immediate feedback to the user and then begin downloading the main application asynchronously in the background.  As soon as the main application has finished downloading, the preloader application will instantiate and then inject the main application into its visual tree.

It is important that the preloader application is as small as possible.  However, most of the preloaders I have seen take advantage of LINQ to parse the AppManifest.xaml file.  LINQ is great, but this requires you to reference System.Xml.Linq.dll in your application.  Uncompressed, this assembly is 118KB.  If you want to minimize your XAP size, you need to eliminate this dependency.  Below, I have written a small preloader that uses the good old-fashioned XmlReader to parse the AppManifest file.  To take advantage of this preloader, simply include this class in your preloader application and call it using the example code below:

XapLoader

public class XapLoader
{
    public event EventHandler<XapLoaderEventArgs> XapLoaded;

    private string m_rootAssembly;
    private string m_typeName;

    public void LoadXap(Uri xapUri, string typeName)
    {
        m_rootAssembly = Path.GetFileNameWithoutExtension(xapUri.ToString());
        m_typeName = typeName;

        WebClient wc = new WebClient();
        wc.OpenReadCompleted += wc_OpenReadCompleted;
        wc.OpenReadAsync(xapUri);
    }

    private void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
    {
        var manifestStream = Application.GetResourceStream(
            new StreamResourceInfo(e.Result, null),
            new Uri("AppManifest.xaml", UriKind.Relative));

        string appManifest = new StreamReader(manifestStream.Stream).ReadToEnd();
        string assemblyName = m_rootAssembly + ".dll";
        XmlReader reader = XmlReader.Create(new StringReader(appManifest));
        Assembly asm = null;
        while (reader.Read())
        {
            if (reader.IsStartElement("AssemblyPart"))
            {
                reader.MoveToAttribute("Source");
                reader.ReadAttributeValue();
                if (reader.Value == assemblyName)
                {
                    var assemblyStream = new StreamResourceInfo(e.Result, "application/binary");
                    var si = Application.GetResourceStream(assemblyStream, new Uri(reader.Value, UriKind.Relative));
                    AssemblyPart p = new AssemblyPart();
                    asm = p.Load(si.Stream);
                    break;
                }
            }
        }

        if (asm == null)
            throw new InvalidOperationException("Could not find specified assembly.");

        var o = asm.CreateInstance(m_typeName);
        if (o == null)
            throw new InvalidOperationException("Could not create instance of requested type.");

        RaiseXapLoadedEvent(o);
    }

    private void RaiseXapLoadedEvent(object instance)
    {
        if (XapLoaded != null)
        {
            XapLoaded(this, new XapLoaderEventArgs(instance));
        }
    }
}

public class XapLoaderEventArgs : EventArgs
{
    public object Instance { get; set; }

    public XapLoaderEventArgs(object instance)
    {
        this.Instance = instance;
    }
}

// USAGE
...
XapLoader loader = new XapLoader();
loader.XapLoaded += loader_XapLoaded;
loader.LoadXap(new Uri("MyTestApp.xap", UriKind.Relative), "MyTestApp.Page");
...
...
private void loader_XapLoaded(object sender, XapLoaderEventArgs e)
{
    var instance = e.Instance as UIElement;
    if(instance != null)
    {
        LayoutRoot.Children.Add(instance);
    }
}

There are probably more optimizations that would shrink the size down even further.  If you can find any significant optimizations, please let me know and I’ll update the code.  I’ve attached a sample project below.  Enjoy!

 


Feedback

# re: A Smaller XAP Preloader for Silverlight

Gravatar Nice solution to load XAP dynamically.
If you integrate some code which copies Application.Resources from App.xaml in the target XAP to preloader's Application.Resources. So, you can not only keep your application resources but also keep design process with designers as well.
As you know, designers hate to touch any code, even though it's really simple copy & paste action. And it's same for me :D 5/19/2009 12:59 AM | Gongdo

# re: A Smaller XAP Preloader for Silverlight

Gravatar Why not just use the splash screen feature or a static image instead? Both would load faster than loading one xap that then loads another xap, and you will get the benefit of it happening simultanously.
With the above approach the small xap is first loaded, then JIT'ed, then run just to do it all again with a larger xap. 5/25/2009 3:11 PM | Morten

# re: A Smaller XAP Preloader for Silverlight

Gravatar Gongdo,

Good suggestion, this would be an important feature to have. 5/27/2009 8:05 AM | pbrooks

# re: A Smaller XAP Preloader for Silverlight

Gravatar Morten,

The non-managed splash screen option would most certainly offer the fastest load time and response time. But in situations where you need the power of managed code, this is the better alternative. This post was aiming at reducing load time in that situation. 5/27/2009 8:11 AM | pbrooks

# re: A Smaller XAP Preloader for Silverlight

Gravatar I noticed that in this solution, you only load the first assembly you encounter.

Why do you not load the other assemblies in the XAP? 5/28/2009 2:04 PM | Fallon Massey

# re: A Smaller XAP Preloader for Silverlight

Gravatar Thanks for the Code! Exacly what I was looking for! 6/26/2009 8:25 AM | Christian Wirth

Comments have been closed on this topic.