Can Unity Container Load Assemblies Dynamically? - c#

I was developing an application. I want unity to resolve my types WITHOUT having to reference the assemblies in the main project. I tought it loaded assemblies automatically by configuring the type registration by using but doesn't seem to work unless I add a reference to the assembly containing dependencies.
Is there a wat to load types from the assemblies in the current directory?
Thanks!

Sounds like you want MEF and not Unity. MEF is designed for dynamic discovery.
Read the answer to this question: What is different between and purpose of MEF and Unity?

I know this was asked a while ago, but for anyone looking for an answer, you have to make sure that Unity can locate the assemblies at run-time. So you either need to put them in the GAC or drop your assembly dll in the same directory as your executable. If you are using a different directory for dependencies and have a web app, then you have to set the probing element in your web.config:
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Dependencies" />
</assemblyBinding>
</runtime>
And for anyone out there looking for a piece of code on how to solve this, the following code will look for all assemblies on your defined directory and register them with Unity:
string DependenciesPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Dependencies");
string[] Dependencies = Directory.GetFiles(DependenciesPath, DLL_PATTERN);
Dictionary<Type, Type> pluginMappings = new Dictionary<Type, Type>();
//Load Dependency Assemblies
foreach (string fileName in Dependencies)
{
string assemblyName = Path.GetFileNameWithoutExtension(fileName);
if (assemblyName != null)
{
Assembly pluginAssembly = Assembly.Load(assemblyName);
foreach (Type pluginType in pluginAssembly.GetTypes())
{
if (pluginType.IsPublic) //Only look at public types
{
if (!pluginType.IsAbstract) //Only look at non-abstract types
{
//Gets a type object of the interface we need the plugins to match
Type[] typeInterfaces = pluginType.GetInterfaces();
foreach (Type typeInterface in typeInterfaces)
{
if (pluginMappings.ContainsKey(typeInterface))
{
throw new DuplicateTypeMappingException(typeInterface.Name, typeInterface, pluginMappings[typeInterface], pluginType);
}
pluginMappings.Add(typeInterface, pluginType);
}
}
}
}
}
}
//Web API resolve dependencies with Unity
IUnityContainer container = new UnityContainer();
foreach (var mapping in pluginMappings)
{
container.RegisterType(mapping.Key, mapping.Value);
}

It might be a bit late to help, but Unity can dynamically load assemblies if you use the XML configuration approach, register each assembly, and then register the types accordingly. I've been using this process for minor extensions to an otherwise very DI-heavy framework for awhile now.
If you're encountering an issue where Unity fails to resolve a type that's registered in the main application but defined in another, unreferenced assembly and referencing said assembly resolves the issue, that most likely means that it just wasn't copied to the application's output directory. By default, only assemblies that are directly referenced are automatically copied. If they're copied manually or by a post-build event, or if you redirect your build paths so that the unreferenced assemblies build to the application's output directory, it should work.

Related

Some types could not be loaded from a dll that was compiled from a local project

Setup
I am working on a C# project without external dependencies, named "Hopper". I have split it up into a couple of different modules, each contained within a corresponding sub-folder. The relevant ones for the problem are the following:
Utils/Hopper.Utils.csproj
Shared/Hopper.Shared.csproj
Core/Hopper.Core.csproj, which references Utils and Shared, more details later
Mine/Hopper.Mine.csproj, which references Core
All of these target .NET 4.8, including Hopper.Core.
I have set the AssemblyName property for each of the projects to the corresponding string. For example, the Hopper.Utils.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net4.8</TargetFramework>
<AssemblyName>Hopper.Utils</AssemblyName>
</PropertyGroup>
</Project>
Hopper.Core.csproj references the other projects via ProjectReference, like so:
<ItemGroup>
<ProjectReference Include="..\Utils\Hopper.Utils.csproj" />
<ProjectReference Include="..\Shared\Hopper.Shared.csproj" />
</ItemGroup>
Hopper.Mine.csproj, created for testing purposes, references core like this:
<ItemGroup>
<ProjectReference Include="..\Core\Hopper.Core.csproj" />
</ItemGroup>
The Hopper.Core project contains some types, both classes and structs. Some source files have been autogenerated by a tool and contain #pragma warning disable at the top. (Removing it did not help).
The two projects referenced by Hopper.Core just contain some types and attributes, used by Core, so nothing of interest to the problem. They do not reference any other projects or dll's.
The problem
I am able to compile Hopper.Mine by running dotnet build in the sub-folder with the project file. I am able to see the output dll's of the referenced sub-projects in bin/Debug/net4.8/Hopper.Whatever.dll under each project's folder. Intellisense in VSCode also works correctly and does not report any errors.
Hopper.Mine has a single class with a Main function, created for a test. It lists the types of Hopper.Core that have loaded successfully, and reports the ones that have not been loaded:
using System;
using System.Reflection;
using System.Text;
namespace Hopper.Mine
{
public class Program
{
public static void Main(string[] args)
{
Assembly lib = typeof(Hopper.Core.Action).Assembly;
Type[] types;
try
{
types = lib.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
StringBuilder sb = new StringBuilder();
foreach (Exception exSub in ex.LoaderExceptions)
{
Console.WriteLine(exSub.Message);
}
types = ex.Types;
}
foreach (Type type in types)
{
if (type != null)
Console.WriteLine(type.FullName);
}
}
}
}
Running this code, I can see a bunch of problematic types, some of which repeat, and the rest of types that have loaded successfully:
Could not load type 'Hopper.Core.Stat.Attack' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Dig' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Move' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Push' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Attack' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Attack' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
... more errors here ...
Hopper.Core.DirectedPredict
Hopper.Core.UndirectedPredict
Hopper.Core.DirectedDo
... more types here ...
Hopper.Core.Stat.Attack+Source+Resistance
Hopper.Core.Stat.Push+Source+Resistance
To be noted 4 things:
The types that failed to load are all structs and derive from IStat.
The source files with these types have been autogenerated by a tool (other files generated by the tool load without errors).
The types do not reference any types from Hopper.Shared or Hopper.Utils.
Some of the types have nested structs, up to 3 level deep, some of the deeper ones seem to have loaded successfully.
I am pretty sure that the dll output from the Hopper.Core's output folder is identical to the one in the output folder of Hopper.Mine. I am also pretty sure that the right dll is linked against when I start the program (I've tried debugging with VS, where I can see which dll is being linked against and the paths matched).
I have tried removing AssemblyName properties, renaming csproj files from Hopper.Whatever.csproj to Hopper_Whatever.csproj, I've obviously tried cleaning up any previous output dll's and building from scratch, none of which helped. I have also been able to reproduce this on a different machine with the exact same results.
I have tried looking at the Hopper.Core dll with a decompiler, ILSpy. I am able to see the "missing" types as they are in code.
I have searched on google for hours for a problem like mine, to no avail. I have found many cases of people setting up their project files incorrectly, due to which they unexpectedly link against a different version of the dll than they expected. For some, such dependency showed up in requirements of one of the referenced third party projects. Some blame GAC for providing incorrect version of the dll. However, I'm pretty sure that my projects are set up right (+ the set up is pretty simple and no third party projects are being referenced) and I am referencing the right dll's (since I can see my types in it). It kind of seems like the types are 'declared' in the dll, but not 'defined' (sort of), so the dll's turn out corrupted. But then why would the decompilation not fail? So I'm currently completely stuck on this.
Loading the assembly manually
I have tried linking against the dll's dynamically, by their names. Listing the types imported from Hopper.Shared and Hopper.Utils works, however, when I try it with Hopper.Core, I get the same output as the example above.
To achieve this, I have copied the previous dll's from the output over to a new folder, removed the reference from Hopper.Mine, compiled the new code with dotnet build, after which moved the executable to the new folder and finally ran it in the console. The code:
using System;
using System.Reflection;
using System.Text;
namespace Hopper.Mine
{
public class Program
{
public static void Main(string[] args)
{
Assembly shared = Assembly.LoadFrom("Hopper.Shared.dll");
Assembly utils = Assembly.LoadFrom("Hopper.Utils.dll");
Assembly core = Assembly.LoadFrom("Hopper.Core.dll");
Type[] types;
try
{
types = core.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
StringBuilder sb = new StringBuilder();
foreach (Exception exSub in ex.LoaderExceptions)
{
Console.WriteLine(exSub.Message);
}
types = ex.Types;
}
foreach (Type type in types)
{
if (type != null)
Console.WriteLine(type.FullName);
}
}
}
}
To reproduce
If it helps, you may try running my code. Here's the github link to the current state of the project. In order to run it, you need:
.NET 4.8 installed.
Clone the repository from the link above.
Some code is still missing. You need to run the project in the folder Meta to generate it. Just run dotnet run in the Meta folder.
Now you can run the Mine subproject.
Found out the reason. There is a bug in the CLR type system that has been around for 6 years. Basically, fields (either static or non-static) of type of a generic struct declared in other structs cause this behavior. See this github thread for more details.
In my case, I have been able to make a shorter version that illustrates the bug.
public struct Index<T>
{
}
public struct Test
{
public static Index<Test> Index;
}
Use the following code to test the program:
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine(typeof(Test).Assembly.GetTypes());
}
}
When the code is run, the following error is thrown:
Unhandled Exception: System.TypeLoadException: Could not load type 'Hopper.Mine.Test' from assembly 'Hopper.Mine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
at Hopper.Mine.Program.Main(String[] args)
However, there is an easy sort of workaround for this. Changing Test from struct to class works:
// This works!
public struct Index<T>
{
}
public class Test
{
public static Index<Test> Index;
}
That may not be acceptable in some cases. So here is another workaround — storing the static field in an array and accessing it with a property also works:
public struct Index<T>
{
}
public struct Test
{
public static Index<Test> Index { get => index[0]; set => index[0] = value; }
public static Index<Test>[] index = new Index<Test>[1];
}

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).

Getting a list of types in an assembly that inherit from a specific interface

Please read before marking as duplicate, already answered, on hold, or off topic.
First of all, I know there are SIMILAR question here, with very good answers. Unfortunately they do not answer my question.
My goal: to create a list of all of the types within an assembly that inherit from a specific interface. This assembly may be located at a local path or a UNC path.
My problem: First, I know how to load an assembly, both from a UNC and a local path, and I know how to use GetTypes(). Many of you may know that GetTypes() has it's own issue. Specifically, it will raise an exception if any of the Types inherit from another type that cannot be loaded. How to prevent ReflectionTypeLoadException when calling Assembly.GetTypes() for instance. The problem is, I don't need to load any of the dependencies. I only need to see if a type declared in the assembly inherits from an other specific type.
some code as an example. This is just the first step, to retrieve the types in the assembly:
Assembly asm = Assembly.Load(File.ReadAllBytes(assemblyPath));
Type[] myTypes = GetLoadableTypes(asm);
public static Type[] GetLoadableTypes(Assembly assembly)
{
// TODO: Argument validation
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
return e.Types.Where(t => t != null);
}
}
If any of the Types inside of the assembly inherit from a type that cannot be loaded, a ReflectionTypeLoadException exception will be thrown and they will show up as null inside of e.Types, so there is still no information on them. So I guess I can't use GetTypes still.
For completeness, here is a reference to my original question.
How to get Type information when ReflectionTypeLoadException is thrown from Assembly.GetType()
OK, made some headway with this and, unless I want to write my own metadata parser, it looks like this is what I am stuck with so I thought I'd share. I'll try to give the pros and cons.
This answer all revolves around using an event handler for the
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve
event. It should be pointed out that this event is only raised when an assembly's dependencies are being resolved. I didn't understand that at first but what it simply means is that the event is not fired for the assembly you are trying to load, but any dependencies in that assembly, which makes sense. Why raise that event when you have to have a starting point in the first place. For my solution, this event will be raised when I call .GetTypes().
Before I post the code let me explain the idea and what's good and bad about it. The idea is that, if I have a dependency, the event will be raised. In the event, I will first check a dictionary, created before hand, of all of the assemblies found in the same folder (including subfolders) as the original assembly. If the args.Name is found in that dictionary, then I read in that assembly and return it from the event. If it's not in the list, I attempt to load the assembly by name only, meaning the dependency assembly must either be part of the project or installed in the local GAC.
I already hinted at some of the cons here, so let's start with those.
Since I'm not just looking at metadata to tell me what dependencies are in the main assembly (in other words those dependencies must be loaded), if a dependency's assembly cannot be found with the original assembly (in the same folder or subfolders), and the dependency is not installed in the local GAC, or referenced in the project, GetTypes() will still throw a ReflectionTypeLoadException exception. It may seem like a rare case to run in to a situation where an dependency assembly is not located with the original assembly or installed in the GAC but I would give PRISM as an example of a situation where that may happen. PRISMs assemblies do not necessarily get installed into the GAC and are not always going to be copied local.
So what's good about this method? Well mainly that it gives you at least some way to handle 99% of the situations where you don't have a dependency's assembly in hand.
One or two final thoughts (actually gotchas) before the code. I use AssemblyName to create a dictionary bases on the assembly's full name without actually loading (either straight out or by ReflectionOnlyLoad) an assembly. There are two ways you can create an instance of AssemblyName, either by the constructor where you pass in the path to the assembly, or with a static method, GetAssemblyName(path). The constructor seems to only handle local paths where GetAssemblyName() can handle a UNC path. Those are the gotchas: don't load the assembly just to get the name and make sure you don't limit yourself to local paths.
Finally, some code:
public class TestClass
{
//This dictionary will hold a list of the full path for an assembly, indexed by the assembly's full name
private Dictionary<string, string> _allAsms = new Dictionary<string, string>();
/// <summary>
/// Tries to list all of the Types inside an assembly and any interfaces those types inherit from.
/// </summary>
/// <param name="pathName">The path of the original assembly, without the assembly file</param>
/// <param name="fileName">The name of the assembly file as it is found on disk.</param>
public void Search(string pathName, string fileName)
{
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(CurrentDomain_ReflectionOnlyAssemblyResolve);
//Getting a list of all possible assembly files that exist in the same location as the original assembly
string[] filePaths = Directory.GetFiles(pathName, "*.dll", SearchOption.AllDirectories);
Assembly asm;
AssemblyName name;
if (!pathName.EndsWith("\\"))
pathName += "\\";
foreach (string path in filePaths)
{
name = AssemblyName.GetAssemblyName(path);
if (!_allAsms.ContainsKey(name.FullName))
_allAsms.Add(name.FullName, path);
}
//This is where we are loading the originaly assembly
asm = System.Reflection.Assembly.ReflectionOnlyLoad(File.ReadAllBytes(pathName + fileName));
Console.WriteLine("Opened assembly:{0}", fileName);
//And this is where the ReflectionOnlyAssemblyResolve will start to be raised
foreach (Type t in asm.GetTypes())
{
Console.WriteLine(" " + t.FullName);
//Get the interfaces for the type;
foreach (Type dep in t.GetInterfaces())
{
Console.WriteLine(" " + dep.FullName);
}
}
}
private Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
{
if (_allAsms.ContainsKey(args.Name))
return Assembly.ReflectionOnlyLoad(File.ReadAllBytes(_allAsms[args.Name]));
else
return System.Reflection.Assembly.ReflectionOnlyLoad(args.Name);
}
}
To check if a type implements a specific interface you can use this:
typeof(yourInterface).IsAssignableFrom(type)
However, if the type could not be loaded there is no information available on it, and you have no chance to figure out whether it implements a specific interface or not.

MEF Composition Error Because of Assembly Names?

Thanks in advance for reviewing this question!
I am using MEF to load some assemblies in a project. Everything was working great until we changed the name of the file that contains the interface.
To make it clearer, I'll summarize the scenario where things worked, then the scenario where things did not work, then specifically show the exception and the code that caused the exception in the scenario that didn't work.
Here's the working scenario:
We've got an interface called IPlugin that is defined in an assembly called Common-1-0.dll.
We have some plugin assemblies that are compiled against Common-1-0.dll.
The application that loads the plugin is compiled against Common-1-0.dll.
Here's the non working scenario:
We've got this an called IPlugin that is defined in an assembly called Common-1-1.dll. The interface has not changed from Common-1-0.dll.
We have some plugin assemblies that are compiled against Common-1-0.dll.
The application that loads the plugin is compiled against Common-1-1.dll.
Now the issue:
When I go to run this code below in the second scenario, I get a CompositionException (shown under the code below). It appears to be caused because the the plugin was compiled against Common-1-0.dll while the application trying to do the composition was compiled against Common-1-1.dll. Nothing within the code has changed between the two files, just the name.
So we'd like to be able to load plugins built against whatever assembly so long as that assembly exports the right interface but I'm not sure if I can do that with MEF or not. That is what I'd like to know as a result of this question.
Code :
private void LoadPlugins(string directory, string searchPattern = "", bool recursive = false)
{
Trace.Agent.Status("Loading plugin(s) from {0}{1}{2}", directory, Path.DirectorySeparatorChar, searchPattern);
try
{
var directoryCatalog = string.IsNullOrEmpty(searchPattern)
? new DirectoryCatalog(directory)
: new DirectoryCatalog(directory, searchPattern);
_container = new CompositionContainer(new AggregateCatalog(directoryCatalog));
_container.ComposeParts(this);
}
catch (CompositionException exc)
{
Trace.Agent.Exception(exc);
}
if (recursive)
{
foreach (string dir in Directory.GetDirectories(directory))
{
LoadPlugins(Path.Combine(directory, dir), recursive:true);
}
}
}
CompositionException :
The export 'TestPlugin.TestPlugin (ContractName="Common.IPlugin")' is not assignable to type 'Common.IPlugin'.
I think I found a solution for your issue, it is a little bit hacked but anyways.
So if you have named your assemblies differently, e.g. assembly1.0 and assembly1.1 this causes issues because you cannot simply redirect the assembly to a new version.
Usually you would just keep the same assembly name and increase the version number. This way you can redirect without any issues (as long as the code supports it).
The solution is to hack into the assembly resolve mechanism by attaching to the AssemblyResolve event of the current AppDomain.
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);
LoadPlugins(currentDomain.BaseDirectory);
var x = _container.GetExport<IPlugin>().Value;
within the event handler you can simply return the "new" assembly
private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
if (args.Name.StartsWith("PluginCore1.0"))
{
return typeof(IPlugin).Assembly;
}
return null;
}
This works, also without signed assemblies (without public key tokens).
To trigger the resolve event you still have to define the assembly redirection within your app.config though:
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="PluginCore1.0"
culture="neutral" />
<bindingRedirect oldVersion="1.0.0.0" newVersion="1.1.0.0" />
</dependentAssembly>
</assemblyBinding>
Again, I would strongly recommend to use the same assembly name (without the version suffix) and simply use the assembly redirection mechanism which should work just fine.

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.

Categories