Load Assembly Outside of ApplicationBase

Jason Ge
2 min readNov 19, 2021

Recently, I worked on a legacy Windows Forms C# application that uses lots of reflection to create instances during runtime. All the type information are stored in a type configuration file. I was asked to develop a tool to verify every type configuration to determine if it is correct or not.

The type configuration entry is in the format of key/value pair, like the one in appSettings. The value takes the format as <fullTypeName, assemblyPath>, for example:

<add key="test" value="Full.Namespace.MyType,.\Libs\MyProxy.dll" />

The assemblyPath is relative the application base directory. In order to verify if the type configuration is correct or not, I would have to load each type configuration using reflection.

Originally, I thought to use the <probling> element in app.config file:

<?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">
<probing privatePath="folder1; folder2; folder3 />
</assemblyBinding>
</runtime>
</configuration>

But soon I realized the privatePath inside <probing> element can only be the subdirectories inside the ApplicationBase directory. In my case, the legacy application has lots of dlls scattered around many folders, subfolders outside the tool I need to develop. I certainly don’t want to copy all the dlls from the legacy application to my tool’s bin folder.

After some research, I finally found out the solution is the AssemblyResolve event handler. This event is triggered when the resolution of an assembly fails.

public delegate System.Reflection.Assembly? ResolveEventHandler(object? sender, ResolveEventArgs args);

This event handler takes It is the responsibility of this event to return the assembly that is specified by the ResolveEventArgs.Name property, or to return null if the assembly is not recognized. Inside this event handler, I can then go through all pre-configured assembly probing directories and try to find the assembly specified in the ResolveEventArgs.Name property. If found, I will call Assembly.LoadFrom() to load the it and return the assembly loaded.

Following is the completed code snippet:

private static readonly string[] probingPaths = @"C:\LegacyApp\Lib;C:\LegacyApp\BusinessObject;C:\LegacyApp\Common;C:\LegacyApp\CORE;TC:\LegacyApp\Vendor;C:\LegacyApp\Proxy".Split(';');
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
private static Assembly AssemblyResolve(object sender, ResolveEventArgs args)
{
// check for assemblies already loaded
Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name);
if (assembly != null) return assembly; // Try to load by filename - split out the filename of the full
// assembly name
// (e.g. Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35)
string filename = args.Name.Split(',')[0] + ".dll";
foreach (var path in probingPaths){
string asmFile = Path.Combine(path, filename);
if (File.Exists(asmFile)){
try{
return Assembly.LoadFrom(asmFile);
}
catch (Exception ex) {
Console.WriteLine($"Cannot load assembly from {asmFile}: {ex.Message}.");
return null;
}
}
}
return null;
}

Happy coding!

--

--

Jason Ge

Software developer with over 20 years experience. Recently focus on Vue/Angular and asp.net core.