Reflection - Can't create a new object instance C# - 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!"));
}
}
}

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.

Reflection not working on assembly that is loaded using Assembly.LoadFrom

I have a library that contains some reflection code which inspects an Asp.Net's primary assembly, any referenced assemblies and does cool stuff. I'm trying to get the same exact code to execute in a console application while still reflecting on an Asp.Net's assemblies and I'm seeing odd results. I've got everything wired up and the code executes, however the reflection code returns false when I know it should be returning true as I'm stepping through it in the debugger.. It's driving me nuts and I can't figure out why reflection is exhibiting different behavior when running from the console app.
Here's a perfect example of some reflection code that gets all of the types that are area registrations in an Asp.Net application (type.IsSubclassOf(typeof(System.Web.Mvc.AreaRegistration))). This returns true for several types when executing in the app domain of an Asp.Net application, however it returns false for those same types when executed under the console application, but still reflecting on those same Asp.Net types.
I've also tried using the Assembly.ReflectionOnlyLoadFrom method but even after writing all the code to manually resolve referenced assemblies the reflection code shown below returns false on types that it should be returning true for.
What can I try to make this work?
public static Assembly EntryAssembly { get; set; } // this is set during runtime if within the Asp.Net domain and set manually when called from the console application.
public CodeGenerator(string entryAssemblyPath = null)
{
if (entryAssemblyPath == null) // running under the Asp.Net domain
EntryAssembly = GetWebEntryAssembly(); // get the Asp.Net main assembly
else
{
// manually load the assembly into the domain via a file path
// e:\inetpub\wwwroot\myAspNetMVCApp\bin\myApp.dll
EntryAssembly = Assembly.LoadFrom(entryAssemblyPath);
}
var areas = GetAreaRegistrations(); // returns zero results under console app domain
... code ...
}
private static List<Type> GetAreaRegistrations()
{
return EntryAssembly.GetTypes().Where(type => type.IsSubclassOf(typeof(System.Web.Mvc.AreaRegistration)) && type.IsPublic).ToList();
}
This has to do with the assembly context in which LoadFrom loads assemblies. Dependencies loaded during LoadFrom will not be used when resolving "regular" assemblies in the Load context.
The same appies the ReflectionOnly overloads, which load into the ReflectionOnly context.
For detailed information see https://stackoverflow.com/a/2493855/292411, and Avoid Assembly.LoadFrom; instead use Assembly.Load for an issue with LoadFrom similar to yours.
When I ran into this issue I switched to using Load and demanded "plugin" assemblies to be in the same path as the executable; I don't know if there are tricks to make things work if the assemblies are in different paths.
Ok, after a lot of debugging I've got this working! It turned out that my library project was compiling against Asp.Net MVC 4.0 even though Nuget and the properties window claimed 5.1. Nuget/MS fail again. The Asp.Net MVC application that my library is reflecting on is using MVC 5.1 so when the Assembly.LoadFrom and the AssemblyResolve event ran it was loading two versions of System.Web.Mvc.dll into the LoadFrom context (4.0 & 5.1) and this caused the IsSubclassOf() method to return false when the expected result should have been true.
The very odd error I mentioned in the comments above while debugging: The type 'System.Web.Mvc.AreaRegistration' exists in both 'System.Web.Mvc.dll' and 'System.Web.Mvc.dll' now makes sense, but only after the fact.
The way I finally tracked this down was by writing out all of the assemblies that AssemblyResolve was called upon to resolve and noticed that System.Web.Mvc.dll was not in the list. I fired up the Assembly Binding Log Viewer and was clearly able to see that System.Web.Mvc.dll was being loaded twice.
In retrospect, one should just skip all the custom logging and just use the Assembly Binding Log Viewer to verify only one of each assembly is being loaded and that it's the correct version your expecting.
Figuring out how to use AssemblyResolve properly was a nightmare so here is my unfinished, but working code for posterity.
public class CodeGenerator
{
public static string BaseDirectory { get; set; }
public static string BinDirectory { get; set; }
static CodeGenerator()
{
BinDirectory = "bin";
// setting this in a static constructor is best practice
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}
public CodeGenerator(string entryAssemblyPath = null, string baseDirectory = null, string binDirectory = null)
{
if (string.IsNullOrWhiteSpace(baseDirectory))
BaseDirectory = AppDomain.CurrentDomain.BaseDirectory;
else
BaseDirectory = baseDirectory;
if (string.IsNullOrWhiteSpace(binDirectory) == false)
BinDirectory = binDirectory;
if (entryAssemblyPath == null) // running under the Asp.Net domain
EntryAssembly = GetWebEntryAssembly(); // get the Asp.Net main assembly
else
{
// manually load the assembly into the domain via a file path
// e:\inetpub\wwwroot\myAspNetMVCApp\bin\myApp.dll
EntryAssembly = Assembly.LoadFrom(entryAssemblyPath);
}
var areas = GetAreaRegistrations(); // reflect away!
... code ...
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
try
{
if (args == null || string.IsNullOrWhiteSpace(args.Name))
{
Logger.WriteLine("cannot determine assembly name!", Logger.LogType.Debug);
return null;
}
AssemblyName assemblyNameToLookFor = new AssemblyName(args.Name);
Logger.WriteLine("FullName is {0}", Logger.LogType.Debug, assemblyNameToLookFor.FullName);
// don't load the same assembly twice!
var domainAssemblies = AppDomain.CurrentDomain.GetAssemblies();
var skipLoading = false;
foreach (var dAssembly in domainAssemblies)
{
if (dAssembly.FullName.Equals(assemblyNameToLookFor.FullName))
{
skipLoading = true;
Logger.WriteLine("skipping {0} because its already loaded into the domain", Logger.LogType.Error, assemblyNameToLookFor.FullName);
break;
}
}
if (skipLoading == false)
{
var requestedFilePath = Path.Combine(Path.Combine(BaseDirectory, BinDirectory), assemblyNameToLookFor.Name + ".dll");
Logger.WriteLine("looking for {0}...", Logger.LogType.Warning, requestedFilePath);
if (File.Exists(requestedFilePath))
{
try
{
Assembly assembly = Assembly.LoadFrom(requestedFilePath);
if (assembly != null)
Logger.WriteLine("loaded {0} successfully!", Logger.LogType.Success, requestedFilePath);
// todo: write an else to handle load failure and search various probe paths in a loop
return assembly;
}
catch (FileNotFoundException)
{
Logger.WriteLine("failed to load {0}", Logger.LogType.Error, requestedFilePath);
}
}
else
{
try
{
// ugh, hard-coding, but I need to get on with the real programming for now
var refedAssembliesPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), #"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.1");
requestedFilePath = Path.Combine(refedAssembliesPath, assemblyNameToLookFor.Name + ".dll");
Logger.WriteLine("looking for {0}...", Logger.LogType.Warning, requestedFilePath);
Assembly assembly = Assembly.LoadFrom(requestedFilePath);
if (assembly != null)
Logger.WriteLine("loaded {0} successfully!", Logger.LogType.Success, requestedFilePath);
// todo: write an else to handle load failure and search various probe paths in a loop
return assembly;
}
catch (FileNotFoundException)
{
Logger.WriteLine("failed to load {0}", Logger.LogType.Error, requestedFilePath);
}
}
}
}
catch (Exception e)
{
Logger.WriteLine("exception {0}", Logger.LogType.Error, e.Message);
}
return null;
}
}

Cannot load assembly into application domain: "Could not load file or assembly XXX, Version=1.0.0.0 ..." [duplicate]

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.

Type.GetType() returning null [duplicate]

This question already has answers here:
Type.GetType("namespace.a.b.ClassName") returns null
(17 answers)
Closed 7 years ago.
I have a web application that dynamically creates a web page using usercontrols.
Within my code I have the following:
private void Render_Modules()
{
foreach (OnlineSystemPageCustom.OnlineSystemPageHdr.OnlineSystemPageModule item in custompage.Header.Modules)
{
if (item.ModuleCustomOrder != 99 && !item.ModuleOptional)
{
string typeName = item.ModuleInternetFile;
Type child = Type.GetType(typeName);
webonlinecustombase ctl = (webonlinecustombase)Page.LoadControl("../IPAM_Controls/webtemplatecontrols/" + child.Name.ToString() + ".ascx");
ctl.Event = Event;
ctl.custompage = custompage;
ctl.custommodule = item;
this.eventprogrammodules.Controls.Add(ctl);
}
}
}
The "typeName" that is being returned (example) is:
IPAMIntranet.IPAM_Controls.webtemplatecontrols.eventorgcommittee
The namespace for the user controls is as follows:
namespace IPAMIntranet.IPAM_Controls
The problem I am having is that Type.GetType(typeName) is returning null. What am I missing here?
Type.GetType(string) only looks in the currently executing assembly and mscorlib when you don't specify the assembly name within the string.
Options:
Use the assembly-qualified name instead
Call Assembly.GetType(name) on the appropriate assembly instead
If you have an easy way of getting hold of the relevant assembly (e.g. via typeof(SomeKnownType).Assembly) then the second option is probably simpler.
Type.GetType looks as the calling assembly, and a few system assemblies. For anything else, you must either use assemblyInstance.GetType(typeName), or you must use the "assembly qualified name" of the type, which includes the assembly details in which the type can be found. Otherwise, it wont be found, and will return null. You can get that from:
string aqn = someType.AssemblyQualifiedName;
I had a very similar problem to the original poster, except that I needed to instantiate the code-behind class of my custom user-control in a static utility class rather than an ASPX page, so LoadControl wasn't available to me. Here's what I ended up doing:
public static class Utils
{
public static string MyFunc(string controlClassName)
{
string result = "";
// get a list of all assemblies in this application domain
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
// the trouble is that we don't know which assembly the class is defined in,
// because we are using the "Web Site" model in Visual Studio that compiles
// them on the fly into assemblies with random names
// -> however, we do know that the assembly will be named App_Web_*
// (http://msdn.microsoft.com/en-us/magazine/cc163496.aspx)
foreach (Assembly assembly in assemblies)
{
if (assembly.FullName.StartsWith("App_Web_"))
{
// I have specified the ClassName attribute of the <%# Control %>
// directive in the relevant ASCX files, so this should work
Type t = assembly.GetType("ASP." + controlClassName);
if (t != null)
{
// use reflection to create the instance (as a general object)
object o = Activator.CreateInstance(t);
// cast to the common base type that has the property we need
CommonBaseType ctrl = o as CommonBaseType;
if (ctrl != null)
{
foreach (string key in ctrl.PropertyWeNeed)
{
// finally, do the actual work
result = "something good";
}
}
}
}
}
return result;
}
}
It's not pretty and not very efficient, and is liable to break if the App_Web_* naming convention changes (although you could then just look through all of them): but it does work ...

How to Load an Assembly to AppDomain with all references recursively?

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.

Categories

Resources