Hosting MEF in Silverlight

In the desktop today in order to use MEF you are required to manually configure the CompositionContainer (and catalogs) in order to allow the application to discover parts. That container often needs to be exposed (preferably not directly but through a wrapper) and passed around to other parts of the application that may need it for dynamically composing new parts.

In Silverlight we've introduced a new api called System.ComponentModel.Composition.CompositionInitializer which resides in System.ComponentModel.CompositionInitialization assembly. CompositionInitializer allows parts to get composed by MEF without having to do any manual bootstrapping as it will automatically configure MEF on demand. With CompositionInitializer any class that has been newed up can have imports and MEF will satisfy them. This means you can use it anywhere within your Silverlight application. A common place to use it is within your App class in order to import the MainView, or the MainViewViewModel if you are applying an MVVM pattern as in the sample below.

using System.ComponentModel.Composition;

public class App : Application {
  public App() {
    this.Startup += this.Application_Startup;
    this.Exit += this.Application_Exit;
    this.UnhandledException += this.Application_UnhandledException;

    InitializeComponent();
  }

  private void Application_Startup(object sender, StartupEventArgs e)
  {
    CompositionInitializer.SatisfyImports(this)
    var mainPage = new MainPage();
    mainPage.DataContext = ViewModel;
    RootVisual = mainPage;
  }

  [Import]
  public MainViewModel ViewModel {get;set;}
}

[Export]
public class MainViewModel, INotifyPropertyChanged {
  [Import]
  public ILogger Loger {get;set;}
}

[Export(typeof(ILogger))]
public class Logger : ILogger {
}
Imports System.ComponentModel.Composition

Partial Public Class App
    Inherits Application

    Public Sub New()
        InitializeComponent()
    End Sub

    Private Sub Application_Startup(ByVal o As Object, ByVal e As StartupEventArgs) Handles Me.Startup
        CompositionInitializer.SatisfyImports(Me) 
        Dim mainPage_Renamed = New MainPage()
        mainPage_Renamed.DataContext = ViewModel
        RootVisual = mainPage_Renamed
    End Sub

    <Import()>
    Public Property ViewModel() As MainViewModel

End Class

<Export()>
Public Class MainViewModel
    Implements INotifyPropertyChanged
    <Import()>
    Public Property Loger() As ILogger
End Class

<Export(GetType(ILogger))> 
Public Class Logger
    Implements ILogger
End Class


The App imports a MainViewModel. Then in the ApplicationStartup method it calls CompositionInitializer passing itself_ in order to have it's imports satisfied. This causes MainViewModel to be discovered by MEF and injected with a Logger instance. The application then creates a MainView instance and sets the DataContext to the imported ViewModel.

What's really happening?

The first time SatisfyImports is called, CompositionInitializer is creating a new shared container behind the scenes which it will use for future calls to SatisfyImports. The catalog for that container contains all parts discovered within the assemblies in the application's main XAP. This means that in addition to the current executing assembly, all referenced assemblies (and their references) also have their parts discovered.

Exports not allowed on the instance passed to SatisfyImports.

Notice that the Application class does not have an export on it. SatisfyImports is designed to be used ONLY for parts that cannot be discovered by the catalog, meaning they do not have exports. It will throw an exception if you pass it a class that has an ExportAttribute on it

Using CompositionInitializer from within XAML created elements.

CompositionInitializer is designed to be called mulitple times with it configuring itself on the first call. This makes it ideal to use not only within the root Application class but also within elements created in XAML. For example see the xaml below.

<UserControl
 x:Class="OrderManagement.OrderView"
 xmlns:c="clr-namespace:MyApp.Controls">
  <StackPanel>
    <OrderHeader/>
    <OrderDetails/>
  </StackPanel>
<UserControl>

OrderHeader and OrderDetails are nested controls within OrderView which are created in XAML. Both however have their own respective view models which they import. For example below is the OrderHeader. Notice it does not have an export.

public class OrderHeader : UserControl {
  [Import]
  public OrderHeaderViewModel ViewModel {get;set;}

  public OrderHeader() {
    CompositionInitializer.SatisfyImports(this);
    this.DataContext = ViewModel;
  }
}
Public Class OrderHeader
    Inherits UserControl
    <Import()>
    Public Property ViewModel() As OrderHeaderViewModel

    Public Sub New()
        CompositionInitializer.SatisfyImports(Me) 
        Me.DataContext = ViewModel
    End Sub

End Class

In this case OrderHeader is directly importing it's ViewModel versus having it externally wired as in the MainView case. In this case we did this in order to allow it simply to be dropped within XAML without pushing any knowledge of it's wiring up to the containing control. This type of pattern makes sense in particular for third-party controls using MEF as it simplifies the usage of the control. It's a matter of preference though.

Caveats about using CompositionInitializer.SatisfyImports

  1. By default only assemblies in the current XAP are discovered. You can override this behavior, to learn about this see the overriding host config topic.
  2. All parts created with CompositionInitializer are held around by MEF until the application shuts down. Thus it is not ideal to use for composing transient multiple-instance parts. For these cases a better choice is to use ExportFactory
  3. As mentioned above Exports are not supported.

Last edited Aug 9, 2010 at 5:52 PM by haveriss, version 7

Comments

pfeffer Jul 6, 2011 at 4:17 PM 
What should I do if I have both [Import] and [Export] attributes in my class? What's the substitute for SatisfyImports?

pckujawa Feb 21, 2011 at 5:47 PM 
The url and breadcrumb link for this page are misspelled, FYI. (Missing the 'o'.)

OlgaLu Sep 22, 2010 at 2:38 PM 
I found the answer to my question in the forums (http://mef.codeplex.com/Thread/View.aspx?ThreadId=214182). There Glenn Block says "Because we want to allow recomposition to occur, we hold on the part so we can recompose it later." He also provides a wrapper that will not do that (http://codepaste.net/mo7afq).

OlgaLu Sep 22, 2010 at 1:03 PM 
If I use composition initializer to satisfy the imports does the container keep a reference to the object whose imports were satisfied by the call? It seems to us that we are having memory leaks on all objects that have imports.

firedog067 Jun 18, 2010 at 2:07 PM 
For me that crashes the SL's designer.

gblock Jun 13, 2010 at 5:39 PM 
Hi jmcfet

Can you post to the forums the test you are trying to execute? That exception occurs during recomposition. It sounds like you are trying to add a catalog to the container / CompositionHost after SatisfyImports has already happened and that the imports are probably not recomposable. If you share your VM / unit-test code on our forums we can help. Otherwise you can email me gblock@microsoft.com.

jmcfet Apr 7, 2010 at 10:37 AM 
I am running into a problem with MEF inside a Silverlight Unit Test that tests a method in a ViewmModel. I have a reference to the project that contains the ViewModel that I am testing and the Compositions DLLs and yet i get a ChangeRejectedException and it gives a long winded monolog that has no specifics, there must be a way to get the real issue as reflection is groping thru the Assemblies and comes up unsatisfied. MEF works fine when I use in the main assembly that conatins the VM.This exception implies that MEF believes I am doing some kind of dynamic loading wihich I am not!