MEF not loading DLL after adding metadata - c#

After adding Metadata to my MEF exports one of my DLL will no longer load, whereas the other does. I declared my variable like so:
[ImportMany]
private IEnumerable<Lazy<IOOOContract, IOOOContractMetaData>> plugins;
and I defined my metadata interface:
public interface IOOOContractMetaData {
string name { get; }
}
To load the code I just used what I think is the standard way:
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(#"...."));
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
The plugin that I want to load is just a class library that is defined like this:
[Export(typeof(IOOOContract))]
[ExportMetadata("name", "TMRAT")]
public class Class1 : IOOOContract
If I run mefx.exe against the directory, I see each DLL properly listing the IOOOContract, and none of them show any output if I use the /rejected flag. The only thing that I can think which might be effecting it is that the DLL which doesn't load has a reference to another DLL that's not part of the GAC. I tried copying that DLL to the same directory listed on the DirectoryCatalog() but that didn't make a difference.

Related

Preloading Referenced Assemblies

I've got a rather large (and growing) application that uses Nancy and subsequently the TinyIoC library. Each of the modules are broken out into individual projects, along with my data models, so my project structure looks sort-of like this:
Solution
|- NancyHost
|- DataModels
|- Modules.Common
|- Modules.Dashboard
|- Modules.Security
|- Repositories
The issue that I'm having is TinyIoC has an AutoRegister event that is supposed to go through the current AppDomain and register all the "register-able" types. This is great except that on startup the assemblies are not yet loaded into the app domain since it uses DI to resolve types.
As a work-around I've come up with classes like this:
public static class RepositoryInitializer
{
public static void Initialize()
{
}
}
Which I then call from the Main on startup, but I don't really like that approach since it requires me to make a dummy initializer class for each linked assembly. I could also do something like:
GC.KeepAlive(typeof(ReferencedAssembly.SomeType));
But again, that requires me to add that same directive for each referenced assembly.
I thought I remember something about a hard-binding attribute you could apply to the assembly that would pre-load a referenced assembly? I can't find anything on it but I swear it was there.
I also tried this:
static void Preload()
{
string location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
List<Type> types = new List<Type>();
foreach (var dllFile in Directory.EnumerateFiles(location, "*.dll"))
{
types.AddRange(Assembly.ReflectionOnlyLoadFrom(dllFile).GetTypes());
}
var assemblies = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
//assemblies does not contain all referenced assemblies
}
Because I read something about using ReflectionOnlyLoadFrom that would cause the DLL to load, but this gives me an exception about having to preload other referenced DLL's to be able to reflect the types (like System.Core).
So the question is, how can I preload my referenced assemblies without using explicit references to the types they contain? Please note that Assembly.GetReferencedAssemblies only contains the loaded assemblies, not all the referenced ones.
This is somewhat duplicate, but not any help: Preload assemblies: referenced, unreferenced, not loaded until they are needed
Edit
Here is a version with Assembly.Load:
static void Preload()
{
string location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var assemblies = new List<Assembly>();
foreach (var dllFile in Directory.EnumerateFiles(location, "*.dll"))
{
assemblies.Add(Assembly.Load(new AssemblyName() { CodeBase = dllFile }));
//Also tried Assembly.Load(File.ReadAllBytes(dllFile)), same effect
}
var refAssemblies = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
}
It does load the assemblies into the assemblies list, but does not cause them to be loaded into the current app domain. (Assembly.GetReferencedAssemblies() returns the same before and after the Preload call).

MEF DirectoryCatalog won't load, but AssemblyCatalog will

I have a folder full of DLL, some of which implement my contract, and so I tried to load them like so:
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(this.dllFolder));
When it finds one of the DLL with my interface, it complains that it can't find get_Name in the DLL. My interface defines a Name property with a getter and no setter, which the DLL does in fact implement.
Is there some trick to using properties with these catalogs? As a temporary workaround I'm doing this, which does seem to work (and I'm able to access the Name property just fine from the rest of the code)
var dlls = Directory.EnumerateFiles(this.dllFolder, "*.dll");
foreach (var dll in dlls)
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.LoadFile(dll)));

Ninject + Auto Discovery

Here's the deal: I want to create a C# console app. When run, this app will look in a particular folder for dll's with classes that implement a particular interface, and then run a method on those dll's.
I haven't done this before but from my reading that should be "easy enough" from an IoC/Ninject perspective. I think you can do something with kernel.Bind() to load assemblies of a certain interface in a certain directory. I think/hope I can figure that part out (if you know otherwise, please do tell!).
But here is my quandary.
Here is a visual to help first:
MainProgramFolder
-- MainProgram.exe
-- MainProgram.exe.config
-- LibraryFolder
----- Library1Folder
--------Library1.dll
--------Library1.dll.config
----- Library2Folder
--------Library2.dll
--------Library2.dll.config
The dll's that implement this interface are technically stand alone apps -- they are just libraries instead of exe's (or, rather, I'd like them to be for IoC purposes). I'd like for them to be run in their own context, with their own app.configs. So for example, MainProgram.exe would bind the ILibrary interface to classes inside Library1.dll and Library2.dll because they implement ILibrary. But inside Library1, it calls ConfigurationManager to get its settings. When I call Class.Method() for each of the bindings from MainProgram, how can I ensure they are referencing their own .config's and not MainProgram.exe.config? (Also, fwiw, these additional libraries will likely not be a part of the assembly or even namespace of the main programs -- we're basically providing a drop folder for an application to kind of "subscribe" to the main program's execution.)
IOW, I know you can attach an app.config to a class library but I wouldn't know how, after the bindings have been resolved from the IOC, to make those dll's "see" its own config rather than the main program's config.
All thoughts appreciated!
Thanks
Tom
First, to load and bind all of your classes you'll need ninject.extensions.conventions, and something like this:
var kernel = new StandardKernel();
/*add relevant loop/function here to make it recurse folders if need be*/
kernel.Bind(s => s.FromAssembliesMatching("Library*.dll")
.Select(type => type.IsClass && type.GetInterfaces().Contains(typeof(ILibrary)))
.BindSingleInterface()
.Configure(x=>x.InSingletonScope()));
To make each instance load its configuration as if it was the entry point you will need to run it in a new app domain. Your ILibrary implementation needs to inherit MarshalByRefObject and be Serializable so that it will run correctly in the alternate appdomain
[Serializable]
public class LibraryA :MarshalByRefObject, ILibrary
You can then add this activation strategy to your kernel that will cause it to swap out instances of ILibrary with an instance loaded in an alternate appdomain with your config file convention before they are returned.
public class AlternateAppDomainStrategy<T> : ActivationStrategy
{
public override void Activate(IContext context, InstanceReference reference)
{
if (reference.Instance.GetType().GetInterfaces().Contains(typeof(T)))
{
var type = reference.Instance.GetType();
var configFilePath = type.Assembly.GetName().Name + ".dll.config";
var file = new FileInfo(configFilePath);
if (file.Exists)
{
var setup = new AppDomainSetup() { ConfigurationFile = file.FullName, ApplicationBase = AppDomain.CurrentDomain.BaseDirectory };
var domain = AppDomain.CreateDomain(type.FullName, null, setup);
var instance = domain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName);
reference.Instance = instance;
}
else
{
throw new FileNotFoundException("Missing config file", file.FullName);
}
}
}
}
And Add it to your kernel
kernel.Components.Add<IActivationStrategy, AlternateAppDomainStrategy<ILibrary>>();
From there you can simply instantiate your ILibrary instances and call methods on them. They will load in their own app domains with their own configs. It gets a lot more complicated if you need to pass things in/out of the instance either via methods or constructor, but from the sound if it you don't so this should be OK.
var libs = kernel.GetAll<ILibrary>();
foreach (var lib in libs)
{
lib.Method();
}

How to export one MEF plugin multiple times, depending on app.config?

I am building a simple MEF application. What I want to achieve is building one plugin, which can be registered multiple times in the same composing application. The registration of the plugin should be dependent on a setting from the configfile of the plugin, but I am not able to do this.
[edit]
My server, which has the CompositionContainer, needs to communicate with 6 different targets (ie Traffic Light Controllers). For every target, I want to add a plugin. The plugin logic is the same, so I want to maintain only 1 plugin. Every target has its own webaddress to communicate (and some other configuration items), I want those to be in (separate) configfiles.
What I tried is putting the plugins in subdirectories and going recursively through those directories to add the plugins in the catalog. This doesn't work however. The second plugin found in the subdirs will be imported, but this one is targeting the first plugin. When looping through the container FASTAdapters, all parts seem to equal to the first.
private void Compose()
{
var catalog = new AggregateCatalog();
string sDir = AppSettingsUtil.GetString("FASTAdaptersLocation", #"./Plugins");
foreach (string d in Directory.GetDirectories(sDir))
{
catalog.Catalogs.Add(new DirectoryCatalog(d));
}
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
I don't know if I can also use the ExportMetadata attribute. It seems that ExportMetadata attributes have to be hardcoded, but I want the attribute being read from the config file if possible.
[/edit]
My goal is to have 6 ControllerAdapters, each targeting a different controller (read: communicating with a different webserver). The logic in the 6 ControllerAdapters is equal.
I thought copying the ClassLibrary (for example to 1.dll, 2.dll and so on) and adding the configfiles (1.dll.config and so on) should do the trick, but no.
When composing, I get multiple instances typeof(FAST.DevIS.ControllerAdapter) in the container, but I don't know how to get further.
Do I need to do something with MetaData in the export?
The importing server
[ImportMany]
public IEnumerable<IFASTAdapter> FASTAdapters { get; set; }
private void Compose()
{
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(AppSettingsUtil.GetString("FASTAdaptersLocation", Path.GetDirectoryName(Assembly.GetAssembly(typeof(ControllerServer)).Location))));
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
The plugin
namespace FAST.DevIS.ControllerAdapter
{
[Export (typeof(IFASTAdapter))]
public class ControllerAdapter : IFASTAdapter
{
...
}
}
The interface
namespace FAST.Common.FastAdapter
{
public interface IFASTAdapter
{
/// Parse plan parameters
///
//Activator
bool ParsePlan(PlansContainer plan);
bool ActivatePlan();
void Configure(string config);
}
}
This may be more a problem with how you are using assemblies than with the MEF solution.
You say:
The logic in the 6 ControllerAdapters is equal.
So is it the same DLL just copied 6 times to different plugin directories? If yes, then this is the problem.
I modeled your approach and ran the some tests to prove what I thought. The code is effectively the same as yours and reads plugins from the subdirectories of bin/plugin directory of the server.
Simple test using NUnit to exercise the server class library:
[Test]
public void Compose()
{
var server = new Server();
server.Compose();
Console.WriteLine("Plugins found: " + server.FASTAdapters.Count());
Console.WriteLine();
foreach (var adapter in server.FASTAdapters)
{
Console.WriteLine(adapter.GetType());
Console.WriteLine(adapter.GetType().Assembly.FullName);
Console.WriteLine(adapter.GetType().Assembly.CodeBase);
Console.WriteLine();
}
Assert.Pass();
}
Test results for one plugin in place:
Plugins found: 1
AdapterPlugin.ControllerAdapter
AdapterPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL
Test result for two plugins in place, using the same plugin assembly copied over to two distinct plugin directories (probably your case):
Plugins found: 2
AdapterPlugin.ControllerAdapter
AdapterPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL
AdapterPlugin.ControllerAdapter
AdapterPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL
You also get the exact same result if you give distinct names to those DLLs because effectively it is still the same assembly inside.
Now I add the third plugin, but this time it is a different plugin assembly:
Plugins found: 3
AdapterPlugin.ControllerAdapter
AdapterPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL
AdapterPlugin.ControllerAdapter
AdapterPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL
AdapterPlugin2.ControllerAdapter
AdapterPlugin2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER3/ADAPTERPLUGIN2.DLL
The different assembly is of course found and identified correctly.
So this all boils down to how the .NET runtime handles assembly loading, which is a complex and strictly defined process and works differently for strongly and weakly named assemblies. I recommend this article for a good explanation of the process: Assembly Load Contexts Subtleties.
In this case, the same process is followed behind the scenes when using MEF:
The .NET runtime finds the first weakly typed plugin assembly and loads it from that location and MEF does it's export processing.
Then MEF tries to process the next plugin assembly it founds using the catalog, but the runtime sees the assembly with the same metadata already loaded. So it uses the already loaded one to look for exports and ends up instantiating the same type again. It does not touch the second DLL at all.
There is no way the same assembly can be loaded more than once by the runtime. Which makes perfect sense when you think of it. Assembly is just bunch of types with their metadata and once loaded the types are available, no need to load them again.
This may not be completely correct, but I hope it helps to explain where the problem lies and it should be clear that duplicating DLLs for this purpose is useless.
Now regarding what you want to achieve. It seems that all you need is just to get multiple instances of the SAME adapter plugin to use them for different purposes, which has nothing to do with multiplying DLLs.
To get multiple adapter instances you can define multiple imports with RequiredCreationPolicy set to CreationPolicy.NonShared in your server which MEF will accordingly instantiate for you:
public class Server
{
[Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
public IFASTAdapter FirstAdapter { get; set; }
[Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
public IFASTAdapter SecondAdapter { get; set; }
// Other adapters ...
public void Compose()
{
var catalog = new AggregateCatalog();
var pluginsDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugins");
foreach (string d in Directory.GetDirectories(pluginsDir))
{
catalog.Catalogs.Add(new DirectoryCatalog(d));
}
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
}
Corresponding NUnit test to check that the adapters are instantiated and that they are different instances:
[Test]
public void Compose_MultipleAdapters_NonShared()
{
var server = new Server();
server.Compose();
Assert.That(server.FirstAdapter, Is.Not.Null);
Assert.That(server.SecondAdapter, Is.Not.Null);
Assert.That(server.FirstAdapter, Is.Not.SameAs(server.SecondAdapter));
}
If all of this will help you to some extent, we can also take look at how you want to configure what and how to instantiate using the app.config.

MEF not loading a references again

I have a interface as follows:
[InheritedExport(typeof(ITransform))]
public interface ITransform
{...}
Now, I have two classes:
namespace ProjectA
{
public class Transform:ITransform {....}
}
And
namespace ProjectB
{
public class Transform:ITransform {....}
}
I am using DirectoryCatalog for loading each parts. Each project is compiled and their binaries(build output) location is given as an input to DirectoryCatalog for further composition.
The code for fetching ITransform parts is as follows:
public static class ExtensionFactory
{
public static ITransform GetExtension(string extensionPath)
{
IEnumerable<ITransform> extensions = null;
try
{
AggregateCatalog catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(extensionPath));
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(catalog);
extensions = container.GetExportedValues<ITransform>();
return extensions.FirstOrDefault();
}
catch (Exception ex) {........}
return extensions.FirstOrDefault();
}
}
I have another project ProjectXYZ(auto generated by third party tool(Altova Mapforce 2012 SP1)).
For ProjectA:
namespace ProjectXYZ
{
public classA{...}
}
For ProjectB:
namespace ProjectXYZ
{
public classA{...}
public classB{...}
}
ProjectA.Transform uses ProjectXYZ.ClassA, whereas ProjectB.Transform uses ProjectXYZ.ClassB from another implementation of ProjectXYZ. The implementation and classes of ProjectXYZ varies across for different implementation of ITransform. The classes in ProjectXYZ are automatically generated through some third-party tools, which I need to use directly. So, I cannot make any changes to ProjectXYZ.
So, when first time MEF loads ProjectA.Transform, it also loads ProjectXYZ to be used as a reference for ProjectA. When ProjectB.Transform is getting loaded/exported, then as ProjectXYZ being already in MEF memory, it uses the ProjectXYZ reference available from ProjectA. Thus, when ProjectB.Transform is executing, it searches for ProjectXYZ.ClassB, which it does not gets as MEF has load ProjectXYZ reference available in ProjectA.
How to resolve this problem. The MEF loads the parts correctly, but it does not load the supporting dll's references in a desired manner. I have also tried PartCreationPolicy attribute, but the results are same.
It not a MEF problem. The problem is in the loading model of .NET. (or better the way you're objects are loaded by .net)
When MEF loads it returns the correct objects. But when looking for class ProjectXYZ when projectB is loaded there is already a ProjectXYZ dll loaded with the correct assembly name projectB is referring to. And the loader the dll actually referenced by projectB is not loaded.

Categories