My DLLs are loaded by a third-party application, which we can not customize. My assemblies have to be located in their own folder. I can not put them into GAC (my application has a requirement to be deployed using XCOPY). When the root DLL tries to load resource or type from another DLL (in the same folder), the loading fails (FileNotFound). Is it possible to add the folder where my DLLs are located to the assembly search path programmatically (from the root DLL)? I am not allowed to change the configuration files of the application.
8 Answers
Sounds like you could use the AppDomain.AssemblyResolve event and manually load the dependencies from your DLL directory.
Edit (from the comment):
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(LoadFromSameFolder);
static Assembly LoadFromSameFolder(object sender, ResolveEventArgs args)
{
string folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll");
if (!File.Exists(assemblyPath)) return null;
Assembly assembly = Assembly.LoadFrom(assemblyPath);
return assembly;
}
-
4Thank you, Mattias! This works: AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve += new ResolveEventHandler(LoadFromSameFolderResolveEventHandler); static Assembly LoadFromSameFolderResolveEventHandler(object sender, ResolveEventArgs args) { string folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); string assemblyPath = Path.Combine(folderPath, args.Name + ".dll"); Assembly assembly = Assembly.LoadFrom(assemblyPath); return assembly; } – isobretatel Sep 03 '09 at 16:03
-
2What would you do if you wanted to "fallback" to the basic Resolver. e.g. `if (!File.Exists(asmPath)) return searchInGAC(...);` – Tomer W Jan 29 '17 at 22:25
-
This worked and I've not been able to find any alternatives. Thanks – TByte Aug 03 '20 at 16:24
-
1FYI: this works even in .Net 5 :) thanks alot! – AlexK Jul 14 '21 at 15:08
-
1This works great - but please keep in mind that this is per-thread. In my case with *many* threads i needed to put this code into the thread start so that all resolutions would work. – user1318024 Feb 21 '22 at 19:26
-
@TomerW There is no need for that. `AssemblyResolve` will be called only when assembly lookup process has failed. This means, that CLR already searched GAC and all other standard places before `LoadFromSameFolder` has been called. – Ari0nhh Nov 08 '22 at 07:20
You can add a probing path to your application's .config file, but it will only work if the probing path is a contained within your application's base directory.

- 225,310
- 48
- 427
- 736
-
3Thanks for adding this. I've seen the `AssemblyResolve` solution so many times, good to have another (and easier) option. – Samuel Neff Jun 03 '15 at 15:01
-
1Don't forget to move the App.config file with your app if you copy your app somewhere else.. – Maxter Nov 08 '19 at 19:10
-
Update for Framework 4
Since Framework 4 raise the AssemblyResolve event also for resources actually this handler works better. It's based on the concept that localizations are in app subdirectories (one for localization with the name of the culture i.e. C:\MyApp\it for Italian) Inside there are resources file. The handler works also if the localization is country-region i.e. it-IT or pt-BR. In this case the handler "might be called multiple times: once for each culture in the fallback chain" [from MSDN]. This means that if we return null for "it-IT" resource file the framework raises the event asking for "it".
Event hook
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(currentDomain_AssemblyResolve);
Event handler
Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
//This handler is called only when the common language runtime tries to bind to the assembly and fails.
Assembly executingAssembly = Assembly.GetExecutingAssembly();
string applicationDirectory = Path.GetDirectoryName(executingAssembly.Location);
string[] fields = args.Name.Split(',');
string assemblyName = fields[0];
string assemblyCulture;
if (fields.Length < 2)
assemblyCulture = null;
else
assemblyCulture = fields[2].Substring(fields[2].IndexOf('=') + 1);
string assemblyFileName = assemblyName + ".dll";
string assemblyPath;
if (assemblyName.EndsWith(".resources"))
{
// Specific resources are located in app subdirectories
string resourceDirectory = Path.Combine(applicationDirectory, assemblyCulture);
assemblyPath = Path.Combine(resourceDirectory, assemblyFileName);
}
else
{
assemblyPath = Path.Combine(applicationDirectory, assemblyFileName);
}
if (File.Exists(assemblyPath))
{
//Load the assembly from the specified path.
Assembly loadingAssembly = Assembly.LoadFrom(assemblyPath);
//Return the loaded assembly.
return loadingAssembly;
}
else
{
return null;
}
}

- 8,220
- 1
- 27
- 30

- 6,414
- 3
- 28
- 45
-
You can use the `AssemblyName` constructor to decode the assembly name instead of relying on parsing the assembly string. – Sebazzz Jul 17 '19 at 09:56
The best explanation from MS itself:
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);
private Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
//This handler is called only when the common language runtime tries to bind to the assembly and fails.
//Retrieve the list of referenced assemblies in an array of AssemblyName.
Assembly MyAssembly, objExecutingAssembly;
string strTempAssmbPath = "";
objExecutingAssembly = Assembly.GetExecutingAssembly();
AssemblyName[] arrReferencedAssmbNames = objExecutingAssembly.GetReferencedAssemblies();
//Loop through the array of referenced assembly names.
foreach(AssemblyName strAssmbName in arrReferencedAssmbNames)
{
//Check for the assembly names that have raised the "AssemblyResolve" event.
if(strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(",")))
{
//Build the path of the assembly from where it has to be loaded.
strTempAssmbPath = "C:\\Myassemblies\\" + args.Name.Substring(0,args.Name.IndexOf(","))+".dll";
break;
}
}
//Load the assembly from the specified path.
MyAssembly = Assembly.LoadFrom(strTempAssmbPath);
//Return the loaded assembly.
return MyAssembly;
}

- 70,104
- 56
- 326
- 368
-
`AssemblyResolve` is for CurrentDomain, not valid for another domain `AppDomain.CreateDomain` – Kiquenet Dec 01 '15 at 13:24
-
I don't understand this code. args.Name.Substring(0, args.Name.IndexOf(",")) is everywhere, so it would be simplier just to write: strTempAssmbPath = "C:\\Myassemblies\\" + args.Name.Substring(0,args.Name.IndexOf(","))+".dll"; without the foreach cycle? Or do you intend to raise FileNotFoundException in the unlikely case when assembly not between the referenced assemblies? Even in this case a arrReferencedAssmbNames.FirstOrDeault is simplier solution. – Peter Krassoi Feb 08 '22 at 14:58
For C++/CLI users, here is @Mattias S' answer (which works for me):
using namespace System;
using namespace System::IO;
using namespace System::Reflection;
static Assembly ^LoadFromSameFolder(Object ^sender, ResolveEventArgs ^args)
{
String ^folderPath = Path::GetDirectoryName(Assembly::GetExecutingAssembly()->Location);
String ^assemblyPath = Path::Combine(folderPath, (gcnew AssemblyName(args->Name))->Name + ".dll");
if (File::Exists(assemblyPath) == false) return nullptr;
Assembly ^assembly = Assembly::LoadFrom(assemblyPath);
return assembly;
}
// put this somewhere you know it will run (early, when the DLL gets loaded)
System::AppDomain ^currentDomain = AppDomain::CurrentDomain;
currentDomain->AssemblyResolve += gcnew ResolveEventHandler(LoadFromSameFolder);

- 1,081
- 9
- 20
-
This is the only answer that works for me in C++/CLI. When it's C#, it's so bloody simple, just loadFrom where ever you want, but once it became c++/CLI, I've tried at least 5 difference code pieces and read a chapter of a book about cli loading convension. Thank you so much. – Alen Wesker Sep 23 '20 at 08:07
I've used @Mattias S' solution. If you actually want to resolve dependencies from the same folder - you should try using Requesting assembly location, as shown below. args.RequestingAssembly should be checked for nullity.
System.AppDomain.CurrentDomain.AssemblyResolve += (s, args) =>
{
var loadedAssembly = System.AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName == args.Name).FirstOrDefault();
if(loadedAssembly != null)
{
return loadedAssembly;
}
if (args.RequestingAssembly == null) return null;
string folderPath = Path.GetDirectoryName(args.RequestingAssembly.Location);
string rawAssemblyPath = Path.Combine(folderPath, new System.Reflection.AssemblyName(args.Name).Name);
string assemblyPath = rawAssemblyPath + ".dll";
if (!File.Exists(assemblyPath))
{
assemblyPath = rawAssemblyPath + ".exe";
if (!File.Exists(assemblyPath)) return null;
}
var assembly = System.Reflection.Assembly.LoadFrom(assemblyPath);
return assembly;
};

- 1,310
- 14
- 31
I came here from another (marked duplicate) question about adding the probing tag to the App.Config file.
I want to add a sidenote to this - Visual studio had already generated an App.config file, however adding the probing tag to the pregenerated runtime tag did not work! you need a seperate runtime tag with the probing tag included. In short, your App.Config should look like this:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Text.Encoding.CodePages" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<!-- Discover assemblies in /lib -->
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="lib" />
</assemblyBinding>
</runtime>
</configuration>
This took some time to figure out so I am posting it here. Also credits to The PrettyBin NuGet Package. It is a package that moves the dlls automatically. I liked a more manual approach so I did not use it.
Also - here is a post build script that copies all .dll/.xml/.pdb to /Lib. This unclutters the /debug (or /release) folder, what I think people try to achieve.
:: Moves files to a subdirectory, to unclutter the application folder
:: Note that the new subdirectory should be probed so the dlls can be found.
SET path=$(TargetDir)\lib
if not exist "%path%" mkdir "%path%"
del /S /Q "%path%"
move /Y $(TargetDir)*.dll "%path%"
move /Y $(TargetDir)*.xml "%path%"
move /Y $(TargetDir)*.pdb "%path%"

- 6,570
- 2
- 30
- 51
look into AppDomain.AppendPrivatePath (deprecated) or AppDomainSetup.PrivateBinPath

- 641
- 6
- 2
-
11From [MSDN](http://msdn.microsoft.com/en-us/library/system.appdomainsetup.aspx): Changing the properties of an AppDomainSetup instance does not affect any existing AppDomain. It can affect only the creation of a new AppDomain, when the CreateDomain method is called with the AppDomainSetup instance as a parameter. – Nathan Aug 10 '11 at 16:11
-
2[`AppDomain.AppendPrivatePath`](https://msdn.microsoft.com/en-us/library/system.appdomain.appendprivatepath%28v=vs.110%29.aspx)’s documentation seems to suggest that it should support dynamically expanding the `AppDomain`’s search path, just that the feature is deprecated. If it works, it’s a much cleaner solution than overloading `AssemblyResolve`. – binki Apr 08 '15 at 06:01
-
For reference, it looks like `AppDomain.AppendPrivatePath` [does nothing in .NET Core](https://github.com/dotnet/runtime/blob/v5.0.0-preview.6.20305.6/src/libraries/System.Private.CoreLib/src/System/AppDomain.cs#L213) and [updates `.PrivateBinPath` in full framework](https://referencesource.microsoft.com/#mscorlib/system/appdomain.cs,24b326bde12777c0). – Kevinoid Jun 26 '20 at 21:42
-
Yea, so it works in .Net 4.7.2 still pretty well but will cease to work in .Net core. Which makes sense if you remember that the concept of AppDomains is no longer a thing. – Robetto Dec 04 '20 at 13:55