Load NuGet dependencies at runtime - c#

I'm looking for a way to run code by executing the following steps:
Receiving a list of NuGet packages (a list of tuples ("package name", "package version", "path to main class").
Retrieving them in a local directory (cf code sample #1)
Loading them in my program at run-time
Running the main classes by introspection (cf code sample #2)
By now I am struggling with the third step. I can't find out how to load my package at run-time.
My main question are:
How can I find out in which folders were stored the retrieved packages?
How can I load the content of those directories into my program?
Code Sample #1:
private static void getPackageByNameAndVersion(string packageID, string version)
{
IPackageRepository repo =
PackageRepositoryFactory.Default
.CreateRepository("https://packages.nuget.org/api/v2");
string path = "C:/tmp_repo";
PackageManager packageManager = new PackageManager(repo, path);
Console.WriteLine("before dl pkg");
packageManager.InstallPackage(packageID, SemanticVersion.Parse(version));
}
Code sample #2:
private static void loadByAssemblyNameAndTypeName(string assemblyName, string typeName)
{
AppDomain isolationAppDomain = AppDomain.CreateDomain("tmp");
object a = isolationAppDomain.CreateInstanceAndUnwrap(assemblyName, typeName);
Type x = a.GetType();
MethodInfo m = x.GetMethod("Main");
m.Invoke(a, new object[] { });
}

Grab a cup of coffee :)
Downloading the nuget package?
Nuget.Core (nuget package) is a good choice, and here is a snippet of code that I have that should be able to download a nuget package by id and version
var repo = PackageRepositoryFactory.Default
.CreateRepository("https://packages.nuget.org/api/v2");
string path = "c:\\temp";
var packageManager = new PackageManager(repo, path);
packageManager.PackageInstalled += PackageManager_PackageInstalled;
var package = repo.FindPackage("packageName", SemanticVersion.Parse("1.0.0"));
if (package != null)
{
packageManager.InstallPackage(package, false, true);
}
Notice that I plugged an event handler to the PackageInstalled event of the PackageManager class.
How do we load an assembly in an isolated app domain?
Since reflection API does not provide a way to load an assembly in a specific domain, We will create a proxy class that act as a loader in our isolated domain:
public class TypeProxy : MarshalByRefObject
{
public Type LoadFromAssembly(string assemblyPath, string typeName)
{
try
{
var asm = Assembly.LoadFile(assemblyPath);
return asm.GetType(typeName);
}
catch (Exception) { return null; }
}
}
And now, is how to put it all together?
Here comes the complex part:
private static void PackageManager_PackageInstalled(object sender,
PackageOperationEventArgs e)
{
var files = e.FileSystem.GetFiles(e.InstallPath, "*.dll", true);
foreach (var file in files)
{
try
{
AppDomain domain = AppDomain.CreateDomain("tmp");
Type typeProxyType = typeof(TypeProxy);
var typeProxyInstance = (TypeProxy)domain.CreateInstanceAndUnwrap(
typeProxyType.Assembly.FullName,
typeProxyType.FullName);
var type = typeProxyInstance.LoadFromAssembly(file, "<KnownTypeName>");
object instance =
domain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName);
}
catch (Exception ex)
{
Console.WriteLine("failed to load {0}", file);
Console.WriteLine(ex.ToString());
}
}
}
Notice that this method is the event handler that gets executed after downloading the nuget package
Also
Note that you will need to replace <KnownTypeName> with the expected type name coming from the assembly (or maybe run a discovery of all public types in the assembly)
Worth noting that I haven't executed this code myself and cannot guarantee that it will work out of the box, and still might need some tweaking. but Hopefully it is the concept that allows you to solve the problem.

Don't do that! You are probably trying to load NuGet content at a customers computer to save some space on distribution of your software. Isn't it that?
The common recommended approach is to download the NuGet content as the second step of an automated build (after downloading the source code), build the software and run the automated tests with the NuGet content you have downloaded. And then distribute the build with the NuGet content you have tested as the complex whole unit.

Related

Could not load type 'Microsoft.Azure.WebJobs.Host.Scale.ConcurrencyManager' from assembly 'Microsoft.Azure.WebJobs.Host

The application is in Azure Functions,
The error that we are getting from container Pod logs is "Could not load type 'Microsoft.Azure.WebJobs.Host.Scale.ConcurrencyManager' from assembly 'Microsoft.Azure.WebJobs.Host, Version=3.0.26.0".
In our application version all of the dll ver is 3.0.30.0
In the "dll" folder of debug is having the version with 3.0.30.0
And in this version 3.0.30.0, it has the class "Microsoft.Azure.WebJobs.Host.Scale.ConcurrencyManager"
Not sure, where this "assembly 'Microsoft.Azure.WebJobs.Host, Version=3.0.26.0" is coming from.
For me this was happening because Azure Functions Core Tools version mismatched due to upgradation of Visual Studio to latest version.
Removing the Azure Function Tools from the system path C:\Users\user.name\AppData\Local\AzureFunctionsTools and Let Visual Studio automatically install Azure Functions Core Tools fixed the issue.
I had the same issue as below log.
Could not load type 'Microsoft.Azure.WebJobs.Host.Scale.ConcurrencyManager' from assembly 'Microsoft.Azure.WebJobs.Host, Version=3.0.25.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.
It was due to my base image for azure functions was old. using the newer base image with below tag(mcr.microsoft.com/azure-functions/dotnet:3.4.2) has fixed my issue.
FROM mcr.microsoft.com/azure-functions/dotnet:3.4.2 AS base
WORKDIR /home/site/wwwroot
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:3.1.416 AS build
WORKDIR /src
This is not direct answer to your question but a tool that would answer it for you. As I had a lot of this kind of errors I have written a helper code to do just that. Its written for the .net framework but with minor changes you can have same thing on core.
//folder where dependencies should be found
var dir = #"C:\Repos\Project\bin";
//dll or exe that you want to inspect
var dll = #"C:\Repos\Project\bin\Project.dll";
var asm = Assembly.ReflectionOnlyLoadFrom(dll);
var stack = new Stack<Data>();
stack.Push(new Data {
ReferencesPath = Array.Empty<Assembly>(),
Assembly = asm
});
List<AssemblyName> visited = new List<AssemblyName>();
while (stack.Any())
{
var current = stack.Pop();
var dependencies = current.Assembly.GetReferencedAssemblies();
visited.Add(current.Assembly.GetName());
foreach (var item in dependencies)
{
if (!visited.Any(x => x.FullName == item.FullName))
{
Assembly dependency;
try
{
dependency = Assembly.ReflectionOnlyLoad(item.FullName);
}
catch
{
var path = Path.Combine(dir, item.Name) + ".dll";
dependency = Assembly.ReflectionOnlyLoadFrom(path);
}
if (dependency.GetName().Version != item.Version)
{
; // put breakpoint here and inspect dependency
// and item when you find your dll in wrong version
// you can inspect current.ReferencesPath to see dependencies
// chain that causes the error
}
stack.Push(new Data
{
Assembly = dependency,
ReferencesPath = current.ReferencesPath.Concat(
new[] { current.Assembly }).ToArray()
});
}
}
}
class Data
{
public Assembly[] ReferencesPath { get; set; }
public Assembly Assembly { get; internal set; }
}

Dynamically load assemblies in ASP.NET 5

I used to have some code which scanned the bin directory of my application for assemblies which weren't loaded in the AppDomain yet and loaded them. It basically looked like:
foreach (var assemblyPath in Directory.GetFiles("path\to\bin", "*.dll"))
{
var inspected = Assembly.ReflectionOnlyLoadFrom(assemblyPath);
Assembly.Load(inspected.GetName());
}
I skipped the try/catch clauses, etc for brevity.
This allowed me to drop assemblies in the bin folder at run-time with implementations for certain interfaces and let the IoC container pick them up automatically. Now with the new Roslyn magic, there are no physical DLL's anymore when debugging. Is there any way to retrieve assembly names, project names or dependency names (in project.json) dynamically.
I guess I have to implement something like this example in the Entropy repo, but I don't know how to implement it for my scenario.
You can use the IAssemblyLoadContextAccessor interface to load ASP.NET 5 class library (.xproj) projects dynamically. The following example code works with Beta 4:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
var assemblyLoadContextAccessor = app.ApplicationServices.GetService<IAssemblyLoadContextAccessor>();
var loadContext = assemblyLoadContextAccessor.Default;
var loadedAssembly = loadContext.Load("NameOfYourLibrary");
}
}
What you are looking for is ILibraryManager implementation which provides access to the complete graph of dependencies for the application. This is already flowed through the ASP.NET 5 DI system. So, you can reach out to it from there.
Sample usage can be found inside RoslynCompilationService.
I solved this issue partly using the ILibraryManager as suggested by #tugberk. I changed the approach a bit which dropped the need of scanning the bin folder for new assemblies. I just want all the loaded assemblies in the current AppDomain.
I injected an instance of the ILibraryManager interface in my type finder class and used the GetReferencingLibraries() method with the name of the core assembly, which is referenced by all the other assemblies in the application.
A sample implementation can be found here, where this is the important part:
public IEnumerable<Assembly> GetLoadedAssemblies()
{
return _libraryManager.GetReferencingLibraries(_coreAssemblyName.Name)
.SelectMany(info => info.Assemblies)
.Select(info => Assembly.Load(new AssemblyName(info.Name)));
}
For .net core users, here is my code for loading assemblies from a specific path. I had to use directives, as it's slightly different for .Net Framework and .Net Core.
In your class header you'll need to declare the using something similar to:
#if NET46
#else
using System.Runtime.Loader;
#endif
And in your function something similar to the following:
string assemblyPath = "c:\temp\assmebly.dll";
#if NET46
Assembly assembly = Assembly.LoadFrom(assemblyPath);
#else
AssemblyLoadContext context = AssemblyLoadContext.Default;
Assembly assembly = context.LoadFromAssemblyPath(assemblyPath);
#endif
Its not ASP.NET but it can be converted easily to asp.net.
bellow if function for loading an assembly, and invoke a method inside a class on that assembly.
private static FormCustomized loadLayout(global::System.String layoutFilename, global::System.String layoutNameSpace)
{
FormCustomized mainForm = default;
Type typeMainLayout = default;
FileInfo layoutFile;
layoutFile = new FileInfo(layoutFilename);
layoutFile.Refresh();
if (!layoutFile.Exists)
{
MessageBox.Show("Layout file not found. You need to reinstall the program");
return default;
}
try
{
Assembly assemblyRaw = Assembly.LoadFrom(layoutFilename);
AssemblyLoadContext context = AssemblyLoadContext.Default;
Assembly assembly = context.LoadFromAssemblyPath(layoutFilename);
Type typeMainLayoutIni = assembly.GetType(layoutNameSpace + ".initializeLayoutClass");
Object iniClass = Activator.CreateInstance(typeMainLayoutIni, true);
MethodInfo methodInfo = typeMainLayoutIni.GetMethod("AssembliesToLoadAtStart");
enVars.assemblies = (Dictionary<string, Environment.environmentAssembliesClass>)methodInfo.Invoke(iniClass, default);
typeMainLayout = assembly.GetType(layoutNameSpace + ".mainAppLayoutForm");
mainForm = Activator.CreateInstance(typeMainLayout, enVars) as FormCustomized;
}
catch (Exception ex)
{
return default;
}
return default;
}

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;
}
}

Change .dll in runtime

I have a huge application where one project of my solution makes reports.
I want to add new report (update report) without building my project, just add .dll files. I read about Assembly and
AppDomain, but I don't know is it really good way to add new dll for new report and how to update old report in runtime?
Here's my example, it takes my first dll, but second time it doesn't. First dll - sum, second - deducted.
static void Main(string[] args)
{
try
{
//first domain
AppDomain domain = AppDomain.CreateDomain("MyDomain");
AssemblyDll asb1 = new AssemblyDll();
Console.WriteLine(asb1.AssemblyMethod(1));
AppDomain.Unload(domain);
Console.ReadKey();
//second domain
AppDomain newDomain = AppDomain.CreateDomain("myNewDomain");
AssemblyDll asb2 = new AssemblyDll();
Console.WriteLine(asb2.AssemblyMethod(2));
AppDomain.Unload(newDomain);
Console.ReadKey();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
public class AssemblyDll
{
public string AssemblyMethod(int version)
{
//loading .dll
Assembly assembly = Assembly.LoadFrom(#"../../../../Assembly/DynamicDLL" + version + ".dll");
Type type = assembly.GetType("DynamicDLL.Dynamic");
object instance = Activator.CreateInstance(type);
MethodInfo[] methods = type.GetMethods();
//invoke method
object result = methods[0].Invoke(instance, new object[] { 5, 3 });
return result.ToString();
}
}
My .dll file comes from:
namespace DynamicDLL
{
public class Dynamic
{
public int DynamicMethod(int a, int b)
{
return a + b;
//return a - b;
}
}
}
If you want to write something like plugins and like the plugin approach, you should take a look at MEF http://msdn.microsoft.com/en/library/vstudio/dd460648.aspx
MEF allows you to use any assembly dynamically and even drop dlls into a folder and build a MEF catalog out of it.
Actually Visual Studio and uses MEF internally for extensiblility (Plugins...)
Assemblies are generally loaded into an AppDomain once and you cannot unload them once loaded.
You can create a new AppDomain and load your assemblies into this and when you release this the assemblies will be unloaded. However the caveat here is you cannot directly communicate between two AppDomain you have to marshal between the two using some other method like remoting.
There's been much wrote on this in terms of plugins and making plugins unloadable, a quick Google search presented these:
http://www.brad-smith.info/blog/archives/500
http://adrianvintu.com/blogengine/post/Unloadable-plugins.aspx
Hopefully these will aid you.

C# Autonomous Update

in my new project (windows-C#-vs2008)I want the executable to be able to autonomously update itself- perhaps from a network server
Here What I do is download any updated dlls to "isolated storage" (each as separate dlls) then modify the CurrentDomain_AssemblyResolve() method
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
such that it first checks for the presence of library in its isolated storage before loading it from executable .
But what if I want to change something in the main exe.
[I tried creating a loader project(console ap) from which, it calls my main program(changed output as dll) ,however this time the assembly resolve event is not getting trigger because the reference dlls are geting generated under bin folder of new loader project during compilation ]
Any help would be highly appreciated..
public static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
try
{
string assemName = new AssemblyName(args.Name).Name;
object i_StoreRootDir = i_StorageFile.GetType().GetField
("m_RootDir", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(i_StorageFile);
string i_StorePath = CombinePaths(i_StoreRootDir.ToString(), I_STOREDIR, assemName, DLL_EXT);
if (File.Exists(i_StorePath))
{
return Assembly.LoadFrom(i_StorePath);
}
else
{
//load it from resource.
return null;
}
}
catch (Exception)
{
throw;
}
You may take a look at ClickOnce deployment as part of the framework. You may also checkout wyUpdate. There's also BitsUpdater.

Categories

Resources