Andrej Tozon's blog

In the Attic

NAVIGATION - SEARCH

Add version 4 components to your Silverlight 3 application with MEF

Note: this post and accompanying source code was updated to reflect the latest MEF build on Codeplex. This build is much more aligned with the version of MEF that is available from Silverlight 4 Beta SDK.

The current Silverlight version is v3, with v4 in the making (in Beta 1 at the time of this posting). Silverlight 4 is bringing a lot of new features in the core framework and to use them, you would have to migrate your applications to the latest version, once it gets released. And that would require all the potential users to upgrade their machines to the latest version as well.

But there’s another way. By using MEF (Managed Extensibility Framework), you can extend your existing Silverlight 3 application with optional package, which would contain Silverlight 4 components only.

Here’s an example: user can select an image file from your local disk in Silverlight 3 only through an OpenFileDialog, while with the new drag/drop feature in Silverlight 4, she would be able drag a picture from the file system and drop it onto the application. Why not allow those with Silverlight 4 installed do it the easy way?

To make this work, the main application should be all Silverlight 3. We’d provide additional Silverlight 4 features in a separate XAP package, which would be downloaded later and tested for the right runtime version. In case user had the latest Silverlight runtime installed, we could bring in additional features in the application. For this post, I’m going to implement the abovementioned picture select feature by providing two controls:

  • a select button for choosing the picture through an OpenFileDialog (Silverlight 3 feature)
  • a drop canvas where user can drop the picture from the file system (Silverlight 4 feature)

Silverlight 3 control

Here’s how the BrowseForPictureControl would look like:

imageThis control would be contained in the main application. It exposes the PictureSelected event, with the FileInfo data passed as an event argument. Because the application is going to subscribe to this event for each control that exposes it, we need to make an interface for it and put that into a new project that would be shared among the both packages.

public interface IPictureControl
{
    event EventHandler<PictureSelectedEventArgs> PictureSelected;
}

public class PictureSelectedEventArgs : EventArgs
{
    public FileInfo File { get; set; }

    public PictureSelectedEventArgs(FileInfo file)
    {
        File = file;
    }
}

The control implements the this interface as:

private void OnBrowse(object sender, RoutedEventArgs e)
{
    OpenFileDialog dialog = new OpenFileDialog();
    bool result = dialog.ShowDialog() ?? false;
    if (result)
    {
        OnPictureSelected(dialog.File);
    }
}

Silverlight 4 control

The improved Silverlight 4 control should be created in a new Silverlight 4 application project, that would be disconnected from the main project, but referencing the previously created shared project. The control looks simpler too:

image

And the interface implementation:

private void OnDrop(object sender, DragEventArgs e)
{
    IDataObject dataObject = e.Data as IDataObject;
    if (e.Data == null)
    {
        return;
    }
    FileInfo[] files = dataObject.GetData(DataFormats.FileDrop) as FileInfo[];

    OnPictureSelected(files[0]);
}

OK, controls done. Now, on to MEF.

Bringing in MEF

MEF for Silverlight 3 is available for download from Codeplex. You’ll need the following assemblies added as a reference in your main application:

  • System.ComponentModel.Composition
  • System.ComponentModel.Composition.Initialization.dll

The second assembly is only required to use from the main project, where composition is performed. The project that is shared between the SL3 and SL4 projects can reference just the first assembly from the list. Also, one downside of maintaining the compatibility with Silverlight 3 is that the Silverlight 4 project must reference the same System.ComponentModel.Composition assembly as other projects. No ‘native’ SL4 MEF there…

Attribute for version

Obviously, loading any Silverlight 4 based code into a Silverlight 3 application should be impossible, therefore we need to mark both of controls with information about the Silverlight runtime they require. Something in a way of:

[ExportablePictureSelector(RequiredVersion = "3.0")]
public partial class BrowseForPictureControl : UserControl, IPictureControl
{
    ...
}

for Silverlight 3 control, and:

[ExportablePictureSelector(RequiredVersion = "4.0")]
public partial class DragDropPictureControl : UserControl, IPictureControl
{
    ...
}

for Silverlight 4 control.

The ExportableSelector attribute is derived from MEF’s ExportAttribute and is declared as:

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ExportablePictureSelectorAttribute : ExportAttribute, IPictureSelectorMetadata
{
    public ExportablePictureSelectorAttribute()
        : base(typeof(IPictureControl))
    {
    }

    public string RequiredVersion { get; set; }
}

Putting it all together

The main application provides a catalog of all the controls that were discovered:

[ImportMany(AllowRecomposition = true)]
public ObservableCollection<Lazy<IPictureControl, IPictureSelectorMetadata>> PictureControls { get; set; }

The PictureControls collection will change whenever a new export is discovered by MEF. When that happens, the newly discovered control will be added to the main form:

private void OnPictureControlsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    foreach (Lazy<IPictureControl, IPictureSelectorMetadata> view in Views)
    {
        Control c = view.Value as Control;
        if (!panel.Children.Contains(c) && Application.Current.Host.IsVersionSupported(view.Metadata.RequiredVersion))
        {
            panel.Children.Add(c);
            view.Value.PictureSelected += OnPictureSelected;
        }
    }
}

The above code shows that the main criteria for adding the control on the form is that it’s RequiredVersion is supported by the runtime – and that is checked with the interop IsVersionSupported method of the plugin host.

When picture is selected, the PictureSelected event is fired by the control and handled to display the picture:

private void OnPictureSelected(object sender, PictureSelectedEventArgs e)
{
    BitmapImage bi = new BitmapImage();
    bi.SetSource(e.File.OpenRead());
    image.Source = bi;
}

Of course, BrowseForPictureControl control being included in the main project, it will immediately be picked by MEF and inserted in the PictureControls collection. For Silverlight 4 based XAP package, however, we need to download it first. Here’s the InitializeContainer method, which initializes a new composition container and triggers the package download:

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

    CompositionHost.InitializeContainer(container);

    Package.DownloadPackageAsync(new Uri("CrossVersioning.Version4Enhancements.xap", UriKind.Relative), (e, p) =>
    {
        if (p != null)
        {
            catalog.AddPackage(p);
        }
    });

    PartInitializer.SatisfyImports(this);
}

The Silverlight 4 package is asynchronously downloaded from the server and added to the package catalog. The last line is there to start the initial composition, causing the v3 control to immediately show up.

Conclusion

There… the application is set up. Users, having the Silverlight 3 runtime installed, will see the it as:image… and Silverlight 4 users will also see that additional feature:

image

This approach will let you gradually update your applications to use Silverlight 4, not forcing the users to update to the latest runtime immediately (although there would probably be no reason not to ;))

You can test the application right here:

Or download the source code from here. Enjoy.