Telerik blogs

In the last episode we were examining a creative approach that the development team behind the CRM demo incorporated for connecting views to their Prism regions using a combination of custom attributes, attached behaviors, and the managed extensibility framework (MEF). One of the commenters was a little critical of the ‘magic’ behind MEF, and rightly so as many of us like being able to trace the exact line that code will go through as opposed to relying on the magic of composition to work everything out for us. Today we bring you one of those unexpected issues in which the ‘magic’ behind MEF is a little demystified as well as our creative solution to the problem.

Our Issue – MEF + Application Library Caching

As we are taking a modular approach to creating the CRM demo application, one of the decisions that we made early on was to utilize the caching found within Silverlight to help reduce download times and some of the overall bulk of the application. This is great for many scenarios and provides a better usability experience when done correctly, however in our case something was amiss.

Each time we were trying to load up new modules with caching enabled we were getting an error around missing dependencies. Of course we thought “How can it be, MEF resolves everything!”, expecting the magic behind the framework to deliver what the application was asking for since we were certain that all imports/exports were aligned and all references were correct when we left Visual Studio. As it turns out… they all were, everything in code was correct but MEF was having an issue of not properly recognizing cached dependencies and loading was failing. Never backing down from a challenge, our developers set out to solve this.

Manifesting a Solution

We knew already that the heart of the issue is dependencies failing to load, so we needed to figure out some method for determining what dependencies were required before MEF looks, and fails, to load cached and/or required dependencies. Thankfully Silverlight provides this information for us within the AppManifest.xaml file that is packaged up within each Xap. I opened up the Contacts module so you can see what one of these looks like:

<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" EntryPointAssembly="CRM.Modules.Contacts" EntryPointType="CRM.Modules.Contacts.App" RuntimeVersion="4.0.60310.0">
  <Deployment.Parts>
    <AssemblyPart x:Name="CRM.Modules.Contacts" Source="CRM.Modules.Contacts.dll" />
  </Deployment.Parts>
  <Deployment.ExternalParts>
    <ExtensionPart Source="System.ComponentModel.Composition.zip" />
    <ExtensionPart Source="System.ComponentModel.DataAnnotations.zip" />
    <ExtensionPart Source="System.ServiceModel.DomainServices.Client.zip" />
    <ExtensionPart Source="System.ServiceModel.DomainServices.Client.Web.zip" />
    <ExtensionPart Source="System.ServiceModel.Web.Extensions.zip" />
    <ExtensionPart Source="Telerik.Windows.Controls.zip" />
    <ExtensionPart Source="Telerik.Windows.Controls.DomainServices.zip" />
    <ExtensionPart Source="Telerik.Windows.Controls.GridView.zip" />
    <ExtensionPart Source="Telerik.Windows.Controls.Input.zip" />
    <ExtensionPart Source="Telerik.Windows.Data.zip" />
    <ExtensionPart Source="System.Windows.Data.zip" />
    <ExtensionPart Source="System.Xml.Linq.zip" />
  </Deployment.ExternalParts>
</Deployment>

Exciting, I know. :) But excitement of xaml extensionpart listings aside, we now have the list of requirements that are needed for MEF to work its magic, but contained within the Xap this is destined to fail. Our high-tech solution? Post-build, process the following event on each module to copy the AppManifest.xaml over to the ClientBin directory:

xcopy "$(TargetDir)*.xaml" "$(ProjectDir)..\CRM.Web\ClientBin\$(TargetName)" /s /y /i /r

Step one complete, we know what dependencies are needed, now we just need to fulfill them somehow.

ApplicationNavigator Leads to DependenciesEnsurer

Working within the same line of thought behind the Infrastructure project, the core application provides us with a spot to manage and maintain some key functionality related to the state and overall maintenance of the application. One way of looking at these two is that if something is required to be shared among modules, like classes, styles, and resources, you should place it within the infrastructure, but if it meets some core application requirement, like module loading or perhaps ensuring dependencies load, place that within the core of the application. This way there is a logical breakdown of what goes into which project, but regardless of how you structure these things modules will have their requirements taken care of by some central location. I know in some cases developers and teams will create something of a .Core project/library as well which they can use to replicate similar functionality easily in different solutions – whatever works for you and your team is the best case scenario, no one of these solutions will apply 100% of the time.

Back on track, as you already guessed from the section heading we are using a class named ApplicationNavigator to handle navigation activities in the overall application – in our case, loading modules. Before diving into the LoadModuleAsync method, there are three methods I won’t dig into here but require a quick mention so you know they exist:

  • ModuleIsLoaded helps to determine if the loadedModules catalog contains a given module
  • NavigateToModule does a check on ModuleIsLoaded and loads either the given URI or the default region if that module is not loaded, triggering a LoadModuleAsync call if the module isn’t loaded
  • NavigateToDefaultRegion to load the default view for the content region    

While these perform important functions in their own respect, the meat of the class is found in LoadModuleAsync:

public void LoadModuleAsync(string uri, Action callback)
{
    if (!this.ModuleIsLoaded(uri))
    {
        EventHandler<LoadModuleCompletedEventArgs> onModuleLoaded = null;
        onModuleLoaded = new EventHandler<LoadModuleCompletedEventArgs>(
            (s, e) =>
            {
                this.ModuleManager.LoadModuleCompleted -= onModuleLoaded;
                this.loadedModules.Add(e.ModuleInfo.ModuleName);
                callback();
            });
  
        this.ModuleManager.LoadModuleCompleted += onModuleLoaded;
  
        var dependencyEnsurer = new DependenciesEnsurer();
  
        EventHandler<EventArgs> onDependenciesEnsured = null;
        onDependenciesEnsured = new EventHandler<EventArgs>(
            (s, e) =>
            {
                dependencyEnsurer.EnsureProcessFinished -= onDependenciesEnsured;
                this.ModuleManager.LoadModule(uri);
            });
  
        dependencyEnsurer.EnsureProcessFinished += onDependenciesEnsured;
  
        dependencyEnsurer.RunEnsureProcessAsync(this.ModuleCatalog.Modules.Single(m => m.ModuleName == uri).Ref);
    }
}

Stepping through code, we first check if the module is loaded. It not, which is the default case since every module needs to load at some point, we set up an event and callback for adding the loaded module to out catalog and then utilize the DependenciesEnsurer (aptly named) to perform our download operations, handled in the RunEnsureProcessAsync method.

DependenciesEnsurer Leads to SynchronousDownloader

Beginning where we left off, RunEnsureProcessAsync is our entry point into DependenciesEnsurer:

public void RunEnsureProcessAsync(string pathToXap)
{
    this.pathToXap = pathToXap;
    var worker = new BackgroundWorker();
    worker.RunWorkerCompleted += worker_RunWorkerCompleted;
    worker.DoWork += worker_DoWork;
    worker.RunWorkerAsync();
}

This doesn’t look that exciting, lets see what the BackgroundWorker is doing to get a better idea of the magic at hand here:

private void worker_DoWork(object sender, DoWorkEventArgs e)
{
    string manifestString = SynchronousDownloader.DownloadString(new Uri(string.Concat(pathToXap.Replace(".xap", ""), "/", "AppManifest.xaml"), UriKind.Relative));
    XDocument xDoc = XDocument.Parse(manifestString);
    var fileNames = xDoc.Descendants().Where(xElement => xElement.Name.LocalName.Equals("ExtensionPart")).Select(xElement => xElement.LastAttribute.Value).ToList();
     
    this.LoadExternalParts(fileNames);
}

Now it’s making a little more sense… Remember those AppManifest files from before? We’re now parsing through via linq-to-xml, putting those into a list of file names, and running LoadExternalParts on that list:

private void LoadExternalParts(IEnumerable<string> fileNames)
{
    foreach (string fileName in fileNames)
    {
        //if (loadedAssemblyNamesCache.Contains(fileName.Replace(".zip", ""))) continue;
        if (AssemblyCache.HasLoadedAssembly(fileName.Replace(".zip", ""))) continue;
 
        Stream fileStream = SynchronousDownloader.DownloadFile(new Uri(fileName, UriKind.Relative)) as Stream;
 
        var assemblyStream = Application.GetResourceStream(new StreamResourceInfo(fileStream, "application / binary"), new Uri(fileName.Replace(".zip", ".dll"), UriKind.Relative)).Stream;
        Deployment.Current.Dispatcher.BeginInvoke(new Action(() =>
                                                             {
                                                                 var part = new AssemblyPart();
                                                                 var loadedAssembly = part.Load(assemblyStream);
                                                                 AssemblyCache.AddAssemblyName(loadedAssembly.FullName.Split(',').First());
                                                             }));
    }
}

Taking each file from the list, we check against an AssemblyCache (you can see refactoring in action there as the loadedAssemblyNamesCache has moved from a local reference to the AssemblyCache class) and if it hasn’t been loaded we utilize the SynchronousDownloader to download the file. When all is said and done the worker fires off EnsureProcessFinished and the callback to load this module eventually fires. Now that’s magic!

Next Episode

Next week we've got something special lined up that rhymes with "we're dropping some code" - I'll let our astute readers guess what that might mean.  In preparation for that, ensure you've got SQL Server Express R2 or any compatible version installed to be sure that our database, humble as it may be right now, will work on your system.  Now, we're not guaranteeing you'll be able to load it up and F5 into the CRM (we'll see what the next week brings), but you will be able to pour some code to see the progress we've made.  I know I'm definitely excited!

Stay tuned!


About the Author

Evan Hutnick

works as a Developer Evangelist for Telerik specializing in Silverlight and WPF in addition to being a Microsoft MVP for Silverlight. After years as a development enthusiast in .Net technologies, he has been able to excel in XAML development helping to provide samples and expertise in these cutting edge technologies. You can find him on Twitter @EvanHutnick.

Comments

Comments are disabled in preview mode.