Cannot GetTypes() in new domain - c#

I have created a new domain, then loaded the assembly into this domain, but when GetTypes() gives an error like the picture attached, hope everyone helps, thanks.
Code
public class Program
{
public static void Main()
{
string assemblyPath = #"D:\Github\BeyConsPlugin\BeyConsProject\bin\x64\Debug\BeyConsRevitProject.dll";
AppDomain appDomain = CreateChildDomain(AppDomain.CurrentDomain, Guid.NewGuid().ToString());
appDomain.AssemblyResolve += AssemblyResolve;
var value = (Proxy)appDomain.CreateInstanceAndUnwrap(typeof(Proxy).Assembly.FullName, typeof(Proxy).FullName);
var assembly = value.GetAssembly(assemblyPath);
var types = assembly.GetTypes();
Console.ReadKey();
}
private static Assembly AssemblyResolve(object sender, ResolveEventArgs args)
{
AssemblyName assemblyName = new AssemblyName(args.Name);
string dependentAssemblyFilename = Path.Combine(#"D:\Github\BeyConsPlugin\BeyConsProject\bin\x64\Debug", assemblyName.Name + ".dll");
if (File.Exists(dependentAssemblyFilename)) return null;
return Assembly.LoadFile(dependentAssemblyFilename);
}
public static AppDomain CreateChildDomain(AppDomain parentDomain, string domainName)
{
Evidence evidence = new Evidence(parentDomain.Evidence);
AppDomainSetup setup = parentDomain.SetupInformation;
return AppDomain.CreateDomain(domainName, evidence, setup);
}
}
public class Proxy : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFile(assemblyPath);
}
catch { return null; }
}
}
Error

Have you checked if BeyConsRevitProject.dll assembly is in the bin directory of your application? This is a possible cause. Try deleting the bin/ and obj/ folders and rebuilding your solution, if the error persists, use this code below to ascertain the real reason for the error:
using System.IO;
using System.Reflection;
using System.Text;
try
{
//The code that causes the error goes here.
}
catch (ReflectionTypeLoadException ex)
{
StringBuilder sb = new StringBuilder();
foreach (Exception exSub in ex.LoaderExceptions)
{
sb.AppendLine(exSub.Message);
FileNotFoundException exFileNotFound = exSub as FileNotFoundException;
if (exFileNotFound != null)
{
if(!string.IsNullOrEmpty(exFileNotFound.FusionLog))
{
sb.AppendLine("Fusion Log:");
sb.AppendLine(exFileNotFound.FusionLog);
}
}
sb.AppendLine();
}
string errorMessage = sb.ToString();
//Display or log the error based on your application.
}
This code was suggested by Ben Gripka here:
Error message 'Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.'

Related

Assembly.CreateInstance returning null even though I can see the class in DefinedTypes

I use the following method to load a new Assembly and create an instance of a class into a new AppDomain.
private static object CreateInstanceFromBinary(AppDomain appDomain, string typeName)
{
Assembly entryAssembly = Assembly.GetEntryAssembly();
byte[] assemblyBinary = LoadAssemblyBinary();
Assembly loadedAssembly = appDomain.Load(assemblyBinary);
if (loadedAssembly != null)
{
return loadedAssembly.CreateInstance(typeName);
}
return null;
}
Which get's called like so.
AppDomain appDomain = AppDomain.CreateDomain(domainName);
appDomainHelper = CreateInstanceFromBinary(appDomain, typeof(MyClass).FullName) as MyClass;
Looking into the loadedAssembly I can see that MyClass exists inside of the DefinedTypes and it's name matches typeName. However, when the code runs
loadedAssembly.CreateInstance(typeName)
it returns null.
This code was working, however, I recently moved this class into the same dll as the one that calls it and now it has started returning null.
Any ideas on how to fix this?
For some short(ish) reproducible code you can use the following. In this code ClassLibrary1 has CopyLocal set to false and then included as an EmbeddedResource to mimic what I have in my live project in case that matters.
Inside of ConsoleApplication1 I have Program.
using ClassLibrary1;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static Program()
{
AppDomain.CurrentDomain.AssemblyResolve += Resolve;
}
static void Main(string[] args)
{
WorkerClass workerClass = new WorkerClass();
workerClass.DoWork();
Console.WriteLine("\r\nPress enter to exit...");
Console.ReadLine();
}
static System.Reflection.Assembly Resolve(object sender, ResolveEventArgs args)
{
if (!args.Name.Contains(","))
{
return null;
}
List<string> rn = System.Reflection.Assembly.GetEntryAssembly().GetManifestResourceNames()
.Where(r => r.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
.ToList();
string assemblyName = rn.FirstOrDefault(r => r.EndsWith(args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll"));
if (!String.IsNullOrEmpty(assemblyName))
{
using (Stream stream = System.Reflection.Assembly.GetEntryAssembly().GetManifestResourceStream(assemblyName))
{
byte[] assemblyBinary = new byte[stream.Length];
stream.Read(assemblyBinary, 0, assemblyBinary.Length);
System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(assemblyBinary);
if (Environment.UserInteractive)
{
Console.WriteLine("Loaded Assembly: " + assembly.FullName);
}
return assembly;
}
}
if (Environment.UserInteractive)
{
Console.WriteLine($"** Failed to find an assembly with name: {args.Name} ** ");
}
return null;
}
}
}
Inside of ClassLibrary1 there is WorkerClass.
using System;
namespace ClassLibrary1
{
public class WorkerClass
{
public void DoWork()
{
try
{
HelperClass hc = HelperClass.Create("Name");
Console.WriteLine("Created");
}
catch (Exception ex)
{
Console.WriteLine("Failed to create: " + ex.ToString());
}
}
}
}
and HelperClass.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Permissions;
namespace ClassLibrary1
{
[Serializable]
public class HelperClass : MarshalByRefObject
{
public AppDomain Domain { get; private set; }
public HelperClass()
{
}
[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.Infrastructure)]
public override object InitializeLifetimeService()
{
return null;
}
public static HelperClass Create(string domainName)
{
AppDomain appDomain = AppDomain.CreateDomain(domainName);
HelperClass helperClass = CreateInstanceFromBinary(appDomain, typeof(HelperClass).AssemblyQualifiedName) as HelperClass;
if (helperClass == null)
{
throw new Exception("Unable to create instance from binary resource.");
}
helperClass.Domain = appDomain;
return helperClass;
}
private static object CreateInstanceFromBinary(AppDomain appDomain, string typeName)
{
Assembly entryAssembly = Assembly.GetEntryAssembly();
IList<string> rn = entryAssembly.GetManifestResourceNames().Where(r => r.EndsWith(".dll")).ToList();
string assembly = rn.FirstOrDefault(r => r.EndsWith($"{typeof(HelperClass).Assembly.GetName().Name}.dll"));
if (!String.IsNullOrEmpty(assembly))
{
using (Stream stream = entryAssembly.GetManifestResourceStream(assembly))
{
byte[] assemblyBinary = new byte[stream.Length];
stream.Read(assemblyBinary, 0, assemblyBinary.Length);
Assembly loadedAssembly = appDomain.Load(assemblyBinary);
if (loadedAssembly != null)
{
return loadedAssembly.CreateInstance(typeName);
}
}
}
return null;
}
}
}
Where it is return loadedAssembly.CreateInstance(typeName); that returns null.
In your function public static HelperClass Create(string domainName) you are passing the AssemblyQualifiedName as the type of the class to create.
I think you just want to pass the type name, ie: ClassLibrary1.HelperClass
//HelperClass helperClass = CreateInstanceFromBinary(appDomain, typeof(HelperClass).AssemblyQualifiedName) as HelperClass;
HelperClass helperClass = CreateInstanceFromBinary(appDomain, typeof(HelperClass).ToString()) as HelperClass;
I tried some variations, each time passing in the Assembly Qualified Name failed, just the type name worked as expected.
Variations tried, and failed:
// Do not work
var x = loadedAssembly.CreateInstance(typeName); //AssemblyQualifiedName
var loadedType = loadedAssembly.GetType(typeName); //AssemblyQualifiedName
// Work
var x = Activator.CreateInstance(typeof(HelperClass)); // Works
var x = loadedAssembly.CreateInstance("ClassLibrary1.HelperClass");
var loadedType = loadedAssembly.GetType("ClassLibrary1.HelperClass");
var x = Activator.CreateInstance(loadedType);

How to load assemblies in different folders C#

I need load a DLL and dependencies. In my database I already save all dependencies (path to the references files).
I.E:
DLL to load:
id: 1
name:"DummyModule.dll"
Dependencies:
DLL id: 1
path: "C:\DLL\ABC.dll"
AssemblyLoader class:
public class AssemblyLoader : MarshalByRefObject
{
public void Load(string path)
{
ValidatePath(path);
Assembly.Load(path);
}
public void LoadFrom(string path)
{
ValidatePath(path);
Assembly.LoadFrom(path);
}
public void LoadBytes(string path)
{
ValidatePath(path);
var b = File.ReadAllBytes(path);
Assembly.Load(b);
}
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.Load(assemblyPath);
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message);
}
}
public Assembly GetAssemblyBytes(string assemblyPath)
{
try
{
var b = File.ReadAllBytes(assemblyPath);
return Assembly.Load(b);
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message);
}
}
private void ValidatePath(string path)
{
if (path == null)
throw new ArgumentNullException("path");
if (!File.Exists(path))
throw new ArgumentException(String.Format("path \"{0}\" does not exist", path));
}
}
The main class:
static void Main(string[] args)
{
string file1 = #"1\DummyModule.dll";
string file2 = #"2\PSLData.dll";
string file3 = #"3\Security.dll";
try
{
AppDomain myDomain = AppDomain.CreateDomain("MyDomain");
var assemblyLoader = (AssemblyLoader)myDomain.CreateInstanceAndUnwrap(typeof(AssemblyLoader).Assembly.FullName, typeof(AssemblyLoader).FullName);
assemblyLoader.LoadBytes(file2);
assemblyLoader.LoadBytes(file3);
var dummy = assemblyLoader.GetAssemblyBytes(file1);
foreach (var t in dummy.GetTypes())
{
var methodInfo = t.GetMethod("D");
if (methodInfo != null)
{
var obj = Activator.CreateInstance(t);
Console.Write(methodInfo.Invoke(obj, new object[] { }).ToString());
}
}
AppDomain.Unload(myDomain);
}
catch (Exception ex)
{
Console.Write(ex.Message);
}
Console.ReadKey();
}
In the code above the "DummyModule.dll" is the main dll, "PSLData.dll" and "Security.dll" are the dependencies.
When I call the method "D" of my "DummyModule.dll" the error appears:
Could not load file or assembly 'DummyModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
All DLL files still in different folders. How I can load all needed files and call a function?
Thanks.
Try using this.. it worked for me..
serviceAgentAssembly =System.Reflection.Assembly.LoadFrom(string.Format(CultureInfo.InvariantCulture, #"{0}\{1}", assemblyPath, assemblyName));
foreach (Type objType in serviceAgentAssembly.GetTypes())
{
//further loops to get the method
//your code to ivoke the function
}
You're using relative paths to the assemblies, so the question is "relative to what?" The new AppDomain you've created and are loading assemblies into is lost in the woods; it doesn't inherit the same probe paths of the AppDomain in which you created it. Take a look at the class System.AppDomainSetup and its properties ApplicationBase and PrivateBinPath along with the form of CreateDomain() that takes an instance of AppDomainSetup as an argument. The simplest solution would be to use the AppDomainSetup instance returned by AppDomain.CurrentDomain.SetupInformation were the current domain is, of course, the app in which the new domain is created.

Dynamic assembly compile and load

I'm compiling assembly at runtime and link it via adding to new domain. I use it and then unload domain. But when I try to compile again during same run I can't get access to that assembly because it currently in use.
Here are some of my code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ClassLibrary2
{
public interface IExtension
{
String GetExtensionName();
}
}
My assembly
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ClassLibrary2;
namespace ClassLibrary1
{
public class Extension1 : MarshalByRefObject, IExtension
{
public Extension1()
{
}
public string GetExtensionName()
{
return "Extension 1 from " + AppDomain.CurrentDomain.FriendlyName;
}
}
}
And the app that uses it
namespace ConsoleApplication7
{
class Program
{
static IEnumerable<IExtension> extensions;
static void Main(string[] args)
{
// Create app domain
AppDomain domain = CreateDomain(Directory.GetCurrentDirectory());
try
{
// Get extensions
extensions = EnumerateExtensions(domain);
foreach (IExtension extension in extensions)
// Execute extension method in separate domain.
Console.WriteLine(extension.GetExtensionName());
// Unload domain
UnloadDomain(domain);
}
finally
{
domain = null;
GC.Collect(2);
extensions = null;
}
Console.ReadKey();
}
private static IEnumerable<IExtension> EnumerateExtensions(AppDomain domain)
{
IEnumerable<string> fileNames = Directory.EnumerateFiles(domain.BaseDirectory, "*.dll");
if (fileNames != null)
{
foreach (string assemblyFileName in fileNames)
{
foreach (string typeName in GetTypes(assemblyFileName, typeof(IExtension), domain))
{
System.Runtime.Remoting.ObjectHandle handle;
try
{
handle = domain.CreateInstanceFrom(assemblyFileName, typeName);
}
catch (MissingMethodException)
{
continue;
}
object obj = handle.Unwrap();
IExtension extension = (IExtension)obj;
yield return extension;
}
}
}
}
private static IEnumerable<string> GetTypes(string assemblyFileName, Type interfaceFilter, AppDomain domain)
{
Assembly asm = domain.Load(AssemblyName.GetAssemblyName(assemblyFileName));
Type[] types = asm.GetTypes();
foreach (Type type in types)
{
if (type.GetInterface(interfaceFilter.Name) != null)
{
yield return type.FullName;
}
}
}
static AppDomain CreateDomain(string path)
{
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = path;
return AppDomain.CreateDomain("Temporary domain", null, setup);
}
static void UnloadDomain(AppDomain domain)
{
AppDomain.Unload(domain);
}
}
}
So in Main() during Console.ReadKey(); assembly still locked and I can't get access to it (can't delete it via Windows for example).
Is there way to solve this?
I think the domain.Load is hooking up the assembly to your program try to the load inside the Extension1 try to move the GetTypes to the class Extension1
I don't remember but I think domain.Load just runs Assembly.LoadFrom( and that's what is connecting your application the the DLL.
Ok, I solved this problem. I used shadow copy, just configured shadow copy in that other domain and it worked for me.

Loading DLLs into a separate AppDomain with known only common interface

I need to load .dll(plugins) in another domain. In main app I don't know anything about plugins types, only that they implement common interface ICommonInterface with some methods. So this code wouldn't help, because I can't create an instance with interface type.
AppDomain domain = AppDomain.CreateDomain("New domain name");
//Do other things to the domain like set the security policy
string pathToDll = #"C:\myDll.dll"; //Full path to dll you want to load
Type t = typeof(TypeIWantToLoad);
TypeIWantToLoad myObject = (TypeIWantToLoad)domain.CreateInstanceFromAndUnwrap(pathToDll, t.FullName);
My question is how I can load assembly in new domain and get the instance, if I know only interface name which implements type I want to create.
UPDATE:
Here is my code:
MainLib.dll
namespace MainLib
{
public interface ICommonInterface
{
void ShowDllName();
}
}
PluginWithOutException.dll
namespace PluginWithOutException
{
public class WithOutException : MarshalByRefObject, ICommonInterface
{
public void ShowDllName()
{
Console.WriteLine("PluginWithOutException");
}
}
}
PluginWithException.dll
namespace PluginWithException
{
public class WithException : MarshalByRefObject, ICommonInterface
{
public void ShowDllName()
{
Console.WriteLine("WithException");
throw new NotImplementedException();
}
}
}
And main application:
static void Main(string[] args)
{
string path = #"E:\Plugins\";
string[] assemblies = Directory.GetFiles(path);
List<string> plugins = SearchPlugins(assemblies);
foreach (string item in plugins)
{
CreateDomainAndLoadAssebly(item);
}
Console.ReadKey();
}
public static List<string> SearchPlugins(string[] names)
{
AppDomain domain = AppDomain.CreateDomain("tmpDomain");
domain.Load(Assembly.LoadFrom(#"E:\Plugins\MainLib.dll").FullName);
List<string> plugins = new List<string>();
foreach (string asm in names)
{
Assembly loadedAssembly = domain.Load(Assembly.LoadFrom(asm).FullName);
var theClassTypes = from t in loadedAssembly.GetTypes()
where t.IsClass &&
(t.GetInterface("ICommonInterface") != null)
select t;
if (theClassTypes.Count() > 0)
{
plugins.Add(asm);
}
}
AppDomain.Unload(domain);
return plugins;
}
Plugins and main app have reference to MainLib.dll. The main aim is to not to load assemblies in default domain, but load them to another domains, so when I don't need them, I just Unload() domain and unload all plugins from application.
For now the exception is FileNotFoundException, Could not load file or assembly 'PluginWithException, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.) on string Assembly loadedAssembly = domain.Load(Assembly.LoadFrom(asm).FullName);(I trying to load plugin with name PluginWithException), I've delete all the dependencies in plugins, exept System, I loaded System.dll in this domain(it loaded correct and it is in domain), but still cant load plugins into domain. Also I checked, that PluginWithException has 2 dependencies - mscorlib and MainLib, and all of them loaded to this domain.
UPDATE: Here I asked this question with more details.
I'm not sure if it's what you need, i'd try to help you with this.
This is how I do to load plugin assemblies. I use a helper class to manage new AppDomain and the instance of the class on that assembly. This is the helper class:
[Serializable, ClassInterface(ClassInterfaceType.AutoDual)]
class helperDomain<T>: MarshalByRefObject where T: class
{
#region private
private AppDomain _app_domain;
private AppDomainSetup _app_domain_info;
private string _assembly_class_name;
private string _assembly_file;
private string _assembly_file_name;
private T _inner_class;
private bool _load_ok;
private string _loading_errors;
private string _path;
#endregion
#region .ctor
public helperDomain(string AssemblyFile,
string configFile = null, string domainName)
{
this._load_ok = false;
try
{
this._assembly_file = AssemblyFile; //full path to assembly
this._assembly_file_name = System.IO.Path.GetFileName(this._assembly_file); //assmbly file name
this._path = System.IO.Path.GetDirectoryName(this._assembly_file); //get root directory from assembly path
this._assembly_class_name = typeof(T).ToString(); //the class name to instantiate in the domain from the assembly
//start to configure domain
this._app_domain_info = new AppDomainSetup();
this._app_domain_info.ApplicationBase = this._path;
this._app_domain_info.PrivateBinPath = this._path;
this._app_domain_info.PrivateBinPathProbe = this._path;
if (!string.IsNullOrEmpty(configFile))
{
this._app_domain_info.ConfigurationFile = configFile;
}
//lets create the domain
this._app_domain = AppDomain.CreateDomain(domainName, null, this._app_domain_info);
//instantiate the class
this._inner_class = (T) this._app_domain.CreateInstanceFromAndUnwrap(this._assembly_file, this._assembly_class_name);
this._load_ok = true;
}
catch (Exception exception)
{
//There was a problema setting up the new appDomain
this._load_ok = false;
this._loading_errors = exception.ToString();
}
}
#endregion
#region public properties
public string AssemblyFile
{
get
{
return _assembly_file;
}
}
public string AssemblyFileName
{
get
{
return _assembly_file_name;
}
}
public AppDomain AtomicAppDomain
{
get
{
return _app_domain;
}
}
public T InstancedObject
{
get
{
return _inner_class;
}
}
public string LoadingErrors
{
get
{
return _loading_errors;
}
}
public bool LoadOK
{
get
{
return _load_ok;
}
}
public string Path
{
get
{
return _path;
}
}
#endregion
}
and then load plugins (each in a diferent folder).
foreach(string pluginassemblypath in pluginspaths)
{
//Each pluginassemblypath (as it says..) is the full path to the assembly
helperDomain<IPluginClass> isoDomain =
helperDomain<IPluginClass>(pluginassemblypath,
pluginassemblypath + ".config",
System.IO.Path.GetFileName(pluginassemblypath) + ".domain");
if (isoDomain.LoadOK)
{
//We can access instance of the class (.InstancedObject)
Console.WriteLine("Plugin loaded..." + isoDomain.InstancedObject.GetType().Name);
}
else
{
//Something happened...
Console.WriteLine("There was en error loading plugin " +
pluginassemblypath + " - " + helperDomain.LoadingErrors);
}
}
Hope it will helps you...
This question seems relevant to what you want to do.
How to Load an Assembly to AppDomain with all references recursively?
After you've loaded the assembly, you can use Assembly.GetTypes() and iterate to find the types that implement your interface.

How to check if an assembly contains unit tests without loading it?

I'm currently using the following method to check for test assemblies:
private bool IsTestAssembly(string path)
{
var assembly = Assembly.LoadFrom(path);
foreach (var type in assembly.GetTypes())
{
var a = type.GetCustomAttributes(true).Select(x => x.ToString());
if (a.Contains("Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute"))
return true;
}
return false;
}
But I would like to check this without loading the assembly in memory because I need to be able to delete it afterwards in case the verification fails.
I was hoping I could simply unload the assembly, but I soon discovered that, according to MSDN:
There is no way to unload an individual assembly without unloading all of the application domains that contain it.
Thanks in advance!
I worked out a short solution as suggested by TheGreatCO, i.e. to load the assembly in a new AppDomain:
1) Usage:
// assemblies are unloaded on disposal
using (var analyser = new AssemblyAnalyser())
{
var path = "my.unit.tests.dll";
var b = analyser.IsTestAssembly(path);
Assert.IsTrue(b);
}
2) Implementation:
public class AssemblyAnalyser : MarshalByRefObject, IDisposable
{
public AssemblyAnalyser()
{
var evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
var appSetup = new AppDomainSetup()
{
ApplicationBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
};
appDomain = AppDomain.CreateDomain(otherDomainFriendlyName, evidence, appSetup);
}
public bool IsTestAssembly(string assemblyPath)
{
if (AppDomain.CurrentDomain.FriendlyName != otherDomainFriendlyName)
{
var analyser = appDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, GetType().FullName);
return ((AssemblyAnalyser)analyser).IsTestAssembly(assemblyPath);
}
else
{
var assembly = Assembly.LoadFrom(assemblyPath);
return ContainsTestClasses(assembly);
}
}
public void Dispose()
{
if (AppDomain.CurrentDomain.FriendlyName != otherDomainFriendlyName)
{
AppDomain.Unload(appDomain);
GC.SuppressFinalize(this);
}
}
~AssemblyAnalyser()
{
Dispose();
}
private bool ContainsTestClasses(Assembly assembly)
{
foreach (var type in assembly.GetTypes())
{
var attr = type.GetCustomAttributes(true).Select(x => x.ToString());
if (attr.Contains("Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute"))
return true;
}
return false;
}
private const string otherDomainFriendlyName = "AssemblyAnalyser";
private AppDomain appDomain;
}
Inspect the assemblies using Mono.Cecil. Cecil does not need to load the assembly to inspect it.

Categories

Resources