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.

https://cid-f8b2fd72406fb218.skydrive.live.com/self.aspx/blog/Hello%20MEF/HelloMEF%5E_Part%5E_IV.zip