I am using a DirectoryCatalog
in MEF to satisfy imports in my application. However, there are sometimes obfuscated assemblies in the directory that cause a ReflectionTypeLoadException
when I try to compose the catalog.
I know I can get round it by using a separate directory or by using a search filter on the DirectoryCatalog
but I want a more general way to solve the problem. Is there some way I can handle the exception and allow composition to continue? Or is there another more general solution?
DirectoryCatalog
already has code to catch ReflectionTypeLoadException
and ignore those assemblies. Unfortunately, as I have reported, merely creating the AssemblyCatalog
will not yet trigger the exception so that code doesn't work.
The exception is actually triggered by the first call to AssemblyCatalog.Parts
.
Instead of using the DirectoryCatalog
from MEF, you will have to do it yourself:
- scan a directory for assemblies
- load each assembly and creates a
AssemblyCatalog
for it
- invoke
AssemblyCatalog.Parts.ToArray()
to force the exception, and catch it
- aggregate all the good catalogs with a
AggregateCatalog
To save others from writing their own implementation of the SafeDirectoryCatalog, here is the one I came up with based upon Wim Coenen's suggestions:
public class SafeDirectoryCatalog : ComposablePartCatalog
{
private readonly AggregateCatalog _catalog;
public SafeDirectoryCatalog(string directory)
{
var files = Directory.EnumerateFiles(directory, "*.dll", SearchOption.AllDirectories);
_catalog = new AggregateCatalog();
foreach (var file in files)
{
try
{
var asmCat = new AssemblyCatalog(file);
//Force MEF to load the plugin and figure out if there are any exports
// good assemblies will not throw the RTLE exception and can be added to the catalog
if (asmCat.Parts.ToList().Count > 0)
_catalog.Catalogs.Add(asmCat);
}
catch (ReflectionTypeLoadException)
{
}
catch (BadImageFormatException)
{
}
}
}
public override IQueryable<ComposablePartDefinition> Parts
{
get { return _catalog.Parts; }
}
}
I was doing this from an API I was writing and the SafeDirectoryCatalog would not log multiple exports matching a single import from different assemblies. MEF debugging is typically done via debugger and TraceListener. I already used Log4Net and I didn't want someone to need to add another entry to the config file just to support logging. http://blogs.msdn.com/b/dsplaisted/archive/2010/07/13/how-to-debug-and-diagnose-mef-failures.aspx I came up with:
// I don't want people to have to add configuration information to get this logging.
// I know this brittle, but don't judge... please. It makes consuing the api so much
// easier.
private static void EnsureLog4NetListener()
{
try
{
Assembly compositionAssembly = Assembly.GetAssembly(typeof (CompositionContainer));
Type compSource = compositionAssembly.GetType("System.ComponentModel.Composition.Diagnostics.CompositionTraceSource");
PropertyInfo canWriteErrorProp = compSource.GetProperty("CanWriteError");
canWriteErrorProp.GetGetMethod().Invoke(null,
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static, null, null,
null);
Type traceSourceTraceWriterType =
compositionAssembly.GetType(
"System.ComponentModel.Composition.Diagnostics.TraceSourceTraceWriter");
TraceSource traceSource = (TraceSource)traceSourceTraceWriterType.GetField("Source",
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Static).GetValue(null);
traceSource.Listeners.Add(new Log4NetTraceListener(logger));
}
catch (Exception e)
{
logger.Value.Error("Cannot hook MEF compisition listener. Composition errors may be swallowed.", e);
}
}