C# reflection: How to load multiple assemblies in different folders - c#

My code uses Assembly.LoadFrom to load the main assembly, and uses reflection to inspect the types and functions in this assembly. This assembly references some other assemblies which are in different folders. When my code tries to inspect the types defined in those other assemblies, FileNotFoundException is thrown.
I can't use app.config setting to solve this problem. It all has to be done programmatically.
How do I solve this problem?

I figured out:
private static List<string> m_otherFolderPaths = new List<string> { #"C:\Projects\DllExport\trunk\Example1\HR Interface\bin\Another folder" };
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
// Ignore missing resources
if (args.Name.Contains(".resources"))
return null;
// check for assemblies already loaded
Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name);
if (assembly != null)
return assembly;
// Try to load by filename - split out the filename of the full assembly name
// and append the base path of the original assembly (ie. look in the same dir)
string filename = args.Name.Split(',')[0] + ".dll".ToLower();
foreach (string folder in m_otherFolderPaths)
{
var assemblyFilePath = Path.Combine(folder, filename);
if (File.Exists(assemblyFilePath))
{
try
{
return Assembly.LoadFrom(assemblyFilePath);
}
catch
{
return null;
}
}
}
return null;
}
static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
PrintAssembly(Assembly.LoadFrom(#"C:\Projects\DllExport\trunk\Example1\HR Interface\bin\au.com.frontedge.DllExport.Example1.DataAccess.dll"));
Console.ReadKey();
}

Related

.NET Core Assembly.LoadFile at PostBuild event

I need generate typescript files from some of my C# classes after build.
I created dotnet cli tool and added post-build event
dotnet tsgenerator "$(TargetPath)"
where $(TargetPath) is macros pointing, for example, D:\Test\bin\Release\netcoreapp2.0\my.dll
Next, i tried to load assembly next way:
public static void Main(string[] args)
{
var dllPath = args[0]; // "D:\Test\bin\Release\netcoreapp2.0\my.dll"
var assembly = Assembly.LoadFile(dllPath);
var types = assembly.GetExportedTypes(); // Throws exception
}
But i got ReflectionTypeLoadException that says Could not load file or assembly for some references assemblies (for example, Microsoft.AspNetCore.Antiforgery).
How i can load assembly for .NET Core applications?
I'm found solution at github issue. Message by amits1995 and angelcalvasp.
I'm added <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> to my csproj and using this code to load assembly:
public static class AssemblyLoader
{
public static Assembly LoadFromAssemblyPath(string assemblyFullPath)
{
var fileNameWithOutExtension = Path.GetFileNameWithoutExtension(assemblyFullPath);
var fileName = Path.GetFileName(assemblyFullPath);
var directory = Path.GetDirectoryName(assemblyFullPath);
var inCompileLibraries = DependencyContext.Default.CompileLibraries.Any(l => l.Name.Equals(fileNameWithOutExtension, StringComparison.OrdinalIgnoreCase));
var inRuntimeLibraries = DependencyContext.Default.RuntimeLibraries.Any(l => l.Name.Equals(fileNameWithOutExtension, StringComparison.OrdinalIgnoreCase));
var assembly = (inCompileLibraries || inRuntimeLibraries)
? Assembly.Load(new AssemblyName(fileNameWithOutExtension))
: AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyFullPath);
if (assembly != null)
LoadReferencedAssemblies(assembly, fileName, directory);
return assembly;
}
private static void LoadReferencedAssemblies(Assembly assembly, string fileName, string directory)
{
var filesInDirectory = Directory.GetFiles(directory).Where(x => x != fileName).Select(x => Path.GetFileNameWithoutExtension(x)).ToList();
var references = assembly.GetReferencedAssemblies();
foreach (var reference in references)
{
if (filesInDirectory.Contains(reference.Name))
{
var loadFileName = reference.Name + ".dll";
var path = Path.Combine(directory, loadFileName);
var loadedAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
if (loadedAssembly != null)
LoadReferencedAssemblies(loadedAssembly, loadFileName, directory);
}
}
}
}
Usage:
public static void Main(string[] args)
{
var dllPath = args[0]; // "D:\Test\bin\Release\netcoreapp2.0\my.dll"
var assembly = AssemblyLoader.LoadFromAssemblyPath(dllPath);
var types = assembly.GetExportedTypes(); // No exceptions
}
Well you are able to load assembly but GetTypes() and GetExportedTypes() depend on the public classes within that assembly, if they have external references you get this exception.
Answer:
This means the Types of that assembly depend on other assembly which the current .NetCore does not have access at the run time because it can not connect to other dependent assemblies
Solution:
Get dependencies of the DLL assemblies and compile all of them, then load each assembly iteratively to get all ExportedTypes (i.e publicly visible Types)
Code:
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;// add this nuget
class Program
{
static void Main(string[] args)
{
var asl = new AssemblyLoader();
var asm = asl.LoadFromAssemblyPath(#"C:\temp\Microsoft.AspNetCore.Antiforgery.dll");
try
{
var y = asm.GetExportedTypes();
Console.WriteLine(y);
}
catch (Exception e1)
{
Console.WriteLine("Got exception at first attempt of GetExportedTypes ");
Console.WriteLine("\t*********" + e1.Message + "**************");
var deped = asl.CallForDependency(asm.GetName());
try
{
Console.WriteLine("\n" + deped.ToString());
Console.WriteLine("----------All Exported Types------------");
foreach (var item in deped.ExportedTypes)
{
Console.WriteLine(item);
}
}
catch (Exception e2)
{
Console.WriteLine("Got exception at second attempt of GetExportedTypes ");
Console.WriteLine("\t*********" + e2.Message + "**************");
}
}
Console.ReadLine();
}
}
public class AssemblyLoader :AssemblyLoadContext
{
protected override Assembly Load(AssemblyName assemblyName)
{
var deps = DependencyContext.Default;
var res = deps.CompileLibraries.Where(d => d.Name.Contains(assemblyName.Name)).ToList();
var assembly = Assembly.Load(new AssemblyName(res.First().Name));
return assembly;
}
public Assembly CallForDependency(AssemblyName assemblyName)
{
return this.Load(assemblyName);
}
}
Output :
Got exception at first attempt of GetExportedTypes
*********Could not load file or assembly 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. An operation is not legal in the current state. (Exception from HRESULT: 0x80131509)**************
Microsoft.AspNetCore.Antiforgery, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
----------All Exported Types------------
Microsoft.Extensions.DependencyInjection.AntiforgeryServiceCollectionExtensions
Microsoft.AspNetCore.Antiforgery.AntiforgeryOptions
Microsoft.AspNetCore.Antiforgery.AntiforgeryTokenSet
Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException
Microsoft.AspNetCore.Antiforgery.IAntiforgery
Microsoft.AspNetCore.Antiforgery.IAntiforgeryAdditionalDataProvider
Microsoft.AspNetCore.Antiforgery.Internal.AntiforgeryFeature
Microsoft.AspNetCore.Antiforgery.Internal.AntiforgeryOptionsSetup
Microsoft.AspNetCore.Antiforgery.Internal.AntiforgerySerializationContext
Microsoft.AspNetCore.Antiforgery.Internal.AntiforgerySerializationContextPooledObjectPolicy
Microsoft.AspNetCore.Antiforgery.Internal.AntiforgeryToken
Microsoft.AspNetCore.Antiforgery.Internal.BinaryBlob
Microsoft.AspNetCore.Antiforgery.Internal.CryptographyAlgorithms
Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery
Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryAdditionalDataProvider
Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenGenerator
Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenSerializer
Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenStore
Microsoft.AspNetCore.Antiforgery.Internal.DefaultClaimUidExtractor
Microsoft.AspNetCore.Antiforgery.Internal.IAntiforgeryFeature
Microsoft.AspNetCore.Antiforgery.Internal.IAntiforgeryTokenGenerator
Microsoft.AspNetCore.Antiforgery.Internal.IAntiforgeryTokenSerializer
Microsoft.AspNetCore.Antiforgery.Internal.IAntiforgeryTokenStore
Microsoft.AspNetCore.Antiforgery.Internal.IClaimUidExtractor
Reference and explanation on ReflectionTypeLoadException:
Assembly.GetTypes Method ()
ReflectionTypeLoadException
The assembly contains one or more types that cannot be loaded. The
array returned by the Types property of this exception contains a Type
object for each type that was loaded and null for each type that could
not be loaded, while the LoaderExceptions property contains an
exception for each type that could not be loaded.
Remarks
The returned array includes nested types.
If the GetTypes method is called on an assembly and a type in that
assembly is dependent on a type in an assembly that has not been
loaded (for example, if it derives from a type in the second
assembly), a ReflectionTypeLoadException is thrown. For example, this
can happen if the first assembly was loaded with the
ReflectionOnlyLoad or ReflectionOnlyLoadFrom methods, and the second
assembly was not loaded. It can also happen with assemblies loaded
using the Load and LoadFile methods if the second assembly cannot be
located when the GetTypes method is called.
Note
If a type has been forwarded to another assembly, it is not included
in the returned array. For information on type forwarding, see Type
Forwarding in the Common Language Runtime.
Linked :
How to load assemblies located in a folder in .net core console app
How to dynamically load assemblies in dotnet core
Try the LoadFrom method for loading in the assembly, rather than LoadFile:
public static void Main(string[] args)
{
var dllPath = args[0]; // "D:\Test\bin\Release\netcoreapp2.0\my.dll"
var assembly = Assembly.LoadFrom(dllPath);
var types = assembly.GetExportedTypes(); // Throws exception
}
You will also need to add the same references that are in the ddl file to your current project, so that the types are defined.

.NET: Custom assembly resolution failing when loading resources

I have a project which needs to load extra assemblies dynamically at runtime for reflection purposes. I need custom code because the path to the DLLs is not known by the framework. This code works fine with normal DLLs, they load fine and I can reflect over them. However, when I attempt to load types which statically uses embedded resources (i.e. a resx) this code fails.
Without my custom assembly resolution code this works fine. Here is my assembly resolution code:
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
bool isName;
string path = GetAssemblyLoadInfo(args, out isName);
return isName ? Assembly.Load(path) : Assembly.LoadFrom(path);
}
static string GetAssemblyLoadInfo(ResolveEventArgs args, out bool isAssemblyName)
{
isAssemblyName = false;
var assemblyName = new AssemblyName(args.Name);
string path = String.Concat(new FileInfo(DllPath).Directory, "\\", assemblyName.Name, ".dll");
if (File.Exists(path))
{
return path;
}
if (path.EndsWith(".resources.dll"))
{
path = path.Replace(".resources.dll", ".dll");
if (File.Exists(path)) return path;
}
var assemblyLocation = AssemblyLocations.FirstOrDefault(al => al.Name.FullName == assemblyName.FullName);
if (null == assemblyLocation)
{
isAssemblyName = true;
return args.Name;
}
else
{
return assemblyLocation.Location;
}
}
Here is a link to a project which recreates the entire issue:
https://drive.google.com/file/d/0B-mqMIMqm_XHcktyckVZbUNtZ28/view?usp=sharing
Once you download the project, you first need to build TestLibrary, and then run ConsoleApp4. It should work fine and write the string "This is the value of the resource" to the console, which comes from the resx file. However, uncomment line 23 in Program.cs and run it again and it will fail with an exception, which indicates that it failed to load the embedded resources.
The solution in this question solved my issue:
AppDomain.CurrentDomain.AssemblyResolve asking for a <AppName>.resources assembly?
Basically, add the following code to the assembly being loaded:
[assembly: NeutralResourcesLanguage("en-GB", UltimateResourceFallbackLocation.MainAssembly)]

Loading dll library from Resource to Current Domain(Embedding dll in main exe file)

I'm trying to load dll libraries during runtime using the following code so that I don't have to provide the user with lot of dll files along with the main executable file. I have inlude all the dll files as an embedded resource and also in the reference part I have include them and have set the CopyLocal property to false. But the problems here are:1. All the dll are getting copied to Bin\Debug folder2. I'm getting FileNotFoundException.I did lot of searches to get these things resolved and finally I'm here. I got a similar code here but still couldn't do anything. What should I do to prevent this exception...??
Is there a better way to do the same thing for a Windows Form Application(Not WPF)...??
using System;
using System.Linq;
using System.Windows.Forms;
using System.Diagnostics;
using System.Reflection;
using System.Collections.Generic;
using System.IO;
namespace MyNameSpace
{
static class Program
{
static int cnt;
static IDictionary<string, Assembly> assemblyDictionary;
[STAThread]
static void Main()
{
AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
if (cnt != 1)
{
cnt = 1;
Assembly executingAssembly = Assembly.GetExecutingAssembly();
string[] resources = executingAssembly.GetManifestResourceNames();
foreach (string resource in resources)
{
if (resource.EndsWith(".dll"))
{
using (Stream stream = executingAssembly.GetManifestResourceStream(resource))
{
if (stream == null)
continue;
byte[] assemblyRawBytes = new byte[stream.Length];
stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
try
{
assemblyDictionary.Add(resource, Assembly.Load(assemblyRawBytes));
}
catch (Exception ex)
{
MessageBox.Show("Failed to load: " + resource + " Exception: " + ex.Message);
}
}
}
}
Program.Main();
}
if (cnt == 1)
{
cnt = 2;
System.Threading.Thread.CurrentThread.Priority = System.Threading.ThreadPriority.Highest;
Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
{
AssemblyName assemblyName = new AssemblyName(args.Name);
string path = assemblyName.Name + ".dll";
if (assemblyDictionary.ContainsKey(path))
{
return assemblyDictionary[path];
}
return null;
}
}
}
If I'm using something unnecessarily in my code then you can show me the right way...
I'm a student working on Windows Form Application v4.0 project for my papers to be submitted.
If it is still the case that you must do this, then use this OnResolveAssembly method. There is no need to preload them into an array if you don't want to. This will load them the first time they are actually needed.
Then just:
add the some.assembly.dll file to the project.
probably not a reference to the project's output
but the file that is the result of the DLL project.
mark it as a Resource in the file properties.
// This function is not called if the Assembly is already previously loaded into memory.
// This function is not called if the Assembly is already in the same folder as the app.
//
private static Assembly OnResolveAssembly(object sender, ResolveEventArgs e)
{
var thisAssembly = Assembly.GetExecutingAssembly();
// Get the Name of the AssemblyFile
var assemblyName = new AssemblyName(e.Name);
var dllName = assemblyName.Name + ".dll";
// Load from Embedded Resources
var resources = thisAssembly.GetManifestResourceNames().Where(s => s.EndsWith(dllName));
if (resources.Any())
{
// 99% of cases will only have one matching item, but if you don't,
// you will have to change the logic to handle those cases.
var resourceName = resources.First();
using (var stream = thisAssembly.GetManifestResourceStream(resourceName))
{
if (stream == null) return null;
var block = new byte[stream.Length];
// Safely try to load the assembly.
try
{
stream.Read(block, 0, block.Length);
return Assembly.Load(block);
}
catch (IOException)
{
return null;
}
catch (BadImageFormatException)
{
return null;
}
}
}
// in the case the resource doesn't exist, return null.
return null;
}
-Jesse
PS: This comes from http://www.paulrohde.com/merging-a-wpf-application-into-a-single-exe/
Try the following:
For each .dll resource:
If the file allready exists on the AppDomain.Current.BaseDirectory then continue to the next resource
Else save the resource to the AppDomain.Current.BaseDirectory. Do this in a try-catch and if it fails, notify the user. For this step to complete successfully you will need write access on the installation folder (usually a subfolder of "Program Files"). This will be solved by running the program as an administrator the first time only ao that the files are written on the file system.
Ιf the assemblies are referenced by your VS project then you do not have to load them yourself. To understand why this work's you will need to understand how assemblies are located by the CLR.
Else you will need to load each assembly yourself using one of the Assembly.Load that take either a string or and AssemblyName as a parameter.

Assembly.LoadFrom() throw exception

public Assembly LoadAssembly(string assemblyName) //#"D://MyAssembly.dll"
{
m_assembly = Assembly.LoadFrom(assemblyName);
return m_assembly;
}
If I put "MyAssembly.dll" both in D: and its copy in "bin" directory, the method will excute successfully. However, I delete any one of them, it will throw exception. Message like below:
Could not load file or assembly 'MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
I wanna load the assembly which exists in D:. why I need put its copy to "bin" directory at the same time?
Maybe MyAssembly.dll refers to some assembly that arent in the directory. Put all assemblies in the same directory.
Or you can handle AppDomain.CurrentDomain, AssemblyResolve event to load the needed assembly
private string asmBase ;
public void LoaddAssembly(string assemblyName)
{
asmBase = System.IO.Path.GetDirectoryName(assemblyName);
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
System.Reflection.Assembly asm = System.Reflection.Assembly.Load(System.IO.File.ReadAllBytes(assemblyName));
}
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
//This handler is called only when the common language runtime tries to bind to the assembly and fails.
//Retrieve the list of referenced assemblies in an array of AssemblyName.
Assembly MyAssembly, objExecutingAssemblies;
string strTempAssmbPath = "";
objExecutingAssemblies = args.RequestingAssembly;
AssemblyName[] arrReferencedAssmbNames = objExecutingAssemblies.GetReferencedAssemblies();
//Loop through the array of referenced assembly names.
foreach (AssemblyName strAssmbName in arrReferencedAssmbNames)
{
//Check for the assembly names that have raised the "AssemblyResolve" event.
if (strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(",")))
{
//Build the path of the assembly from where it has to be loaded.
strTempAssmbPath = asmBase + "\\" + args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";
break;
}
}
//Load the assembly from the specified path.
MyAssembly = Assembly.LoadFrom(strTempAssmbPath);
//Return the loaded assembly.
return MyAssembly;
}
To get any specific exceptions you could try this:
try
{
return Assembly.LoadFrom(assemblyName);
}
catch (Exception ex)
{
var reflection = ex as ReflectionTypeLoadException;
if (reflection != null)
{
foreach (var exception in reflection.LoaderExceptions)
{
// log / inspect the message
}
return null;
}
}

Reflection - Can't create a new object instance C#

I am trying to create a plug-in type archetecure for my project. I would like the ability to load an assembly, get a type that is derived from an abstract base class in my project, instantiate it and load that derived type into the main processing object.
My problem right now is that when I instantiate the object from the reflected assembly, it is always null. I feel that the problem may lie in the fact that the referenced assembly has 3rd party dlls that it is using. Here is the code: the only exception that gets hit is the final one.
static void Main(string[] args)
{
string engineFilePath = ConfigurationManager.AppSettings["EngineFilesDirectory"]
+ "\\" + ConfigurationManager.AppSettings["EngineDllFileName"];
Assembly asm = Assembly.LoadFile(engineFilePath);
Type engineType = asm.GetType(ConfigurationManager.AppSettings["EngineType"]);
if (!engineType.IsSubclassOf(typeof(EngineConcrete)))
{
throw new ArgumentException("Engine is not derived from base implimentation.");
}
object engineInstance = asm.CreateInstance(engineType.Namespace + "." + engineType);
if (engineInstance == null)
{
//always thrown at this point
throw new Exception(string.Format("Engine object is null."));
}
return;
}
If I change the instantiation line to Activator.CreateInstance(engineType), I receive an error saying that one of the 3rd party dll's being referenced by the reflected assembly can't be found, though they are in the same directory as the .dll being reflected.
There is a public constructor for the type that is being reflected as well. It has no parameters and inherits from the EngineConcrete class.
I think null is what you get when the type can't be found; the namespace name plus the type name may not be sufficient (strong naming issues). What happens if you put the 3rd-party dlls in the directory of the executing application, not the directory of the plugin?
This is the bug: engineType.Namespace + "." + engineType — this expression evaluates into "The.Namespace.The.Namespace.Type" instead of "The.Namespace.Type". Either use engineType.FullName or the Activator.CreateInstance class and use the engineType directly.
Update: Note that MSDN says “… or null if typeName is not found.” about Assembly.CreateInstance's return value.
The issue is that the CLR is not finding the third-party assemblies because they are not in a folder that is being probed.
You could add an event handler for the AppDomain.CurrentDomain.AssemblyResolve event to handle those cases. Below is an example implementation that worked for me where assemblies are being dynamically loaded from a "Plugins" folder that is contained within the bin folder:
class Program
{
static string _PluginDirectory;
static string PluginDirectory
{
get
{
if (_PluginDirectory == null)
{
_PluginDirectory = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), #"Plugins");
}
return _PluginDirectory;
}
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
AssemblyName assemblyName = new AssemblyName(args.Name);
return Assembly.LoadFile(System.IO.Path.Combine(PluginDirectory, assemblyName.Name + ".dll"));
}
static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
var asm = Assembly.LoadFile(System.IO.Path.Combine(PluginDirectory, #"Plugin.dll"));
var transmogrifier = asm.CreateInstance("Plugin.ConcreteTransmogrifier") as Common.Transmogrifier;
if (transmogrifier != null)
{
Console.WriteLine(transmogrifier.Transmogrify("Wowzers!"));
}
}
}

Categories

Resources