views:

482

answers:

2

The biggest problem I'm having so far with MEF is that when I compose parts in my plugin loader wrapper, loading completely bails when it finds an import resolution problem with one of the assemblies. Ideally, I'd like ComposeParts to exhibit some sort of "ignore and continue" behavior, because the ideal user experience would entail loading as many plugins as possible, and simply logging an event when a specific plugin fails to load. I haven't been able to find information about this in the documentation anywhere.

If you have any other suggestions for how to solve this, I'm listening!

+2  A: 

You can use the AllowDefault parameter. Setting it to true on an import will cause the dependency to be null if no available part can satisfy the import.

public class MyComponent
{
    [Import(AllowDefault=true)]
    public IMyDependency MyDependency { get; set; }
}

To load all available plugins but ignore those which cannot be loaded because of missing parts, [ImportMany] will already do what you want by default:

[Export]
public class MyApplication
{
   [ImportMany(typeof(IPlugin))]
   public IEnumerable<IPlugin> Plugins { get; set; }
}

Note that the above techniques only eliminate composition errors that are caused by missing parts. If a part and its imports are actually available, but it then throws an unexpected exceptions when the constructor is called, then you will still get an exception. To ignore such problems which are not composition-related, you can invoke the container directly like this:

IEnumerable<IPlugin> GetPluginsFromContainer(CompositionContainer container)
{
   foreach (Lazy<IPlugin> pluginExport in container.GetExports<IPlugin>())
   {
       try
       {
          yield return pluginExport.Value;
       }
       catch (Exception e)
       {
          // report failure to instantiate plugin somewhere
       }
   }   
}
Wim Coenen
Thank you, Wim! I will give this a try.
Dave
+3  A: 

Wim's example has the basic ideas but instead of pulling on the container directly I would suggest you do a Lazy ImportMany like such:

[Export]
public class MyApplication
{
   [ImportMany(typeof(IPlugin))]
   public IEnumerable<Lazy<IPlugin>> Plugins { get; set; }
}

Then you can initialize the plugins one by one and catch any errors from them like:

void InitializePlugins()
{
   foreach (Lazy<IPlugin> plugin in Plugins)
   {
       try
       {
          plugin.Value.Initialize();
       }
       catch (CompositionException e)
       {
          // Handle the error accordingly
       }
   }   
}

The actual plugin will not be created until you pull on .Value the first time and that is when errors will occur if the plugin has bugs in the constructor or property setters of the imports. Also note that I'm catch CompositionException which is what will come out of the .Value call if the plugin does something wrong.

Wes Haggard
that sounds good, too. I'll give it a try as well. Thanks!
Dave
+1 yep, it's not actually necessary to access the container directly. I hadn't realized that though it seems obvious now.
Wim Coenen
@Wes I still haven't used Lazy<T> for what you've described, but it did end up helping me out for a recent issue I was having. Now I can totally see how it will work in this particular situation as well!
Dave