I'm having trouble getting my log4net.config file to load when using Visual Studio in debug mode for an Excel VSTO Plugin. The config file is in the top level directory of my project. I have the property "Copy to Output Directory" set to "Copy Always". This ensures the file is copied to bin/Debug/log4net.config. I can verify this is the case when I build.
However, the file won't load when I run in Debug mode. I gave up on trying to get the file to load automatically and decided to do it by code, as per the OP's code at the bottom of this question.
However, I realised that I needed to use an absolute path to the config file, as relative paths weren't picking it up. On further investigation, I realised that the executing DLL wasn't actually the DLL in the debug/bin folder. It was in the following location:
C:\Users\cbhandal\AppData\Local\assembly\dl3\MO52QQWP.9ZL\K36XZHGN.1PB\230751e6\d09b7fb2_19f6d401
Also the current working directory, as found by System.IO.Directory.GetCurrentDirectory(); was set to "C:\\Users\\cbhandal\\Documents".
Hard-coding the path as an absolute path works as in the following code:
var log4netConfig = "C:\\" + path + "\\Log4net.config";
var log4netInfo = new FileInfo(log4netConfig);
log4net.Config.XmlConfigurator.ConfigureAndWatch(log4netInfo);
But that's not a solution I can deploy. I'm stuck here. Wondering if there's a way to either force Visual studio to copy the .config file to that appdata/temp location, or if there's a way to programatically reference the folder where the original DLL lay- the one that was built. Or if anyone had any other solution?
For me the easiest solution was to use this:
https://stackoverflow.com/a/6963420/4754981
But there are several other solutions on that link for different approaches, each with their caveats.
So mine looks like this:
using System.Reflection;
using System.IO;
using System;
public static class Extensions {
private static string GetDirectory(this Assembly a) {
string codeBase = a.CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
private static void AlterLogPath(this log4net.Repository.ILoggerRepository repo, string newPath, string directory="") {
log4net.Repository.Hierarchy.Hierarchy h = (log4net.Repository.Hierarchy.Hierarchy) repo;
foreach (log4net.Appender.IAppender a in h.Root.Appenders) {
if (a is log4net.Appender.FileAppender) {
var fa = (log4net.Appender.FileAppender)a;
var fileName = Path.GetFileName(fa.File);
fa.File = newPath + (String.IsNullOrEmpty(directory)?"":(directory + Path.DirectorySeparatorChar.ToString())); // edit: filename is attached after next line automatically.
fa.ActivateOptions();
break;
}
}
}
}
and in the bootup (via [assembly: System.Web.PreApplicationStartMethod] or otherwise for asp), or main app..
static void Main() {
var PATH = Assembly.GetExecutingAssembly().GetDirectory() + Path.DirectorySeparatorChar.ToString();
log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(PATH + "log4net.config"));
log4net.LogManager.GetRepository().AlterLogPath(PATH, "Logs");
}
In a MVC controller I use AssemblyLoadContext.Default.LoadFromAssemblyPath(pathToDll); to load an assembly. I want to delete or replace the given .dll file during runtime. This is not possible because the file is not disposed. Is there any way to dispose the .dll file? There are solutions using the AppDomain class, which is not available in asp.net core.
Background:
The user is able to upload a custom .dll file which contains implementations of a given interface. The user should also be able to replace his file. I use the following code in a controller to access the implementations:
var conventions = new ConventionBuilder();
conventions
.ForTypesDerivedFrom<IPluginContract>()
.Export<IPluginContract>()
.Shared();
var configuration = new ContainerConfiguration().WithAssembliesInPath(path, conventions);
using (var container = configuration.CreateContainer())
{
var plugins = container.GetExports<IPluginContract>();
return plugins;
}
With
public static ContainerConfiguration WithAssembliesInPath(
this ContainerConfiguration configuration,
string path, AttributedModelProvider conventions,
SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
var fileNames = Directory
.GetFiles(path, "*.dll", searchOption);
List<Assembly> assemblies = new List<Assembly>();
foreach (string relativePath in fileNames)
{
Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(relativePath));
assemblies.Add(assembly);
}
configuration = configuration.WithAssemblies(assemblies, conventions);
return configuration;
}
OPTION 1:
Try loading dll with method LoadFromStream, then you can remove dll without exceptions.
Ex:
foreach (string relativePath in fileNames)
{
using (var fs = File.Open(relativePath , FileMode.Open))
{
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(fs);
assemblies.Add(assembly);
}
File.Delete(relativePath); //It doesn't throw exception
}
NOTE: tested with Net Core 3.1 but could work with previous versions.
OPTION 2:
If you have a problem when try to reload assemblies with LoadFromStream you should try to call AssemblyLoadContext.Default.Unload() before to LoadFromStream()
But I'm not sure if it works with AssemblyLoadContext.Default, so if you still keep any exception you should create any class that inherit from AssemblyLoadContext with flag isCollectible to true like this:
public class PluginLoadContext : AssemblyLoadContext
{
public PluginLoadContext() : base(isCollectible: true)
{
}
}
And the code should be:
//var pluginContext = new PluginLoadContext(); //In some place to call unload later
pluginContext.Unload();
foreach (string relativePath in fileNames)
{
using (var fs = File.Open(relativePath , FileMode.Open))
{
Assembly assembly = pluginContext.LoadFromStream(fs);
assemblies.Add(assembly);
}
File.Delete(relativePath); //It doesn't throw exception
}
OPTION 3:
There is another option that override Load method of your custom PluginLoadContext, you only need to load your entry dll, and the reference dll is knew with deps.json file of your entry dll.
In this example is using MemoryStream to prevent attach plugin dll.
public class PluginLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver;
public PluginLoadContext(string pluginPath) : base(isCollectible: true)//isCollectible doesn't appear in netstandard2.1
{
_resolver = new AssemblyDependencyResolver(pluginPath);
}
protected override Assembly Load(AssemblyName assemblyName)
{
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
//Using MemoryStream to prevent attach dll to this .exe
MemoryStream ms = new MemoryStream();
using (var fs = File.Open(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
fs.CopyTo(ms);
}
ms.Position = 0;
return LoadFromStream(ms);
}
return null;
}
}
Then you can load your entry plugin dll like this.
var dllPath = "<path to your entry dll>" // dll and deps.json file together .
var pc = new PluginLoadContext(dllPath);
var assembly = pc.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(dllPath)));
//You can load a reference dll too if you need it
var referenceAssembly = pc.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension("<path of reference dll>")));
REF:
https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support#load-plugins
When you load a dll into your application domain, this dll is not free before the appDomain is being destroyed (i.e. your process is stopped) there is no dispose for a dll.
For references on how to reach your desired functionality please have a look at these questions that are answered already:
Using AppDomain to dynamically load and unload dll
Hot unload and reload of a dll used by an application
It sounds very similar to MEF ( Managed Extensibility Framework ). It allows inject DLL's and also helps to manage the lifecycle.
Example:
public static class MefInjection
{
private static CompositionContainer mycontainer;
public static CompositionContainer MyContainer
{
get
{
if (mycontainer == null)
{
var catalog =
new DirectoryCatalog(".", "MyMEFProject.*");
mycontainer = new CompositionContainer(catalog);
}
return mycontainer;
}
}
}
The preceding code will grab all the exported values from all the assemblies in the same directory starting with "MyMEFProject". Then you can use mycontainer to get loaded DLL's functionality.
I have a .exe file as an embedded resource named IDMan.exe in my c# project. I want it to be copied in D:\ directory. i have the following code
public static void extractResource(String embeddedFileName, String destinationPath)
{
Assembly currentAssembly = Assembly.GetExecutingAssembly();
string[] arrResources = currentAssembly.GetManifestResourceNames();
foreach (string resourceName in arrResources)
if (resourceName.ToUpper().EndsWith(embeddedFileName.ToUpper()))
{
Stream resourceToSave = currentAssembly.GetManifestResourceStream(resourceName);
var output = File.OpenWrite(destinationPath);
resourceToSave.CopyTo(output);
resourceToSave.Close();
}
}
But whatever directory I choose it says
Access to path 'D:\' is Denied.
I have a string containing a full qualified name like MyNamespace.MyType which I know is in a loaded assembly.
I need to get the Type instance of this, in a windows store app.
The issue I'm having is that while there is some reflection classes in windows store apps, it's very limited, and I simply can't use what I've been using for a desktop app.
If I can get an assembly I can find my type within it so I'm currently trying to get all loaded assemblies, which I can't do easily as AppDomain doesn't exist.
I found the following:
private async System.Threading.Tasks.Task<IEnumerable<Assembly>> GetAssembliesCore()
{
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
List<Assembly> assemblies = new List<Assembly>();
foreach (Windows.Storage.StorageFile file in
await folder.GetFilesAsync().AsTask().ConfigureAwait(false))
{
if (file.FileType == ".dll")
{
AssemblyName name = new AssemblyName() { Name = file.Name };
Assembly asm = Assembly.Load(name);
assemblies.Add(asm);
}
}
return assemblies;
}
I added the .AsTask().ConfigureAwait(false) to stop it hanging, but it now fails trying to load the assembly:
"Could not load file or assembly 'FDE.Audio.dll' or one of its dependencies.
The system cannot find the file specified.":"FDE.Audio.dll"
Is there something I need to set up in the manifest? Something else?
How can I load an assembly in my program's folder (AppX I think)?
Try to extract file name without extension
var filename = file.Name.Substring(0, file.Name.Length - file.FileType.Length);
AssemblyName name = new AssemblyName() { Name = filename };
Assembly asm = Assembly.Load(name);
I'm working on a C# program that uses iTextSharp.dll and WebCam_Capture.dll. When I build the program, it creates executable in the debug folder and it also copies these two dll's to the debug folder as expected. I want to merge them into a single executable, however I failed. These two libraries are visible in the references normally in the solution explorer. I also add them as resources. Executable size got bigger which equals the sum of three files, nevertheless the executable still requires these libraries in its directory... I played with "build action" property of the resource files but no change. I also tried ILmerge but it gave me an error. so what should I do?
Update: This is what I get from ILmerge:
An exception occurred during merging:
Unresolved assembly reference not allowed: System.Core.
at System.Compiler.Ir2md.GetAssemblyRefIndex(AssemblyNode assembly)
at System.Compiler.Ir2md.GetTypeRefIndex(TypeNode type)
It is just a windows application by the way, a form to be filled and printed as pdf with a photo taken via webcam if available. Thanks all!
You can use ILMerge to merge multiple assemblies together. You've already said you did this, and you've received an error. Though I don't know why, you can use an alternative: if the libraries are open source (and their licenses are compatible with yours), you can download the source code, add it to your project and compile. This will result in a single assembly.
The ILMerge page also lists Jeffrey Richter's blog as yet another alternative to solve your issue:
Many applications consist of an EXE file that depends on many DLL
files. When deploying this application, all the files must be
deployed. However, there is a technique that you can use to deploy
just a single EXE file. First, identify all the DLL files that your
EXE file depends on that do not ship as part of the Microsoft .NET
Framework itself. Then add these DLLs to your Visual Studio project.
For each DLL file you add, display its properties and change its
“Build Action” to “Embedded Resource.” This causes the C# compiler to
embed the DLL file(s) into your EXE file, and you can deploy this one
EXE file.
At runtime, the CLR won’t be able to find the dependent DLL
assemblies, which is a problem. To fix this, when your application
initializes, register a callback method with the AppDomain’s
ResolveAssembly event. The code should look something like this:
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {
String resourceName = "AssemblyLoadingAndReflection." +
new AssemblyName(args.Name).Name + ".dll";
using (var stream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream(resourceName)) {
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
};
Now, the first time a thread calls a method that references a type in
a dependent DLL file, the AssemblyResolve event will be raised and the
callback code shown above will find the embedded DLL resource desired
and load it by calling an overload of Assembly’s Load method that
takes a Byte[] as an argument.
Add the DLL files to your Visual Studio project.
For each file go to "Properties" and set its Build Action to "Embedded Resource"
On your code retrive the resource using the GetManifestResourceStream("DLL_Name_Here") this returns a stream that can be loadable.
Write an "AssemblyResolve" event handler to load it.
Here is the code:
using System;
using System.Collections.Generic;
using System.Reflection;
using System.IO;
namespace WindowsForm
{
public partial class Form1 : Form
{
Dictionary<string, Assembly> _libs = new Dictionary<string, Assembly>();
public Form1()
{
InitializeComponent();
AppDomain.CurrentDomain.AssemblyResolve += FindDLL;
}
private Assembly FindDLL(object sender, ResolveEventArgs args)
{
string keyName = new AssemblyName(args.Name).Name;
// If DLL is loaded then don't load it again just return
if (_libs.ContainsKey(keyName)) return _libs[keyName];
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("YourNamespaceGoesHere." + keyName + ".dll")) // <-- To find out the Namespace name go to Your Project >> Properties >> Application >> Default namespace
{
byte[] buffer = new BinaryReader(stream).ReadBytes((int)stream.Length);
Assembly assembly = Assembly.Load(buffer);
_libs[keyName] = assembly;
return assembly;
}
}
//
// Your Methods here
//
}
}
Hope it helps,
Pablo
I modified Pablo's code a little bit and it worked for me.
It was not getting the DLL's resource name correctly.
IDictionary<string, Assembly> _libs = new Dictionary<string, Assembly>();
public Form1()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
InitializeComponent();
}
// dll handler
System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string keyName = new AssemblyName(args.Name).Name;
// If DLL is loaded then don't load it again just return
if (_libs.ContainsKey(keyName)) return _libs[keyName];
using (Stream stream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream(GetDllResourceName("itextsharp.dll"))) // <-- To find out the Namespace name go to Your Project >> Properties >> Application >> Default namespace
{
byte[] buffer = new BinaryReader(stream).ReadBytes((int)stream.Length);
Assembly assembly = Assembly.Load(buffer);
_libs[keyName] = assembly;
return assembly;
}
}
private string GetDllResourceName(string dllName)
{
string resourceName = string.Empty;
foreach (string name in Assembly.GetExecutingAssembly().GetManifestResourceNames())
{
if (name.EndsWith(dllName))
{
resourceName = name;
break;
}
}
return resourceName;
}
The answer you are looking for:
// To embed a dll in a compiled exe:
// 1 - Change the properties of the dll in References so that Copy Local=false
// 2 - Add the dll file to the project as an additional file not just a reference
// 3 - Change the properties of the file so that Build Action=Embedded Resource
// 4 - Paste this code before Application.Run in the main exe
AppDomain.CurrentDomain.AssemblyResolve += (Object sender, ResolveEventArgs args) =>
{
String thisExe = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
System.Reflection.AssemblyName embeddedAssembly = new System.Reflection.AssemblyName(args.Name);
String resourceName = thisExe + "." + embeddedAssembly.Name + ".dll";
using (var stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
{
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return System.Reflection.Assembly.Load(assemblyData);
}
};
Check out the AssemblyResolve event on the app domain.
I don't have a sample but you basically check what is asked for and stream back the resource DLL. I believe LinqPAD does this well - you could have a look at Joseph Albahari's implementation with a decompiler etc.
Add this anonymous function code on the top of our application constructor. This will add dll from embedded resource in same project.
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
string resourceName = new AssemblyName(args.Name).Name + ".dll";
string resource = Array.Find(this.GetType().Assembly.GetManifestResourceNames(), element => element.EndsWith(resourceName));
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource))
{
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
};
I know that topic is old but i'll write it for future persons that will want to use it.
i base on code by userSteve.
i would suggest to change this.
String thisExe = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
into this
String thisExe = System.Reflection.Assembly.GetExecutingAssembly().EntryPoint.DeclaringType.Namespace;
that way it would work even if namespace is different than assembly name
also if you want to use DLL from directory you can use it like that (directory Resources as Example)
String resourceName = thisExe + ".Resources." + embeddedAssembly.Name + ".dll";
if you still can't find where place this code in C# Form application paste it inside file "Program.cs" above line:
Application.Run(new Form_1());
and below lines:
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
You didn't reference using WPF, but if you are, this could be the cause of your error. If not, ILMerge should work fine for you. If you are using WPF, here is a solution that works well:
http://blogs.interknowlogy.com/2011/07/13/merging-a-wpf-application-into-a-single-exe/