Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making binding redirects work for office add-ins

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?

like image 931
animaonline Avatar asked Sep 04 '14 11:09

animaonline


3 Answers

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.

like image 156
animaonline Avatar answered Oct 27 '22 16:10

animaonline


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>
like image 24
Michael Csikos Avatar answered Oct 27 '22 16:10

Michael Csikos


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.

like image 1
Steven Avatar answered Oct 27 '22 18:10

Steven