Library Caching of Toolkit break when using with Prism 4

Topics: Prism v4 - Silverlight 4
Nov 14, 2011 at 3:34 AM

When one of my module contains a view with Label (System.Windows.Controls.Data.Input, Silverlight Toolkit)

and I used library caching, it throw error about filenotfound exception.

it worked if NOT using library caching (reduce xap filesize)

and when I test without prism, library caching with Label worked ok.

is this a known issue in Prism 4 using library caching?

I have uploaded a sample: https://skydrive.live.com/?cid=e6666d4e95ba2779&sc=documents&uc=1&id=E6666D4E95BA2779%21146#

LibraryCachingBug.zip

There are 2 views in SilverlightApplication1:

SilverlightControl1 (just a TextBlock)

SilverlightControl2 (just a Label)

the module is now straight poping the 2nd view, it break at runtime with library caching.

using SilverlightControl1 is ok, so its nothing wrong with my codes.

Developer
Nov 14, 2011 at 8:48 PM

Hi,

We have downloaded you repro-sample application and we could reproduce the problem you are mentioning. We have also found that if you add a reference to the System.Windows.Controls.Data.Input.dll library in the Shell project, this problem doesn't appear and the application works correctly.

It seems that when the SilverlightApplication1 project is discovered as a module and the SilverlightControl2 control is initialized, the System.Windows.Controls.Data.Input.dll library is missing and the exception is thrown.

Based on my understanding the reason behind this problem might be related to the fact that the SilverlightApplication1 project is not referenced by the Shell project (which is the recommended approach when using Prism). When the Silverlight application is compiled, the only project that the application "knows" is the Shell project, which doesn't have a reference to the SilverlightApplication1 project or to the System.Windows.Controls.Data.Input.dll library. So, it might be possible that, as the Shell project doesn't know the System.Windows.Controls.Data.Input.dll library or any project that requires it, the library is not "cached."

I hope you find this useful,

Damian Cherubini
http://blogs.southworks.net/dcherubini

Nov 15, 2011 at 2:50 AM
DCherubini wrote:

Hi,

... So, it might be possible that, as the Shell project doesn't know the System.Windows.Controls.Data.Input.dll library or any project that requires it, the library is not "cached."

....

Damian Cherubini
http://blogs.southworks.net/dcherubini


does this mean, if my another module using another other extra/difference dependencies, I must add them onto Shell if using library chaching?

But it worked without library caching, as Shell still has no reference to this extra thing?

i come across an article about library cahing, manually zipping and grouped those dependencies in different way. I can't find this article anymore. Dunno will this help resolve this kind of problem?

Developer
Nov 15, 2011 at 8:51 PM

Hi,

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">
  <Deployment.Parts>
    <AssemblyPart x:Name="SilverlightApplication1" Source="SilverlightApplication1.dll" />
    <AssemblyPart x:Name="Infrastructure" Source="Infrastructure.dll" />
  </Deployment.Parts>
  <Deployment.ExternalParts>
    <ExtensionPart Source="System.Windows.Controls.Data.Input.zip" />
    <ExtensionPart Source="System.ComponentModel.DataAnnotations.zip" />
  </Deployment.ExternalParts>
</Deployment>

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)) { xmlReader.MoveToContent(); while (xmlReader.Read()) { if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.Name == "Deployment.Parts") { using (XmlReader xmlReaderAssemblyParts = xmlReader.ReadSubtree()) { while (xmlReaderAssemblyParts.Read()) { if (xmlReaderAssemblyParts.NodeType == XmlNodeType.Element && xmlReaderAssemblyParts.Name == "AssemblyPart") { AssemblyPart assemblyPart = new AssemblyPart(); assemblyPart.Source = xmlReaderAssemblyParts.GetAttribute("Source"); assemblyParts.Add(assemblyPart); } } } break; } } } return assemblyParts; } (...)

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.

Agustin Adami
http://blogs.southworks.net/aadami