Using MEF

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.

Key Steps in the Composition Process

The following three steps are key in the composition process with MEF:

  • Export parts
  • Import parts
  • Compose the parts together

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.

Exporting Parts

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

images 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.

Importing Parts

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.

Importing a Single Part

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; }

images 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

images 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; }

images 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.”

Importing Multiple 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; }

images Note You can specify what contract an ImportMany attribute should use in the same way you do with the Import attribute.

Composing the Parts

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.

Approach 1: Manually Configuring the Catalogs and Composition Container

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);

images 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.

Approach 2: Using the CompositionInitializer Class

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.

images 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.”

Choosing an Approach

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.

Managing the Lifetime of Parts

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; }

images 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.

Lazy Imports

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;

Obtaining New Export Instances on Demand

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

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.

images Note All of these catalogs reside under the System.ComponentModel.Composition.Hosting namespace.

AssemblyCatalog

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());
TypeCatalog

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));
DeploymentCatalog

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.

images 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.

AggregateCatalog

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);

images Note You can also add parts to an aggregate catalog, adding them to the collection provided by its Parts property.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset