adrift in the sea of experience

Tuesday, May 10, 2011

Problems encountered when using MEF as a dependency injection container

When we started using MEF as a dependency injection container, I figured the trade-off was a bit like this. Good: Already part of the .NET 4 framework, dynamic discovery of components for extensibility. Bad: missing some advanced features like AOP and parametrized construction. Glenn Block posted about this in Should I use MEF for my general IoC needs?.

Since we didn't need those features, MEF seemed like a good choice. But as it turns out, there is a more subtle problem when using MEF as a general purpose dependency injection container. Consider the following example:

   public class Program 
   {
      static void Main(string[] args)
      {
         var container = new CompositionContainer(
            new AssemblyCatalog(Assembly.GetExecutingAssembly()));
         var a = container.GetExportedValue<A>();
      }
   }

   [Export]
   public class A
   {
      private readonly B b;

      [ImportingConstructor]
      public A(B b)
      {
         this.b = b;
      }
   }

   [Export]
   public class B
   {
      private readonly C c;

      [ImportingConstructor]
      public B(C c)
      {
         this.c = c;
      }
   }

   public class C
   {
   }

The export attribute is missing on the C class in my example. Since class A indirectly depends on class C, we get an error when we try to get an A instance from the container:

System.ComponentModel.Composition.ImportCardinalityMismatchException was unhandled
  Message=No valid exports were found that match the constraint '((exportDefinition.ContractName == "DITest.A") AndAlso (exportDefinition.Metadata.ContainsKey("ExportTypeIdentity") AndAlso "DITest.A".Equals(exportDefinition.Metadata.get_Item("ExportTypeIdentity"))))', invalid exports may have been rejected.
  Source=System.ComponentModel.Composition
  StackTrace: ...

So the C export is missing, but surprisingly the error message is complaining about the A class! This is because of "stable composition". Basically, whenever a dependency for a certain part is missing, MEF will resiliently attempt to do the composition without that part. For an example of how that can be useful, see Implementing Optional Exports with MEF Stable Composition.

Useful as it may be, stable composition comes at a price. Since MEF can't tell the difference between critical and non-critical parts, the missing dependency error may cascade upward until you get a mysterious ImportCardinalityMismatchException like the one above.

The MEF documentation on Diagnosing Composition Problems acknowledges this and provides some hints on how to debug such problems. Still, for large compositions the process is far from pleasant. Things are even worse if you have some circular dependencies.

Perhaps it would be better to use Autofac to do the core application composition. It doesn't attempt dynamic discovery or stable composition, so the error messages point straight at the missing dependencies. And with the MEF integration, you can still use MEF for the parts of the application where you do need dynamic discovery and stable composition.

3 comments:

Daniel said...

You'll probably be happy to hear that in MEF vNext, there is a way to disable rejection so that it will throw an exception any time a part would be rejected. This should make it much easier to find the problem in systems where you don't expect anything to be rejected.

Also I've written a blog post on MEF debugging. It has a little bit more information than the documentation you linked.

Jim said...

Daniel's post is very good, I recently gave a code camp talk using his blog as a primary resource. I also had some ideas about integration testing using Composition.Diagnostics, posted here.

Anonymous said...

Take a look at CompositionOptions.DisableSilentRejection:

http://msdn.microsoft.com/en-us/library/ff603380.aspx

That could help, at least for debuggin purposes.