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!