Поделиться через


How to Debug and Diagnose MEF Failures

The Managed Extensibility Framework (MEF) helps make it easy to write extensible applications.  We hope that it is simple to understand the basics and get started.  However, MEF brings with it the possibility of new types of failures.  Without the proper knowledge, these failures can be difficult to diagnose.  This blog post will cover some background information you need to understand when dealing with MEF failures, the common types of MEF failures, and how you can diagnose these issues.

Import Cardinality

MEF imports have one of three cardinalities.  A collection import has a cardinality of ZeroOrMore.  MEF will supply all of the exports that match the import, whether there are zero, one, ten, or ten thousand.  The ImportManyAttribute is used to declare a collection import.

 [ImportMany] 
public IEnumerable<IPlugin> Plugins { get; set; }

An import with a cardinality of ExactlyOne only expects one export to satisfy it, and can be called a required import.  This can be declared with the ImportAttribute.

 [Import] 
public IPlugin Plugin { get; set; }

Finally, you can declare an optional import by setting the AllowDefault property of the ImportAttribute to true.  The cardinality of this import is ZeroOrOne.

 [Import(AllowDefault=true)] 
public IPlugin Plugin { get; set; }

Rejection

A part with an import with a cardinality of ZeroOrMore or ZeroOrOne does not have any specific requirements as to the number of exports that match the import.  The part should work when there are none available, when there is one, and when there are many.  On the other hand, an import with a cardinality of ExactlyOne is a declaration of a requirement that the part needs in order to function correctly.  If the import cannot be satisfied, MEF assumes the part cannot function correctly and will reject the part, which means it will not be available at all in the container.

A rejection can also be caused when there is more than one matching export.  MEF has no way to choose between multiple exports that match an import of cardinality ZeroOrOne or ExactlyOne.  So in this case, MEF will not use any of the matching exports.  The part will be rejected if the import cardinality was ExactlyOne, and the import will be set to null if the cardinality was ZeroOrOne.  If this behavior is not what you want, see these blog posts on Picking an Export and Using Defaults with MEF.

Rejection makes it simpler to write parts, because they can simply assume that their imports with a cardinality of ExactlyOne will be available instead of writing code to handle the case where they aren’t.  However, rejection effectively makes parts disappear from the container, so it can be hard to figure out what is wrong if you don’t know to expect it.  Furthermore, a problem with one part can cause a chain of rejections.  Imagine you have part A which depends (via a required import) on part B, which depends on part C.  If part C has an import which can’t be satisfied, then it will be rejected, which will in turn cause part B and part A to be rejected.  The symptom of the problem will be that part A doesn’t show up in the container, but the root cause of the problem is with part C.  Without understanding rejection, you might look for a problem with part A, see that everything appears to be correct, and be unable to figure out why it isn’t showing up.

Failures Where an Export is not Available

A common type of MEF failure is when an export is not available.  The symptoms of this failure can either be that the export is missing from a collection import you expected it to be present in, or that the container throws an exception when you attempt to access it.  In the first case, since the export was not required, this is not a composition error as far as MEF is concerned, and your application will often continue to run, albeit without the functionality that the missing export provided.  In the second case, MEF is unable to satisfy a request you have made of it.  For example, if you call container.GetExport<IFoo>() and there is no IFoo export available, the container will throw a CardinalityMismatchException.  Likewise, an exception will be thrown if you call CompositionInitializer.SatisfyImports(), passing in a part that has an import that cannot be satisfied.

There are several possible causes for an export not being available.

There is no export

The export may simply not exist.  This can happen if you forget to add an ExportAttribute to your class.

The part providing the export is not in the catalog

If a part is not in the catalog, then its exports will not be available.  This can happen if the corresponding assembly was not placed in the correct folder, if it was out of date, or if there was an error loading the assembly (for example, if it references an assembly that is not available).  The application may not be set up to include the assembly providing the export in the catalog.  It is also possible that the type has a PartNotDiscoverableAttribute on it, preventing it from showing up in the catalog, or that the type is abstract, or that the export is on a base/ancestor type and not the actual type which is in the catalog.

The export does not match the import

The export may be in the catalog and available in the container, but if it doesn’t match an import, then it won’t be used to satisfy that import.  An easy way for this to happen is to put an export attribute on a class without specifying the contract. Consider the following two exports

 [Export]
public class Plugin1 : IPlugin { }

[Export(typeof(IPlugin))]
public class Plugin2 : IPlugin { }

The contract for the first export is the Plugin1 class, not the IPlugin interface, and so it wouldn’t match an import of IPlugin.  The second export specifies that the IPlugin interface is the contract for the export, and would match an IPlugin import.

Here is a list of issues that can prevent an export from matching an import:

  • Contract name doesn’t match

  • Export type identity doesn’t match

  • Metadata doesn’t match

    • Metadata key is not present
    • Metadata value is not of correct type
  • Creation policy doesn’t match

Under the hood, a MEF import uses a constraint which takes an ExportDefinition and returns a boolean value indicating whether the export matches the import.  This constraint is represented as an expression, and you may see these constraints when debugging MEF or using tools to analyze MEF parts.  A typical import constraint looks like this:

 exportDefinition => ((exportDefinition.ContractName == "MEFSample.IPlugin")
    AndAlso (exportDefinition.Metadata.ContainsKey("ExportTypeIdentity")
    AndAlso "MEFSample.IPlugin".Equals(exportDefinition.Metadata.get_Item("ExportTypeIdentity"))))

The constraint requires both the contract name and the exported type (which is stored under the metadata key “ExportTypeIdentity”) to match.  In many cases, you won’t specify a contract name, and the contract name and the export type identity will be the same.  However, if you do specify the contract name, you can still have a type identity mismatch.  The following code is an example of this—the export type identity is the Plugin class, but it would need to be the IPlugin interface to match the import.

 [Export("SpecialPlugin")]
public class Plugin : IPlugin { }

public class Importer
{
    [Import("SpecialPlugin")]
    public IPlugin SpecialPlugin { get; set; }
}

Using an interface to access MEF metadata values adds additional requirements to the constraint.  For each property in the metadata view interface, an export must have a metadata item with a name matching the name of the property, and the value needs to be of the type of the property.  To make a piece of metadata optional, you can put a DefaultValueAttribute on the property in the interface.  If the following metadata interface is used on an import, then only exports which have metadata with the name “Name” with a value of type string will match the import, but the exports won’t need to have a piece of metadata with the name “Priority”.

 public interface IPluginMetadata
{
    string Name { get; }
    [DefaultValue(0)]
    int Priority { get; }
}

Finally, an importer can specify a required creation policy (using the RequiredCreationPolicy properties of the Import and ImportMany attributes).  Also, using an ExportFactory (currently only available in the Silverlight version of MEF) will set the required creation policy to NonShared.  If the export comes from a part with an incompatible creation policy, it won’t match.

The part providing the export is rejected

If a part is rejected, its exports will not be available.  As covered in the rejection section, a part will be rejected if there are no exports available to satisfy any of its required imports, or because there are more than one that could satisfy it.  If there aren’t any exports available, this can be for any of the reasons for an export not to be available—there may be no export, the part providing the export might not be in the catalog, the export you think should satisfy the import might not actually match, and the part providing the export could be rejected.  There may be a chain of rejections, and at the end of it will be the root cause of the problem which needs to be fixed in order to make the original export available (as well as all of the intermediate ones in the chain).

Recomposition Failures

Another type of failure in MEF is a recomposition failure.  MEF supports recomposition, which means that you can change the parts that are available in the container while the application is running, and the container will update the composition graph.  This lets you add new extensions at runtime which is especially useful in Silverlight applications for keeping the initial download small and downloading additional functionality after the application starts.  However, imports have to opt in to recomposition to allow MEF to add or remove exports after the part has been initialized.  If they don’t, or if the recomposition would violate other MEF guarantees (such as import cardinality), MEF will cancel the change by throwing a ChangeRejectedException.

The operations which cause recomposition (and hence can throw a ChangeRejectedException) are:

Recomposition with a DeploymentCatalog is different than the other methods of recomposition in that the ChangeRejectedException won’t be thrown from a method you call.  The DeploymentCatalog starts downloading the XAP asynchronously when you call the DownloadAsync method, and when the download completes it triggers recomposition and reports the result by raising the DownloadCompleted event.  If the change is rejected, then the Error property of the event args will be set to the ChangeRejectedException.

Assembly Load Issues

In .NET, there are different contexts into which an assembly can be loaded.  The default load context is generally the best one to use, but it cannot load assemblies that are not in the application base directory, subdirectories of the application base which are included in the probing path, or the GAC.  When using a DirectoryCatalog or passing a path to the AssemblyCatalog constructor, MEF will attempt to load assemblies in the default load context.  However, if the assemblies are not in the probing path or the GAC, this will not be possible, and MEF will load them in the load-from context instead.

The load-from context can lead to type identity issues that result in an InvalidCastException, MissingMethodException, or other errors.  In order to avoid these issues in a MEF application, you can put any extension directories under the application base directory, and add them to the probing private path in your application configuration file.  For other options and more information about assembly loading in .NET, see the Best Practices for Assembly Loading MSDN document.

Methods and Tools for Investigating MEF Failures

 

Inspecting the catalog in the debugger

A simple way of debugging a MEF application is to inspect the catalog in the Visual Studio debugger while the application is running.  You can look at the Parts property to see what parts are in the catalog.  This will help you verify that MEF is actually finding the parts you expect it to.  You can also drill down and examine the ImportDefinitions and ExportDefinitions properties of each part to see its imports and exports.

Inspecting the Parts property of a MEF catalog in the Visual Studio debugger 

Rejection trace messages

MEF uses standard .NET tracing to report parts which are rejected.  When a part is rejected, it will trace a message which includes the name of the rejected part, the reason for the rejection, and the import which caused the rejection.  An example of a rejection trace message is shown below, with the most important parts in bold.

System.ComponentModel.Composition Warning: 1 : The ComposablePartDefinition 'MEFConsole.PrintCommand' has been rejected. The composition remains unchanged. The changes were rejected because of the following error(s): The composition produced a single composition error. The root cause is provided below. Review the CompositionException.Errors property for more detailed information.

1) No valid exports were found that match the constraint '((exportDefinition.ContractName == "MEFConsole.IPrinterService") AndAlso (exportDefinition.Metadata.ContainsKey("ExportTypeIdentity") AndAlso "MEFConsole.IPrinterService".Equals(exportDefinition.Metadata.get_Item("ExportTypeIdentity"))))', invalid exports may have been rejected.

Resulting in: Cannot set import 'MEFConsole.PrintCommand.PrintService (ContractName="MEFConsole.IPrinterService")' on part 'MEFConsole.PrintCommand'.

Element: MEFConsole.PrintCommand.PrintService (ContractName="MEFConsole.IPrinterService") -->  MEFConsole.PrintCommand -->  AssemblyCatalog (Assembly="MEFConsole, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")

When debugging an application in Visual Studio, these trace messages will be shown in the output window.  You can enable MEF tracing in an application that is not being debugged by adding a trace listener to the System.ComponentModel.Composition source in your application configuration file.  Below is an example of how to log MEF trace information to a log file.

 <?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.diagnostics>
        <sources>
            <source name="System.ComponentModel.Composition"
                    switchValue="All">
                <listeners>
                    <add name="fileListener"
                         type="System.Diagnostics.TextWriterTraceListener"
                         initializeData="composition.log" />
                </listeners>
            </source>
        </sources>
        <trace autoflush="true" indentsize="4" />
    </system.diagnostics>
</configuration>

MEF doesn’t examine parts to determine if they are rejected until it needs to in order to fulfill a request to the container.  This means that the rejection won’t be traced until the point in the program’s execution where an export of the part would be supplied.  Also, due to the details of how MEF implements composition, there are a lot of cases where the container doesn’t definitively determine whether a part is rejected, and so it doesn’t send a trace message.  In practice, this means that when there is a chain of rejections, only the part at the end of the chain (the opposite end from the root cause) will be traced as rejected, and no rejections at all will be traced when calling the container’s Compose method or the ComposeParts extension method.  These limitations mean that tracing can be helpful in detecting that you have a rejection, but will often not give you all the information you need to determine the problem.

Composition Diagnostics with Mefx

The Managed Extensibility Framework Explorer (Mefx) is a command line tool that you can use to diagnose a wide variety of MEF issues.  It can display information about MEF parts, determine which parts will be rejected, analyze the root cause for a rejection, and diagnose mismatches between exports and imports.

When using Mefx, you specify what assemblies should be examined for MEF parts with the /file and /dir parameters.  Then you use one of the possible action parameters to specify what should be displayed.  The /parts parameter lists all of the parts found.  The /rejected parameter lists the parts which will be rejected.  The /causes command helps find root causes—it lists “primary rejections,” parts that are rejected due to errors not related to the rejection of other parts.  Here are some samples of using Mefx, with the output shown in italics:

> mefx /file:MefConsole.exe /parts

MEFConsole.GreetCommand MEFConsole.PrintCommand MEFConsole.PrintService MEFConsole.Program

> mefx /file:MefConsole.exe /rejected

MEFConsole.PrintCommand MEFConsole.PrintService

> mefx /file:MefConsole.exe /causes

MEFConsole.PrintService

In these samples, the PrintCommand and the PrintService were both rejected, but the PrintService was the root cause of the problem.  The /verbose parameter can be used to display detailed information about parts, including a list of their exports and imports.  Along with each import the /verbose parameter will show any exports that satisfy the import, any errors with the import, and any “unsuitable” exports—exports that have the same contract name as the import, but don’t match it for some other reason.  Using the /verbose parameter to the /causes command will give the following output, which indicates that the reason PrintService was rejected was because it had an import for IPrintSpooler, for which there was no export available:

[Part] MEFConsole.PrintService from: AssemblyCatalog (Assembly="MEFConsole, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")

  [Primary Rejection]

  [Export] MEFConsole.PrintService (ContractName="MEFConsole.IPrinterService")

  [Import] MEFConsole.PrintService.Spooler (ContractName="MEFConsole.IPrintSpooler")

    [Exception] System.ComponentModel.Composition.ImportCardinalityMismatchException: No valid exports were found that match the constraint '((exportDefinition.ContractName == "MEFConsole.IPrintSpooler") AndAlso (exportDefinition.Metadata.ContainsKey("ExportTypeIdentity") AndAlso "MEFConsole.IPrintSpooler".Equals(exportDefinition.Metadata.get_Item("ExportTypeIdentity"))))', invalid exports may have been rejected. 

You can also display information about a single part with the /type parameter.  To figure out why an export isn’t being supplied to an import, use the /type parameter with the type name for the part that has the import, together with the /verbose parameter.  In the below example, this shows that there are two exports with the same contract name as the Commands import, but that the second of them is missing the required metadata for the Name.

> mefx /file:MEFConsole.exe /type:MEFConsole.Program /verbose

[Part] MEFConsole.Program from: AssemblyCatalog (Assembly="MEFConsole, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null") [Export] MEFConsole.Program (ContractName="MEFConsole.Program") [Import] MEFConsole.Program.Commands (ContractName="MEFConsole.ICommand") [SatisfiedBy] MEFConsole.PrintCommand (ContractName="MEFConsole.ICommand") from: MEFConsole.PrintCommand from: AssemblyCatalog (Assembly="MEFConsole, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null") [Unsuitable] MEFConsole.GreetCommand (ContractName="MEFConsole.ICommand") from: MEFConsole.GreetCommand from: AssemblyCatalog (Assembly="MEFConsole, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null") [Because] RequiredMetadata, The import requires metadata '[Name, System.String]' but this is not provided by the export.

Running Mefx without any arguments will display the full list of possible parameters.  More details about Mefx can be found in this MSDN document.

Visual Mefx

Mefx is a command-line tool which runs on the full desktop .NET framework, and cannot analyze Silverlight applications.  Visual Mefx is a GUI-based version of Mefx, which runs under both Silverlight and desktop .NET.  It shows a list of parts in the left pane, and the details of the selected part in the right pane.  The parts are color-coded according to their rejection status: non-rejected parts use green, primary rejections use red, and non-primary rejections use yellow.  Below is a screenshot of Visual Mefx analyzing the same PrintCommand/PrintService rejection issue that we saw earlier.

Visual MEFX Rejection

Mefx Limitations

Mefx analyzes the parts from the files you specify.  If your application adds any parts or exports to the container, Mefx won’t know about them and the analysis it performs won’t match what happens in your application.  Other customizations to the container, such as using custom export providers or a hierarchy of scoped containers, will also prevent Mefx from accurately analyzing composition issues.

However, you can integrate Mefx’s composition diagnostics functionality into your application to avoid these limitations.  Mefx and Visual Mefx both are built on top of a composition diagnostics library.  You can reference this library in your application, and use the functionality it provides to perform analysis on the container that your application uses, with any extra parts or customizations that you have made to it.

Mefx Credits and Download

Mefx and the composition diagnostics library was originally written by Nicholas Blumhardt when he was on the MEF team and included in the MEF codeplex releasesJoe McBride ported it to Silverlight and WPF as Visual MEFX.  I took his version and made some further improvements.  I plan to add Mefx to the community open source MefContrib project, but for now you can download Mefx and Visual Mefx here.

Composition.Diagnostics.zip

Comments

  • Anonymous
    July 13, 2010
    Awesome comprehensive article, Daniel. I don't really deserve the credit for MEFX by the way (I just happened to type in the code :)) It would be lovely to see MEFX, the diagnostic libs and (if possible) the visual version on MefContrib. Seeing Visual MEFX with the nice new interface is inspiring! Maybe with some wider involvement, the "Explorer" part could really be emphasised, with graph visualisation perhaps? Being WPF, I'm sure it could be shoehorned into a Visual Studio 2010 tool window somehow...... Wish I could have made across the Pacific it to Tech.Ed for your presentation on MEF diagnostics, bet it was very interesting. Next time perhaps! Nick

  • Anonymous
    July 13, 2010
    Here here, the UI has been nicely improved. One thing that you might want to consider is allowing incrementally adding new assemblies each time you click "Import Files". Also adding support for folders over individual assemblies. Reason this is nseeded is because sometimes your parts may have refs to assemblies that are not in the folder where the part is located, and not gaced. Good job!

  • Anonymous
    July 13, 2010
    @Glenn: I did add support for incrementally adding assemblies/xaps.  Clicking "Import Files" keeps adding assemblies to analyze, and Reset clears them all.

  • Anonymous
    July 14, 2010
    Thanks! That's realy great article!!!

  • Anonymous
    July 18, 2010
    Thanks for sharing Debugging is the bugging part of MEF this article helps a lot to come out of it

  • Anonymous
    January 21, 2011
    The comment has been removed

  • Anonymous
    September 06, 2011
    The comment has been removed

  • Anonymous
    February 03, 2012
    @Kathleen: I recently gave a code camp talk which drew heavily from this entry and I followed up with a blog entry showing how to integrate the library behind MEFx into integration tests.  I've also played with dumping diagnostic information from failed compositions in the application itself.  Check out the post and comment if you have any suggestions or questions. ihadthisideaonce.wordpress.com/.../stop-guessing-about-mef-composition-and-start-testing

  • Anonymous
    May 31, 2012
    Helpful article, thank you. MEF is great, unless you have a composition problem. Shame on Miscrosoft to release this part of the framework with so unusable exceptions and no way to get informed about warnings except their own TRACE. Exceptions should have been thrown using the standard outer/inner structure, the Data dictionary should have been used to contain information about the cause(s) of the problem (rejection or other). Once again Microsoft is supplying half-way solutions without regard to the persons who are using them.

  • Anonymous
    February 17, 2014
    Thank you so much for the article. Succinct and comprehensive!

  • Anonymous
    March 03, 2015
    Hi, I have a problem with plugin loading, described in stackoverflow.com/.../error-when-try-load-plugin-using-mef According to section "Assembly Load Issues" my issue can be fixed by adding "privatePath" to config file with path to plugin directory ("under application base directory"). I tried, but it doesn't solve my issue.

  • Anonymous
    March 03, 2015
    Hi, I have a problem to load plugin from correct directory, because "legacy" plugin placed into application base directory (stackoverflow.com/.../error-when-try-load-plugin-using-mef). According to section Assembly Load Issues, "private path" and plugin directory "under the application base directory" should solve the issue. I tried the way (github.com/.../MEF), but it didn't solve my problem.