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.
Related
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();
}
This question already has an answer here:
Could not load file or assembly error on GetTypes method
(1 answer)
Closed 1 year ago.
The structure of my solution is as follows:
I got 2 projects. Independent of each other at compile time (no cross references). The second one is a class library that is loaded at runtime from the first one.
That library has its own dll dependencies, which I guess is what causes the exception thrown when I'm trying to do execute code using these dlls. I supposed that these referenced dlls are probably not loaded at runtime, except from the class library dll. Therefore, I added some code to load the referenced assemblies before executing any code of the loaded assembly.
To my surprise the "problematic" dll is already included in the loaded assemblies when the following code is executed, its location is correct, but the error still occurs and I have no idea how to further troubleshoot the issue.
Any help is highly appreciated.
try
{
Assembly a = Assembly.LoadFile(Path.GetFullPath(filename));
//Try to find the type the derived plugin class
foreach (Type t in a.GetTypes())
{
if (t.IsSubclassOf(typeof(PluginBase)))
{
Console.WriteLine("Plugin class detected! {0}", t.Name);
//Get Referenced Assemblies
AssemblyName[] l = a.GetReferencedAssemblies()
//Get Loaded Assemblies
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (AssemblyName a2 in l)
{
var asm = loadedAssemblies.FirstOrDefault(a => a.FullName == a2.FullName);
if (asm == null)
{
Assembly test = null;
try
{
//First try to load using the assembly name just in case its a system dll
test = Assembly.Load(a2);
//Try to load the assemly from the plugin directory
if (test == null)
{
test = Assembly.LoadFrom(Path.Join(a.Location, a2.Name + ".dll"));
}
}
catch
{
Callbacks.Log($"Unable to load assembly {a2.Name}", LogVerbosityLevel.WARNING);
}
if (test != null)
{
Callbacks.Log($"Loaded Assembly {a2.Name}", LogVerbosityLevel.WARNING);
AppDomain.CurrentDomain.Load(test.GetName());
}
}
}
object c = Activator.CreateInstance(t, new object[] { this });
Plugins[Path.GetFileName(filename)] = c as PluginBase;
//Call Dll initializers
t.GetMethod("OnLoad").Invoke(c, new object[] { });
break;
}
}
}
catch (Exception ex)
{
Log("Error during loading of plugin " + filename, LogVerbosityLevel.INFO);
Log("Exception type " + ex.Data, LogVerbosityLevel.INFO);
}
Aight, I think I figured it out. So before the reason why I saw the referenced assemblies loaded was because I iteratively loaded them by browsing in a dll folder that contains the class library + its references. The main problem is that I am so stupid that I forgot to load them to the AppDomain calling AppDomain.CurrentDomain.Load(assemblyname). However when I did try to fix that I realized that trying to just fetch the related AssemblyName object does not work. I get the same FileNotFoundException.
What fixed the issue was to use Assembly.LoadFrom instead of Assembly.LoadFile. I read through the documentation that it states that LoadFile treats the loaded assemblies differently regardless if they are exactly the same dll file just in different location. In my case there is only a single path that I tried to load the assembly from, but I guess that LoadFile also differentiates the loaded assemblies in this case as well. However, I am still not sure why trying to use an AssemblyName coming from LoadFile crashes compared to what comes out of LoadFrom. I would expect that they would be identical...
I also added a failsafe mechanism to try and load only the desired dll files. I just expect that all dlls that will be loaded will be prepended with a text identifier. So at first the desired dll is loaded and before invoking any code, its references are loaded using the AssemblyName object or the actual path if the first fails.
Everything seems to be running nicely till now so hopefully this solves it.
foreach (string filename in Directory.GetFiles("Plugins"))
{
if (!filename.EndsWith(("dll")))
continue;
if (!Path.GetFileName(filename).StartsWith(("Test")))
continue;
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
//Load Assembly
try
{
Assembly a = Assembly.LoadFrom(Path.GetFullPath(filename));
AppDomain.CurrentDomain.Load(a.GetName());
//Try to find the type the derived plugin class
foreach (Type t in a.GetTypes())
{
if (t.IsSubclassOf(typeof(PluginBase)))
{
Console.WriteLine("Plugin class detected! {0}", t.Name);
//Load Referenced Assemblies
AssemblyName[] l = a.GetReferencedAssemblies();
loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (AssemblyName a2 in l)
{
var asm = loadedAssemblies.FirstOrDefault(a => a.FullName == a2.FullName);
if (asm == null)
{
Assembly test = null;
try
{
//First try to load using the assembly name just in case its a system dll
test = Assembly.Load(a2);
}
catch (FileNotFoundException ex)
{
try
{
Callbacks.Log($"Unable to load assembly {a2.Name}, Looking in plugin directory...", LogVerbosityLevel.WARNING);
test = Assembly.LoadFrom(Path.Join(Path.GetDirectoryName(a.Location), a2.Name + ".dll"));
} catch (Exception ex2)
{
Callbacks.Log($"Unable to load assembly {a2.Name}, Error: {ex2.Message}", LogVerbosityLevel.WARNING);
}
}
if (test != null)
{
Callbacks.Log($"Loaded Assembly {a2.Name}", LogVerbosityLevel.WARNING);
AppDomain.CurrentDomain.Load(test.GetName());
}
}
}
object c = Activator.CreateInstance(t, new object[] { this });
Plugins[Path.GetFileName(filename)] = c as PluginBase;
//Call Dll initializers
t.GetMethod("OnLoad").Invoke(c, new object[] { });
break;
}
}
}
catch (Exception ex)
{
Log("Error during loading of plugin " + filename, LogVerbosityLevel.INFO);
Log("Exception type " + ex.Data, LogVerbosityLevel.INFO);
}
}
I have runtime-compiled assembly that references another assembly (DynLoadedAssembly) that is located in specific path and loaded dynamically. When I execute runtime-compiled assembly, I get an error: System.IO.FileNotFoundException Could not load file or assembly 'DynLoadedAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies.
Check out code:
public string Run()
{
var text = #"
using DynLoadedAssembly;
public class DynClass
{
public static string Evaluate()
{
return new SomeClass().Do();
}
}";
var dynAssemblyPath = #"c:\path\to\DynLoadedAssembly.dll";
Assembly.LoadFile(dynAssemblyPath);
var tree = SyntaxFactory.ParseSyntaxTree(text);
var compilation = CSharpCompilation.Create(
"calc",
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary),
syntaxTrees: new[] { tree },
references: new[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(dynAssemblyPath)
}
);
Assembly compiledAssembly;
using (var stream = new MemoryStream())
{
var compileResult = compilation.Emit(stream);
compiledAssembly = Assembly.Load(stream.GetBuffer());
}
Type dynClass = compiledAssembly.GetType("DynClass");
MethodInfo evaluate = dynClass.GetMethod("Evaluate");
return evaluate.Invoke(null, null).ToString();
}
Here SomeClass is defined in DynLoadedAssembly that loaded dymanically from dynAssemblyPath.
If I place DynLoadedAssembly into execution assembly folder - then it works fine.
Other workarounds are:
specify probing (https://msdn.microsoft.com/en-us/library/823z9h8w(v=vs.110).aspx) element in App.config
use AssemblyResolve event handler
But I want to load referenced assembly dynamically. So my questions are:
Why "Step 2: Checking for Previously Referenced Assemblies" (https://msdn.microsoft.com/en-us/library/yx7xezcf(v=vs.110).aspx#step2) failed? Why execuded code do not see DynLoadedAssembly library in current domain?
How to make my compiled assembly find previosly loaded assembly in current domain?
I want to load to a new AppDomain some assembly which has a complex references tree (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole.dll)
As far as I understood, when an assembly is being loaded to AppDomain, its references would not be loaded automatically, and I have to load them manually.
So when I do:
string dir = #"SomePath"; // different from AppDomain.CurrentDomain.BaseDirectory
string path = System.IO.Path.Combine(dir, "MyDll.dll");
AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
setup.ApplicationBase = dir;
AppDomain domain = AppDomain.CreateDomain("SomeAppDomain", null, setup);
domain.Load(AssemblyName.GetAssemblyName(path));
and got FileNotFoundException:
Could not load file or assembly 'MyDll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
I think the key part is one of its dependencies.
Ok, I do next before domain.Load(AssemblyName.GetAssemblyName(path));
foreach (AssemblyName refAsmName in Assembly.ReflectionOnlyLoadFrom(path).GetReferencedAssemblies())
{
domain.Load(refAsmName);
}
But got FileNotFoundException again, on another (referenced) assembly.
How to load all references recursively?
Do I have to create references tree before loading root assembly? How to get an assembly's references without loading it?
You need to invoke CreateInstanceAndUnwrap before your proxy object will execute in the foreign application domain.
class Program
{
static void Main(string[] args)
{
AppDomainSetup domaininfo = new AppDomainSetup();
domaininfo.ApplicationBase = System.Environment.CurrentDirectory;
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain domain = AppDomain.CreateDomain("MyDomain", adevidence, domaininfo);
Type type = typeof(Proxy);
var value = (Proxy)domain.CreateInstanceAndUnwrap(
type.Assembly.FullName,
type.FullName);
var assembly = value.GetAssembly(args[0]);
// AppDomain.Unload(domain);
}
}
public class Proxy : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFile(assemblyPath);
}
catch (Exception)
{
return null;
// throw new InvalidOperationException(ex);
}
}
}
Also, note that if you use LoadFrom you'll likely get a FileNotFound exception because the Assembly resolver will attempt to find the assembly you're loading in the GAC or the current application's bin folder. Use LoadFile to load an arbitrary assembly file instead--but note that if you do this you'll need to load any dependencies yourself.
Once you pass the assembly instance back to the caller domain, the caller domain will try to load it! This is why you get the exception. This happens in your last line of code:
domain.Load(AssemblyName.GetAssemblyName(path));
Thus, whatever you want to do with the assembly, should be done in a proxy class - a class which inherit MarshalByRefObject.
Take in count that the caller domain and the new created domain should both have access to the proxy class assembly. If your issue is not too complicated, consider leaving the ApplicationBase folder unchanged, so it will be same as the caller domain folder (the new domain will only load Assemblies it needs).
In simple code:
public void DoStuffInOtherDomain()
{
const string assemblyPath = #"[AsmPath]";
var newDomain = AppDomain.CreateDomain("newDomain");
var asmLoaderProxy = (ProxyDomain)newDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(ProxyDomain).FullName);
asmLoaderProxy.GetAssembly(assemblyPath);
}
class ProxyDomain : MarshalByRefObject
{
public void GetAssembly(string AssemblyPath)
{
try
{
Assembly.LoadFrom(AssemblyPath);
//If you want to do anything further to that assembly, you need to do it here.
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message, ex);
}
}
}
If you do need to load the assemblies from a folder which is different than you current app domain folder, create the new app domain with specific dlls search path folder.
For example, the app domain creation line from the above code should be replaced with:
var dllsSearchPath = #"[dlls search path for new app domain]";
AppDomain newDomain = AppDomain.CreateDomain("newDomain", new Evidence(), dllsSearchPath, "", true);
This way, all the dlls will automaically be resolved from dllsSearchPath.
http://support.microsoft.com/kb/837908/en-us
C# version:
Create a moderator class and inherit it from MarshalByRefObject:
class ProxyDomain : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFrom(assemblyPath);
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message);
}
}
}
call from client site
ProxyDomain pd = new ProxyDomain();
Assembly assembly = pd.GetAssembly(assemblyFilePath);
On your new AppDomain, try setting an AssemblyResolve event handler. That event gets called when a dependency is missing.
You need to handle the AppDomain.AssemblyResolve or AppDomain.ReflectionOnlyAssemblyResolve events (depending on which load you're doing) in case the referenced assembly is not in the GAC or on the CLR's probing path.
AppDomain.AssemblyResolve
AppDomain.ReflectionOnlyAssemblyResolve
It took me a while to understand #user1996230's answer so I decided to provide a more explicit example. In the below example I make a proxy for an object loaded in another AppDomain and call a method on that object from another domain.
class ProxyObject : MarshalByRefObject
{
private Type _type;
private Object _object;
public void InstantiateObject(string AssemblyPath, string typeName, object[] args)
{
assembly = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + AssemblyPath); //LoadFrom loads dependent DLLs (assuming they are in the app domain's base directory
_type = assembly.GetType(typeName);
_object = Activator.CreateInstance(_type, args); ;
}
public void InvokeMethod(string methodName, object[] args)
{
var methodinfo = _type.GetMethod(methodName);
methodinfo.Invoke(_object, args);
}
}
static void Main(string[] args)
{
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = #"SomePathWithDLLs";
AppDomain domain = AppDomain.CreateDomain("MyDomain", null, setup);
ProxyObject proxyObject = (ProxyObject)domain.CreateInstanceFromAndUnwrap(typeof(ProxyObject).Assembly.Location,"ProxyObject");
proxyObject.InstantiateObject("SomeDLL","SomeType", new object[] { "someArgs});
proxyObject.InvokeMethod("foo",new object[] { "bar"});
}
The Key is the AssemblyResolve event raised by the AppDomain.
[STAThread]
static void Main(string[] args)
{
fileDialog.ShowDialog();
string fileName = fileDialog.FileName;
if (string.IsNullOrEmpty(fileName) == false)
{
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
if (Directory.Exists(#"c:\Provisioning\") == false)
Directory.CreateDirectory(#"c:\Provisioning\");
assemblyDirectory = Path.GetDirectoryName(fileName);
Assembly loadedAssembly = Assembly.LoadFile(fileName);
List<Type> assemblyTypes = loadedAssembly.GetTypes().ToList<Type>();
foreach (var type in assemblyTypes)
{
if (type.IsInterface == false)
{
StreamWriter jsonFile = File.CreateText(string.Format(#"c:\Provisioning\{0}.json", type.Name));
JavaScriptSerializer serializer = new JavaScriptSerializer();
jsonFile.WriteLine(serializer.Serialize(Activator.CreateInstance(type)));
jsonFile.Close();
}
}
}
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string[] tokens = args.Name.Split(",".ToCharArray());
System.Diagnostics.Debug.WriteLine("Resolving : " + args.Name);
return Assembly.LoadFile(Path.Combine(new string[]{assemblyDirectory,tokens[0]+ ".dll"}));
}
I have had to do this several times and have researched many different solutions.
The solution I find in most elegant and easy to accomplish can be implemented as such.
1. Create a project that you can create a simple interface
the interface will contain signatures of any members you wish to call.
public interface IExampleProxy
{
string HelloWorld( string name );
}
Its important to keep this project clean and lite. It is a project that both AppDomain's can reference and will allow us to not reference the Assembly we wish to load in seprate domain from our client assembly.
2. Now create project that has the code you want to load in seperate AppDomain.
This project as with the client proj will reference the proxy proj and you will implement the interface.
public interface Example : MarshalByRefObject, IExampleProxy
{
public string HelloWorld( string name )
{
return $"Hello '{ name }'";
}
}
3. Next, in the client project, load code in another AppDomain.
So, now we create a new AppDomain. Can specify the base location for assembly references. Probing will check for dependent assemblies in GAC and in current directory and the AppDomain base loc.
// set up domain and create
AppDomainSetup domaininfo = new AppDomainSetup
{
ApplicationBase = System.Environment.CurrentDirectory
};
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain exampleDomain = AppDomain.CreateDomain("Example", adevidence, domaininfo);
// assembly ant data names
var assemblyName = "<AssemblyName>, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null|<keyIfSigned>";
var exampleTypeName = "Example";
// Optional - get a reflection only assembly type reference
var #type = Assembly.ReflectionOnlyLoad( assemblyName ).GetType( exampleTypeName );
// create a instance of the `Example` and assign to proxy type variable
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( assemblyName, exampleTypeName );
// Optional - if you got a type ref
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( #type.Assembly.Name, #type.Name );
// call any members you wish
var stringFromOtherAd = proxy.HelloWorld( "Tommy" );
// unload the `AppDomain`
AppDomain.Unload( exampleDomain );
if you need to, there are a ton of different ways to load an assembly. You can use a different way with this solution. If you have the assembly qualified name then I like to use the CreateInstanceAndUnwrap since it loads the assembly bytes and then instantiates your type for you and returns an object that you can simple cast to your proxy type or if you not that into strongly-typed code you could use the dynamic language runtime and assign the returned object to a dynamic typed variable then just call members on that directly.
There you have it.
This allows to load an assembly that your client proj doesnt have reference to in a seperate AppDomain and call members on it from client.
To test, I like to use the Modules window in Visual Studio. It will show you your client assembly domain and what all modules are loaded in that domain as well your new app domain and what assemblies or modules are loaded in that domain.
The key is to either make sure you code either derives MarshalByRefObject or is serializable.
`MarshalByRefObject will allow you to configure the lifetime of the domain its in. Example, say you want the domain to destroy if the proxy hasnt been called in 20 minutes.
I hope this helps.
I'm using the Activator to instantiate a new class based on the short name of an assembly (e.a. 'CustomModule'). It throws a FileNotFoundException, because the assembly isn't there. Is there a way to check if a certain assembly name is present?
I'm using the following code:
System.Runtime.Remoting.ObjectHandle obj =
System.Activator.CreateInstance(assemblyName, className);
The main objective is to rather test for the presence of the assembly than to wait for the exception to occur.
If you'll notice my comment to your question it will be evident that I'm not rightly sure exactly how you want or need to go about this, but until we have a more elaborate description I can only offer you this in the hope it fits well to your situation (the key is in 'searching' the assemblies):
var className = "System.Boolean";
var assemblyName = "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var assembly = (from a in assemblies
where a.FullName == assemblyName
select a).SingleOrDefault();
if (assembly != null)
{
System.Runtime.Remoting.ObjectHandle obj =
System.Activator.CreateInstance(assemblyName, className);
}
.NET 2.0 Compatible Code
Assembly assembly = null;
var className = "System.Boolean";
var assemblyName = "mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
{
if (a.FullName == assemblyName)
{
assembly = a;
break;
}
}
if (assembly != null)
{
System.Runtime.Remoting.ObjectHandle obj =
System.Activator.CreateInstance(assemblyName, className);
}
If you want to determine whether or not the file exists before trying to load it (a good practice) then, given you have its name and know the desired location, simply try to find the file when the assembly is being resolved:
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
var className = "StackOverflowLib.Class1";
var assemblyName = "StackOverflowLib.dll";
var currentAssemblyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var obj = Activator.CreateInstance(Path.Combine(currentAssemblyPath, assemblyName), className);
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var currentAssemblyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (File.Exists(Path.Combine(currentAssemblyPath, args.Name)))
{
return Assembly.LoadFile(Path.Combine(currentAssemblyPath, args.Name));
}
return null;
}
I think it is better not to try to avoid the exception. The reason is that if you have code like
if (DoesAssemblyExist(asmName)) {
object = Activator.CreateInstance(typeName);
}
else {
MessageBox.Show("Assembly does not exist");
}
there is always a risk in a pre-emptive multitasking OS that the assembly might be added/removed between the check and the actual creation. Yes, I realize this risk is minimal, but I still think the exception variant looks better because it is atomic.
Missing assembly definitely constitutes an exception, try/catch FileNotFoundException and handle the situation as per your logic.
I hope it will help for someone in the future:
On every external .dll you are using, create its own uniqe key, like so:
string key = "fjrj3288skckfktk4owoxkvkfk4o29dic";
And then, when you load your form, for every single external .dll that you have got, simply check if the key is exists like so:
If(isMyLib.Variables.key == key)
// continue
else
// .dll does not exists or broken.