Can not load managed assembly that is located in the same folder - c#

To recreate my production environment I created the following folder structure:
c:\TEST\tested.dll
c:\TEST\tested\tools.dll
The tested.dll is compiled using the following App.config file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="tested"/>
</assemblyBinding>
</runtime>
</configuration>
As far as I know the application should look for it's tools file in the subfolder. When I try to start the station I still get the error that the file was not found.
To give some context here is an example tested.dll source:
namespace ConsoleApplication1
{
public static class Testable
{
public static tools.IToolAble usefultool = null;
public static void initialisation()
{
if (usefultool == null) usefultool = new UsefulTest()
}
}
public class UsefulTest : tools.IToolAble
{
}
}
and an example tools.dll source:
namespace tools
{
public interface IToolAble
{
}
}
The code that crashes is my testcode that works like this:
private CustomMock controller = new CustomMock();
public void TestFixtureSetUp()
{
controller.LoadFrom(#"c:\TEST\tested.dll");
//The next line crashes because tools assembly is needet but not found
controller.InvokeInitialisation();
}
What am I missing?
Is the App.config correct?
EDIT:
The Answer below is correct, the path is only known once the correct dll can be chosen. So the other team has to add a new ResolveEventHandler before loading. Here is a simplified version of that:
internal void AddResolveEventHandler(string assemblyname, string assemblylocation)
{
AppDomain.CurrentDomain.AssemblyResolve +=
new ResolveEventHandler(
(sender, args) =>
{
Assembly ret = null;
if (
new AssemblyName(args.Name).Name == assemblyname &&
File.Exists(assemblylocation))
{
ret = Assembly.LoadFrom(assemblylocation);
}
return ret;
}
);
}

the tested.dll is compiled using the following App.config file
It needs to be an yourapp.exe.config file, not a .config file for the DLL. The CLR only ever looks for a .config file associated with the main process.
And watch out for app.vshost.exe.config, required when you debug with the hosting process enabled.
And watch out when using unit test runners, another .exe file
Do consider if this is actually worth the trouble. Your user won't care where the DLL is located.

Related

VS2022 [C# , .NET 6] reference third party .dll with path, instead of including in build [duplicate]

I am trying to modify 2 .exes to load DevExpress dlls from 1 location.
The .exes in the "Products" folder are use the same .dlls as the launcher does. I want to avoid having to put the same .dlls into the Products directory, and instead have the .exes read from 1 directory back(the launchers directory).
How can I achieve this?
You can handle the AppDomain.AssemblyResolve event and load the assemblies from the directory yourself using Assembly.LoadFile giving the fullpath to the assembly it is trying to resolve.
Example:
.
.
.
// elsewhere at app startup time attach the handler to the AppDomain.AssemblyResolve event
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
.
.
.
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
AssemblyName assemblyName = new AssemblyName(args.Name);
// this.ReadOnlyPaths is a List<string> of paths to search.
foreach (string path in this.ReadOnlyPaths)
{
// If specified assembly is located in the path, use it.
DirectoryInfo directoryInfo = new DirectoryInfo(path);
foreach (FileInfo fileInfo in directoryInfo.GetFiles())
{
string fileNameWithoutExt = fileInfo.Name.Replace(fileInfo.Extension, "");
if (assemblyName.Name.ToUpperInvariant() == fileNameWithoutExt.ToUpperInvariant())
{
return Assembly.Load(AssemblyName.GetAssemblyName(fileInfo.FullName));
}
}
}
return null;
}
You may set folder(s) path in assemblyBinding>probing::privatePath tag in app.config for the common language runtime to search when loading assemblies.
like this code
Reference MSDN
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="libs" />
</assemblyBinding>
</runtime>
</configuration>
You can create a class:
using System;
using System.Reflection;
namespace myNamespace
{
public sealed class EntryPoint
{
[STAThread]
public static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
var app = new App();
app.InitializeComponent();
app.Run();
}
private static Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
{
// do whatever necessary...
}
}
}
As WPF already has a built-in Main() method, you will get a compiler error. So go to project properties > Application > "Startup object" and set it to myNamespace.EntryPoint. In your own Main() method, you have full control over everything, so you can set the AssemblyResolve handler before instantiating the App.

Assembly.Load and its weirdness... "Could not find file or assembly" error

Our localization team was trying to use LocBaml (.NET Framework version, 4.6.1) to localize some resources. They kept on getting errors saying "Could not find file or assembly..." So, I looked into it, saw the note that the x.resources.dll file had to be in same directory as x.dll. ("x" just means some name). Tried that, still go the same error. I then built a debug version and also downloaded the .NET source code. Turns out some exception was occuring in the guts of .NET. If I could summarize, it was failing when trying to do Assembly.Load("x"). So I wrote a program trying to duplicate the situation...
using System;
using System.Reflection;
using System.Linq;
namespace Nothing
{
class Loader
{
public static void Main(string[] args)
{
try
{
if (args.Count() == 1)
{
Assembly asm = Assembly.Load(args[0]);
Console.WriteLine($"Name of asembly: {asm.FullName}");
}
else
{
Console.WriteLine("Need to specify filename");
}
}
catch (Exception x)
{
Console.WriteLine("Exception: {0}", x);
}
}
}
}
The file was named AsmLoader.cs and compiled to AsmLoader.exe.
Well from the directory x.dll was in, I typed in \path\to\AsmLoader.exe x.dll and \path\to\AsmLoader.exe x. Same error, "Could not find file or assembly..."
Looked at the stack trace for the exception and saw that "codebase" was an argument for some function on the stack. Gave it a thought, and copied AsmLoader.exe to the same directory as x.dll.
Gave .\AsmLoader.exe x.dll a try..still same error. Remembered that the argument to the exception was just "x". Tried .\AsmLoader.exe x .... bingo... worked. For grins, copied LocBaml.exe and it's .config file to the same directory, and tried .\LocBaml x.resources.dll ... ding, ding, ding... success. Finally.
So, for now, I'll just tell the localiztion team to copy LocBaml to the same directory as the files and all should be good.
However, I can't help but feel this could somehow be solved with code. How can I make changes to the code in the example so that AsmLoader.exe doesn't have to be in the same directory as the DLL I want to load? I had even changed my path environment variable to ensure AsmLoader.exe and both x.dll directories were in the path. That didn't work...
So what do I need to change for it to work in my base program...and then maybe I can do the same for LocBaml...???
Well, I came up with a solution to add an AssemblyResolve event handler to the current app domain. Solved it for this simple example and my rebuilt LocBaml...
In main, add:
AppDomain.CurrentDomain.AssemblyResolve += LoadFromCurrentDirectory;
Implement LoadFromCurrentDirectly like:
static Assembly LoadFromCurrentDirectory(object sender, ResolveEventArgs args)
{
string name = args.Name;
bool bCheckVersion = false;
int idx = name.IndexOf(',');
if (idx != -1)
{
name = name.Substring(0, idx);
bCheckVersion = true;
}
string sCurrentDir = Directory.GetCurrentDirectory();
if (!name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) && !name.EndsWith(".exe"))
{
string[] exts = { ".dll", ".exe" };
foreach( string ext in exts)
{
string tryPath = Path.Combine(sCurrentDir, name + ext);
if (File.Exists(tryPath))
{
name = name += ext;
break;
}
}
}
string path = Path.Combine(sCurrentDir, name);
if (!string.IsNullOrEmpty(path) && File.Exists(path))
{
Assembly assembly = Assembly.LoadFrom(path);
if (assembly != null & bCheckVersion)
{
if (assembly.FullName != args.Name)
return null;
}
return assembly;
}
else
{
var reqAsm = args.RequestingAssembly;
if (reqAsm != null)
{
string requestingName = reqAsm.GetName().FullName;
Console.WriteLine($"Could not resolve {name}, {path}, requested by {requestingName}");
}
else
{
Console.WriteLine($"Could not resolve {args.Name}, {path}");
}
}
return null;
}
I'm sure it could be optimized to add a global list of directories, or to use the path when searching for files to load. However, for our use case, works just fine. When I added it to our LocBaml code, it solved the loading problem for us...also got rid of necessity of copying the en\x.resources.dll files to our output directory. As long as we run the program from the output directory, LocBaml finishes parsing.

c# embed config file within exe

I have a console program 'A' that at a given point will run program 'B' and program 'C'. However I'm having an issue with the app.config associate with each of the program. Basically program A is just a wrapper class that calls different console application, It should not have any app.config but it should use the current running program's app config. So in theory there should be only 2 app.config one for Program B and another for program C.
So if we run Program A and program B gets executed, it should use program B's app.config to get the information and after when program C gets executed it should use Program C's app.config.
Is there a way to do this? Currently i'm doing this:
var value = ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).AppSettings.Settings["ProgramBKey"].Value;
It does not seem to work. I checked the debug on Assembly.GetExecutingAssembly().Location it's variable is the \bin\Debug\ProgramB.exe and 'ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).AppSettings' has setting with Count=0 when there are key values as seen below.
sample code Program A:
static void Main(string[] args)
{
if(caseB)
B.Program.Main(args)
else if(caseC)
C.Program.Main(args)
}
sample app.config for Program B:
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="ProgramBKey" value="Works" />
</appSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
</startup>
</configuration>
Edit: the following answer pertains to this question from the original post, "Is it possible to compile the app.config for B and C within the exe of the program."
You can use the "Embedded Resource" feature. Here's a small example of using an XML file that's been included as an embedded resource:
public static class Config
{
static Config()
{
var doc = new XmlDocument();
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Fully.Qualified.Name.Config.xml"))
{
if (stream == null)
{
throw new EndOfStreamException("Failed to read Fully.Qualified.Name.Config.xml from the assembly's embedded resources.");
}
using (var reader = new StreamReader(stream))
{
doc.LoadXml(reader.ReadToEnd());
}
}
XmlElement aValue = null;
XmlElement anotherValue = null;
var config = doc["config"];
if (config != null)
{
aValue = config["a-value"];
anotherValue = config["another-value"];
}
if (aValue == null || anotheValue == null)
{
throw new XmlException("Failed to parse Config.xml XmlDocument.");
}
AValueProperty = aValue.InnerText;
AnotherValueProperty = anotherValue.InnerText;
}
}
You can have multiple application using the same config file. That way when you switch applications, they can both find their own parts of the config file.
The way I usually do it is... first let each application "do its own thing", then copy the relevant sections of config file A into config file B.
It will look like this:
<configSections>
<sectionGroup>
<sectionGroup name="applicationSettings"...A>
<sectionGroup name="userSettings"...A>
<sectionGroup name="applicationSettings"...B>
<sectionGroup name="userSettings"...B>
<applicationSettings>
<A.Properties.Settings>
<B.Properties.Settings>
<userSettings>
<A.Properties.Settings>
<B.Properties.Settings>
For me, the whole thing sounds like a "design issue". Why should you want to open Programm B with the config of Programm A?
Are you the author of all those Programms? You might want to use a dll-file instead. This will save you the trouble as all code runs with the config of the Programm running.
Here how you can do it:
Make App.config as "Embedded Resource" in the properties/build action
Copy to output Directory : Do not copy
Add this code to Proram.cs Main
if (!File.Exists(Application.ExecutablePath + ".config"))
{
File.WriteAllBytes(Application.ExecutablePath + ".config", ResourceReadAllBytes("App.config"));
Process.Start(Application.ExecutablePath);
return;
}
Here are the needed functions:
public static Stream GetResourceStream(string resName)
{
var currentAssembly = Assembly.GetExecutingAssembly();
return currentAssembly.GetManifestResourceStream(currentAssembly.GetName().Name + "." + resName);
}
public static byte[] ResourceReadAllBytes(string resourceName)
{
var file = GetResourceStream(resourceName);
byte[] all;
using (var reader = new BinaryReader(file))
{
all = reader.ReadBytes((int)file.Length);
}
file.Dispose();
return all;
}

AppDomain.CurrentDomain.BaseDirectory does not return same folder for UnitTesting project [duplicate]

I have a web project like:
namespace Web
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
lbResult.Text = PathTest.GetBasePath();
}
}
}
The method PathTest.GetBasePath() is defined in another Project like:
namespace TestProject
{
public class PathTest
{
public static string GetBasePath()
{
return AppDomain.CurrentDomain.BaseDirectory;
}
}
}
Why it's display ...\Web\ while the TestProject assembly is compiled into bin folder(in other words it should display ...\Web\bin in my thought).
Now I got a troublesome if I modified method into:
namespace TestProject
{
public class FileReader
{
private const string m_filePath = #"\File.config";
public static string Read()
{
FileStream fs = null;
fs = new FileStream(AppDomain.CurrentDomain.BaseDirectory + m_filePath,FileMode.Open, FileAccess.Read);
StreamReader reader = new StreamReader(fs);
return reader.ReadToEnd();
}
}
}
The File.config is created in TestProject. Now AppDomain.CurrentDomain.BaseDirectory + m_filePath will returen ..\Web\File.config (actually the file was be copied into ..\Web\bin\File.config), an exception will be thrown.
You could say that I should modified m_filePath to #"\bin\File.config". However If I use this method in a Console app in your suggest, AppDomain.CurrentDomain.BaseDirectory + m_filePath will return ..\Console\bin\Debug\bin\File.config (actually the file was copyed into .\Console\bin\Debug\File.config), an exception will be thrown due to surplus bin.
In other words, in web app, AppDomain.CurrentDomain.BaseDirectory is a different path where file be copyed into (lack of /bin), but in console app it's the same one path.
Any one can help me?
Per MSDN, an App Domain "Represents an application domain, which is an isolated environment where applications execute." When you think about an ASP.Net application the root where the app resides is not the bin folder. It is totally possible, and in some cases reasonable, to have no files in your bin folder, and possibly no bin folder at all. Since AppDomain.CurrentDomain refers to the same object regardless of whether you call the code from code behind or from a dll in the bin folder you will end up with the root path to the web site.
When I've written code designed to run under both asp.net and windows apps usually I create a property that looks something like this:
public static string GetBasePath()
{
if(System.Web.HttpContext.Current == null) return AppDomain.CurrentDomain.BaseDirectory;
else return Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"bin");
}
Another (untested) option would be to use:
public static string GetBasePath()
{
return System.Reflection.Assembly.GetExecutingAssembly().Location;
}
In case you want a solution that works for WinForms and Web Apps:
public string ApplicationPath
{
get
{
if (String.IsNullOrEmpty(AppDomain.CurrentDomain.RelativeSearchPath))
{
//exe folder for WinForms, Consoles, Windows Services
return AppDomain.CurrentDomain.BaseDirectory;
}
else
{
//bin folder for Web Apps
return AppDomain.CurrentDomain.RelativeSearchPath;
}
}
}
The above code snippet is for binaries locations.
The AppDomain.CurrentDomain.BaseDirectory is still a valid path for Web Apps, it's just the root folder where the web.config and Global.asax are, and is same as Server.MapPath(#"~\");
If you use AppDomain.CurrentDomain.SetupInformation.PrivateBinPath instead of BaseDirectory, then you should get the correct path.
When ASP.net builds your site it outputs build assemblies in its special place for them. So getting path in that way is strange.
For asp.net hosted applications you can use:
string path = HttpContext.Current.Server.MapPath("~/App_Data/somedata.xml");

How can I load two versions of same assemblies into two different domains from two different subfolders?

I'm trying to build a small tool for comparing types in a bunch of assemblies. For this purpose I created two subfolders and put the respective dlls there:
..\Dlls\v1.1
..\Dlls\v1.2
where .. is the application folder
I also created a proxy object:
public class ProxyDomain : MarshalByRefObject
{
public Assembly LoadFile(string assemblyPath)
{
try
{
//Debug.WriteLine("CurrentDomain = " + AppDomain.CurrentDomain.FriendlyName);
return Assembly.LoadFile(assemblyPath);
}
catch (FileNotFoundException)
{
return null;
}
}
}
and used it for loading in the following routine that should load an dll and get all types declared in it:
private static HashSet<Type> LoadAssemblies(string version)
{
_currentVersion = version;
var path = Path.Combine(Environment.CurrentDirectory, Path.Combine(_assemblyDirectory, version));
var appDomainSetup = new AppDomainSetup
{
ApplicationBase = Environment.CurrentDirectory,
};
var evidence = AppDomain.CurrentDomain.Evidence;
var appDomain = AppDomain.CreateDomain("AppDomain" + version, evidence, appDomainSetup);
var proxyDomainType = typeof(ProxyDomain);
var proxyDomain = (ProxyDomain)appDomain.CreateInstanceAndUnwrap(proxyDomainType.Assembly.FullName, proxyDomainType.FullName);
_currentProxyDomain = proxyDomain;
var assemblies = new HashSet<Type>();
var files = Directory.GetFiles(path, "*.dll");
foreach (var file in files)
{
try
{
var assembly = proxyDomain.LoadFile(file);
if (assembly != null)
{
assemblies.UnionWith(assembly.DefinedTypes.Where(t => t.IsPublic));
}
}
catch (Exception)
{
}
}
return assemblies;
}
So far nothing unusual... but it didn't work like that in this case (probably because of the subfolders) so I searched a bit and found that a settting in the app.config might help so I tried to add two probing paths:
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Dlls\v1.1;Dlls\v1.2" />
</assemblyBinding>
</runtime>
Now there wasn't any FileNotFoundExpections anymore but as the dlls have the same names it loaded only dlls from the first subdirectory (v1.1) into both domains so I removed it and instead tried to implement the AppDomain.CurrentDomain.AssemblyResolve event handler like that:
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) =>
{
var path = Path.Combine(Environment.CurrentDirectory, Path.Combine(_assemblyDirectory, _currentVersion));
path = Path.Combine(path, e.Name.Split(',').First());
path = path + ".dll";
var assembly = _currentProxyDomain.LoadFile(path);
return assembly;
};
But unfortunatelly with this event handler I created an infinite loop and it calls itself each time it tries to load a dll it cannot find. I have no more ideas what else I could try.
UPDATE-1
As #SimonMourier suggested I tried to use a custom .config for my new AppDomains and created two more *.configs like:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Dlls\v1.1" />
</assemblyBinding>
</runtime>
</configuration>
I named them v1.1.config and v1.2.config. Then I set the new ConfigurationFile property:
var appDomainSetup = new AppDomainSetup
{
ApplicationBase = Environment.CurrentDirectory,
ConfigurationFile = Path.Combine(Environment.CurrentDirectory, string.Format("{0}.config", version)),
};
I've set the option Copy to Output Directory to Copy always ;-)
It didn't work so I googled and tried another suggestion:
AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", appDomainSetup.ConfigurationFile);
but this didn't help either. Still FileNotFoundException like my custom configs weren't there.
Using the SetConfigurationBytes method instead had also no effect:
var domainConfig = #"
<configuration>
<startup>
<supportedRuntime version=""v4.0"" sku="".NETFramework,Version=v4.5"" />
</startup>
<runtime>
<assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
<probing privatePath=""Dlls\{0}"" />
</assemblyBinding>
</runtime>
</configuration>";
domainConfig = string.Format(domainConfig, version).Trim();
var probingBytes = Encoding.UTF8.GetBytes(domainConfig);
appDomainSetup.SetConfigurationBytes(probingBytes);
However if I call the GetData method the new appdomain uses the custom .config:
Debug.WriteLine("Current config: " + AppDomain.CurrentDomain.GetData("APP_CONFIG_FILE"));
It outputs the path that I set via ConfigurationFile
UPDATE-2
This is really confusing. The Stack Trace reveals that despite of what the GetData returs the Assembly.LoadFile still uses the original .config:
=== LOG: This bind starts in default load context. LOG: Using application configuration file:
C:[...]\bin\Debug\MyApp.exe.Config
LOG: Using host configuration file: LOG: Using machine configuration file from
C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
UPDATE-3
OK, I did some more experimenting and found out that by implementing #SimonMourier suggestion it indeed worked. The FileNotFoundException wasn't thrown by the LoadFile method in the ProxyDomain class but in the Main method of my app. I gues the assembly and the types are allowed to live only within the ProxyDomain context and cannot be transfered into the main domain as I have tried.
public IEnumerable<Type> LoadFile(string assemblyPath)
{
//try
{
// does't throw any exceptions
var assembly = Assembly.LoadFile(assemblyPath);
// returning the assembly itself or its types will throw an exception in the main application
return assembly.DefinedTypes;
}
//catch (FileNotFoundException)
{
// return null;
}
}
Method in the main domain:
private static HashSet<Type> LoadAssemblies(string version)
{
// omitted
foreach (var file in files)
{
//try
{
// the exception occurs here, when transfering types between domains:
var types = proxyDomain.LoadFile(file);
assemblies.UnionWith(types.Where(t => t.IsPublic));
}
}
// omitted
}
I'll need to rewrite the comparison algorithm now by at least the assemblies can be loaded ;-)
If you don't want to recompile the assemblies with a different strong name, than you can load them into different AppDomains, with different setup configuration, and different .config files, exactly like IIS does with multiple web sites, each one in its one AppDomain, completely independent, all hosted in only one AppPool process - w3wp.exe.
Strong name your dll .
Make 2 exe to run separate app domain.
Otherwise there is no way to reference same dll name twice.
You have to rename new dll with _new.dll and then you can use it with fully qualified names pace class method name format.
Meaning rename all v2 dll

Categories

Resources