Reputation: 15016
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!
Upvotes: 6
Views: 4974
Reputation: 5031
My solution to this problem is to call ComposeParts
for each plugin and keep a list of plugins for which it completed. Once all plugins have been indidvidually loaded, create a new AggregateCatalog
with all the plugins from the list and use ComposeParts on that catalog:
string[] pluginDirectories = Directory.GetDirectories(pluginsFolder);
List<string> workingPlugins = new List<string>();
foreach (string pluginDirectory in pluginDirectories)
{
Trace.WriteLine(String.Format("Discovering Plugins from '{0}'.", pluginDirectory));
try
{
// Try loading each Plugin one by one
DirectoryCatalog catalog = new DirectoryCatalog(pluginDirectory, "*Plugin.dll");
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(this);
// Plugin worked
workingPlugins.Add(pluginDirectory);
}
catch (Exception ex)
{
// Plugin didn't work
Trace.WriteLine(String.Format("Exception loading Plugin: {0}", ex.Message));
}
}
if (workingPlugins.Count > 0)
{
// Create the catalog that will be used for the final composition
AggregateCatalog catalog = new AggregateCatalog();
foreach (string plugin in workingPlugins)
{
try
{
catalog.Catalogs.Add(new DirectoryCatalog(plugin, "*Plugin.dll"));
}
catch (Exception ex)
{
Trace.WriteLine(String.Format("Exception loading Plugin: {0}", ex.Message));
}
}
// Create the CompositionContainer with the parts in the catalog.
try
{
_container = new CompositionContainer(catalog);
_container.ComposeParts(this);
Trace.WriteLine(String.Format("{0} plugins succesfully loaded.", _container.Catalog.Count()));
}
catch (Exception ex)
{
Trace.WriteLine(String.Format("Exception loading Plugins: {0}", ex.Message));
}
}
else
{
Trace.WriteLine(String.Format("No Plugins discovered."));
}
Upvotes: 0
Reputation: 4364
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.
Upvotes: 10
Reputation: 66753
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
}
}
}
Upvotes: 3