To use MEF, you need to add the following two assemblies to your project:
System.ComponentModel.Composition.dll
System.ComponentModel.Composition.Initialization.dll
The first assembly is the key MEF assembly, and the second adds additional Silverlight-specific features to MEF. Let's look at how you go about using MEF in your Silverlight application.
The following three steps are key in the composition process with MEF:
You export parts, import them, and then compose them. When you think of MEF in these terms, you can really start to see how simple it is. Let's look at each of these steps in more depth.
You can export a class by decorating it with the Export
attribute, found in the System.ComponentModel.Composition
namespace of the assembly by the same name. This turns it into a part that MEF can compose with other parts. For example:
[Export]
public class MyExportedPart
By exporting a class in this manner, with no contract supplied to the Export
attribute, the export will create a default contract using the class's type (i.e., MyExportedPart
). However, for this part to be imported, the import will need to reference the part by its type, which tightly couples the two together. Ideally, you should specify a contract that will decouple them from one another. You can do so by providing the Export
attribute's constructor with a contract for it to be discovered by. This may be a base class that the class being exported derives from, an interface that it implements, or simply a string that can be used to identify the export. For example, you can tell the Export
attribute to use an interface as the contract, like so:
[Export(typeof(IExamplePart))]
public class MyExportedPart : IExamplePart
Alternatively, you can simply give the export a name as a string, as follows:
[Export("ExamplePart")]
public class MyExportedPart : IExamplePart
By simply providing a name for the contract in this manner, the class will be exported as an object, so you will usually want to give the contract a type, like so:
[Export("ExamplePart", typeof(IExamplePart))]
public class MyExportedPart : IExamplePart
Note Mostly, you will use an interface as the contract, but using a string to name the contract is useful when you have multiple exports that implement the same contract but want to be able to differentiate between them and choose which one should be used in a given scenario.
When you want to consume a part, you import it. Any class that imports other parts becomes a part itself. In other words, MEF will then see this class as a part, and compose it together with the part that it's importing.
When you import a part, MEF will provide an instance of that class for you, without the need for you to “new up” an instance of the class. “Newing up” an instance of a part requires the consumer to know what the concrete type of the import will be. MEF enables the consumer to be completely unaware of the concrete type that will be imported, and leaves MEF to handle the details of discovering the concrete type and injecting it where it is required. This allows for the parts to be loosely coupled.
After you've exported a part, another part can consume it by importing it. The easiest way to get a reference to an instance of a part is by creating a public property on your class that the instance can be assigned to, and decorate it with the Import
attribute, found in the System.ComponentModel.Composition
namespace of the assembly by the same name. For example:
[Import]
public IExamplePart MyImportedPart { get; set; }
Note You can decorate only public
fields and properties with the Import
attribute. Attempting to do so for private/protected/internal
fields or properties will result in an exception.
When MEF composes the parts, it will see that a part is required, search for a corresponding part matching the contract specified by the import among the exports, and assign an instance of the part that it finds to the property. In this case, no contract was explicitly passed to the Import
attribute, so MEF will infer the contract from the property's type—that is, it will look for an export using the IExamplePart
interface as its contract. For example, MEF will match the following export to the previous import:
[Export(typeof(IExamplePart))]
public class MyExportedPart : IExamplePart
Note If no part is found matching the import's contract, an exception will be thrown.
You can explicitly specify a contract to be used when you import a part. This is particularly necessary when you've named the contract using a string. For example, when you've exported a class, like so:
[Export("ExamplePart", typeof(IExamplePart))]
public class MyExportedPart : IExamplePart
you can import it as follows:
[Import("ExamplePart")]
public IExamplePart MyImportedPart { get; set; }
Note When you import a part, by default that instance will be shared wherever it is imported in your application. You can change this behavior by using a few different techniques, which we'll discuss in the section “Managing the Lifetime of Parts.”
The previous section demonstrated importing a part into a property on another part. However, if you create another class that is exported using the same contract and run your application, MEF will generate a composition error. This is because there are two parts matching the import's contract, and it doesn't know which one it should import. When you want to import multiple parts exported using the same contract, you need to change the property to an array/collection, and decorate it with the ImportMany
attribute instead of the Import
attribute. This is particularly useful when implementing a plug-in architecture, where you have multiple plug-ins that implement the same interface that MEF can discover and import for you.
The following code demonstrates a property that multiple parts can be imported into.
[ImportMany]
public IExamplePart[] MyImportedParts { get; set; }
Note You can specify what contract an ImportMany
attribute should use in the same way you do with the Import
attribute.
After you've marked your exports and imports, you need to tell MEF to compose them together. In other words, you need to tell MEF to fulfill the demands of the imports. Usually, you would do this in the constructor of the class containing the imports.
As previously mentioned, MEF uses a catalog to discover the parts, and a composition container to compose them. The following code demonstrates how you can create a catalog and a composition container, providing the composition container with the catalog for it to use:
var catalog = new DeploymentCatalog();
var container = new CompositionContainer(catalog);
Note We'll take a look at the available catalog types and when to use each later in this chapter. The DeploymentCatalog
catalog used here looks for all the exports in the current XAP file.
A consumer of parts can then ask the composition container to fulfill its imports, like so:
container.ComposeParts(this);
When this method is called, the composition container will reflect over the class passed into the ComposeParts
method as its parameter looking for imports. When it finds one, it will find an export in the catalog with a matching contract, and inject it into the import.
Silverlight's edition of MEF provides a simpler way to initiate the composition process, by providing a static class named CompositionInitializer
, which contains a SatisfyImports
method. You can find this class in the System.ComponentModel.Composition
namespace of the System.ComponentModel.Composition.Initialization.dll
assembly. Instead of manually initializing the composition container and configuring the catalogs for it to use, as demonstrated in the previous section, you can simply use the following line of code:
CompositionInitializer.SatisfyImports(this);
The CompositionInitializer
creates a default container that uses a catalog containing all the parts in the current XAP file. This default container will be maintained for the duration of the application's lifetime, and is used throughout the application.
You can take control over the container that the CompositionInitializer
uses if you wish, using the static CompositionHost
class to configure it. For example, you can provide a catalog for the default container to use by calling the Initialize
method on the CompositionHost
class, and passing it the catalog to be used, like so:
var catalog = new DeploymentCatalog();
CompositionHost.Initialize(catalog);
You can even provide it a container for it to use, as follows:
var catalog = new DeploymentCatalog();
var container = new CompositionContainer(catalog);
CompositionHost.Initialize(container);
Now whenever you call the CompositionInitializer.SatisfyImports
method, the catalog/container that you configured in this manner will be used to compose the parts.
Note When a container instantiates a part, it retains that part and reuses it for future imports. This means that when you take this approach, any parts that the container instantiates will be kept alive for the duration of the application's lifetime. We'll look at how you can deal with this behavior in the section “Managing the Lifetime of Parts.”
Using the CompositionInitializer.SatisfyImports
method is the approach generally taken when using MEF in Silverlight applications. However, it does have one major downside, in that it only works when the consumer does not itself contain exports. If you attempt to satisfy the imports of a class that also contains exports, such as the following class:
[Export]
public class PartImporterAndExporter
{
[Import]
public IExamplePart MyImportedPart { get; set; }
public PartImporterAndExporter()
{
CompositionInitializer.SatisfyImports(this);
}
}
you will find that the following composition exception will be thrown:
Cannot call SatisfyImports on a object of type 'XXX' because it is marked with one or more
ExportAttributes.
Therefore, in the scenario where the consumer does contain exports, you will need to take the first approach instead—that is, you will need to manually configure the catalogs and composition container.
By default, a composition container creates singleton objects. That is, it creates an instance of a part once, and reuses that instance wherever that part is imported in your application. However, this is not always the behavior that you want. Often, you will want to have a new instance of the part each time it is imported. For example, say that you export a ViewModel
class, which is then imported by a view. Each time the view is opened, it will generally want to use a new instance of the ViewModel
class. MEF allows you to configure this behavior either when the ViewModel
class is exported, or when the ViewModel
class is imported.
You can configure that a new instance of a class should be created each time it is imported by decorating it with the PartCreationPolicy
attribute. This attribute's constructor accepts a CreationPolicy
enumeration parameter, containing the following values:
Any
: This will default the policy to Shared
, unless the import requests a NonShared
instance. This is the default behavior when a creation policy isn't provided for an export.NonShared
: A new instance of this class will be created each time it is imported.Shared
: A single instance will be shared between all imports of this part.The following code demonstrates decorating a class with the PartCreationPolicy
attribute, telling the composition container to create a new instance of the class each time it is imported:
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ProductListViewModel
Alternatively, an import can tell the composition container that it wants a new instance of the class that it is importing by assigning the NonShared
value of the CreationPolicy
enumeration to the Import
attribute's RequiredCreationPolicy
property, as the following code demonstrates:
[Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
public IExamplePart MyImportedPart { get; set; }
Note When you set a part's creation policy to NonShared
, the composition container will not maintain a reference to the part, enabling it to be garbage collected.
When you designate an import, you don't necessary want an instance of the part to be instantiated during the composition process. Sometimes, you want the part to be instantiated only when you first use it. This saves unnecessary instantiations when a part might not even actually end up being used.
MEF supports this scenario, by enabling you to turn an import into a “lazy import.” Say you have the following import:
[Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
public IExamplePart MyImportedPart { get; set; }
The composition container will create an instance of the concrete type to be imported when the part containing the import is composed. That is, when the following method is called in the class's constructor:
CompositionInitializer.SatisfyImports(this);
You can change the import to be lazy, like so:
[Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
public Lazy<IExamplePart> MyImportedPart { get; set; }
The Lazy<T>
type was introduced with Silverlight 4, and allows you to delay the instantiation of a type until you need it. The concrete type that MEF imports will be instantiated only when you get the value of the property's Value
property, like so:
IExamplePart myImportedPartInstance = MyImportedPart.Value;
The previous section demonstrated how a part can be instantiated only when it is first used, but sometimes the consumer will want to make use of multiple instances of an export, and create those instances on demand. You can use an export factory for this purpose. The following code demonstrates the declaration of an export factory:
[Import]
public ExportFactory<IExamplePart> MyExportFactory { get; set; }
As you can see, we have a property designated as an import, of type ExportFactory<T>
, where T
is the type contract of the export. Each time you need a new instance of the part that the factory provides, you can simply call the property's CreateExport
method, like so:
IExamplePart examplePartInstance = MyExportFactory.CreateExport().Value;
Catalogs discover parts and make them available to the composition container to use. MEF provides a number of catalogs that discover parts in different ways from one another. Let's look at the different types of catalogs that MEF provides.
Note All of these catalogs reside under the System.ComponentModel.Composition.Hosting
namespace.
The AssemblyCatalog
catalog discovers all the exports within a given assembly. You simply pass the assembly for it to discover exports in to its constructor. For example, the following code demonstrates configuring an AssemblyCatalog
catalog to search for all exports in the current assembly:
var catalog =
new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());
The TypeCatalog
catalog discovers all the exports within a given set of parts. You pass the catalog the types for it to discover exports in to its constructor. For example, the following code demonstrates configuring a TypeCatalog
catalog to search for all the exports defined in three classes: MyExportedPart, MyExportedPart1
, and MyExportedPart2
.
var catalog = new TypeCatalog(typeof(MyExportedPart),
typeof(MyExportedPart1),
typeof(MyExportedPart2));
The DeploymentCatalog
catalog discovers all the exports within a given XAP file. The following code demonstrates configuring a DeploymentCatalog
catalog to search for all exports in the current XAP file:
var catalog = new DeploymentCatalog();
You can also use the DeploymentCatalog
catalog to download other XAP files, and discover their parts. Simply pass the DeploymentCatalog
class's constructor the URI to a XAP file, and then call its DownloadAsync
method, like so:
var catalog = new DeploymentCatalog(new Uri("Module1.xap", UriKind.Relative));
catalog.DownloadAsync();
While the XAP file is downloading, the composition container will often continue to be used in the meantime to compose parts using the exports that it currently has available to it. When the XAP file has finished loading, the DeploymentCatalog
will notify the composition container that new parts are available. The composition container will then update the imports of any previously composed parts as a result of new exports becoming available—a process known as recomposition. For imports to be successfully updated, however, you need to explicitly set the AllowRecomposition
property of the Import/ImportMany
attribute to true, like so:
[ImportMany(AllowRecomposition=true)]
public ObservableCollection<IExamplePart> MyImportedParts { get; set; }
You must set this property to true on any imports that may be recomposed as a result of new exports being found in the downloaded XAP file; otherwise, it will result in an exception being thrown during the recomposition process.
You can detect for any errors in downloading the XAP file or errors that occurred during the composition process by handling the DeploymentCatalog
object's DownloadCompleted
event. This event is raised after the XAP file has finish downloading, and the composition container has recomposed it with the existing imports. The AsyncCompletedEventArgs
object passed into the event handler as a parameter has an Error
property that you can use to check for any errors.
Note You can even use the DeploymentCatalog
catalog to download modules on demand. We look at how you can implement this “on demand” behavior in the “Downloading Modules on Demand Using MEF” section of Chapter 17.
The AggregateCatalog
catalog enables you to aggregate multiple catalogs, potentially of different types. In other words, it's a catalog that contains other catalogs. For example, say you want to provide discovery of parts across several specific assemblies. You can create multiple AssemblyCatalog
instances (each referencing an assembly), and add them to an AggregateCatalog
catalog. You can then pass this AggregateCatalog
catalog to the composition container to enable it to use all the parts across those catalogs. Start by creating an AggregateCatalog
object, as follows:
var aggregateCatalog = new AggregateCatalog();
You can then add catalogs to its Catalogs
collection, like so:
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
aggregateCatalog.Catalogs.Add(catalog);
Usually, you will make use of an AggregateCatalog
catalog to aggregate DeploymentCatalog
catalogs. For example, the following code demonstrates aggregating the current XAP file and another that it downloads on demand:
var aggregateCatalog = new AggregateCatalog();
var catalog1 = new DeploymentCatalog();
aggregateCatalog.Catalogs.Add(catalog1);
var catalog2 = new DeploymentCatalog(new Uri("Module1.xap", UriKind.Relative));
catalog2.DownloadAsync();
aggregateCatalog.Catalogs.Add(catalog2);
Note You can also add parts to an aggregate catalog, adding them to the collection provided by its Parts
property.