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.
Related
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.'
So this is what I'm trying to do. I would like to have a log lived AssemblyLoadContext instance in order to quickly execute loaded assemblies.
When the assemblies updated event comes I would like to unload the context (I'm using .net core 3) and create a new context with versions of assemblies. When I try to do that however I get an error when unloading for the second time
so I'm guessing I'm doing something (very) wrong. I'm guessing the context doesn't get actually unloaded?
If the loaded assembly depends on some external dll I also get an error that that dll cannot be loaded, even though it is in the same folder. How can I prevent that?
Any advice would be very appreciated. This is my code
class Program
{
private static AssembliesContext assembliesContext;
const int iterations = 10;
static void Main(string[] args)
{
for (int i = 0; i < iterations; i++)
{
assembliesContext = new AssembliesContext();
RunViaReflection();
var wr = new WeakReference(assembliesContext);
assembliesContext.Unload();
assembliesContext = null;
int retries = 0;
while (wr.IsAlive && retries < 5)
{
GC.Collect();
GC.WaitForPendingFinalizers();
retries++;
}
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void RunViaReflection()
{
try
{
string dllPath = Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "Execution.Sample", "bin", "Debug", "netcoreapp2.2", "win10-x64", "publish", "Execution.Sample.dll");
using (var fs = new FileStream(dllPath, FileMode.Open, FileAccess.Read))
{
var assembly = assembliesContext.LoadFromStream(fs);
var executableType = assembly.GetTypes()
.Where(x => typeof(IExecutableCode).IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract)
.FirstOrDefault();
IExecutableCode instance = (IExecutableCode)Activator.CreateInstance(executableType);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
public class AssembliesContext : AssemblyLoadContext
{
public AssembliesContext(): base(true)
{
}
protected override Assembly Load(AssemblyName assemblyName)
{
return null;
}
}
Edit
I got rid of the error by casting the invoke result to Task (since it's an async method):
((Task)method.Invoke(instance, new object[] { })).ConfigureAwait(false).GetAwaiter().GetResult();
I also loaded the depending assemblies like this:
foreach (var ass in assembly.GetReferencedAssemblies())
{
string path = Path.Combine(basePath, $"{ass.Name}.dll");
using (var dependantAssemblyStream = new FileStream(path, FileMode.Open, FileAccess.Read))
{
assembliesContext.LoadFromStream(dependantAssemblyStream);
}
}
I am still wondering how to manage a long lived AssemblyLoadContext since if I try to unload it as a class member it wil not unload, even if I set it to null and instantiate a new one.
Is a different approach warranted?
Edit #2
Right now I have a ConcurrentDictionary of WeakReference items which seems so work fairly ok, but every WeakReference in it loses it's target when the GC runs. Logical, but I still don't know how to make everything work.
If I do something like this
namespace AssemblyLoadTest
{
class Program
{
static void Main(string[] args)
{
AssemblyExecutor executor = new AssemblyExecutor();
for (int i = 0; i < 1; i++)
{
var assemblyPath = Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "Assembly", "Sinapro.IoT.Testing.DotNetRuleTestImplementation.dll");
executor.Execute(assemblyPath);
Console.WriteLine($"Iteration {i} completed");
if (i > 0 && i % 10 == 0)
{
executor.Clear();
}
}
executor.Clear();
while (true)
{
Thread.Sleep(100);
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}
public class AssemblyExecutor
{
private readonly ConcurrentDictionary<string, CollectibleAssemblyLoadContext> references = new ConcurrentDictionary<string, CollectibleAssemblyLoadContext>();
[MethodImpl(MethodImplOptions.NoInlining)]
public void Clear()
{
var keys = references.Keys.ToList();
foreach (var k in keys)
{
references.TryRemove(k, out CollectibleAssemblyLoadContext ctx);
ctx.Unload();
}
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("GC called");
}
public void Execute(string assemblyPath)
{
var wr = references.AddOrUpdate(
assemblyPath,
(k) =>
{
return CreateReference(assemblyPath);
},
(k, existingReference) =>
{
if (existingReference == null || !existingReference.Assemblies.Any())
{
return CreateReference(assemblyPath);
}
return existingReference;
}
);
ExecuteAssembly(wr);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void ExecuteAssembly(CollectibleAssemblyLoadContext context)
{
//CollectibleAssemblyLoadContext context = wr.Target as CollectibleAssemblyLoadContext;
if (context == null)
return;
var assembly = context.Assemblies.FirstOrDefault(a => a.ExportedTypes.Any(t => t.Name == "TestEntry"));
var types = assembly.GetTypes().ToList();
var type = assembly.GetType("Sinapro.IoT.Testing.DotNetRuleTestImplementation.TestEntry");
var greetMethod = type.GetMethod("Execute");
var instance = Activator.CreateInstance(type);
var result = greetMethod.Invoke(instance, new object[] { null, null });
}
[MethodImpl(MethodImplOptions.NoInlining)]
private CollectibleAssemblyLoadContext CreateReference(string assemblyPath)
{
Console.WriteLine($"Creating new context");
var context = new CollectibleAssemblyLoadContext(assemblyPath);
using (var fs = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read))
{
var assembly = context.LoadFromStream(fs);
}
return context;
}
}
public class CollectibleAssemblyLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver;
public CollectibleAssemblyLoadContext(string path)
: base(isCollectible: true)
{
_resolver = new AssemblyDependencyResolver(path);
}
protected override Assembly Load(AssemblyName assemblyName)
{
var deps = AssemblyLoadContext.Default;
var res = deps.Assemblies.Where(d => d.FullName.Contains(assemblyName.Name)).ToList();
if (res.Any())
{
return Assembly.Load(new AssemblyName(res.First().FullName));
}
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath == null)
return null;
return LoadFromAssemblyPath(assemblyPath);
}
}
}
everything runs fine, but the memory consumption keeps increasing. I find it curious though, since there shouldn't be any reference to the AssemblyLoadContext anymore so the GC should've cleared it?
At the moment, I have a module folder configured, and all my module assemblies and their dependencies live there. I worry that in six months time, someone builds a new module, and its dependencies overwrite the older versions of the dependencies.
Should I maybe develop some sort of module registry, where a developer registers a new module, and assigns it a sub-folder name in the modules folder? This kind of dampens the convenience of using a DirectoryCatalog though, if I have to tell the host about the modules.
I've had a similar problem in the past. Below I present my solution, which I think is similar to what you are trying to accomplish.
Using MEF like this is really fascinating, but here are my words of caution:
It gets complicated quick
You have to make a couple compromises like inheriting MarshalByRefObject and plugins not building with solution
And, as I decided, simpler is better! Other non-MEF designs may be a better choice.
Ok, disclaimers out of the way...
.NET allows you to load multiple versions of the same assembly into memory, but not to unload them. This is why my approach will require an AppDomain to allow you to unload modules when a new version becomes available.
The solution below allows you to copy plugin dlls into a 'plugins' folder in the bin directory at runtime. As new plugins are added and old ones are overwritten, the old will be unloaded and the new will be loaded without having to re-start your application. If you have multiple dlls with different versions in your directory at the same time, you may want to modify the PluginHost to read the assembly version through the file's properties and act accordingly.
There are three projects:
ConsoleApplication.dll (References Integration.dll only)
Integration.dll
TestPlugin.dll (References Integration.dll, must be copied to ConsoleApplication bin/Debug/plugins)
ConsoleApplication.dll
class Program
{
static void Main(string[] args)
{
var pluginHost = new PluginHost();
//Console.WriteLine("\r\nProgram:\r\n" + string.Join("\r\n", AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name)));
pluginHost.CallEach<ITestPlugin>(testPlugin => testPlugin.DoSomething());
//Console.ReadLine();
}
}
Integration.dll
PluginHost allows you to communicate with plugins. There should only ever be one instance of PluginHost. This also acts as a polling DirectoryCatalog.
public class PluginHost
{
public const string PluginRelativePath = #"plugins";
private static readonly object SyncRoot = new object();
private readonly string _pluginDirectory;
private const string PluginDomainName = "Plugins";
private readonly Dictionary<string, DateTime> _pluginModifiedDateDictionary = new Dictionary<string, DateTime>();
private PluginDomain _domain;
public PluginHost()
{
_pluginDirectory = AppDomain.CurrentDomain.BaseDirectory + PluginRelativePath;
CreatePluginDomain(PluginDomainName, _pluginDirectory);
Task.Factory.StartNew(() => CheckForPluginUpdatesForever(PluginDomainName, _pluginDirectory));
}
private void CreatePluginDomain(string pluginDomainName, string pluginDirectory)
{
_domain = new PluginDomain(pluginDomainName, pluginDirectory);
var files = GetPluginFiles(pluginDirectory);
_pluginModifiedDateDictionary.Clear();
foreach (var file in files)
{
_pluginModifiedDateDictionary[file] = File.GetLastWriteTime(file);
}
}
public void CallEach<T>(Action<T> call) where T : IPlugin
{
lock (SyncRoot)
{
var plugins = _domain.Resolve<IEnumerable<T>>();
if (plugins == null)
return;
foreach (var plugin in plugins)
{
call(plugin);
}
}
}
private void CheckForPluginUpdatesForever(string pluginDomainName, string pluginDirectory)
{
TryCheckForPluginUpdates(pluginDomainName, pluginDirectory);
Task.Delay(5000).ContinueWith(task => CheckForPluginUpdatesForever(pluginDomainName, pluginDirectory));
}
private void TryCheckForPluginUpdates(string pluginDomainName, string pluginDirectory)
{
try
{
CheckForPluginUpdates(pluginDomainName, pluginDirectory);
}
catch (Exception ex)
{
throw new Exception("Failed to check for plugin updates.", ex);
}
}
private void CheckForPluginUpdates(string pluginDomainName, string pluginDirectory)
{
var arePluginsUpdated = ArePluginsUpdated(pluginDirectory);
if (arePluginsUpdated)
RecreatePluginDomain(pluginDomainName, pluginDirectory);
}
private bool ArePluginsUpdated(string pluginDirectory)
{
var files = GetPluginFiles(pluginDirectory);
if (IsFileCountChanged(files))
return true;
return AreModifiedDatesChanged(files);
}
private static List<string> GetPluginFiles(string pluginDirectory)
{
if (!Directory.Exists(pluginDirectory))
return new List<string>();
return Directory.GetFiles(pluginDirectory, "*.dll").ToList();
}
private bool IsFileCountChanged(List<string> files)
{
return files.Count > _pluginModifiedDateDictionary.Count || files.Count < _pluginModifiedDateDictionary.Count;
}
private bool AreModifiedDatesChanged(List<string> files)
{
return files.Any(IsModifiedDateChanged);
}
private bool IsModifiedDateChanged(string file)
{
DateTime oldModifiedDate;
if (!_pluginModifiedDateDictionary.TryGetValue(file, out oldModifiedDate))
return true;
var newModifiedDate = File.GetLastWriteTime(file);
return oldModifiedDate != newModifiedDate;
}
private void RecreatePluginDomain(string pluginDomainName, string pluginDirectory)
{
lock (SyncRoot)
{
DestroyPluginDomain();
CreatePluginDomain(pluginDomainName, pluginDirectory);
}
}
private void DestroyPluginDomain()
{
if (_domain != null)
_domain.Dispose();
}
}
Autofac is a required dependency of this code. The PluginDomainDependencyResolver is instantiated in the plugin AppDomain.
[Serializable]
internal class PluginDomainDependencyResolver : MarshalByRefObject
{
private readonly IContainer _container;
private readonly List<string> _typesThatFailedToResolve = new List<string>();
public PluginDomainDependencyResolver()
{
_container = BuildContainer();
}
public T Resolve<T>() where T : class
{
var typeName = typeof(T).FullName;
var resolveWillFail = _typesThatFailedToResolve.Contains(typeName);
if (resolveWillFail)
return null;
var instance = ResolveIfExists<T>();
if (instance != null)
return instance;
_typesThatFailedToResolve.Add(typeName);
return null;
}
private T ResolveIfExists<T>() where T : class
{
T instance;
_container.TryResolve(out instance);
return instance;
}
private static IContainer BuildContainer()
{
var builder = new ContainerBuilder();
var assemblies = LoadAssemblies();
builder.RegisterAssemblyModules(assemblies); // Should we allow plugins to load dependencies in the Autofac container?
builder.RegisterAssemblyTypes(assemblies)
.Where(t => typeof(ITestPlugin).IsAssignableFrom(t))
.As<ITestPlugin>()
.SingleInstance();
return builder.Build();
}
private static Assembly[] LoadAssemblies()
{
var path = AppDomain.CurrentDomain.BaseDirectory + PluginHost.PluginRelativePath;
if (!Directory.Exists(path))
return new Assembly[]{};
var dlls = Directory.GetFiles(path, "*.dll").ToList();
dlls = GetAllDllsThatAreNotAlreadyLoaded(dlls);
var assemblies = dlls.Select(LoadAssembly).ToArray();
return assemblies;
}
private static List<string> GetAllDllsThatAreNotAlreadyLoaded(List<string> dlls)
{
var alreadyLoadedDllNames = GetAppDomainLoadedAssemblyNames();
return dlls.Where(dll => !IsAlreadyLoaded(alreadyLoadedDllNames, dll)).ToList();
}
private static List<string> GetAppDomainLoadedAssemblyNames()
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
return assemblies.Select(a => a.GetName().Name).ToList();
}
private static bool IsAlreadyLoaded(List<string> alreadyLoadedDllNames, string file)
{
var fileInfo = new FileInfo(file);
var name = fileInfo.Name.Replace(fileInfo.Extension, string.Empty);
return alreadyLoadedDllNames.Any(dll => dll == name);
}
private static Assembly LoadAssembly(string path)
{
return Assembly.Load(File.ReadAllBytes(path));
}
}
This class represents the actual Plugin AppDomain. Assemblies resolved into this domain should load any dependencies they require from the bin/plugins folder first, followed by the bin folder, since it is part of the parent AppDomain.
internal class PluginDomain : IDisposable
{
private readonly string _name;
private readonly string _pluginDllPath;
private readonly AppDomain _domain;
private readonly PluginDomainDependencyResolver _container;
public PluginDomain(string name, string pluginDllPath)
{
_name = name;
_pluginDllPath = pluginDllPath;
_domain = CreateAppDomain();
_container = CreateInstance<PluginDomainDependencyResolver>();
}
public AppDomain CreateAppDomain()
{
var domaininfo = new AppDomainSetup
{
PrivateBinPath = _pluginDllPath
};
var evidence = AppDomain.CurrentDomain.Evidence;
return AppDomain.CreateDomain(_name, evidence, domaininfo);
}
private T CreateInstance<T>()
{
var assemblyName = typeof(T).Assembly.GetName().Name + ".dll";
var typeName = typeof(T).FullName;
if (typeName == null)
throw new Exception(string.Format("Type {0} had a null name.", typeof(T).FullName));
return (T)_domain.CreateInstanceFromAndUnwrap(assemblyName, typeName);
}
public T Resolve<T>() where T : class
{
return _container.Resolve<T>();
}
public void Dispose()
{
DestroyAppDomain();
}
private void DestroyAppDomain()
{
AppDomain.Unload(_domain);
}
}
Finally your plugin interfaces.
public interface IPlugin
{
// Marker Interface
}
The main application needs to know about each plugin so an interface is required. They must inherit IPlugin and be registered in the PluginHost BuildContainer method
public interface ITestPlugin : IPlugin
{
void DoSomething();
}
TestPlugin.dll
[Serializable]
public class TestPlugin : MarshalByRefObject, ITestPlugin
{
public void DoSomething()
{
//Console.WriteLine("\r\nTestPlugin:\r\n" + string.Join("\r\n", AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name)));
}
}
Final thoughts...
One reason that this solution worked for me is that my plugin instances from the AppDomain had a very short lifetime. However, I believe that modifications could be made to support plugin objects with a longer lifetime. This would likely require some compromises like a more advanced plugin wrapper that could perhaps recreate the object when the AppDomain is reloaded (see CallEach).
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.
Is there any way to check if a resource exists in an assembly without having to use exception handling? I'm currently loading images from several assemblies, and if they don't exist then I'm handling the IOException, which causes quite a bit of overhead.
Would something like this work for you?
// Member Variable
string [] resourceNames;
// Function
Boolean ResourceExists(string resourceName)
{
if (resourceNames == null)
{
resourceNames =
Assembly.GetExecutingAssembly().GetManifestResourceNames();
}
return resourceNames.Contains(resourceName);
}
Copied answer from here:
public static bool ResourceExists(string resourcePath)
{
var assembly = Assembly.GetExecutingAssembly();
return ResourceExists(assembly, resourcePath);
}
public static bool ResourceExists(Assembly assembly, string resourcePath)
{
return GetResourcePaths(assembly)
.Contains(resourcePath);
}
public static IEnumerable<object> GetResourcePaths(Assembly assembly)
{
var culture = System.Threading.Thread.CurrentThread.CurrentCulture;
var resourceName = assembly.GetName().Name + ".g";
var resourceManager = new ResourceManager(resourceName, assembly);
try
{
var resourceSet = resourceManager.GetResourceSet(culture, true, true);
foreach(System.Collections.DictionaryEntry resource in resourceSet)
{
yield return resource.Key;
}
}
finally
{
resourceManager.ReleaseAllResources();
}
}