We examined the way Prism downloads Xap files and found that the XapModuleTypeLoader
reads the AppManifest.xaml file inside your module's Xap file, searches for the
Deployment.Parts elements and then loads the assemblies corresponding to the ones specified in the
Source property of the AssemblyPart elements in the aforementioned XML element. However, dependencies that have been placed on a zip file using Application Library Caching are referenced in
ExtensionPart elements inside the Deployment.ExternalParts element, and the physical file is stored outside your Xap file.
To illustrate this, here's how the AppManifest for a module might look like:
<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" EntryPointAssembly="SilverlightApplication1" EntryPointType="SilverlightApplication1.App" RuntimeVersion="4.0.50826.0">
<AssemblyPart x:Name="SilverlightApplication1" Source="SilverlightApplication1.dll" />
<AssemblyPart x:Name="Infrastructure" Source="Infrastructure.dll" />
<ExtensionPart Source="System.Windows.Controls.Data.Input.zip" />
<ExtensionPart Source="System.ComponentModel.DataAnnotations.zip" />
Now, when a module's Xap file has been downloaded, the XapModuleTypeLoader.HandleModuleDownloaded
method is called, which loads in memory the results obtained by calling the
XapModuleTypeLoader.GetParts method, which ignores the Deployment.ExternalParts element inside the
AppManifest.xaml file (as seen in the code snippet above):
private static IEnumerable<AssemblyPart> GetParts(Stream stream)
List<AssemblyPart> assemblyParts = new List<AssemblyPart>();
var streamReader = new StreamReader(Application.GetResourceStream(new StreamResourceInfo(stream, null), new Uri("AppManifest.xaml", UriKind.Relative)).Stream);
using (XmlReader xmlReader = XmlReader.Create(streamReader))
if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.Name == "Deployment.Parts")
using (XmlReader xmlReaderAssemblyParts = xmlReader.ReadSubtree())
if (xmlReaderAssemblyParts.NodeType == XmlNodeType.Element && xmlReaderAssemblyParts.Name == "AssemblyPart")
AssemblyPart assemblyPart = new AssemblyPart();
assemblyPart.Source = xmlReaderAssemblyParts.GetAttribute("Source");
As a possible workaround to avoid this problem from happening, we created a modified version of the
XapModuleTypeLoader (named XapModuleTypeLoaderWithLibraryCachingFix), which loads the extension parts in memory before loading the assembly parts. To do this, we modified the
IFileDownloader_DownloadCompleted method to obtain an ExternalPartsLoader and call its
LoadExtensionParts method before calling the HandleModuleDownloaded. The
ExternalPartsLoader.LoadExtensionParts method basically receives a stream, searches for the
AppManifest.xaml inside that stream, downloads the zip files containing the extension parts and loads the dll file inside them in memory. Once this operation has finished, the
HandleModuleDownloaded (which had been passed as a callback) is called, and the module loading process continues as it would regularly do. Also you can check the code of the
ExternalPartsLoader class included in the sample to verify this behavior.
You can find the sample that contains all the aforementioned modifications in my
Skydrive account under the name LibraryCachingBug.zip.
On the other hand, to make this work, we had to create a class that inherits from
ModuleManager and overrides its ModuleTypeLoaders
property to use the XapModuleTypeLoaderWithLibraryCachingFix class instead of the regular
XapModuleTypeLoader. We then overrided the ConfigureContainer
method in the bootstrapper to register this custom module manager (named
ModuleManagerWithLibraryCachingFix) in the container, so that it is used instead of the regular one.
Please note that in this sample we only created a XapModuleTypeLoaderWithLibraryCachingFix
that works with Unity; if you want to use this with Mef, you will have to apply a similar workaround modifying the
MefXapModuleTypeLoader class. Also note that this workaround might not be proper to all scenarios and has only been tested in this case.
I hope you find it useful.