Even though Silverlight has fallen out of grace with the powers that be, there are still some companies using it today for entertainment or line of business applications. Particularly for that last category, modularity can be a powerful tool in creating a flexible solution.
Over the past few years, I've worked with modular Silverlight applications based on Microsoft Prism. Every module is it's own XAP file, which is loaded on demand by the framework. Over the years, people have also rolled their own lightweight modular applications, also making use of XAP files. This article is about a specific problem with building these types of applications: optimization of the total application download size.
The Issue
Every individual XAP module, including the main Silverlight application, will depend on a number of assemblies. Normally, these assemblies (except the ones that are included in the Silverlight runtime) as included in the XAP file. This is fine for single XAP applications, but if we have multiple XAP files (a main application and several modules), there is likely to be an overlap in these dependencies. It would be a waste of download time and bandwidth if these assemblies were included in each and every XAP file.
Silverlight has a feature called Application Library Cache which looks like it might help us with this problem. It makes it possible to place dependencies in a ZIP file outside the normal XAP file which will then be loaded on demand. Unfortunately, Prism does not support this mechanism unless you do some heavy customization. Even with the feature, it's rather difficult to control exactly what gets put into the XAP file however. Visual Studio seems to ignore the .extmap.xml files that are supposed to control the behavior under some circumstances.
There is another way of controlling which assemblies get included into your XAP file: the Copy Local property on the reference inside each of your projects. If you set Copy Local to false, the file will not be copied to the project's output directory and it will also not be included in the XAP file. It sounds pretty straightforward, but unfortunately it's not. Visual Studio only takes the Copy Local options that are on the XAP project into account. Suppose your application has a dependency structure like this:
- Main Application
- Microsoft.CSharp.dll
- System.Xml.Linq.dll
- Module A
- MyLibrary.dll (Copy Local = false)
- Microsoft.CSharp.dll (Copy Local = true)
- System.Xml.Linq.dll (Copy Local = true)
These standard assemblies will end up both in MainApplication.xap and ModuleA.xap, wasting 221 KB in downloads (compressed) for each of your users. After all, any module is always loaded after the main application, so this particular assembly is already loaded into the application domain. You cannot put MyLibrary.dll on Copy Local, because it too will not be included in the XAP file and the application will fail.
There is a way to trick Visual Studio to exclude the duplicate assemblies. You can add them as a reference on the Module A project and then set Copy Local to false there. This works fine, but unfortunately, it's incredibly unmaintainable. What if a new assembly dependency is added on the main application, so the module no longer needs to include it? What if your colleague decides to use his favorite refactoring tool to remove unused references? You can start all over with your optimization. Very uncool.
XapReduce
To address this issue, I've written a simple command line tool which analyzes the contents of your XAP files and removes any unneeded assemblies. The idea is that you can easily include this tool in a post-build step for your module and have it take care of this optimization.
You will have to provide the list of XAP files that need to be analyzed on the command line. The tool cannot determine which module will be loaded before which, so you have to provide this information on the command line:
You've just reduced the size of your module to a mere 11% of the original size! You can specify more than 1 source file, and you can provide a filename for the output as well if you prefer to not overwrite the original. Use XapReduce --help for a full list of options.
How it Works
Basically, XAP files are ZIP files with an AppManifest.xaml embedded in them, so it is quite easy to modify them using the new .NET 4.5 ZipFile class. Full source code can be found on GitHub (under MIT license), including unit tests for 100% code coverage. I've used this as a trial project to familiarize myself with the NSubstitute isolation framework .
If you don't want to bother with source code, there is a binary download as well.
Unpredictable Loading Orders
What if there are common assembly dependencies between two modules, but there is no fixed order in which these modules are loaded? You can solve this by defining a third module and loading it before either other module. If you're using Microsoft Prism, you can do this by defining a dependency between the modules in configuration. This utility will not save you from doing some manual optimization, but you can still use it after creating this common module:
XapReduce --input ModuleA.xap --sources MainApplication.xap CommonModule.xap
XapReduce --input ModuleB.xap --sources MainApplication.xap CommonModule.xap
Files: