Silverlight 4 Beta – More on MEF and the PackageCatalog

One of the things that I talked about in the screencasts that I made about Silverlight 4 and MEF was the PackageCatalog.

By the way – to make any sense of this stuff you need to install Visual Studio 2010 Beta 2 and Silverlight 4 Beta.

The PackageCatalog is currently part of the Silverlight 4 Toolkit. It comes with a friend called Package which can asynchronously download a XAP file to produce an instance of Package. You can then take that instance and add it to a PackageCatalog that’s being used by a MEF CompositionContainer in order to add functionality dynamically and asynchronously to an already running application

It’s important to remember that the Silverlight Toolkit represents a place for innovation and is something that’s not supported in the same way as Silverlight itself so when you’re using types from there, bear that in mind ( and especially when things are in beta/preview phases ).

As a simple example, imagine I’ve got an “application” that looks like this ( code deliberately shortened to the shortest I could make it ) with some XAML;

<UserControl
    x:Class="SilverlightApplication42.MainPage"
    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"
        Background="White">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition
                Height="Auto" />
        </Grid.RowDefinitions>
        <ContentPresenter
            Content="{Binding ExtraContent}" />
        <Button
            Grid.Row="1"
            Margin="6"
            Click="OnDownload"
            Content="Add PlugIn" />
    </Grid>
</UserControl>

So, just a grid with a Button and a ContentPresenter ready to display some content that’s data-bound to the ExtraContent property. If I have some code behind;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel.Composition;
using System.ComponentModel;
using System.ComponentModel.Composition.Packaging;
using System.ComponentModel.Composition.Hosting;

namespace SilverlightApplication42
{
  public partial class MainPage : UserControl, INotifyPropertyChanged
  {
    public MainPage()
    {
      InitializeComponent();

      this.Loaded += (s, e) =>
        {
          PropertyChanged = (a, b) => { };

          this.DataContext = this;

          packageCatalog = new PackageCatalog();

          CompositionHost.InitializeContainer(new CompositionContainer(packageCatalog));

          PartInitializer.SatisfyImports(this);
        };
    }
    [Import(AllowDefault=true, AllowRecomposition=true)]
    public FrameworkElement ExtraContent
    {
      get
      {
        return (extraContent);
      }
      set
      {
        extraContent = value;
        PropertyChanged(this, new PropertyChangedEventArgs("ExtraContent"));
      }
    }
    void OnDownload(object sender, EventArgs e)
    {
      Package.DownloadPackageAsync(new Uri("ExtraContent.xap", UriKind.Relative),
        (args, package) =>
        {
          if (!args.Cancelled && (args.Error == null))
          {
            packageCatalog.AddPackage(package);
          }
        });
    }
    public event PropertyChangedEventHandler PropertyChanged;

    PackageCatalog packageCatalog;

    FrameworkElement extraContent;
  }
}

then you can hopefully see that the idea is that we have this property ExtraContent that MEF will compose for us and we’re using the PackageCatalog and when you click the button we go back to the site of origin looking for ExtraContent.xap and we load that up into MEF which may cause a recomposition in MEF and cause our property ExtraContent to be populated.

Inside of ExtraContent.xap I simply have this class which provides a bit of content that MEF will find and compose for me;

  public class ContentFactory
  {
    [Export]
    public FrameworkElement Content
    {
      get
      {
        if (content == null)
        {
          content = new Viewbox()
          {
            Child = new TextBlock()
            {
              Text = "Extra Content"
            }
          };
        }
        return (content);
      }
    }
    FrameworkElement content;
  }

That’s cool in that the PackageCatalog surfaces any Packages that have been handed to it but it’d be great if it also had a mechanism for configuring or discovering which Packages to load at run time without having to write explicit code to do so.

I’m thinking of some kind of XML/XAML configuration file that you author to tell the framework which XAPs it should be loading and then you can change the app just by changing an external file.

When I was experimenting with the PackageCatalog, I wrote a bit of extra code to do something a bit like that and make its use a bit more declarative and that’s really the purpose of this blog post – to share that code. Note that this is just experimental code so feel free to take it if it’s of use to you but it was sketched out pretty quickly so don’t hold out too much hope for some great design/implementation here 🙂

My first thought was to do everything “automatically” for the case where I know up-front which XAPs I want to load into my application and so I can do something like this in my App.xaml and everything “just happens” – note that PackageCatalogConfiguration and PackageConfiguration are my own custom classes which go on to use Package and PackageCatalog;

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
             x:Class="SilverlightApplication42.App"
             xmlns:pkg="clr-namespace:PackageCatalogContrib;assembly=PackageCatalogConfiguration">
    <Application.ApplicationLifetimeObjects>
        <pkg:PackageCatalogConfiguration>
            <pkg:PackageConfiguration
                Name="Test"
                Source="ExtraContent.xap" />
        </pkg:PackageCatalogConfiguration>
    </Application.ApplicationLifetimeObjects>
</Application>

that code then does the work to create the CompositionContainer, create a PackageCatalog, slot it into the container and then uses Package to do the async download of ExtraContent.xap ( and any other XAP files listed – it’s a list rather than a single item as this example suggests ).

As an aside, this is using the Silverlight feature for custom, plugged-in application lifetime objects which I talked about here once in video form.

So, that allows me to auto-start downloading things and consequently there’s no longer any need for code-behind in my MainPage that has anything to do with PackageCatalog or Package or CompositionContainer so it just looks like;

  public partial class MainPage : UserControl, INotifyPropertyChanged
  {
    public MainPage()
    {
      InitializeComponent();

      this.Loaded += (s, e) =>
        {
          PropertyChanged = (a, b) => { };

          this.DataContext = this;

          PartInitializer.SatisfyImports(this);
        };
    }
    [Import(AllowDefault=true, AllowRecomposition=true)]
    public FrameworkElement ExtraContent
    {
      get
      {
        return (extraContent);
      }
      set
      {
        extraContent = value;
        PropertyChanged(this, new PropertyChangedEventArgs("ExtraContent"));
      }
    }
    public event PropertyChangedEventHandler PropertyChanged;

    FrameworkElement extraContent;
  }

Kind of neat. But what if you needed to create the container yourself rather than have my code create it? That can be answered by a CreatingContainer event;

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
             x:Class="SilverlightApplication42.App"
             xmlns:pkg="clr-namespace:PackageCatalogContrib;assembly=PackageCatalogConfiguration">
    <Application.ApplicationLifetimeObjects>
        <pkg:PackageCatalogConfiguration
            CreatingContainer="OnCreatingContainer">
            <pkg:PackageConfiguration
                Name="Test"
                Source="ExtraContent.xap" />
        </pkg:PackageCatalogConfiguration>
    </Application.ApplicationLifetimeObjects>
</Application>

which just shows that there’s a CreatingContainer event which I can handle on the App class as in;

    void OnCreatingContainer(object sender, CreatingContainerEventArgs args)
    {
      // We get passed a catalog and a container and we can return a new
      // container if we want
      args.ProposedContainer = new CompositionContainer(args.Catalog);
    }

Cool – but what about errors or being aware of progress as the various packages download? There’s events for that too…adding them to the XAML;

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
             x:Class="SilverlightApplication42.App"
             xmlns:pkg="clr-namespace:PackageCatalogContrib;assembly=PackageCatalogConfiguration">
    <Application.ApplicationLifetimeObjects>
        <pkg:PackageCatalogConfiguration
            CreatingContainer="OnCreatingContainer"
            PackageDownloading="OnPackageDownloading"
            PackageDownloaded="OnPackageDownloaded"
            PackagesDownloading="OnPackagesDownloading"
            PackagesDownloaded="OnPackagesDownloaded">
            <pkg:PackageConfiguration
                Name="Test"
                Source="ExtraContent.xap" />
        </pkg:PackageCatalogConfiguration>
    </Application.ApplicationLifetimeObjects>
</Application>

with some sketched code below to try and show what info comes into these events;

    void OnPackageDownloading(object sender, PackageConfigurationEventArgs e)
    {
      // Which package is it?
      string name = e.PackageConfiguration.Name;
      Uri source = e.PackageConfiguration.Source;
    }
    void OnPackageDownloaded(object sender, PackageDownloadedEventArgs e)
    {
      // Did it work?
      if ((e.Error == null) && (!e.Cancelled))
      {
        Package package = e.Package;
      }
      else
      {
        // Force a stop?
        e.HaltDownloads = false;
      }
    }
    void OnPackagesDownloading(object sender, EventArgs e)
    {
      // No info here
    }
    void OnPackagesDownloaded(object sender, PackagesDownloadResultsEventArgs e)
    {
      // What happened?
      foreach (var item in e.Results)
      {
        // Which package was it?
        string name = item.Item1.Name;
        Uri source = item.Item1.Source;

        // Did it work?
        if ((item.Item2.Error == null) && (!item.Item2.Cancelled))
        {
          // etc.
        }
      }
    }

Ok, but what if I don’t know my package configuration at build-time and want it in an external file? I can replace my definition again to take out the definition of the packages and just point to a file with a ConfigurationUri setting;

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
             x:Class="SilverlightApplication42.App"
             xmlns:pkg="clr-namespace:PackageCatalogContrib;assembly=PackageCatalogConfiguration">
    <Application.ApplicationLifetimeObjects>
        <pkg:PackageCatalogConfiguration            
            ConfigurationUri="packageConfiguration.xaml"
            ConfigurationLoaded="OnConfigurationLoaded"
            CreatingContainer="OnCreatingContainer"
            PackageDownloading="OnPackageDownloading"
            PackageDownloaded="OnPackageDownloaded"
            PackagesDownloading="OnPackagesDownloading"
            PackagesDownloaded="OnPackagesDownloaded">
        </pkg:PackageCatalogConfiguration>
    </Application.ApplicationLifetimeObjects>
</Application>

and now the configuration of the modules is left to that external file packageConfiguration.xaml which is just a loose file I’ve dropped onto the web-server at the site of origin ( i.e. next to the XAPs ) and it looks like the one below and has effectively just “outsourced” the package definitions to this extra file outside of the app;

<?xml version="1.0" encoding="utf-8" ?>
<pkg:PackageConfigurationList
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:pkg="clr-namespace:PackageCatalogContrib;assembly=PackageCatalogConfiguration">

  <pkg:PackageConfiguration
    Name="Extra Content Stuff"
    Source="ExtraContent.xap"/>

</pkg:PackageConfigurationList>

when this loads ( asynchronously ) it’ll fire the ConfigurationLoaded event before going on to download the packages so I can handle that event as well on my App class;

    void OnConfigurationLoaded(object sender, AsyncCompletedEventArgs args)
    {
      // Did it work etc?
      if (!args.Cancelled && (args.Error == null))
      {
        // ...
      }
    }

and what if I don’t want to do this stuff automatically at application startup but want to do something later on? I can write manual code if I want to such as this code that I wrote into the Startup event of the App class;

  void Application_Startup(object s, StartupEventArgs e)
    {
      PackageCatalogConfiguration config = new PackageCatalogConfiguration()
      {
        ConfigurationUri = new Uri("packageConfiguration.xaml", UriKind.Relative)
      };

      CompositionHost.InitializeContainer(new CompositionContainer(config.Catalog));

      config.ConfigurationLoaded += (sender, args) =>
        {
          if (!args.Cancelled && (args.Error == null))
          {
            config.DownloadPackagesAsync();
          }
        };

      config.LoadConfigurationAsync();

      this.RootVisual = new MainPage();
    }

and so this is still loading from the packageConfiguration.xaml file but if I wanted to be really manual about it I could do;

  void Application_Startup(object s, StartupEventArgs e)
    {
      PackageCatalogConfiguration config = new PackageCatalogConfiguration();

      config.Packages.Add(new PackageConfiguration()
      {
        Name = "My XAP",
        Source = new Uri("ExtraContent.xap", UriKind.Relative)
      });

      CompositionHost.InitializeContainer(new CompositionContainer(config.Catalog));

      config.PackagesDownloaded += (sender, args) =>
        {
          // Do something?
        };

      config.DownloadPackagesAsync();

      this.RootVisual = new MainPage();
    }

and that’s all she wrote 🙂

Here’s the code for download in case you want to have a poke around – remember that’s it was just “written for fun” as part of playing with PackageCatalog.