BindingRedirect en 5 min

Le “BindingRedirect” permet d’indiquer comment charger des assemblies à la compilation d’un exécutable. L’intérêt de cette technique est:

  • de pouvoir choisir une version différente de celle utilisée à la compilation,
  • de faire ces indications sans avoir à recompiler l’application.

Chargement des assemblies

Par défaut, les dépendances sont chargées par Fusion exécuté par le compilateur JIT au moment où une méthode nécessite ce chargement. Ce chargement se fait en cherchant les assemblies à différents endroits dans un ordre déterminé et suivant certaines conditions. Les assemblies sont chargées en fonction de leur identité “AssemblyIdentify”.

L’identité “AssemblyIdentify” d’une assembly se définit par:

  • Le nom de l’assembly
  • Sa clé publique: la PublicKeyToken est présente si l’assembly est signée c’est-à-dire qu’elle possède un nom fort. La PublicKeyToken n’est pas indispensable si l’application n’est pas elle-même signée.
  • Sa version: la version recherchée par défaut est la version définie à la compilation.
  • Sa culture
  • Les indications d’architecture cible: l’architecture doit être compatible avec l’architecture de l’application (cf. Plateforme cible en .NET en 5 min).

Le chargement de l’assembly se fait dans un ordre déterminé en cherchant le fichier qui satisfait tous les critères qui se trouvent dans “l’AssemblyIdentity”:

  1. Répertoire de l’application: la recherche se fait d’abord dans le répertoire de l’application, y compris les répertoires enfants de ce répertoire. Si l’application n’est pas signée, l’assembly qui se trouve dans le même répertoire ne doit pas forcément être signée.
  2. GAC: l’assembly est recherché dans le Global Assembly Cache. Si elle se trouve dans le GAC, elle est forcément signée.

Si l’assembly n’a pas été trouvée, une exception de type FileNotFoundException est lancée. Pour éviter cette erreur il est possible d’indiquer une version différente de celle recherchée ou en indiquant un répertoire différent du répertoire de l’application. Le mécanisme de BindingRedirect permet de donner des informations à fusion pour qu’il puisse charger une dépendance différemment qu’avec la méthode par défaut.

Redirection par fichier de configuration

Redirection de la version de l’assembly

Le “BindingRedirect” peut se faire à des niveaux différents et les paramètres d’un niveau peuvent être surchargé par ceux d’un autre niveau.

Les niveaux de paramétrage de la redirection de la version d’une assembly sont:

  1. Par la politique de l’éditeur: ce niveau permet aux éditeurs d’effectuer une redirection d’une assembly vers une version supérieure de façon à ce qu’elle s’applique dès qu’une application consomme l’assembly lorsqu’elle se trouve dans le GAC. Les paramêtres de redirection se trouvent aussi dans le GAC. Plus de détails sur la redirection par la politique de l’éditeur sur MSDN.
  2. Par l’intermédiaire du fichier de configuration de l’application: ce niveau peut surcharger la redirection définie au niveau de la politique de l’éditeur.
  3. Au niveau de la machine: ce niveau peut surcharger les redirections définies dans les 2 niveaux précédents. Les paramètres de redirection à ce niveau peuvent être définies localement sur la machine dans un fichier machine.config, user.config ou system.config de façon à modifier le comportement de Fusion respectivement pour la machine, l’utilisateur ou le système.

Redirection de la version d’une assembly par fichier de configuration

On peut indiquer une version supérieure ou inférieure à la version définie à la compilation. Toutefois il faut avoir en tête que ce sont juste des indications à Fusion pour qu’il modifie son comportement lors du chargement d’une dépendance. Il faut que l’assembly vers laquelle on effectue la redirection soit compatible avec celle définie à la compilation.

Cette redirection se fait en complêtant le fichier de configuration de l’application en ajoutant le noeud assemblyBinding:

<configuration> 
  <runtime> 
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> 
      <dependentAssembly> 
        <assemblyIdentity  
          name="AssemblyToRedirect"  
          publicKeyToken="bcabfaff346163aa" 
          culture="neutral" /> 
        <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" 
          newVersion="3.0.0.0" />
      </dependentAssembly>
    </assemblyBinding> 
  </runtime> 
</configuration>

Cette configuration permet de rediriger la version de l’assembly “AssemblyToRedirect” avec la clé publique “bcabfaff346163aa” et dont la version est entre 1.0.0.0 et 2.0.0.0 vers la version 3.0.0.0.

ATTENTION

Pour que la redirection fonctionne il faut absolument renseigner l’attribut xmlns="urn:schemas-microsoft-com:asm.v1" dans le noeud assemblyBinding.

Surcharger la redirection définie par la politique de l’éditeur

Pour une assembly donnée, si on veut appliquer une redirection différente de celle définie par la politique de l’éditeur, il faut ajouter le noeud publisherPolicy dans le fichier de configuration de l’application de la façon suivante:

<configuration> 
  <runtime> 
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> 
      <dependentAssembly> 
        <assemblyIdentity  
          name="AssemblyToRedirect"  
          publicKeyToken="bcabfaff346163aa" 
          culture="neutral" /> 
        <publisherPolicy apply="no" />
      </dependentAssembly>
    </assemblyBinding> 
  </runtime> 
</configuration>

Dans ce cas, il n’y aura pas de redirection appliquée et le chargement de l’assembly se fera suivant le comportement par défaut.

Limiter la redirection à une version spécifique du framework .NET

Dans le cas où on redéfinit la redirection d’assembly du framework .NET, il est possible de préciser une condition d’application pour qu’elle s’applique à une version spécifique du framework.

Pour indiquer cette condition d’application, il faut ajouter l’attribut “appliesTo” dans le noeud “assemblyBinding” en précisant la version du framework à laquelle s’applique la redirection:

<configuration> 
  <runtime> 
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1" 
       appliesTo="v3.5"> 
      <dependentAssembly>  
        <assemblyIdentity name="AssemblyToRedirect" 
           publicKeyToken="b03f5f7f11d50a3a" culture=""/> 
        <bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" 
           newVersion="3.5.1.17000"/> 
      </dependentAssembly> 
    </assemblyBinding> 
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1" 
        appliesTo="v4.0.30319">
      <dependentAssembly>  
          <assemblyIdentity name="AssemblyToRedirect" 
             publicKeyToken="b03f5f7f11d50a3a" culture=""/> 
          <bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" 
             newVersion="4.5.30319.17000"/> 
      </dependentAssembly> 
    </assemblyBinding> 
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> 
      <dependentAssembly>  
        <assemblyIdentity name="AssemblyToRedirect" 
             publicKeyToken="b03f5f7f11d50a3a" culture=""/> 
        <bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" 
             newVersion="4.6.30319.17000"/> 
      </dependentAssembly> 
    </assemblyBinding> 
  </runtime> 
</configuration>

Indiquer des chemins de recherche des assemblies

Indiquer des sous-répertoires de recherche des assemblies

Il est possible de préciser des chemins de répertoires enfant du répertoire de l’application que Fusion devra parcourir pour chercher une dépendance.

Ces chemins peuvent être préciser en utilisant l’attribut “privatePath” du noeud “probing”:

<configuration> 
   <runtime> 
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> 
         <probing privatePath="subFolder1;subFolder2\subSubFolder" /> 
      </assemblyBinding> 
   </runtime> 
</configuration>

Indiquer le chemin d’une assembly

On peut préciser à Fusion directement le chemin d’une assembly. Toutefois ce paramètre s’applique suivant certaines conditions:

  • Si l’assembly est signée: le chemin peut rediriger vers une adresse sur un intranet local, internet ou sur la machine.
  • Si l’assembly n’est pas signée: le chemin doit être un sous-répertoire du répertoire de l’application. Le chemin indiqué doit être relatif à ce répertoire.

Ce paramètre peut être définit en utilisant le noeud codeBase:

<configuration> 
  <runtime> 
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> 
      <dependentAssembly> 
        <assemblyIdentity name="AssemblyToRedirect"  
            publicKeyToken="b03f5f7f11d50a3a"  
            culture="neutral" /> 
        <codeBase version="2.0.0.0" 
            href="http://assemblies.com/AssemblyToRedirect.dll" /> 
      </dependentAssembly> 
    </assemblyBinding> 
  </runtime> 
</configuration>

Le chemin sur la machine peut être préciser de la façon suivante:

<codeBase version="2.0.0.0" 
     href="file:///C:\folder\AssemblyToRedirect.dll"/>

Redirection par programmation

Il est possible d’indiquer des informations de redirection à Fusion en utilisant l’évènement AppDomain.AssemblyEvent.

Cet évènement ne se déclenche que si Fusion n’a pas réussi à trouver l’assembly suivant le comportement par défaut (c’est-à-dire en cherchant dans le répertoire de l’application puis dans le GAC).

L’évènement AppDomain.AssemblyEvent peut s’utiliser de la façon suivante:

using System; 
using System.Reflection; 
//... 
 
AppDomain.CurrentDomain.AssemblyResolve += 
   new ResolveEventHandler(ResolveEventHandler); 

Avec:

private static Assembly ResolveEventHandler(object sender, ResolveEventArgs args) 
{ 
  string longName = "CustomAssembly, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; 
  return Assembly.Load(longName); 
}

Pour utiliser AppDomain.AssemblyEvent, il faut prendre certaines précautions:

  • S’abonner à l’évènement avant l’exécution du compilateur JIT: l’abonnement à l’évènement doit se faire avant l’exécution de Fusion et donc avant toute méthode dont la compilation par la compilateur JIT exécutera le chargement d’une dépendance.
  • Assemblies chargées dans l’ordre des dépendances: si une assembly dépend d’une assembly tierce, l’assembly tierce doit être chargée avant.
  • StackoverflowException et Assembly.Load(): si le chargement de l’assembly avec Assembly.Load() échoue, l’évènement sera redéclenché, ce qui peut mener à une boucle infinie puis une exception de type StackOverflowException.

Pour définir les paramètres de chargement de l’assembly

On peut utiliser l’objet AssemblyName:

private static Assembly ResolveEventHandler(object sender, ResolveEventArgs args)  
{  
  AssemblyName requestedAssembly = new AssemblyName(args.Name); 
  if (requestedAssembly.Name != shortName) 
        return null; 
 
  requestedAssembly.Version = new Version(4, 0, 0, 0); 
  requestedAssembly.SetPublicKeyToken( 
    new AssemblyName("CustomAssembly, PublicKeyToken=" 
       + publicKeyToken).GetPublicKeyToken()); 
  requestedAssembly.CultureInfo = CultureInfo.InvariantCulture; 
  return Assembly.Load(requestedAssembly);  
}

Pour éviter la boucle infinie

Si le chargement échoue et pour éviter une StackOverflowException, on peut se désabonner de l’évènement dans la callback de l’évènement:

public static void AddResolveEventHandler(string longName)  
{ 
    ResolveEventHandler handler = (sender, args) => { 
      AppDomain.CurrentDomain.AssemblyResolve -= handler; 
 
      return Assembly.Load(longName); 
    }; 
 
    AppDomain.CurrentDomain.AssemblyResolve += handler; 
}

Plus d’information sur l’évènement AppDomain.AssemblyResolve sur MSDN.

Redirection automatique avec Visual Studio

A partir de Visual Studio 2013, si on utilise des assemblies de version différente dans une même solution, on aura une erreur du type:

Found conflicts between different versions of the same dependent assembly.  
Please set the "AutoGenerateBindingRedirects" property to true in the project file.  
For more information, see http://go.microsoft.com/fwlink/?LinkId=294190.

La fonction “AutoGenerateBindingRedirects” permet d’ajouter dans le fichier de configuration de l’application des indications de redirection “BindingRedirect”. Ces indications permettront à l’exécutable d’utiliser une seule version de l’assembly dupliquée dans les projets de la solution. Le fichier de configuration source n’est pas modifié, seul le fichier de configuration résultat (i.e. dont le nom est [nom de l’assembly].exe.config) dans le répertoire de l’exécutable est modifié.

Activer la redirection automatique

Il faut ajouter le noeud AutoGenerateBindingRedirects directement en éditant le fichier .csproj:

<?xml version="1.0" encoding="utf-8"?> 
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"  
     Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> 
  <PropertyGroup> 
    ... 
    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
  </PropertyGroup> 
... 
</Project>

Désactiver la redirection automatique

La redirection automatique du fichier de configuration peut mener à des conflits si on souhaite indiquer manuellement les indications de redirection “BindingRedirect”.

Pour désactiver la fonctionnalité, il faut paramétrer AutoGenerateBindingRedirects à “false” en éditant le fichier .csproj:

<AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects>

Plus de détails sur la redirection automatique sur MSDN.

Leave a Reply