Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to achieve assembly binding redirect in a plugin scenario?

I have a plugin P extending and application A (.NET40) that I have no control over.
The P assembly (.NET40) has a shared dependency D (.NET35).

Both P and D depend on FSharp.Core, but different versions:

P is compiled against FSharp.Core, Version=4.4.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
D is compiled against FSharp.Core, Version=2.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a

Only FSharp.Core, Version=4.4.0.0 is deployed and I subscribe to AppDomain.AssemblyResolve to load the deployed assemblies.

While I'm testing on my machine where both FSharp.Core versions are installed in the GAC, they both end up being loaded with the plugin.

My understanding is that a binding redirect would be the solution here but how can it be done without access to the app.config?

like image 690
oli Avatar asked Aug 04 '17 15:08

oli


People also ask

What are binding redirects in assemblies?

In .NET, binding redirects are there to handle version conflicts or version redirection among referenced assemblies.

How do I enable or disable binding redirection in an app?

You can enable automatic binding redirection if your app targets older versions of the .NET Framework. You can override this default behavior by providing binding redirection information in the app.config file for any assembly, or by turning off the binding redirection feature.

How do I set Assembly binding in configuration files?

Specifying assembly binding in configuration files. You use the same XML format to specify binding redirects whether it’s in the app configuration file, the machine configuration file, or the publisher policy file. To redirect one assembly version to another, use the <bindingRedirect> element.

What happens when an assembly is redirected to another machine?

If an assembly is redirected in the machine's configuration file, called machine.config, all apps on that machine that use the old version are directed to use the new version. The machine configuration file overrides the app configuration file and the publisher policy file.


2 Answers

You could deploy D as a publisher policy assembly.

The benefit of this approach is that client directories do not need to contain *.config files to redirect to a newer version. Publisher policy allows the publisher of an assembly to install a binary version of a *.config file into the GAC (along with the assembly). This way the CLR will be able to perform the requested redirection at the level of the GAC.

If you want to bypass the publisher policy for a certain app, you can specify so in the app’s *.config file using the <publisherPolicy> element.

<?xml version="1.0" encoding="utf-8" ?>
<configuration> 
    <runtime>
        <assemblyBinding xmlns=“urn:schemas-microsoft-com:asm.v1”> 
            <publisherPolicy apply="no" />
        </assemblyBinding>
    </runtime> 
</configuration>
like image 180
Funk Avatar answered Oct 17 '22 22:10

Funk


Yes, indeed. If you are writing a plugin, an app.config file is useless for redirecting assemblies. The plugin will look 1st to the machine.config file on the user's computer, then look to the *.config file of the main program.

Here is the two-step process I used for carrying out assembly-binding redirecting in a plugin scenario when writing a plugin for SDL Trados 2017--

Step One: Use a try-catch statement in the plugin itself to discover the information about the assembly which is failing to load.
In my case, I suspected one of the handful of assemblies required to create a Google Cloud AutoML client was to blame, so I put a try-catch statement around the point where the plugin first tries to create a Google Cloud AutoML client:

    try
    {
        client = AutoMlClient.Create(); 
    }
    catch (Exception err)
    {
        using (System.IO.StreamWriter file = new System.IO.StreamWriter("C:/Desktop/log.txt", true))
        {
            file.WriteLine(err.ToString());
        }
    }

When I checked the "log.txt" file created during the error, I found the following information:

    System.IO.FileNotFoundException: Could not load file or assembly 'Google.Apis.Auth, Version=1.41.1.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab' or one of its dependencies. The system cannot find the file specified.

So! It was clear that, in the process of creating an AutoML client, the plugin was trying to find.Google.Apis.Auth version 1.41.1.0. However, in order to get my plugin to compile correctly, I had to install Google.Apis.Auth version 1.42.0.0 with NuGet. Hence the need for the assembly binding redirect.

Step Two: Add an event handler related to that particular assembly which will change it's version / public key token information before loading it. At the very beginning of the program--where the main form window of the plugin is initialized, I added this code:

    public Main_form()
    {
        InitializeComponent();

        Version V14200 = new Version("1.42.0.0");
        RedirectAssembly("Google.Apis.Auth", V14200, "4b01fa6e34db77ab");
    }

    public static void RedirectAssembly(string assembly_name, Version targetVersion, string publicKeyToken)
    {
        ResolveEventHandler handler = null;

        handler = (sender, args) => {
            //gets the name of the assembly being requested by the plugin
            var requestedAssembly = new AssemblyName(args.Name);

            //if it is not the assembly we are trying to redirect, return null
            if (requestedAssembly.Name != assembly_name)
                return null;

            //if it IS the assembly we are trying to redirect, change it's version and public key token information
            requestedAssembly.Version = targetVersion;
            requestedAssembly.SetPublicKeyToken(new AssemblyName("x, PublicKeyToken=" + publicKeyToken).GetPublicKeyToken());
            requestedAssembly.CultureInfo = CultureInfo.InvariantCulture;

            //finally, load the assembly
            return Assembly.Load(requestedAssembly);
        };
        AppDomain.CurrentDomain.AssemblyResolve += handler;
    }

So basically, you have to get information from the plugin (using a try-catch statement) about which assembly is failing to load. Then, you have to add an event handler, which will go into effect when the assembly in question begins to load.

In my case, I knew that Google.Apis.Auth was the problem--the plugin wanted to load version 1.41.1.0, but my plugin contained version 1.42.0.0. When the plugin begins looking for Google.Apis.Auth (1.41.1.0), the event handler steps in and changes the version number, so the plugin loads version 1.42.0.0.

Having never had any formal training in computer science or programming, I don't know how robust/recommendable this solution is, but it worked for me.

like image 45
todbott Avatar answered Oct 17 '22 22:10

todbott