In a large .Net project, it can be inevitable to have complex dependencies. To make it worse, multiple dependencies may have the dependency on the same assembly but different versions. There is already a way to redirect to bind a different version of assembly in your app. This document outlines how to do it to an application. Sometimes, that's not enough.
The document outlines these approaches
- The vendor of assemblies includes a publisher policy file with the new assembly.
- Specify the binding in the configuration file at the application level
- Specify the binding in the configuration file at the machine level.
The first approach requires the vendor to publish the publisher policy file. The file has to be in global assembly cache which will affect every application on the machine.
What if the vendor doesn't provide this file. Then we can specify the binding in the configuration file by using <bindingRedirect>. The configuration file can be applied to the specified application if it's at the application level or every application if it's at the machine leve.
What if there is not a publisher policy file, or there is no configuration file for the binding at the application level or the machine level? Is it possible to have it happening when you're writing your own application. Probably not. This issue probably happens when you write a plugin or some assembly that're run in a different aplication that you don't own. For example, you're writing a test that's run by vstest. You use a libary A which has a dependency on assembly B version 1.0, and you also use a library C which has a dependency on assembly B version 2.0. At runtime, one version of the assembly B will not be loaded. You don't own the assembly B, and you don't own the application that runs your assembly. Because of that, you cannot count on the publisher policy file or the application-level configuration file. You don't want to create a machine-level configuration file either. There is no assembly level configuration file. The assembly level configuration file is ignored at runtime. I think the best bet of solving it is to load the dependency in the program by yourself. When the runtime doesn't find the right assembly, it raises the event AppDomain.AssemblyResolve.
How do we use AppDomain.AssemblyResolve? The basic idea is:
- Check whether the assembly is loaded.
- If it's loaded, and if the loaded version satisfies your requirements, then return the loaded one.
- If the assembly isn't loaded, and you find one that satisfied your requirements, you can call Assembly.LoadFile to load the assembly and return it.
In a pseudo code, it is
static Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.Name.Contains("AssemblyB"))
{
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.FullName.Contains("AssemblyB"))
{
return assembly;
}
}
return Assembly.LoadFile("PathToAssemblyB");
}
return null;
}
There are, however, some caveats. First, it's at the app domain level, meaning it may impact every assembly in the same app domain. AppDomain.AssemblyResolve passes an event parameter ResolveEventArgs. It has a property ResolveEventArgs.RequestingAssembly to indicate which assembly is requesting to load the one that cannot be resolved. You can use it to make sure that you're loading the assembly in the right context. Second, if you happen to use one of Assembly.Load overloads and it causes AssemblyResolve event, you'll get a stack overflow. You can check out this guidance.
Using well, I think AppDomain.AssemblyResolve can supplement configuration file in handling assembly binding issues in the application.