Making binding redirects work for office add-ins
Asked Answered
W

3

7

I'm using Microsoft.Bcl.Async in my Word addin, my addin is compiled as an exe (test_addin.exe) file, that is loaded as an assembly from Microsoft Word, when I start the executable directly, everything's working fine, but when I run it from Word, I'm getting an error saying that it failed to load the Systems.Threading.Tasks assembly.

Could not load file or assembly System.Threading.Tasks...

It looks like that its related to the binding redirects, when I try to run the application from Word it expects the config file to be located in the 'C:\Program Files (x86)\Microsoft Office\Office15' folder and be named WINWORD.exe.config, that is unfortunately impossible because I might not have access to that folder.

My test_addin.exe.config file:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
    </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-2.6.9.0" newVersion="2.6.9.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Threading.Tasks" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-2.6.9.0" newVersion="2.6.9.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

I have tried setting AppDomain.CurrentDomain.SetupInformation.ConfigurationFile to point to the correct path, but it doesn't seem to help, are there other ways to make it work for an Office add-in?

Washedout answered 4/9, 2014 at 11:31 Comment(0)
W
6

I have solved this problem by implementing a custom AssemblyResolve handler

    Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs e)
    {
        try
        {
            if (!e.Name.ToLower().StartsWith("system.threading.tasks"))
                return null;

            AddoDebug.Instance.WriteLine("Assembly_Resolve");
            var assemblyDetail = e.Name.Split(',');
            var assemblyBasePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            var assembly = Assembly.LoadFrom(assemblyBasePath + @"\" + assemblyDetail[0] + ".dll");

            return assembly;
        }
        catch (Exception ex)
        {
            AddoDebug.Instance.WriteLine("An exception occurred: " + ex, ADDOTraceStatus.Exception);
            return null;
        }
    }

But I'm not sure it's a good solution, so I'm leaving this question open for new answers.

Washedout answered 4/9, 2014 at 12:18 Comment(1)
Prefer using Assembly.Load over Assembly.LoadFrom; LoadFrom does not deduplicate loaded assemblies and leading the common language runtime to believe that the multiple instances of the assembly are different assemblies.Oxyacetylene
O
4

Office 2016 and 365 read the binding redirects from your config file. If your plugin, however, needs to function on older Office versions, the AppDomain.AssemblyResolve event (as @animaonline mentioned) will allow implementing custom binding redirect behavior.

The following code demonstrates a solution that, for each assembly that failed to load, tries to fall back to loading the assembly with the same name from the application's base directory. If an assembly by that name exists in the application directory, that assembly is loaded and returned.

using System;
using System.IO;
using System.Reflection;

public partial class ThisAddIn
{
    static ThisAddIn()
    {
        // The event must be hooked as early as possible. That's why
        // we use the static constructor.
        AppDomain.CurrentDomain.AssemblyResolve += TryLoadFromBaseDirectory;
    }

    private static Assembly TryLoadFromBaseDirectory(object sender, ResolveEventArgs e)
    {
        // This event is called for any assembly that fails to resolve.
        var name = new AssemblyName(e.Name);

        var assemblyPath =
            Path.Combine(AppDomain.CurrentDomain.BaseDirectory, name.Name + ".dll");

        if (File.Exists(assemblyPath))
        {
            // If we find this missing assembly in the application's base directory,
            // we simply return it.
            return Assembly.Load(AssemblyName.GetAssemblyName(assemblyPath));
        }
        else
        {
            return null;
        }
    }
    ...
}

Since the assembly versions you publish with the application are typically the one's you'd like the .NET runtime to use, this seems a robust 'catch all' mechanism.

Oxyacetylene answered 14/10, 2021 at 17:51 Comment(0)
T
2

I was facing the same problem, however, in my case my add-in was a DLL. Forcing the DLL to generate binding redirects solved it. Edit the csproj and add the following:

<PropertyGroup>
  <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
  <GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
</PropertyGroup>
Transpire answered 16/9, 2021 at 23:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.