Matthew Murdoch
Matthew Murdoch

Reputation: 31463

How to prevent 'bad' implementations from poisoning GetExportedValues in MEF?

I'm using MEF to discover and instantiate plugins and want to make the process as robust as possible. In particular I don't want a badly written plugin to adversely affect the execution of the host or other plugins.

Unfortunately I'm finding that when using GetExportedValues() an exception thrown from any of the implementation class constructors effectively 'poisons' the container, preventing all 'good' implementations from being returned.

The following code demonstrates this:

using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Linq;

namespace MefPoisoning {
    class Program {
        static void Main(string[] args) {
            var catalog = new TypeCatalog(
                typeof(GoodImplementation), 
                typeof(BadImplementation));

            using (var container = new CompositionContainer(catalog)) {
                try {
                    var implementations =
                        container.GetExportedValues<IInterface>();
                    Console.WriteLine("Found {0} implementations",
                        implementations.Count());
                }
                catch (CompositionException e) {
                    Console.WriteLine(e.Message);
                }
            }
        }
    }

    [InheritedExport]
    public interface IInterface {
    }

    public sealed class GoodImplementation : IInterface {
    }

    public sealed class BadImplementation : IInterface {
        public BadImplementation() {
            throw new InvalidOperationException();
        }
    }
}

With the BadImplementation throwing an exception from its constructor GetExportedValues() throws an exception and therefore doesn't even return the good implementation. The error clearly states that the underlying problem is due to the BadImplementation constructor:

The composition produced a single composition error. The root cause is 
provided below. Review the CompositionException.Errors property for more 
detailed information.

1) Operation is not valid due to the current state of the object.

Resulting in: An exception occurred while trying to create an instance 
of type 'MefPoisoning.BadImplementation'.

...

[Note that GetExports() fails similarly, but later, when its Lazy<IInterface> return values are queried].

Is there any way I can work around this apparent limitation?

Upvotes: 3

Views: 801

Answers (1)

Matthew Murdoch
Matthew Murdoch

Reputation: 31463

The key to making this work is to use GetExports() instead of GetExportedValues(). As noted within the question GetExports() returns an IEnumerable<Lazy<T>>. This can be iterated over and the implementations instantiated one at a time within a try-catch block to check whether they are well behaved. If not then they can simply be ignored.

The following code demonstrates this and can replace the using block in the example within the question:

using (var container = new CompositionContainer(catalog)) {
    var goodImplementations = new List<IInterface>();
    var lazyImplementations = container.GetExports<IInterface>();

    foreach (var lazyImplementation in lazyImplementations) {
        try {
            goodImplementations.Add(lazyImplementation.Value);
        }
        catch (CompositionException e) {
            // Failed to create implementation, ignore it
        }
    }

    Console.WriteLine("Found {0} implementations", goodImplementations.Count());
}

Upvotes: 6

Related Questions