How to achieve assembly binding redirect in a plugin scenario?
Asked Answered
R

2

7

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?

Rowney answered 4/8, 2017 at 15:13 Comment(2)
What do you mean by shared dependency, where can D be found?Blat
I just meant that D is another project (part of my code base) that is referenced by other projects of mine. Having D target .NET40 and reference the same FSharp.Core version would prevent the version conflict. But I need D targeting .NET35 for at least another project.Rowney
B
3

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>
Blat answered 6/8, 2017 at 10:27 Comment(1)
Is there no solution to this problem without using the GAC?Esmerolda
J
4

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.

Jennettejenni answered 9/1, 2020 at 4:44 Comment(0)
B
3

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>
Blat answered 6/8, 2017 at 10:27 Comment(1)
Is there no solution to this problem without using the GAC?Esmerolda

© 2022 - 2024 — McMap. All rights reserved.