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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://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.

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

    I’m using SL5 and I can’t find the System.ComponentModel.Composition.Packaging.Toolkit.dll. So which dll should reference ?

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

    @Clarrs if you look at the later posts in the series they work with the latest bits. Starting with Part IV.

    http://codebetter.com/blogs/glenn.block/archive/2010/03/07/building-hello-mef-part-iv-deploymentcatalog.aspx

  • Clarrs

    This code is outdated and no longer even compiles in VS 2010 and SL4

  • Clarrs

    This code no longer works with the April release.

  • Troy Nothnagel

    It looks like with the April 2010 release of the Toolkit that System.ComponentModel.Composition.Packaging.Toolkit.dll is no longer available, and functionality has been moved to CompositionHost and DeploymentCatalog?

  • Nk54

    Hi ! I have done your mef I, II tuto, and now make this one.

    I love your style, easy to understand, we know what we are doing and why.

    You just forget to say that we need to add references in the main app :
    when you are talking about Initializing the container to support dynamic download

    (in your tuto, after making ExportWidgetAttribute with export…, you say that we can delete the other references.

    So at this point, we do not have the references needed.
    using System.ComponentModel.Composition.Hosting;
    using System.ComponentModel.Composition.Packaging;

    They are needed to use Catalog.

    And using System.ComponentModel.Composition;
    is needed to get container.ComposeExportedValue() method. Else, container will just have Compose() method.

    Thanks a lot, i can’t wait to read your amazing post on MEF IV and MEF V !

    ++

  • http://denisvuyka.wordpress.com Denis Vuyka

    Extremely nice article, Glenn. I’ve just finished introducing loose modules support for my application in SL3. Had to rip the “PackageCatalog” and “Package” from SL4 toolkit. The rest works pretty fine.

    As a hint: in order to minimize the module I’m setting “CopyLocal=False” for MEF-related and some other stuff that I’m 100% sure are already loaded by the main shell. In the very basic scenario the resulting package contains only “AppManifest.xaml” and module assembly.

    Thanks for the article, nice one as always

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

    @Albert, if you are using the app with the current design, that won’t work. Everytime recomposition happens all of the available widgets are populated.

    If you want to have different logic, you should look at changing the design. You will start with the code OnImportsSatisfed method which currently loops through all.

    Hope this helps
    Glenn

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

    @Fallon, we pulled PackageCatalog from MEF because we were not happy with the implementation / design. We moved it into the PictureViewer sample in MEF 3.5. It was not a decision taken lightly.

    We think very hard about what we put “in the box”. If we are really not happy with the design we pull it, as we always have the option to add it later. We don’t have the option to pull it once it ships.

    MEF in SL3 was never our final target, though we had planned to support it on CodePlex. The reasoning for not putting the package stuff in the Toolkit for Sl3, was based on test/dev cost.

    However, I am happy to say we have revamped the design of the PackageCatalog and are now looking to bring support into MEF proper (not the same API but similar capabilities and more) in SL4. This mens it will likely also show up in one of our our next few CodePlex drops for SL3.

    Thanks
    Glenn

  • Fallon Massey

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

    I don’t understand why this was taken out of MEF, but it does cause major problems. Firstly, I really don’t need to have to depend on the toolkit, that’s a forced dependency.

    However, secondly, it has caused major problems in building Silverlight 3 apps when moving to the latest code. The support is gone in MEF, and it doesn’t exist in the SL3 version of the toolkit. Has SL3 become a red headed step child so soon?

    If this dependency on the toolkit persists, could you please provide an example of how we can integrate our existing XAP downloading code to play well with MEF.

    Thanks.

  • Albert

    Hi,

    I’m a newbie to silverlight and like MEF to setup a modular application. I also like the provided examples, but I was wondering how to hide or delete a previous loaded widget. Let’s say I’ve got to buttons (module 1 and module 2). If I click on the first button module 1 will be loaded. If I click on the second button I would like to hide or delete the first module and show the second module. If I say Topwigets.children.clear it won’t work. What am I doing wrong?

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

    For TPEs you need to know the assembly within the zip that you are pulling out. The manifest just tells you the URI of the XAP.

    It is possible to have convention that requires the XAP filename to contain the assembly name (minus dll), thus you can use the XAP name to figure out the assembly.

    The big problem with adding TPE support for us now is that it is late in the cycle and it will dramatically increase our test cost.

  • Bruno Martinez

    Glenn: What do you need beyond the contents of the AppManifest.xaml ?

  • Wouter

    Really interesting, love the series, thanks!

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

    Bruno there is several problems with the caching. I am not sure if you and I are speaking of the same thing. What I mean is the feature in SL that allows a xap to automatically download references it needs when it loads. These assemblies are stored in a cache on the server.

    Reasons why we can’t do it:

    Top of the list is the manifest does not give you the information necessary to find the cached zip. We would need to create a standard convention which would indicate where it would reside, and we don’t think that’s the right thing to do long term.

    Second, the feature is something that Silverlight needs to support proper, it is not the feature for the MEF team to implement. The SL team unfortunately does not have time prior to SL4 shipping to add the necessary functionality, which requires a signficant amount of work.

    Hope this explains things

    Thanks
    Glenn

  • Bruno Martinez

    What’s the difficulty in supporting assembly caching? You can take a look at our assembly loader in component one’s control explorer.

    About Rob question number 2, repeated downloading of the same assemblies, you will find that nothing needs to be done. The browser caches the .xaps and .zips and re loading an assembly has no effect.

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

    Thanks Rob as always. Comments to your points below.

    1. Yes, thanks. Currently we don’t report progress in our API.  We are currently making some design changes though based on feedback and adding a progress notification. As far as why mine does not have a spinny thing, well I am still a noob :-)

    2. We currently don’t prevent that, however with the redesign we plan on addressing this as well. Today you could check on the PackageCatalog a property to see what was downloaded already.

    3. That is actually very easy. The module would have logic in it’s constructor that executes when it creates it. Or it could implement IPartImportSatisfacationNotification if it wants to have property imports as well. Finally the importing part which pulls in modules could also implement IPartImportSatisfactionNotification to run logic when new modules show up. Do you think any of these would work?  Are you asking me to illustrate this in my sample?

    4. Brad A I believe has a post on his blog about using MEF’s dynamic download with the navigation infrastructure.

    5. You get a call back from the Package, the catalog also sends a notification. You could have the delegate invoke a service that was injected thus you could mock it. What would you expect here?

    Appreciate where you are coming from. I deliberately did not do MVVM initially as I don’t want to stay very focused on the MEF specifics and not cloud it with philosophical debate. However, if you read through my post, I said I will be getting to MVVM, TDD, etc soon.

    It felt ugly initially, but then I got used to it. Regardless of how much I love SRP / SOC, I dont’ want folks who want to use MEF have to buy in to those precepts in order to use it.

    Thanks!

    Glenn

  • http://www.caliburnproject.org Rob Eisenberg

    Interesting Glenn. I would like to see you address several other real world issues related to dynamic download with your MEF extensions:

    1. You would almost never download an extension without displaying some sort of loading or progress indicator, so I would like to see how this would work in.

    2. In complex applications, you can come across scenarios which would repeatedly trigger the downloading of the same dynamic resource. So, I would like to see how you would prevent downloading of already downloaded packages (if the present API doesn’t do that automatically).

    3. Often times, when a dynamic module is downloaded, it needs to not only load its parts into the container, but needs to initiate some sort of action. For example, lets say I click on a menu item that should display a screen which is part of a package that has not been downloaded…and that screen must fit into a larger screen activation framework.

    4. In a navigation style application, you often have deep linking through urls. Suppose someone links to a url for a page that has not yet been downloaded?

    5. How would you test that the proper actions initiated the download and activation of a package?

    On another note, I would really like to see you blog a bit less about plugging user controls together and more about ViewModel composition. In my experience, almost all the scenarios I mentioned above are easier to solve outside of the context of views.