Preloading Referenced Assemblies - c#

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

Related

Strange behavior when loading assemblies and its dependencies programatically

The following experimental codes/projects are using netcore 2.0 and netstandard 2.0 in VS2017. Let's say I have two versions of a third party dll v1.0.0.0 and v2.0.0.0, which contains only one class Constants.cs.
//ThirdPartyDependency.dll v1.0.0.0
public class Constants
{
public static readonly string TestValue = "test value v1.0.0.0";
}
//ThirdPartyDependency.dll v2.0.0.0
public class Constants
{
public static readonly string TestValue = "test value v2.0.0.0";
}
Then I created my own solution named AssemblyLoadTest, which contains:
Wrapper.Abstraction: class library with no project references
namespace Wrapper.Abstraction
{
public interface IValueLoader
{
string GetValue();
}
public class ValueLoaderFactory
{
public static IValueLoader Create(string wrapperAssemblyPath)
{
var assembly = Assembly.LoadFrom(wrapperAssemblyPath);
return (IValueLoader)assembly.CreateInstance("Wrapper.Implementation.ValueLoader");
}
}
}
Wrapper.V1: class library with project reference Wrapper.Abstractions and dll reference ThirdPartyDependency v1.0.0.0
namespace Wrapper.Implementation
{
public class ValueLoader : IValueLoader
{
public string GetValue()
{
return Constants.TestValue;
}
}
}
Wrapper.V2: class library with project reference Wrapper.Abstractions and dll reference ThirdPartyDependency v2.0.0.0
namespace Wrapper.Implementation
{
public class ValueLoader : IValueLoader
{
public string GetValue()
{
return Constants.TestValue;
}
}
}
AssemblyLoadTest: console application with project reference Wrapper.Abstraction
class Program
{
static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += (s, e) =>
{
Console.WriteLine($"AssemblyResolve: {e.Name}");
if (e.Name.StartsWith("ThirdPartyDependency, Version=1.0.0.0"))
{
return Assembly.LoadFrom(#"v1\ThirdPartyDependency.dll");
}
else if (e.Name.StartsWith("ThirdPartyDependency, Version=2.0.0.0"))
{
//return Assembly.LoadFrom(#"v2\ThirdPartyDependency.dll");//FlagA
return Assembly.LoadFile(#"C:\FULL-PATH-TO\v2\ThirdPartyDependency.dll");//FlagB
}
throw new Exception();
};
var v1 = ValueLoaderFactory.Create(#"v1\Wrapper.V1.dll");
var v2 = ValueLoaderFactory.Create(#"v2\Wrapper.V2.dll");
Console.WriteLine(v1.GetValue());
Console.WriteLine(v2.GetValue());
Console.Read();
}
}
STEPS
Build AssemblyLoadTest in DEBUG
Build Wrapper.V1 project in DEBUG, copy files in Wrapper.V1\bin\Debug\netstandard2.0\ to AssemblyLoadTest\bin\Debug\netcoreapp2.0\v1\
Build Wrapper.V2 project in DEBUG, copy files in Wrapper.V2\bin\Debug\netstandard2.0\ to AssemblyLoadTest\bin\Debug\netcoreapp2.0\v2\
Replace FULL-PATH-TO in AssemblyLoadTest.Program.Main with the correct absolute v2 path that you copied in step 3
Run AssemblyLoadTest - Test1
Comment FlagB line and uncomment FlagA line, run AssemblyLoadTest - Test2
Comment AppDomain.CurrentDomain.AssemblyResolve, run AssemblyLoadTest - Test3
My results and questions:
Test1 succeeds and prints v1.0.0.0 and v2.0.0.0 as expected
Test2 throws exception at v2.GetValue()
System.IO.FileLoadException: 'Could not load file or assembly
'ThirdPartyDependency, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=null'. Could not find or load a specific file.
(Exception from HRESULT: 0x80131621)'
Question1: Why LoadFile with absolute path works as expected, while LoadFrom with relative path not working, meanwhile LoadFrom with relative path works for v1.0.0.0 in the first if statement?
Test3 fails with the same exception above at the same place, here my understanding is CLR locates the assemblies with the following priority rule:
Rule1: Check if AppDomain.AssemblyResolve is registered (highest priority)
Rule2: Otherwise check if the assembly is loaded.
Rule3: Otherwise search the assembly in folders(can be configured in probing and codeBase.
Here in Test3 where AssemblyResolve is not registered, v1.GetValue works because Rule1 and Rule2 is N/A, AssemblyLoadTest\bin\Debug\netcoreapp2.1\v1 is in Rule3 scan candidates. When executing v2.GetValue, Rule1 is still N/A, however Rule2 is applied here (if Rule3 is applied, why exceptions?)
Question2: Why the version is ignored even Wrapper.V2 reference ThirdPartyDependency.dll using
<Reference Include="ThirdPartyDependency, Version=2.0.0.0">
<HintPath>..\lib\ThirdPartyDependency\2.0.0.0\ThirdPartyDependency.dll</HintPath>
</Reference>
Great answer from Vitek Karas, original link here.
Kind of unfortunately all of the behavior you describe is currently as designed. That doesn't mean it's intuitive (which it's totally not). Let me try to explain.
Assembly binding happens based on AssemblyLoadContext (ALC). Each ALC can have only one version of any given assembly loaded (so only one assembly of a given simple name, ignoring versions, culture, keys and so on). You can create a new ALC which then can have any assemblies loaded again, with same or different versions. So ALCs provide binding isolation.
Your .exe and related assemblies are loaded into a Default ALC - one which is created at the start of the runtime.
Assembly.LoadFrom will try to load the specified file into the Default ALC - always. Let me stress the "try" word here. If the Default ALC already loaded assembly with the same name, and the already loaded assembly is equal or higher version, then the LoadFrom will succeed, but it will use the already loaded assembly (effectively ignoring the path you specified). If on the other hand the already loaded assembly is of a lower version then the one you're trying to load - this will fail (we can't load the same assembly for the second time into the same ALC).
Assembly.LoadFile will load the specified file into a new ALC - always creates a new ALC. So the load will effectively always succeed (there's no way this can collide with anything since it's in its own ALC).
So now to your scenarios:
Test1
This works because your ResolveAssembly event handler loads the two assemblies into separate ALCs (LoadFile will create a new one, so the first assembly goes to the default ALC, and the second one goes into its own).
Test2
This fails because LoadFrom tries to load the assembly into the Default ALC. The failure actually occurs in the AssemblyResolve handler when it calls the second LoadFrom. First time it loaded v1 into Default, the second time it tries to load v2 into Default - which fails because Default already has v1 loaded.
Test3
This fails the same way because it internally does basically exactly what Test2 does. Assembly.LoadFrom also registers event handler for AssemblyResolve and that makes sure that dependent assemblies can be loaded from the same folder. So in your case v1\Wrapper.V1.dll will resolve its dependency to v1\ThirdPartyDependency.dll because it's next to it on the disk. Then for v2 it will try to do the same, but v1 is already loaded, so it fails just like in Test2. Remember that LoadFrom loads everything into the Default ALC, so collisions are possible.
Your questions:
Question1
LoadFile works because it loads the assembly into its own ALC, which provides full isolation and thus there are never any conflicts. LoadFrom loads the assembly into the Default ALC, so if that already has assembly with the same name loaded, there might be conflicts.
Question2
The version is actually not ignored. The version is honored which is why Test2 and Test3 fail. But I might not understand this question correctly - it's not clear to me in which context you're asking it.
CLR binding order
The order of Rules you describe is different.
It's basically:
Rule2 - if it's already loaded - use it (including if higher version is already loaded, then use that)
Rule1 - if everything fails - as a last resort - call AppDomain.AssemblyResolve
Rule 3 actually doesn't exist. .NET Core doesn't have a notion of probing paths or code base. It sort of does for the assemblies which are statically referenced by the app, but for dynamically loaded assemblies no probing is performed (with the exception of LoadFrom loading dependent assemblies from the same folder as the parent as described above).
Solutions
To make this fully work, you would need to do either:
Use the LoadFile along with your AssemblyResolve handler. But the problem here is that if you LoadFile an assembly which itself has other dependencies, you will need to handle those in your handler as well (you lose the "nice" behavior of LoadFrom which loads dependencies from the same folder)
Implement your own ALC which handles all dependencies. This is technically the cleaner solution, but potentially more work. And it's similar in that regard that you still have to implement the loading from the same folder if needed.
We are actively working on making scenarios like this easy. Today they are doable, but pretty hard. The plan is to have something which solves this for .NET Core 3. We're also very aware of the lack of documentation/guidance in this area. And last but not least, we are working on improving the error messages, which are currently very confusing.

MEF not loading DLL after adding metadata

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.

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.

Load and execute assemblies at runtime

I have an application that needs to load and execute assemblies at runtime.
The Add-In or Plug-In approach will not work in my case since the number of assemblies will be in the hundreds and the appropriate ones would be loaded and executed at runtime.
I am looking for two things:
Sample code to load and execute assemblies whose internal structure is known to me at compile time.
Best practices to ensure stability performance and security or authenticity of loaded assemblies.
My application code logic will look like this:
public static void Main()
{
foreach (string filename in directory.GetFiles())
{
Assembly assembly = LoadAssemblyFromFile(filename);
object instance = CreateInstanceOfClassXyzInAssembly(assembly);
instance.CallSomeMethodWithKnownInterface();
instance = null;
UnloadAssembly(assembly);
}
}

Can Unity Container Load Assemblies Dynamically?

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.

Categories