Building Hello MEF – Part III – XAP Partitioning (with the host’s permission) and the sweetness of recomposition.

In our last post we saw how using metadata in MEF allows us to provide additional self-describing information about exports. This has a range of uses including providing hints on how the export should be handled (such as UI location) as well as allowing us to filter out creating exports that we don’t need or which are not relevant to the current application context. In this post, we’ll see how we can now take our parts which all currently reside in a single XAP and we can split them up into multiple XAPs. We can use this for several reasons including reducing the general download time of our application, as well as allowing third-parties to extend our Silverlight applications. We’ll also see how we can combine dynamic download with a winning feature in MEF called recomposition.

Just to remind you, this is what our dashboard looked like when we left off.

image

You can get the code for where we left out here

For our next exercise, we’re going to do something contrived though this is “based on a real story”, don’t worry. We’re going to take our dashboard app and extend it so that when we hit the “Hello MEF” button, new parts show up that are dynamically downloaded from a separate XAP on the server. I’ll show you two different ways of doing this, in this post we’ll do it with the host’s permission and in the next post without.

Below is a diagram of what this looks like.

image

Where is the dynamic XAP download support in MEF? I don’t see it.

If you are familiar with MEF’s apis in Silverlight 4, you might be asking this question. The question is a valid one. The reason you don’t see it, is because it’s not there ;-) Instead we have initially shipped this feature as part of the Silverlight 4 Beta Toolkit, which you can download here. You’ll need this to follow along on this post.

Introducing the Package apis

If you download the toolkit, you’ll find our dynamic XAP support in the System.ComponentModel.Composition.Packaging.Toolkit.dll. The apis are below.

     public class Package
    {
        public Package(Uri packageUri, IEnumerable<Assembly> assemblies);
        public IEnumerable<Assembly> Assemblies { get; }
        public static Package Current { get; }
        public Uri Uri { get; }
        public static void DownloadPackageAsync(Uri packageUri, Action<AsyncCompletedEventArgs, Package> packageDownloadCompleted);
    }
     public class PackageCatalog : ComposablePartCatalog, INotifyComposablePartCatalogChanged
    {
        public PackageCatalog();
        public IEnumerable<Package> Packages { get; }
        public override IQueryable<ComposablePartDefinition> Parts { get; }
        public event EventHandler<ComposablePartCatalogChangeEventArgs> Changed;
        public event EventHandler<ComposablePartCatalogChangeEventArgs> Changing;
        public void AddPackage(Package package);
    }

The api has two parts. First there is the Package class. Package has a static method called DownloadPackageAsync which you pass a Uri for a XAP as well as a completed action. As soon as you call this method, MEF will begin the download. Once the XAP is received, it will invoke the callback passing in an instance of Package. In the callback handler, the next step is to add the Package to a PackageCatalog. In order for MEF to see the PackageCatalog it must have been added to an instance of MEF’s container. Until now we’ve avoided talking about the container but well learn a bit about it shortly. Once the new package is added, it will then be available to parts in the container. We’ll see how with recomposition, parts that import any of the exports in the new package will suddenly “light up” with the newly available parts.

OK, let’s get going.

Creating a separate XAP.

The first thing we need to do to get going is create a separate XAP. You might be surprised if you go looking for the “Create Secondary XAP” template, as there is none :-) However, all hope is not lost. Fortunately we can create a XAP by using the Silverlight Application template. Right click on the HelloMEF solution can click  “Add New Project”. Enter HelloMEF.Extensions as the project name.

image

Once you do, hit OK on the next page and you’ll get a new Silverlight application. Next go into the project and remove the App.Xaml and

image 

One last step, we need to set our web application so it copies our new XAP to the output folder. Fortunately the tooling support for SL4 brings this to VS2010 out of the box. If you right click on the HelloMEF.Web project and check the “Silverlight Applications” tab, you’ll see.

image

I am guessing this was not intended for the application partitioning solution and was rather for having multiple Silverlight applications in a single web application. However, it works great for our scenario so we’re not complaining!

Once you create the new application, VS automatically creates a new start page in your web site which it sets as the default. We need to reset it back, so go right-click on HelloMEFTestPage.html under HelloMEF.Web and set it as the startup page.

image

Create a contract assembly

Now that we’ve created our separate XAP, we can now go start building our new extension. OK, all we do is go and create our widget, but wait, how do we get access to the Widget contract? Currently all the widget definition information resides in the app. We could just go and a reference back to the application right? Technically you could as the app won’t be referencing us directly, but please DON’T do it. Every time you do a puppy dies :-)

What we need to is to split our contracts out into a separate assembly so that those definitions can be shared across XAPs. We don’t have to embed the shared library over and over fortunately.

First thing we’ll do is create a new Silverlight Class library project. You know the drill, right click the solution, new project…

image

Delete the Class1.cs file. Then go cut/paste ExportWidgetAttribute.cs, WidgetLocation.cs and IWidgetMetadata.cs from the HelloMEF project into HelloMEF.Contracts. You will then need to add a reference to System.ComponentModel.Composition (which should be in your recent references) in the new project. Remember it’s in the “.\Program Files\Microsoft SDKs\Silverlight\v4.0\Libraries\Client folder.

image

Next go add back to the HelloMEF project and add a reference to the new HelloMEF.Contracts assembly.

Compile the project. If you followed each of the above step correctly it “should” compile. If it doesn’t work, you can grab a working version here

Building a dynamically downloadable widget

First we need to add a reference to MEF in our HelloMEF.Extensions project as we did above. Next we’ll add a reference to HelloMEF.Contracts. After adding it, go right-click on the reference and set Copy-Local to false in the reference properties. As our shared library is referenced by the app we know it will be loaded into memory and we shouldn’t copy the assembly over and over. If we do, we could run into trouble.

Now go add a new UserControl to the HelloMEF.Extensions project. Call it Widget3. We’ll make this have a textbox with a green border. Below is the Widget3.xaml.

 <UserControl x:Class="HelloMEF.Extensions.Widget3"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    
       <Grid x:Name="LayoutRoot" Height="Auto">
            <TextBox Background="Green" Text="Hello MEF!" TextAlignment="Center" Height="100" Width="300"/>
       </Grid>
</UserControl>

 

Next go drop in the code behind and make it a widget.

 namespace HelloMEF.Extensions
{
    [ExportWidget(Location=WidgetLocation.Bottom)]
    public partial class Widget3 : UserControl
    {
        public Widget3()
        {
            InitializeComponent();
        }
    }
}

Initializing the container to support dynamic download

If you are new to MEF, you might not be aware that behind the scenes the PartInitializer uses a class called the CompositionContainer to do all the work. By default we create a container which has all the exports in the current XAP. You can however override that container to do special things, like support dynamic XAP download. You override the container by calling the CompositionHost.InitializeContainer method.

Add the following code to the App.xaml.cs class in the main app.

 private void InitializeContainer()
{
    var catalog = new PackageCatalog();
    catalog.AddPackage(Package.Current);
    var container = new CompositionContainer(catalog);
    container.ComposeExportedValue(catalog);
    CompositionHost.InitializeContainer(container);
}

The code above is performing several functions.

  • It creates a PackageCatalog. Catalogs are a generic concept and they provide parts to the container. In this case the PackageCatalog will provide parts found in XAPs. Future downloaded XAPs will get added to this catalog.
  • It adds Package.Current, which contains all the assemblies in the main XAP.
  • Creates a container which uses the new catalog.
  • Composes the catalog into the container. This makes the PackageCatalog available to importers so they can use it. You can also wrap the PackageCatalog in a service like IPackageService if you like and export it that way, but I am keeping it simple.
  • Finally it overrides PartInitializer’s container with this new container.

Now we have one more thing to do which is to call our new method. Replace the Application_Startup method with the following code.

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

Now compile and run the app to test that the overridden container is working. You should the same result.

Enabling a widget to download new widgets

We’ve added our widget, but it’s not showing up, why? Well, because we haven’t used the Package class to download it. Let’s do that now. First thing we need to do is add a reference to the packaging dll. You’ll need to look in your Toolkit folder.

image

Next we’ll change Widget1 to go and download our new widgets. We’ll start with adding a using statement for System.ComponentModel.Composition.Packaging at the top, and then we’ll implement the logic.

 using System;
using System.ComponentModel.Composition;

using System.Windows.Controls;

using System.Windows;

using System.ComponentModel.Composition.Packaging;
namespace HelloMEF
{
    [ExportWidget(Location=WidgetLocation.Top)]
    public partial class Widget1 : UserControl
    {
        [Import]
        public PackageCatalog Packages { get; set; }
        public Widget1()
        {
            InitializeComponent();
            Button.Click += new RoutedEventHandler(Button_Click);
        }
        void Button_Click(object sender, RoutedEventArgs e)
        {
            Package.DownloadPackageAsync(new Uri("HelloMEF.Extensions.xap",UriKind.Relative), (args, p) => Packages.AddPackage(p));
        }
    }
}

Here’s what we’ve done.

  • We added an import to get to our PackageCatalog that was explicitly added to the container.
  • We’ve subscribed to the button click event.
  • In the handler, we’ve used the static Package.DownloadPackageAsync method to grab “HelloMEF.Extensions.xap”. The method takes a delegate, which we are passing a lambda to for adding the package upon it’s receipt.

Now compile and run the app. Once the app is running, press the button and…….Exception!

image

What’s happening is our new package has been downloaded, but it has been rejected by MEF, why? Oh, due to non-recomposable import, of course :-) What in the world is that? :-) Let’s scroll and look at the rest of the exception.

image

What MEF is basically saying here is that new exports have showed up in our XAP which will satisfy the import MainPage.Widgets, but they have been rejected because the Widgets import itself is not recomposable.

Rejection and Recomposition

We just saw two things coming into play here. First MEF rejects things sometimes. In this case it is rejecting the new parts in the catalog due to recomposition, but it will also reject for other reasons such as a part having an import which cannot be satisfied because no exports are present. Rejecting is part of a very powerful feature called Stable Composition feature which we built MEF on top of and which your systems can rely on. For more on Stable Composition, check this post.

But what is Recomposition? Recomposition means that parts in MEF are not static once they are composed and they can opt in to be recomposed as new things show up, recomposed means all recomposable imports are re-satisfied. If you connect the dots, that means your systems on top of MEF are dynamic. There’s one caveat though, you have to opt-in to recomposition, otherwise parts are rejected.

Doing that is as simple as setting AllowComposition = True in the Import / ImportMany attribute as we’ve done below.

 using System;
using System.Windows.Controls;

using System.ComponentModel.Composition;
namespace HelloMEF
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            PartInitializer.SatisfyImports(this);
            foreach (var widget in Widgets)
            {
                if (widget.Metadata.Location == WidgetLocation.Top)
                    TopWidgets.Items.Add(widget.Value);
                else if (widget.Metadata.Location == WidgetLocation.Bottom)
                    BottomWidgets.Items.Add(widget.Value);
            }
        }
        [ImportMany(AllowRecomposition=true)]
        public Lazy<UserControl,IWidgetMetadata>[] Widgets { get; set; }
    }
}

Now run the app, press the button and we don’t get an exception.

 

image

Awesome, but aren’t we back where we started? There’s no new widget, where is it?

Designing for Recomposition

The widgets are there, they are just not showing. Why? Because we populated the widgets when the control loaded. The Widgets property has been updated, but the MainPage doesn’t know to do anything when it is. What we need to do is make some changes to our MainPage to make it recomposition aware. There are two ways we could do this. First we could change our auto property to a real property, and override the getter when it is set. This is a bit ugly, so let’s not do that.

Instead let’s take advantage of an interface in MEF which although it has a hideous name, is very nice. It’s called IPartImportsSatisfactionNotification :-) What it does is allow the part to get notified by MEF whenever recomposition occurs. Thus, the perfect place for our logic for populating the widgets, AND we can still use automatic properties.

Below is the code for MainPage to do this.

 using System;
using System.Windows.Controls;

using System.ComponentModel.Composition;
namespace HelloMEF
{
    public partial class MainPage : UserControl, IPartImportsSatisfiedNotification
    {
        public MainPage()
        {
            InitializeComponent();
            PartInitializer.SatisfyImports(this);
        }
        [ImportMany(AllowRecomposition=true)]
        public Lazy<UserControl,IWidgetMetadata>[] Widgets { get; set; }
        #region IPartImportsSatisfiedNotification Members
        public void OnImportsSatisfied()
        {
            TopWidgets.Items.Clear();
            BottomWidgets.Items.Clear();
            foreach (var widget in Widgets)
            {
                if (widget.Metadata.Location == WidgetLocation.Top)
                    TopWidgets.Items.Add(widget.Value);
                else if (widget.Metadata.Location == WidgetLocation.Bottom)
                    BottomWidgets.Items.Add(widget.Value);
            }
        }
        #endregion
    }
}

You can see now that when composition occurs, we are clearing our two controls and repopulating. In future post we’ll see how we can use a ViewModel to make this code much cleaner.

OK, drum roll please. Compile and Run the app, press the button and….

image

Voila, dynamic download of XAPs!

A few caveats about our Package API

I am sure by now you are thinking, wow this looks great! However, there are some unsupported capabilities when using our Package API that you should be aware of.

  • Does not support Silverlight caching. Meaning if your XAP has references to things that are not in the XAP, the will only be found if they have been previously loaded, as the contracts are because the main app references them.
  • Does not support loose resources such as images, xaml files, etc sitting in the XAP. They must be resources embedded in assemblies.
  • Does not support versioning. Let’s say you have a XAP that uses version 1 of a type, and another XAP that uses version 2. The loader will load both rather than unify on the latest version.
  • Does not support localization, though you can probably come up with your own localization mechanism.

Summary

In this post, we learned how we can add dynamic XAP downloading support to our applications.

Along the way we also learned the following:

  • MEF has a CompositionContainer behind the scenes which it uses for composition. This container can be overridden.
  • MEF Contracts can be shared across XAPs and do not need to be embedded in every XAP.
  • MEF rejects parts that it cannot work with.
  • MEF supports recomposition allowing parts to be recomposed when new things show up.
  • IPartImportsSatisfiedNotification allows us to get notifications within our parts when they are recomposed.

This opens up a Pandora’s box of power. It can be used for good or evil :-) On the good side we can use it to allow our Silverlight apps to be extended by third-parties, as well as to partition our apps for a better user experience.

What’s next

In the next post we’ll explore this scenario a bit further. We’ll see how you can create a part that dynamically downloads XAPs without needing PartInitializer to be configured to allow it. MEF on!

Code is attached.

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