Empaqueter et déployer des ressources dans des applications .NET

Les applications s’appuient sur le gestionnaire des ressources du .NET Framework, représenté par la classe ResourceManager, pour récupérer des ressources localisées. Le gestionnaire des ressources suppose qu’un modèle Hub and Spoke est utilisé pour empaqueter et déployer des ressources. Le hub est l’assembly principal qui contient le code exécutable non localisable et les ressources pour une culture unique, appelée culture neutre ou par défaut. La culture par défaut est la culture de secours de l’application ; il s’agit de la culture dont les ressources sont utilisées si aucune ressource localisée ne peut être trouvée. Chaque spoke se connecte à un assembly satellite qui contient les ressources d’une culture unique, mais ne contient pas de code.

Ce modèle présente plusieurs avantages :

  • Vous pouvez ajouter de façon incrémentielle des ressources pour de nouvelles cultures après avoir déployé une application. Comme le développement ultérieur de ressources spécifiques à une culture peut nécessiter du temps, cela vous permet de libérer d’abord votre application principale, puis de proposer des ressources spécifiques à une culture ultérieurement.
  • Vous pouvez mettre à jour et changer les assemblys satellites d’une application sans recompiler l’application.
  • Une application doit charger uniquement les assemblys satellites qui contiennent les ressources nécessaires pour une culture particulière. Cela peut réduire considérablement l’utilisation des ressources système.

Cependant, ce modèle présente également des inconvénients :

  • Vous devez gérer plusieurs ensembles de ressources.
  • Le coût initial du test d’une application augmente, car vous devez tester plusieurs configurations. À longue échéance, il sera plus facile et moins coûteux de tester une application principale et plusieurs satellites que de tester et de tenir à jour en parallèle plusieurs versions internationales.

Conventions de nommage des ressources

Quand vous empaquetez les ressources de votre application, vous devez les nommer en utilisant les conventions d’affectation de noms pour les ressources que le Common Language Runtime attend. Le runtime identifie une ressource par son nom de culture. Chaque culture a un nom unique, qui est en général une combinaison d’un nom de culture à deux lettres en minuscules associé à une langue et, si nécessaire, un nom de sous-culture à deux lettres en majuscules associé à un pays ou une région. Le nom de la sous-culture suit le nom de la culture, séparés par un tiret (-). Les exemples incluent ja-JP pour le japonais tel qu’il est parlé au Japon, en-US pour l’anglais tel qu’il est parlé aux États-Unis, de-DE pour l’allemand tel qu’il est parlé en Allemagne ou de-AT pour l’allemand tel qu’il est parlé en Autriche. Consultez la colonne Balise de langue dans la liste des noms de langue/région pris en charge par Windows. Les noms de culture respectent la norme définie par BCP 47.

Notes

Il existe certaines exceptions pour les noms de culture à deux lettres, comme zh-Hans pour le chinois (simplifié).

Pour plus d’informations, consultez Créer des fichiers de ressources et Créer des assemblys satellites.

Processus de secours pour les ressources

Le modèle Hub and Spoke pour l’empaquetage et le déploiement de ressources utilise un processus de secours pour localiser les ressources appropriées. Si l’utilisateur d’une application demande une ressource localisée qui n’est pas disponible, le common language runtime recherche dans la hiérarchie des cultures une ressource de secours appropriée, la plus proche de celle demandée par l’utilisateur et lève une exception uniquement en dernier ressort. À chaque niveau de la hiérarchie, si une ressource appropriée est trouvée, le runtime l’utilise. Si la ressource est introuvable, la recherche se poursuit au niveau suivant.

Pour améliorer les performances de recherche, appliquez l’attribut NeutralResourcesLanguageAttribute à votre assembly principal et passez-lui le nom de la langue neutre à utiliser avec votre assembly principal.

Processus de secours pour les ressources .NET Framework

Le processus de secours pour les ressources .NET Framework comprend les étapes suivantes :

Conseil

Vous pouvez peut-être utiliser l’élément de configuration <relativeBindForResources> pour optimiser le processus de secours pour les ressources et le processus selon lequel le runtime recherche des assemblys de ressources. Pour plus d’informations, consultez Optimisation du processus de secours pour les ressources.

  1. Le runtime recherche d’abord dans le Global Assembly Cache un assembly qui correspond à la culture demandée pour votre application.

    Le Global Assembly Cache peut stocker des assemblys de ressources partagés par de nombreuses applications. Cela vous évite d’avoir à inclure des ensembles de ressources spécifiques dans la structure de répertoires de chaque application que vous créez. Si le runtime trouve une référence à l’assembly, il recherche la ressource demandée dans cet assembly. S’il trouve l’entrée dans l’assembly, il utilise la ressource demandée. S’il ne trouve pas l’entrée, il continue la recherche.

  2. Le runtime recherche ensuite un sous-répertoire correspondant à la culture demandée dans le répertoire de l’assembly en cours d’exécution. S’il le trouve, il y recherche un assembly satellite valide pour la culture demandée. Le runtime recherche ensuite dans l’assembly satellite la ressource demandée. S’il trouve la ressource dans l’assembly, il l’utilise. S’il ne trouve pas la ressource, il continue la recherche.

  3. Le runtime interroge ensuite Windows Installer pour déterminer si l’assembly satellite doit être installé à la demande. Dans ce cas, il gère l’installation, charge l’assembly et y recherche la ressource demandée. S’il trouve la ressource dans l’assembly, il l’utilise. S’il ne trouve pas la ressource, il continue la recherche.

  4. Le runtime déclenche l’événement AppDomain.AssemblyResolve pour indiquer qu’il lui est impossible de trouver l’assembly satellite. Si vous choisissez de gérer l’événement, le gestionnaire d’événements peut retourner une référence à l’assembly satellite dont les ressources seront utilisées pour la recherche. Sinon, le gestionnaire d’événements retourne null et la recherche se poursuit.

  5. Le runtime effectue ensuite une nouvelle recherche dans le Global Assembly Cache, cette fois pour trouver l’assembly parent de la culture demandée. Si l’assembly parent existe dans le Global Assembly Cache, le runtime recherche la ressource demandée dans cet assembly.

    La culture parente est définie comme culture de secours appropriée. Considérez les parents comme des candidats de secours, car n’importe quelle ressource est préférable à la levée d’une exception. Ce processus vous permet également de réutiliser les ressources. Vous devez inclure une ressource donnée au niveau parent uniquement si la culture enfant n’a pas besoin de localiser la ressource demandée. Par exemple, si vous fournissez des assemblys satellites pour en (anglais neutre), en-GB (anglais du Royaume-Uni) et en-US (anglais des États-Unis), le satellite en contient la terminologie commune et les satellites en-GB et en-US une terminologie de remplacement, seulement pour les termes qui diffèrent.

  6. Le runtime vérifie ensuite le répertoire de l’assembly en cours d’exécution à la recherche d’un répertoire parent. Si un répertoire parent existe, le runtime y recherche un assembly satellite valide pour la culture parente. S’il trouve l’assembly, le runtime y recherche la ressource demandée. S’il trouve la ressource, il l’utilise. S’il ne trouve pas la ressource, il continue la recherche.

  7. Le runtime interroge ensuite Windows Installer pour déterminer si l’assembly satellite parent doit être installé à la demande. Dans ce cas, il gère l’installation, charge l’assembly et y recherche la ressource demandée. S’il trouve la ressource dans l’assembly, il l’utilise. S’il ne trouve pas la ressource, il continue la recherche.

  8. Le runtime déclenche l’événement AppDomain.AssemblyResolve pour indiquer qu’il lui est impossible de trouver une ressource de secours appropriée. Si vous choisissez de gérer l’événement, le gestionnaire d’événements peut retourner une référence à l’assembly satellite dont les ressources seront utilisées pour la recherche. Sinon, le gestionnaire d’événements retourne null et la recherche se poursuit.

  9. Le runtime effectue ensuite des recherches dans les assemblys parents, comme aux trois étapes précédentes, dans les différents niveaux. Chaque culture n’a qu’un seul parent, qui est défini par la propriété CultureInfo.Parent, mais un parent peut avoir son propre parent. La recherche des cultures parentes s’arrête quand la propriété Parent d’une culture retourne CultureInfo.InvariantCulture ; pour la culture de secours pour les ressources, la culture indifférente n’est pas considérée comme une culture parente ou une culture qui peut avoir des ressources.

  10. Si la culture spécifiée à l’origine et tous les parents ont fait l’objet de la recherche et que la ressource est toujours introuvable, la ressource de la culture par défaut (de secours) est utilisée. En règle générale, les ressources de la culture par défaut sont incluses dans l’assembly d’application principal. Toutefois, vous pouvez spécifier la valeur Satellite pour la propriété Location de l’attribut NeutralResourcesLanguageAttribute pour indiquer que l’emplacement de secours ultime pour les ressources est un assembly satellite, plutôt que l’assembly principal.

    Notes

    La ressource par défaut est la seule ressource qui peut être compilée avec l’assembly principal. Sauf si vous spécifiez un assembly satellite à l’aide de l’attribut NeutralResourcesLanguageAttribute, il s’agit du secours ultime (parent final). Par conséquent, nous vous recommandons de toujours inclure un ensemble de ressources par défaut dans votre assembly principal. Vous empêchez ainsi la levée d’exceptions. En ajoutant un fichier de ressources par défaut, vous fournissez une solution de secours pour toutes les ressources et garantissez qu’au moins une ressource est toujours présente pour l’utilisateur, même si elle n’est pas propre à une culture.

  11. Enfin, si le runtime ne trouve pas une ressource pour une culture par défaut (de secours), une exception MissingManifestResourceException ou MissingSatelliteAssemblyException est levée pour indiquer que la ressource est introuvable.

Supposons par exemple que l’application demande une ressource localisée en espagnol (Mexique) (la culture es-MX). Tout d’abord, le runtime recherche l’assembly correspondant à es-MX dans le Global Assembly Cache, mais ne le trouve pas. Il recherche ensuite un répertoire es-MX dans le répertoire de l’assembly en cours d’exécution. En cas d’échec, il effectue une nouvelle recherche dans le Global Assembly Cache pour trouver un assembly parent qui reflète la culture de secours correspondante, ici es (espagnol). S’il ne trouve pas l’assembly parent, le runtime fouille tous les niveaux potentiels des assemblys parents, à la recherche de la culture es-MX, jusqu’à ce qu’il trouve une ressource correspondante. Si aucune ressource n’est trouvée, le runtime utilise la ressource pour la culture par défaut.

Optimiser le processus de secours pour les ressources .NET Framework

Dans les conditions suivantes, vous pouvez optimiser le processus par lequel le runtime recherche des ressources dans les assemblys satellites :

  • Les assemblys satellites sont déployés au même emplacement que l’assembly de code. Si l’assembly de code est installé dans le Global Assembly Cache, les assemblys satellites sont également installés dans le Global Assembly Cache. Si l’assembly de code est installé dans un répertoire, les assemblys satellites sont installés dans des dossiers spécifiques à la culture de ce répertoire.

  • Les assemblys satellites ne sont pas installés à la demande.

  • Le code d’application ne gère pas l’événement AppDomain.AssemblyResolve.

Vous optimisez la recherche des assemblys satellites en incluant l’élément <relativeBindForResources> et en affectant à son attribut enabled la valeur true dans le fichier de configuration de l’application, comme indiqué dans l’exemple suivant.

<configuration>
   <runtime>
      <relativeBindForResources enabled="true" />
   </runtime>
</configuration>

La recherche optimisée des assemblys satellites est une fonctionnalité d’abonnement. Autrement dit, le runtime suit la procédure décrite dans Processus de secours pour les ressources, sauf si l’élément <relativeBindForResources> est présent dans le fichier de configuration de l’application et que son attribut enabled a la valeur true. Dans ce cas, le processus de recherche d’un assembly satellite est modifié comme suit :

  • Le runtime utilise l’emplacement de l’assembly de code parent pour rechercher l’assembly satellite. Si l’assembly parent est installé dans le Global Assembly Cache, le runtime effectue la recherche dans le cache, mais pas dans le répertoire de l’application. Si l’assembly parent est installé dans un répertoire de l’application, le runtime effectue la recherche dans le répertoire de l’application, mais pas dans le Global Assembly Cache.

  • Le runtime n’interroge pas Windows Installer pour l’installation à la demande des assemblys satellites.

  • Si la recherche d’un assembly de ressource particulier échoue, le runtime ne déclenche pas l’événement AppDomain.AssemblyResolve.

Processus de secours pour les ressources .NET Core

Le processus de secours pour les ressources .NET Core comprend les étapes suivantes :

  1. Le runtime tente de charger un assembly satellite pour la culture demandée.

    • Il recherche un sous-répertoire correspondant à la culture demandée dans le répertoire de l’assembly en cours d’exécution. S’il le trouve, il y recherche un assembly satellite valide pour la culture demandée et le charge.

      Notes

      Sur les systèmes d’exploitation dont le système de fichiers respecte la casse (autrement dit, Linux et macOS), la recherche d’un sous-répertoire du nom de la culture est sensible à la casse. La casse du nom du sous-répertoire doit être identique à celle de CultureInfo.Name (par exemple, es ou es-MX).

      Notes

      Si le programmeur a dérivé un contexte de chargement d’assembly personnalisé à partir de AssemblyLoadContext, la situation est complexe. Si l’assembly en cours d’exécution a été chargé dans le contexte personnalisé, le runtime charge l’assembly satellite dans le contexte personnalisé. Ce document ne couvre pas ce processus en détail. Consultez AssemblyLoadContext.

    • S’il n’a pas trouvé l’assembly satellite, AssemblyLoadContext déclenche l’événement AssemblyLoadContext.Resolving pour le signaler. Si vous choisissez de gérer l’événement, votre gestionnaire d’événements peut charger et retourner une référence à l’assembly satellite.

    • Si l’assembly satellite reste introuvable, AssemblyLoadContext conduit AppDomain à déclencher un événement AppDomain.AssemblyResolve pour le signaler. Si vous choisissez de gérer l’événement, votre gestionnaire d’événements peut charger et retourner une référence à l’assembly satellite.

  2. Dès qu’il trouve un assembly satellite, le runtime y recherche la ressource demandée. S’il trouve la ressource dans l’assembly, il l’utilise. S’il ne trouve pas la ressource, il continue la recherche.

    Notes

    Pour trouver une ressource dans l’assembly satellite, le runtime recherche le fichier de ressources demandé par ResourceManager pour CultureInfo.Name. Dans le fichier de ressources, il cherche le nom de la ressource demandée. En cas d’échec, la ressource est traitée comme introuvable.

  3. Le runtime parcourt ensuite les assemblys de culture parente à différents niveaux potentiels, en répétant à chaque fois les étapes 1 et 2.

    La culture parente est définie comme une culture de secours appropriée. Considérez les parents comme des candidats de secours, car n’importe quelle ressource est préférable à la levée d’une exception. Ce processus vous permet également de réutiliser les ressources. Vous devez inclure une ressource donnée au niveau parent uniquement si la culture enfant n’a pas besoin de localiser la ressource demandée. Par exemple, si vous fournissez des assemblys satellites pour en (anglais neutre), en-GB (anglais du Royaume-Uni) et en-US (anglais des États-Unis), le satellite en contient la terminologie commune et les satellites en-GB et en-US une terminologie de remplacement, seulement pour les termes qui diffèrent.

    Chaque culture n’a qu’un seul parent, qui est défini par la propriété CultureInfo.Parent, mais un parent peut avoir son propre parent. La recherche des cultures parentes s’arrête lorsque la propriété Parent d’une culture retourne CultureInfo.InvariantCulture. En ce qui concerne la culture de secours pour les ressources, la culture invariante n’est pas considérée comme une culture parente ou une culture pouvant comporter des ressources.

  4. Si la culture spécifiée à l’origine et tous les parents ont fait l’objet de la recherche et que la ressource est toujours introuvable, la ressource de la culture par défaut (de secours) est utilisée. En règle générale, les ressources de la culture par défaut sont incluses dans l’assembly d’application principal. Toutefois, vous pouvez spécifier la valeur Satellite pour la propriété Location afin d’indiquer que l’emplacement de secours ultime des ressources est un assembly satellite, plutôt que l’assembly principal.

    Notes

    La ressource par défaut est la seule ressource qui peut être compilée avec l’assembly principal. Sauf si vous spécifiez un assembly satellite à l’aide de l’attribut NeutralResourcesLanguageAttribute, il s’agit du secours ultime (parent final). Par conséquent, nous vous recommandons de toujours inclure un ensemble de ressources par défaut dans votre assembly principal. Vous empêchez ainsi la levée d’exceptions. En ajoutant un fichier de ressources par défaut, vous fournissez une solution de secours pour toutes les ressources et garantissez qu’au moins une ressource est toujours présente pour l’utilisateur, même si elle n’est pas propre à une culture.

  5. Enfin, si le runtime ne trouve pas de fichier de ressources pour une culture (de secours) par défaut, une exception MissingManifestResourceException ou MissingSatelliteAssemblyException est levée pour indiquer que la ressource est introuvable. Si le fichier de ressources est trouvé, mais que la ressource demandée n’est pas présente, la demande retourne null.

Secours ultime pour l’assembly satellite

Vous pouvez éventuellement supprimer des ressources de l’assembly principal et spécifier que le runtime doit charger les ressources de secours ultime depuis un assembly satellite qui correspond à une culture spécifique. Pour contrôler le processus de secours, vous utilisez le constructeur NeutralResourcesLanguageAttribute(String, UltimateResourceFallbackLocation) et fournissez une valeur pour le paramètre UltimateResourceFallbackLocation qui spécifie si le gestionnaire des ressources doit extraire les ressources de secours à partir de l’assembly principal ou d’un assembly satellite.

L’exemple .NET Framework suivant utilise l’attribut NeutralResourcesLanguageAttribute pour stocker les ressources de secours d’une application dans un assembly satellite pour la langue française (fr). L’exemple comprend deux fichiers de ressources textuels qui définissent une ressource de type chaîne unique nommée Greeting. Le premier, resources.fr.txt, contient une ressource de langue française.

Greeting=Bon jour!

Le second, resources.ru.txt, contient une ressource de langue russe.

Greeting=Добрый день

Ces deux fichiers sont compilés en fichiers .resources en exécutant le Générateur de fichiers de ressources (Resgen.exe) à partir de la ligne de commande. Pour la ressource de langue française, la commande est :

resgen.exe resources.fr.txt

Pour la ressource de langue russe, la commande est :

resgen.exe resources.ru.txt

Les fichiers .resources sont incorporés dans des bibliothèques de liens dynamiques en exécutant Assembly Linker (al.exe) à partir de la commande de ligne pour la ressource de langue française comme suit :

al /t:lib /embed:resources.fr.resources /culture:fr /out:fr\Example1.resources.dll

et pour la ressource de langue russe comme suit :

al /t:lib /embed:resources.ru.resources /culture:ru /out:ru\Example1.resources.dll

Le code source de l’application réside dans un fichier nommé Example1.cs ou Example1.vb. Il inclut l’attribut NeutralResourcesLanguageAttribute pour indiquer que la ressource d’application par défaut se trouve dans le sous-répertoire fr. Il instancie le gestionnaire des ressources, récupère la valeur de la ressource Greeting et l’affiche dans la console.

using System;
using System.Reflection;
using System.Resources;

[assembly:NeutralResourcesLanguage("fr", UltimateResourceFallbackLocation.Satellite)]

public class Example
{
   public static void Main()
   {
      ResourceManager rm = new ResourceManager("resources",
                                               typeof(Example).Assembly);
      string greeting = rm.GetString("Greeting");
      Console.WriteLine(greeting);
   }
}
Imports System.Reflection
Imports System.Resources

<Assembly: NeutralResourcesLanguage("fr", UltimateResourceFallbackLocation.Satellite)>
Module Example
    Public Sub Main()
        Dim rm As New ResourceManager("resources", GetType(Example).Assembly)
        Dim greeting As String = rm.GetString("Greeting")
        Console.WriteLine(greeting)
    End Sub
End Module

Vous pouvez ensuite compiler le code source C# à partir de la ligne de commande comme suit :

csc Example1.cs

La commande pour le compilateur Visual Basic est très semblable :

vbc Example1.vb

Comme aucune ressource n’est incorporée dans l’assembly principal, il est inutile de compiler à l’aide du commutateur /resource.

Quand vous exécutez l’exemple à partir d’un système dont la langue est autre que le russe, la sortie suivante s’affiche :

Bon jour!

Autre empaquetage suggéré

Des contraintes de temps ou de budget peuvent vous empêcher de créer un ensemble de ressources pour chaque sous-culture que votre application prend en charge. À la place, vous pouvez créer un seul assembly satellite pour une culture parente qui peut être utilisé par toutes les sous-cultures apparentées. Par exemple, vous pouvez fournir un seul assembly satellite anglais (en) qui est récupéré par les utilisateurs qui demandent des ressources anglaises spécifiques à une région et un seul assembly satellite allemand (de) pour les utilisateurs qui demandent des ressources allemandes spécifiques à une région. Par exemple, les demandes pour l’allemand tel qu’il est parlé en Allemagne (de-DE), en Autriche (de-AT) et en Suisse (de-CH) reviennent à l’assembly satellite allemand (de). Comme les ressources par défaut représentent les ressources de secours final et doivent dont être les ressources qui seront demandées par la majorité des utilisateurs de votre application, choisissez-les avec précaution. Cette solution déploie des ressources qui sont moins spécifiques à une culture, mais peut réduire de manière significative les coûts de localisation de votre application.

Voir aussi