Load assembly and its dependencies all in memory - c#

I need load an assembly and its dependecies dynamically. It's because I need that the assemblies are not locked for any process.
For example, I've created a solution with three projects.
First project is called Entities, that has one class called Person
namespace Entities
{
public class Person
{
public string Name { get; set; }
}
}
Second project is called Business, tah has on class called PersonBusiness. This project referencies the project Entities
using Entities;
namespace Business
{
public class PersonBusiness
{
public string GetPersonName(string name)
{
return name;
}
public Person GetPerson(string name)
{
Person person = new Person();
person.Name = name;
return person;
}
}
}
The last project is called Runner. This project loads the assembly of project Businnes dynamically
using System.Runtime.Loader;
namespace Runner
{
public class AssemblyLoader : AssemblyLoadContext
{
internal AssemblyLoader() : base(isCollectible: true)
{
}
}
internal class Assembly
{
private dynamic Instance { get; set; }
private AssemblyLoader AssemblyLoader { get; set; }
private Type AssemblyType { get; set; }
public void Load()
{
AssemblyLoader = new AssemblyLoader();
using (var fs = new FileStream(#"C:\temp\assemblies\business.dll", FileMode.Open, FileAccess.Read))
{
System.Reflection.Assembly assembly = AssemblyLoader.LoadFromStream(fs);
AssemblyType = assembly.GetType("Business.PersonBusiness");
Instance = Activator.CreateInstance(AssemblyType);
Console.WriteLine(Instance.GetPersonName("Person name"));
// With next line works, but lock tem assembly in memory
//AssemblyLoadContext.Default.LoadFromAssemblyPath(#"C:\temp\assemblies\Entities.dll");
Console.WriteLine(Instance.GetPerson("Person name 2").Name);
}
}
public void Unload()
{
AssemblyLoader.Unload();
}
}
}
It returns the following error:
System.IO.FileNotFoundException: 'Could not load file or assembly 'Entities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
I tried using AssemblyLoadContext.Default.LoadFromAssemblyPath. It works, but the assembly is locked.
Project in github

You could read the complete assembly dll file into memory, close the file stream (so the file is no longer locked) and then create the Assembly object from the file data in memory? Would that solve your issue?
byte[] dllAssemblyData = null;
Assembly dllAssembly = null;
// Read assembly file into byte array
using (FileStream fs = File.Open("test.dll", FileMode.Open))
{
dllAssemblyData = new byte[fs.Length];
if (fs.Read(dllAssemblyData, 0, dllAssemblyData.Length) != fs.Length)
{
// error - count of bytes read does not match file/stream length
dllAssemblyData = null;
}
}
// Create Assembly from memory
if (dllAssemblyData != null)
{
dllAssembly = Assembly.Load(dllAssemblyData);
}
// Use assembly
if (dllAssembly != null)
{
// TODO: use assembly
}

Related

Unable to load NuGet dll with platform specific dlls in netcoreapp

I am unable to load System.Data.Client dll from it's nuget package using the ICompilationAssemblyResolver I have available in a netcoreapp. The bulk of the Assembly resolving is borrowed from here, and works great for the most part. It looks like so:
internal sealed class AssemblyResolver : IDisposable
{
private readonly ICompilationAssemblyResolver assemblyResolver;
private readonly DependencyContext dependencyContext;
private readonly AssemblyLoadContext loadContext;
public AssemblyResolver(string path)
{
this.Assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
this.dependencyContext = DependencyContext.Load(this.Assembly);
this.assemblyResolver = new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[]
{
new AppBaseCompilationAssemblyResolver(Path.GetDirectoryName(path)),
new ReferenceAssemblyPathResolver(),
new PackageCompilationAssemblyResolver()
});
this.loadContext = AssemblyLoadContext.GetLoadContext(this.Assembly);
this.loadContext.Resolving += OnResolving;
}
public Assembly Assembly { get; }
public void Dispose()
{
this.loadContext.Resolving -= this.OnResolving;
}
private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name)
{
bool NamesMatch(RuntimeLibrary runtime)
{
return string.Equals(runtime.Name, name.Name, StringComparison.OrdinalIgnoreCase);
}
RuntimeLibrary library =
this.dependencyContext.RuntimeLibraries.FirstOrDefault(NamesMatch);
if (library != null)
{
var wrapper = new CompilationLibrary(
library.Type,
library.Name,
library.Version,
library.Hash,
library.RuntimeAssemblyGroups.SelectMany(g => g.AssetPaths),
library.Dependencies,
library.Serviceable);
var assemblies = new List<string>();
this.assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies);
if (assemblies.Count > 0)
{
return this.loadContext.LoadFromAssemblyPath(assemblies[0]);
}
}
return null;
}
}
However I am unable to load a dll that references System.Data.Client # 4.3.1 as at runtime I get the error message:
Exception has occurred: CLR/System.IO.FileNotFoundException
An unhandled exception of type 'System.IO.FileNotFoundException'
occurred in System.Private.CoreLib.ni.dll: 'Could not load file or
assembly 'System.Data.SqlClient, Version=4.1.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.'
I am not sure why it is trying to load 4.1.0 when I have specified 4.3.0 but I think that is a bit of a red herring. I suspect that the PackageCompilationAssemblyResolver only looks under the lib folder, and the package in question does not have one for netstandard. It does however have one for specific runtimes:
Armed with this information I have created an incredibly crude AssemblyLoader that looks under the runtimes folder for a nuget package and I am able to load the dll and run my program as I expect.
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.DotNet.PlatformAbstractions;
using Microsoft.Extensions.DependencyModel;
using Microsoft.Extensions.DependencyModel.Resolution;
namespace Loader
{
public class CrudeCompilationAssemblyResolver : ICompilationAssemblyResolver
{
private readonly string[] _nugetPackageDirectories;
public CrudeCompilationAssemblyResolver()
{
var basePath = Environment.GetEnvironmentVariable("HOME");
var defaultPath = Path.Combine(basePath, ".nuget", "packages");
_nugetPackageDirectories = new [] { defaultPath };
}
public bool TryResolveAssemblyPaths(CompilationLibrary library, List<string> assemblies)
{
if (_nugetPackageDirectories == null || _nugetPackageDirectories.Length == 0 || !string.Equals(library.Type, "package", StringComparison.OrdinalIgnoreCase))
{
return false;
}
foreach (var directory in _nugetPackageDirectories)
{
string packagePath;
var fullPath = Path.Combine(directory, library.Name, library.Version, "runtimes", "unix", "lib", "netstandard1.3", $"{library.Name}.dll");
if (File.Exists(fullPath))
{
assemblies.AddRange(new[] { fullPath });
return true;
}
}
return false;
}
}
}
My question is: Is there a better/officially sanctioned way of loading this troublesome assembly from a nuget package? Or do I need to make my crude loader a lot less crude?
Full repo is here: CustomAssemblyResolver

Need help correcting reflection

The following program works only with the classes which are located within the main project. Would you be so kind to advise or correct the code so that it becomes possible to use the classes from other projects. I added reference of the ClassLibrary1 project to the Example01 project.
The error which I am getting is
Unhandled Exception: System.ArgumentNullException: Value cannot be null.
Parameter name: type
at System.Activator.CreateInstance(Type type, Boolean nonPublic)
at System.Activator.CreateInstance(Type type)
at Example01.Program.InstantiateObject(String assemblyName, String fullName) in c:\Projects\Example01\Example01\Program.cs:line 59
object obj = Activator.CreateInstance(objectToInstantiate);
at Example01.Program.RandomizeList[TClass](Int32 count, String assemblyName) in c:\Projects\Example01\Example01\Program.cs:line 34
randomizedList.Add(
at Example01.Program.Main(String[] args) in c:\Projects\Example01\Example01\Program.cs:line 18
List<Class02> randomizedList03 = RandomizeList<Class02>();
Here is my code with the reference what works and what fails:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ClassLibrary1.Classes;
namespace Example01
{
class Program
{
static void Main(string[] args)
{
// This works
List<Class01> randomizedList01 = RandomizeList<Class01>();
// This works
List<Test01> randomizedList02 = RandomizeList<Test01>();
// This fails
List<Class02> randomizedList03 = RandomizeList<Class02>();
Console.ReadKey();
}
private static List<TClass> RandomizeList<TClass>(int count = 10, string assemblyName = "")
{
if (assemblyName.Length == 0)
assemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
var listOfSubClasses = SubClasses<TClass>();
var randomizedList = new List<TClass>();
var rand = new Random();
count = 10;
for (int i = 0; i < count; i++)
randomizedList.Add(
(TClass)
InstantiateObject(assemblyName,
listOfSubClasses.ElementAt(rand.Next(listOfSubClasses.Count()))
.FullName));
return new List<TClass>(randomizedList as IEnumerable<TClass>);
}
// Enumerate all subclasses for the specified class
public static IEnumerable<Type> SubClasses<TClass>()
{
var subclasses =
(from assembly in AppDomain.CurrentDomain.GetAssemblies()
from type in assembly.GetTypes()
where type.IsSubclassOf(typeof(TClass))
select type).ToList();
return subclasses;
}
private static object InstantiateObject(string assemblyName, string fullName)
{
Assembly аsm = Assembly.Load(assemblyName);
// Get the assembly metadata
Type objectToInstantiate = аsm.GetType(fullName);
// Create object on the fly
object obj = Activator.CreateInstance(objectToInstantiate);
return obj;
}
}
#region Sample Classes
public class Class01
{
public string Name { get; set; }
public int Age { get; set; }
}
public class SubClass0101 : Class01
{
public int Length { get; set; }
}
public class SubClass0102 : Class01
{
public int Length { get; set; }
}
public class SubClass0103 : Class01
{
public int Length { get; set; }
}
#endregion
}
Many thanks in advance!!!
Your problem is this line:
assemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name
You need to enumerate all the referenced assemblies, not just the current executing assembly, so probably need to use GetReferencedAssemblies() on the result of GetExecutingAssembly() as your main project is the executing assembly and your referenced project would be a referenced assembly.

Loading Assemblies into a separate AppDomain in a WindowsAzure WorkerRole

I'm developing an Azure Worker Role that will need to periodically perform actions based on work pulled from a WCF service. Due to the nature of Azure deployments, a decision was made to implement a plugin framework in Azure to allow quick updates to our product while minimizing the downtime by removing the need for full azure deployments, as well as supporting multiple versions of various assemblies.
A plugin is downloaded from Cloud storage and loaded into a class that contains all relevant info needed to load the assembly. (See PluginAssemblyInfo below)
In other development work I've developed the below ReflectionHelper class. This class works in other applications and I've verified it works in a console application.
However when running in the Azure emulator I get the following error: (I've added a comment on the line the exception is being thrown on.)
Type is not resolved for member 'MyCompanyName.ReflectionHelper,MyCompanyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
The ReflectionHelper class is added as a source file to the same assembly that is calling it. (I did edit the name of the assembly/namespaces for anonymity reasons)
Given the knowledge that this class works in other cases, my only guess is that it may be a trust issue, but I've not found much info online relating to this. Any help would be greatly appreciated.
This is the code for the ReflectionHelper class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Serialization;
using System.Web;
namespace MyCompanyName
{
[Serializable]
public class ReflectionHelper
{
public AppDomain appDomain { get; set; }
public byte[] PluginBytes { get; set; }
public string PluginClassName { get; set; }
public string PluginAssemblyName { get; set; }
public Dictionary<string, byte[]> AssemblyArchive { get; set; }
public object GetInstance(AppDomain NewDomain, byte[] PluginBytes, string PluginClassName, string PluginAssemblyName, Dictionary<string, byte[]> AssemblyArchive)
{
try
{
this.appDomain = NewDomain;
this.PluginBytes = PluginBytes;
this.PluginClassName = PluginClassName;
this.AssemblyArchive = AssemblyArchive;
this.PluginAssemblyName = PluginAssemblyName;
//this is the line that throws the serializationexception
appDomain.AssemblyResolve += new ResolveEventHandler((sender, args) =>
{
AssemblyName x = new AssemblyName(args.Name);
string Name = x.Name;
if (System.IO.Path.GetExtension(x.Name) != ".dll")
Name += ".dll";
Assembly Ret = appDomain.Load(this.AssemblyArchive[Name]);
return Ret;
});
appDomain.DoCallBack(LoaderCallBack);
return appDomain.GetData("Plugin");
}
catch (Exception ex)
{
throw ex;
}
}
public void LoaderCallBack()
{
ObjectHandle PluginObject = appDomain.CreateInstance(string.Format(this.PluginAssemblyName), PluginClassName);
appDomain.SetData("Plugin", PluginObject.Unwrap());
}
}
}
This is the code snippet that invokes it
AppDomain currentDomain = AppDomain.CurrentDomain;
AppDomainSetup domainSetup = new AppDomainSetup() { };
AppDomain BrokerJobDomain = AppDomain.CreateDomain("WorkerPluginDomain" + Guid.NewGuid().ToString());//, null, new AppDomainSetup { ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase });
ReflectionHelper helper = new ReflectionHelper();
object Instance = helper.GetInstance(BrokerJobDomain, pluginAssembly.PluginBytes, pluginAssembly.ClassName, pluginAssembly.AssemblyName, pluginAssembly.AssemblyArchive);
IWorkPlugin executor = (IWorkPlugin)Instance;
This is the assembly info class that is populated when the plugin is downloaded from cloud storage
public class PluginAssemblyInfo
{
public string ClassName { get; set; }
public byte[] PluginBytes { get; set; }
public Dictionary<string, byte[]> AssemblyArchive { get; set; }
public string AssemblyName { get; set; }
public DateTime LoadDate { get; set; }
}
The issue seems to be related to the RelativeSearchPath not being set up on the child domain. Ensuring the child domain had the same setup credentials seemed to fix this.
AppDomain currentDomain = AppDomain.CurrentDomain;
AppDomain BrokerJobDomain = AppDomain.CreateDomain("WorkerPluginDomain" + Guid.NewGuid().ToString(), null, AppDomain.CurrentDomain.SetupInformation );

How do I use AppDomain.CreateDomain with AssemblyResolve?

I want to load my assemblies from WCF by using memory. Everything is working good WHEN:
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
Assembly[] assBefore = AppDomain.CurrentDomain.GetAssemblies();
foreach (byte[] binary in deCompressBinaries)
loadedAssembly = AppDomain.CurrentDomain.Load(binary);
But I want to use AppDomain.CreateDomain, not the current domain:
protected void LoadApplication()
{
this.ApplicationHost = AppDomain.CreateDomain("TestService", null, new AppDomainSetup
{
ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase
});
ApplicationHost.AssemblyResolve += new ResolveEventHandler(OnAssemblyResolve);
foreach (AssemblyName asmbly in System.Reflection.Assembly.GetExecutingAssembly().GetReferencedAssemblies())
{
ApplicationHost.Load(asmbly);
}
List<byte[]> deCompressBinaries = new List<byte[]>();
foreach (var item in AppPackage.Item.AssemblyPackage)
deCompressBinaries.Add(item.Buffer);
var decompressvalues = DeCompress(deCompressBinaries);
deCompressBinaries.Clear();
deCompressBinaries = decompressvalues.ToList();
foreach (byte[] binary in deCompressBinaries)
ApplicationHost.Load(binary);
Assembly[] assAfter = AppDomain.CurrentDomain.GetAssemblies();
}
Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
{
return Assembly.Load(args.Name);
}
I have two class libraries, ClassLibrary1 and ClassLibrary2 using the below:
namespace ClassLibrary2
{
public class Class1 : MarshalByRefObject
{
public Class1()
{
}
public int GetSum(int a , int b)
{
try
{
ClassLibrary1.Class1 ctx = new ClassLibrary1.Class1();
return ctx.Sum(a, b);
}
catch
{
return -1;
}
}
public int GetMultiply(int a, int b)
{
return a * b;
}
}
}
Classlibrary2 depends on ClassLibrary1. So I am using assemblyresolver. But I get an error on ApplicationHost.Load(binary);:
Error: Could not load file or assembly 'ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
Also it is NOT FIRING ASSEMBLYRESOLVER. My cursor is not going to the Assemblyresolver method. How do I use AppDomain.CreateDomain with the resolve method?
I personally don't like loading assemblies from a byte array. I think it is better to save your assemblies to a temporary folder and then load them from that folder. Take a look at this article: Application Domains is hard….

Error deserializing object having a field of type declared in the other assembly loaded on AssemblyResolve

I have an application which embedes (via BuildAction: Embedded Resource) referenced assembly (called ClassLibrary1) inside itself and loads it on AppDomain.CurrentDomain.AssemblyResolve event.
Main assembly defines a class Class1:
public class Class1
{
public Class2 MyField { get; set; }
}
It has a property of type Class2 defined in ClassLibrary1.
Definition of Class2:
public class Class2
{
public int A { get; set; }
}
In the main method I`m creating a new XmlSerializer(typeof(Class1)):
static void Main()
{
SubscribeAssemblyResolver();
MainMethod();
}
private static void MainMethod()
{
XmlSerializer xs2 = new XmlSerializer(typeof(Class1));
Class1 cl = new Class1();
}
While executing a programm I get the following error:
Unable to generate a temporary class (result=1).
error CS0012: The type 'ClassLibrary1.Class2' is defined in an assembly that is not referenced. You must add a reference to assembly 'ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c06f123f2868e8c8'.
error CS0266: Cannot implicitly convert type 'object' to 'ClassLibrary1.Class2'. An explicit conversion exists (are you missing a cast?)
Any ideas?
The rest of the code:
private static void SubscribeAssemblyResolver()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
static Dictionary<String, Assembly> _assemblies = new Dictionary<String, Assembly>(StringComparer.OrdinalIgnoreCase);
static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
return ResolveAssembly(args.Name);
}
private static Assembly ResolveAssembly(string argsName)
{
Assembly dll;
var name = "WindowsFormsApplication1.Libs." + new AssemblyName(argsName).Name + ".dll";
if (!_assemblies.TryGetValue(name, out dll))
{
Assembly res = typeof(Program).Assembly;
using (var input = res.GetManifestResourceStream(name))
{
if (input == null)
{
//TODO: log
return null;
}
Byte[] assemblyData = new Byte[input.Length];
input.Read(assemblyData, 0, assemblyData.Length);
if (null == (dll = Assembly.Load(assemblyData)))
{
//TODO: log
return null;
}
//TODO: log
_assemblies[name] = dll;
return dll;
}
}
return dll;
}
UPDATE: Created a BUG on the microsoft Connect site. You can also download a sample visual stuido 2010 solution (just expand Details fieldgroup) from there to reproduce it.
I've solved similar problem by saving assembly in temporary folder
public static byte[] ReadFully(Stream input)
{
var buffer = new byte[16 * 1024];
using (var ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
public App()
{
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
var assemblyName = new AssemblyName(args.Name);
if (assemblyName.Name != "Omikad.Core")
return null;
var resourceName = "Terem." + assemblyName.Name + ".dll";
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
{
if (stream == null)
return null;
var assemblyData = ReadFully(stream);
var tmp = Path.Combine(Path.GetTempPath(), "Omikad.Core.dll");
File.WriteAllBytes(tmp, assemblyData);
return Assembly.LoadFrom(tmp);
}
};
}
Try to add atribute:
[XmlInclude(typeof(Class2))]
public class Class1
{
public Class2 MyField { get; set; }
}
As for now I`ve ended up with two somewhat bad solutions:
While you can`t instanciate XmlSerializer for the type Class1, you still can instanciate it for the type Class2 from the main assembly. That does mean that if you move Class1 to ClassLibrary1 or Class2 to the main assembly - it will deserialize without errors. It works, but it is not possible to use this solution everywhere, plus it is ideologically wrong.
Use ILMerge to merge those assemblies into one. But it only works for non-wpf stuff, plus you should manage the situation with the assemblies attributes (there could be conflicts).
And one very bad idea:
Generate ClassLibrary1.XmlSerializer.dll with sgen.exe.
Also embed it into the main assembly.
Explicitly load it to the XmlSerializer cache calling one of it`s internal methods via reflection.
Although I had to use solution number one for now, I`m not satisfied with it, because it is too constraining.
I'd try the XmlSerializer(Type, Type[]) constructor and provide Class2 as an additional type using the second parameter. I've few experience with the XmlSerializer, but for DataContractSerializer this does the trick.

Categories