Building Hello MEF – Part IV – DeploymentCatalog

Continuing on with the series. In part III we introduced the concept of application partitioning through the use of the PackageCatalog which ships in the Silverlight toolkit. In this post we’ll take a look at a new API known as the DeploymentCatalog which ships in the box! We also look at some changes to our hosting APIs. If you are following along, the completed code from that post is available here.

Note: Instead of using the MEF binaries that ship as part of SL4 beta, we’re going to use our newer bits available on Codeplex from this point forward. It is very likely these same APIs will be available in the box when SL4 ships ;-)

DeploymentCatalog

With our latest codeplex drop we introduced a new API called DeploymentCatalog. DeploymentCatalog is basically a redesign of the catalog that we included in the toolkit. As part of the redesign we address some thread-safety issues as well as made the catalog more robust.

image

DeploymentCatalog (DC) is used for dynamically downloading MEF catalogs in an async fashion. To use it you call the DownloadAsync method passing in a uri with the default overload accepting a relative uri. The uri should point to a XAP which contains MEF parts. Once you initiate the download, DC will start the download. Upon completion DC will rip open the XAP, load all the assemblies within and add any attributed parts to the catalog. You’ll notice that unlike PackageCatalog which holds a collection of packages for multiple XAPs, each DC corresponds to a single XAP.

Below is a snippet of how to use it.

var catalog = new DeploymentCatalog("Extensions.xap");
catalog.DownloadAsync();

Passing deployment catalogs to PartInitializer

In the previous bits, CompositionHost.InitializerContainer was called in order to pass a container configured with a PackageCatalog which PartInitializer could use.In the new bits PartInitializer has been renamed to CompositionInitializer (more on that later). Additionally we’ve renamed InitializeContainer to Initializer and added a new convenience overload which accepts a param array of catalogs. When you use this overload you need to decide if you want the default XAP’s parts to be added. To do this pass in a DeploymentCatalog created with the default constructor such as the example below:

CompositionHost.Initialize(new DeploymentCatalog(), catalog);

Using the catalog from the previous example this will add all of the parts from the current XAP as well as any parts that are discovered in “Extensions.xap”.

Recomposition in DeploymentCatalog

Each DC downloads in an asynchronous manner. Because DC is recomposable (implements INotifyComposablePartCatalogChanged), it will notify whenever the download completes. This means that you are not required to subscribe to the DownloadCompleted event, as because the catalog is recomposable, it will force the container to recompose upon the receipt of parts. In the previous example, adding a catalog to DC does not mean that it’s parts have been downloaded yet. However once those parts are downloaded, the container will recompose.

Tracking completion, errors and progress.

You can, and we recommend you do track download completion and errors. Whenever a download completes, the catalog will raise a “DownloadCompleted” event. If the download fails for some reason, then DownloadCompleted will be fired setting the Error to the download error. You can also track overall progress of the download by subscribing to the DownloadProgressChanged event. Finally if your application logic requires it you can even cancel the download by calling CancelAsync.

For example see the snippet below.

void DownloadCatalog(string uri) {
    var catalog = new DeploymentCatalog(uri);
    catalog.DownloadCompleted += new EventHandler<AsyncCompletedEventArgs>(DownloadCompleted);
    catalog.DownloadProgress += new EventHandler<DownloadProgressChangedEventArgs>(DownloadProgressChanged);
    catalog.DownloadAsync();

    //cancel the catalog download due to a timeout
    catalog.CancelAsync();
}

void DownloadCompleted(object sender, AsyncCompletedEventArgs e)
{
    if (e.Error != null)
         throw e.Error;
}

void catalog_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
  //progress logic here
}

Allowing parts to download XAPs.

In the last post we added the PackageCatalog directly to the container in order to allow parts to import it. A cleaner approach is to encapsulate the catalog management into a nice service that other parts can depend on. This allows the host to have more fine-grained control over how and what enters the container. In the sections that follow you’ll see I’ve introduced a DeploymentCatalogService for this purpose.

Migrating the code

OK, let’s start migrating the code to use the new DeploymentCatalog apis. Again the starter code is available here

Download MEF bits

First we need to go download the latest MEF bits from Codeplex from here. Extract the zip (make sure to unblock all files if you are letting windows extract it for you). Next open the HelloMEF solution in Visual Studio and go and remove all references to SystemComponentModel.Composition.dll, System ComponentModel.Composition.Initialization.dll, and SystemComponentModel.Composition.Packaging.Toolkit.dll from each project.

Fix MEF References

Now go add references to the newer binaries located in the .\bin\SL folder. Here are the projects and references.

  • HelloMEF –> Add System.ComponentModel.Composition.dll and System.ComponentModel.Composition.Initialization.dll.
  • HelloMEF.Contracts –> Add System.ComponentModel.Composition.dll.
  • HelloMEF.Extensions –> Add System.ComponentModel.Composition.dll.

Introducing DeploymentCatalog service.

In order to handle initializing the host as well allowing other parts to download, we’ll create DeploymentCatalogService.

First add a new interface to HelloMEF.Contracts using the following code.

public interface IDeploymentCatalogService
{
    void AddXap(string uri, Action<AsyncCompletedEventArgs> completedAction = null);
    void RemoveXap(string uri);

}

Next add a new DeploymentCatalogService to the HelloMEF project.

[Export(typeof(IDeploymentCatalogService))]
public class DeploymentCatalogService : IDeploymentCatalogService
{
    private static AggregateCatalog _aggregateCatalog;

    Dictionary<string, DeploymentCatalog> _catalogs;

    public DeploymentCatalogService()
    {
        _catalogs = new Dictionary<string, DeploymentCatalog>();
    }

    public static void Initialize()
    {
        _aggregateCatalog = new AggregateCatalog();
        _aggregateCatalog.Catalogs.Add(new DeploymentCatalog());
        CompositionHost.Initialize(_aggregateCatalog);
    }

    public void AddXap(string uri, Action<AsyncCompletedEventArgs> completedAction = null )
    {
        DeploymentCatalog catalog;
        if (!_catalogs.TryGetValue(uri, out catalog))
        {
            catalog = new DeploymentCatalog(uri);
            if (completedAction != null)
                catalog.DownloadCompleted += (s, e) => completedAction(e);
            else
                catalog.DownloadCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(catalog_DownloadCompleted);

            catalog.DownloadAsync();
            _catalogs[uri] = catalog;
        }
        _aggregateCatalog.Catalogs.Add(catalog);
    }

    void catalog_DownloadCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
    {
        if (e.Error != null)
            throw e.Error;
    }

    public void RemoveXap(string uri)
    {
        DeploymentCatalog catalog;
        if (_catalogs.TryGetValue(uri, out catalog))
        {
            _aggregateCatalog.Catalogs.Remove(catalog);
        }
    }
}

DeploymentCatalogService handles adding / removal of DeploymentCatalogs. It also exposes a static Initialize method which handles setting up the host to allow dynamic download. This method creates an aggregate catalog, and adds the parts in the default XAP to it. It then calls CompositionHost.Initialize to pass that catalog to the host. Once the host has been configured, any catalogs added to the aggregate will then automatically show up in the host.

Note: DeploymentCatalogService is simply a helper, it is NOT required in order to use DeploymentCatalog.

Initializing the host

Now that DeploymentCatalogService is added, go remove the old Initialization code which used PackageCatalog from the App class and have it call Initialize.

private void Application_Startup(object sender, StartupEventArgs e)
{
    DeploymentCatalogService.Initialize();
    this.RootVisual = new MainPage();
}

Refactoring Widget1 to use DeploymentCatalogService

Next go change Widget1.xaml.cs replacing the importing PackageCatalog with IDeploymentCatalogService.

[ExportWidget(Location=WidgetLocation.Top)]
public partial class Widget1 : UserControl
{
    [Import]
    public IDeploymentCatalogService CatalogService { get; set; }

    public Widget1()
    {
        InitializeComponent();
        Button.Click += new RoutedEventHandler(Button_Click);
    }

    void Button_Click(object sender, RoutedEventArgs e)
    {
        CatalogService.AddXap("HelloMEF.Extensions.xap");
    }
}

Build it!

Go build the project and run it. Press the top “Hello MEF” button and you should see a new green part appear which was dynamically downloaded through the new DeploymentCatalog.

image

 

A few caveats about DeploymentCatalog.

DeploymentCatalog is a very exciting addition to the MEF box. There are a few caveats to it’s usage.

  • Does not support Silverlight cached assemblies. (Otherwise known as TPEs). This means that DC will not download referenced assemblies that are packaged with the Silverlight caching infrastructure (.extmap).
    • This includes cached assemblies in the main xap.
    • If the cached assemblies are referenced by the main app, they will be available to the assemblies in a downloaded XAP.
    • You can create shared xaps which contain assemblies to be used by other xaps and download those with the DeploymentCatalog. As long as the shared xaps are downloaded first they will be available to others.
    • If you do decide to use either of the previous approaches, be sure to set your references to “Copy Local = False” in your XAPs otherwise you will be embedding the shared assemblies again.Does not support localization.
    • We are looking into cached assembly support in the future.
  • Does not support non-embedded resources.
  • Local resource references in XAML also are not supported i.e. using pack URIs. You can programatically access embedded resources though.

Summary

Used properly, DeploymentCatalog is a great way to partition your Silverlight applications in order to improve startup time as well as allowing third-parties to extend your Silverlight applications.

Completed code is attached.

This entry was posted in HelloMEF, MEF, silverlight, SL4. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • phi

    Hi Glenn,

    first of all a BIG thank you – you provided the kick start I needed.
    One minor comment:
    In the DeploymentCatalogService.AddXap method you alwasy add a new instance of the DeploymentCatalog to the AggregateCatalog.
    When you call AddXap twice for the same uri an error is thrown.
    So changing that to

    if (_catalogs.ContainsKey(uri))
    return;

    would make sense I guess.

    As I told – a minor comment.

    phi

  • Frank

    Very interesting article.

    I’m using the deployment service in my own project.
    I have one question though. During testing I saw that after removing a xap from the container (method RemoveXap) it will not be added to the container if it’s downloaded again. Doesn’t the method RemoveXap need a statement like _catalogs.Remove(uri); to remove it from the dictionary also so that it will be added to the container again on a future download?

    Frank.

  • Nk54

    When you clic the first time on the button, it dowloads the xap.
    Try to click the button again, you get a white page :)
    does that means that we always have to check if the xap is already download ?
    What does that change in a scenario where you get a menu where each item are linked to a xap ? -> if i click another time on the menu, what will that make ?

    Thanks.

  • Lorenzo

    Hi Glenn, absolutely fantastic!! Thanks a lot for your tutorial.

    I have two question for you.

    1) In this sample you have Widget1. This UserControl allows to download Widget3. ok? Now, I would have only Widget3 (contained into HelloMEF.Extensions) and download it from HelloMEF project without using Widget1. Is it possible?

    2) Once, Widget3 is downloaded, how can I obtain a its reference from MainPage.xaml.cs (of HelloMEF project)? Is it possible to obtain a reference of all the components included in the downloaded XAP from HelloMEF project?

    Thanks in advance. Regards.

  • Przemek

    Oof, I have managed to run the sample project as well. I had to go and unblock each file that was blocked in it. Took me some time, but that was it. Now, it compiles and runs as well.
    Przemek

  • Przemek

    Thanks,

    removing the blocks on the dlls helped. Although, surprisingly it did not build for some time, only after 3 or 4 attempts, without any changes in the source, it all of a sudden ran without any problems. That is in the project that I created on my own basing on the tutorial.
    In the downloaded solution, I removed the existing references and replaced them with the same unblocked bits and it still doesn’t build. The error I get is this:

    Error 1 The “ValidateXaml” task failed unexpectedly.
    System.IO.FileLoadException: Could not load file or assembly ‘file:///C:\Users\Przemek\Downloads\HelloMEF_Part_IV\HelloMEF\Bin\Debug\System.ComponentModel.Composition.dll’ or one of its dependencies. Operation is not supported. (Exception from HRESULT: 0×80131515)
    File name: ‘file:///C:\Users\Przemek\Downloads\HelloMEF_Part_IV\HelloMEF\Bin\Debug\System.ComponentModel.Composition.dll’ —> System.NotSupportedException: An attempt was made to load an assembly from a network location which would have caused the assembly to be sandboxed in previous versions of the .NET Framework. This release of the .NET Framework does not enable CAS policy by default, so this load may be dangerous. If this load is not intended to sandbox the assembly, please enable the loadFromRemoteSources switch. See http://go.microsoft.com/fwlink/?LinkId=155569 for more information.

    at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
    at System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
    at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection, Boolean suppressSecurityChecks)
    at System.Reflection.RuntimeAssembly.InternalLoadFrom(String assemblyFile, Evidence securityEvidence, Byte[] hashValue, AssemblyHashAlgorithm hashAlgorithm, Boolean forIntrospection, Boolean suppressSecurityChecks, StackCrawlMark& stackMark)
    at System.Reflection.Assembly.LoadFrom(String assemblyFile)
    at Microsoft.Silverlight.Build.Tasks.ValidateXaml.XamlValidator.Execute(ITask task)
    at Microsoft.Silverlight.Build.Tasks.ValidateXaml.XamlValidator.Execute(ITask task)
    at Microsoft.Silverlight.Build.Tasks.ValidateXaml.Execute()
    at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
    at Microsoft.Build.BackEnd.TaskBuilder.ExecuteInstantiatedTask(ITaskExecutionHost taskExecutionHost, TaskLoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask, Boolean& taskResult)

  • http://codebetter.com/members/gblock/default.aspx Glenn Block

    Hi

    Did you right click the MEF Preview bits and unblock all files? If not you will get that error.

    As far as DC, you need to be using the version of MEF that ships on Codeplex as the post describes.

    Glenn

  • Przemek

    Hi,

    I have been following the tutorial, downloaded MEF Preview 9 and replaced the bits as requested. There are problems with resolving the DeploymentCatalog type. So, I downloaded the sample application and opened it up. Again, there is something wrong with the DeploymentCatalog. Please see at the attached image. Object Browser shows that the namespace System.ComponentModel.Composition.Hosting does not contain type DeploymentCatalog. What can be wrong? Strangely enough, in the MEF9 Preview bits that I referenced in the previous solution , DC shows up. Unfortunately, when I try tu build I get an exception: “System.NotSupportedException: An attempt was made to load an assembly from a network location which would have caused the assembly to be sandboxed in previous versions of the .NET Framework.”

    http://picasaweb.google.com/psoszynski/Technical#5446359783362067474