Dynamic binding redirect at runtime

Recently had an issue where loading some dll at runtime would cause two different assemblies of different versions in the app domain.

While the app domain support this scenario, this not optimal and could cause lots of runtime confusion. Especially if using reflection on certain types of these assemblies.

To work around this scenario, we created a custom assembly resolver that will ensure assembly is loaded only if asm is not already loaded, and will update references similar to assembly binding.

 

    public static class AssemblyResolver
    {
        public static List<string> AssemblyFolders = new List<string>();

        public static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            AssemblyFolders = AssemblyFolders.Distinct().ToList();
               var binBath = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase).Substring(6);

            var binFolder = new DirectoryInfo($"{binBath}");

            var requestedAssembly = new AssemblyName(args.Name);

            List<FileInfo> files = new List<FileInfo>();

            if (!AssemblyFolders.Contains(binFolder.FullName))
                AssemblyFolders.Add(binFolder.FullName);

            foreach (var assemblyFolder in AssemblyFolders)
            {
                files.AddRange(new DirectoryInfo(assemblyFolder).GetFiles("*.*", SearchOption.AllDirectories).Where(V => V.Name.Contains(requestedAssembly.Name)));
            }

            List<Assembly> asemblies = new List<Assembly>();
            foreach (var file in files)
            {
                if (!(file.FullName.ToLower().EndsWith(".dll") || file.FullName.ToLower().EndsWith(".exe")))
                    continue;

                var foundAsm = AssemblyName.GetAssemblyName(file.FullName);
                if (foundAsm.Version > requestedAssembly.Version)
                {
                    //This is a way to perform binding redirect of assemblies at runtime. 
                    Debug.WriteLine("Redirecting assembly load of " + args.Name
                      + ",\tloaded by " + (args.RequestingAssembly == null ? "(unknown)" : args.RequestingAssembly.FullName));
                    requestedAssembly.Version = foundAsm.Version;
                    //For security reasons public key token should match;
                    //requestedAssembly.SetPublicKeyToken(new AssemblyName("x, PublicKeyToken=" + foundAsm.GetPublicKeyToken()).GetPublicKeyToken());


                }
                else if (foundAsm.Version < requestedAssembly.Version)
                    continue;

                asemblies.Add(Assembly.LoadFrom(file.FullName));
                break;
            }

            foreach (var assembly in asemblies)
            {
                if (assembly.FullName.Split(',').ElementAt(0) == args.Name.Split(',').ElementAt(0))
                    return assembly;

            }
            return null;

        }

    }
}

Then simply register it on the app domain

 

  AppDomain.CurrentDomain.AssemblyResolve += Newsoft.Middleware.Assemblies.AssemblyResolver.CurrentDomain_AssemblyResolve;

Submit a Comment

Your email address will not be published. Required fields are marked *

Share This