Simplification du déploiement et de la résolution de DLL Hell avec le .NET Framework

 

Steven Pratschner
Microsoft Corporation

Mise à jour de novembre 2001

Résumé: Cet article présente le concept d’un assembly et décrit comment l'.NET Framework utilise des assemblys pour résoudre les problèmes de contrôle de version et de déploiement. (16 pages imprimées)

Contenu

Introduction
Énoncé du problème
Caractéristiques de la solution
Assemblys : blocs de construction
Contrôle de version et partage
Stratégie de version
Déploiement
Résumé

Introduction

Microsoft® .NET Framework introduit plusieurs nouvelles fonctionnalités visant à simplifier le déploiement d’applications et à résoudre DLL Hell. Les utilisateurs finaux et les développeurs sont familiarisés avec les problèmes de contrôle de version et de déploiement qui peuvent se produire avec les systèmes basés sur les composants d’aujourd’hui. Par exemple, pratiquement tous les utilisateurs finaux ont installé une nouvelle application sur leur ordinateur, uniquement pour trouver qu’une application existante cesse mystérieusement de fonctionner. La plupart des développeurs ont également passé du temps avec Regedit, essayant de conserver toutes les entrées de Registre nécessaires de manière à activer une classe COM.

Les directives de conception et les techniques d’implémentation utilisées dans le .NET Framework pour résoudre DLL Hell sont basées sur le travail effectué dans Microsoft Windows ® 2000, comme décrit par Rick Anderson dans The End of DLL Hell, et par David D’Souza, BJ Whalen et Peter Wilson dans l’implémentation du partage de composants côte à côte dans les applications (développé). Le .NET Framework étend ce travail précédent en fournissant des fonctionnalités, notamment l’isolation des applications et les composants côte à côte pour les applications créées avec du code managé sur la plateforme .NET. Notez également que Windows XP fournit les mêmes fonctionnalités d’isolation et de contrôle de version pour le code non managé, notamment les classes COM et les DLL Win32 (voir How To Build and Service Is isolation Applications and Side-by-Side Assemblys for Windows XP pour plus d’informations).

Cet article présente le concept d’un assembly et décrit comment .NET utilise des assemblys pour résoudre les problèmes de contrôle de version et de déploiement. En particulier, nous allons discuter de la structure des assemblys, de leur nom et de la façon dont les compilateurs et le Common Language Runtime (CLR) utilisent des assemblys pour enregistrer et appliquer les dépendances de version entre des éléments d’une application. Nous aborderons également la façon dont les applications et les administrateurs peuvent personnaliser le comportement de contrôle de version par le biais des stratégies de version que nous appelons.

Une fois les assemblys introduits et décrits, plusieurs scénarios de déploiement seront présentés, fournissant un échantillonnage des différentes options d’empaquetage et de distribution disponibles dans le .NET Framework.

Énoncé du problème

Contrôle de version

Du point de vue du client, le problème de version le plus courant est ce que nous appelons DLL Hell. Il suffit d’indiquer que DLL Hell fait référence à l’ensemble des problèmes causés lorsque plusieurs applications tentent de partager un composant commun comme une bibliothèque de liens dynamiques (DLL) ou une classe COM (Component Object Model). Dans le cas le plus courant, une application installe une nouvelle version du composant partagé qui n’est pas compatible avec la version déjà sur l’ordinateur. Bien que l’application qui vient d’être installée fonctionne correctement, les applications existantes qui dépendent d’une version précédente du composant partagé peuvent ne plus fonctionner. Dans certains cas, la cause du problème est encore plus subtile. Par exemple, envisagez le scénario où un utilisateur télécharge un contrôle Microsoft ActiveX ® comme effet secondaire de la visite d’un site Web. Lorsque le contrôle est téléchargé, il remplace toutes les versions existantes du contrôle présents sur l’ordinateur. Si une application installée sur l’ordinateur se produit pour utiliser ce contrôle, elle peut également cesser de fonctionner.

Dans de nombreux cas, il existe un délai important avant qu’un utilisateur découvre qu’une application a cessé de fonctionner. Par conséquent, il est souvent difficile de se rappeler quand une modification a été apportée à l’ordinateur qui aurait pu affecter l’application. Un utilisateur peut se rappeler d’installer quelque chose il y a une semaine, mais il n’y a pas de corrélation évidente entre cette installation et le comportement qu’il voit maintenant. Pour aggraver les choses, il existe aujourd’hui peu d’outils de diagnostic pour aider l’utilisateur (ou la personne de support qui les aide) à déterminer ce qui est incorrect.

La raison de ces problèmes est que les informations de version sur les différents composants d’une application ne sont pas enregistrées ou appliquées par le système. En outre, les modifications apportées au système pour le compte d’une application affecteront généralement toutes les applications sur l’ordinateur. La création d’une application aujourd’hui complètement isolée des modifications n’est pas facile.

Une raison pour laquelle il est difficile de créer une application isolée est que l’environnement d’exécution actuel autorise généralement l’installation d’une seule version d’un composant ou d’une application. Cette restriction signifie que les auteurs de composants doivent écrire leur code d’une manière qui reste compatible descendante, sinon ils risquent de briser les applications existantes lorsqu’ils installent un nouveau composant. Dans la pratique, l’écriture de code qui est toujours compatible descendante est extrêmement difficile, si ce n’est pas impossible. Dans .NET, la notion de côte à côte est essentielle à l’histoire de contrôle de version. Côte à côte est la possibilité d’installer et d’exécuter plusieurs versions du même composant sur l’ordinateur en même temps. Avec les composants qui prennent en charge côte à côte, les auteurs ne sont pas nécessairement liés au maintien d’une compatibilité descendante stricte, car différentes applications sont libres d’utiliser différentes versions d’un composant partagé.

Déploiement et installation

L’installation d’une application aujourd’hui est un processus en plusieurs étapes. En règle générale, l’installation d’une application implique la copie d’un certain nombre de composants logiciels sur le disque et la création d’une série d’entrées de Registre qui décrivent ces composants dans le système.

La séparation entre les entrées du Registre et les fichiers sur disque rend très difficile la réplication des applications et leur désinstallation. En outre, la relation entre les différentes entrées requises pour décrire entièrement une classe COM dans le Registre est très libre. Ces entrées incluent souvent des entrées pour les coclasses, les interfaces, les typeslibs et les ID d’application DCOM, sans mentionner les entrées effectuées pour inscrire des extensions de document ou des catégories de composants. Souvent, vous terminez par les conserver manuellement.

Enfin, cette empreinte de Registre est nécessaire pour activer n’importe quelle classe COM. Cela complique considérablement le processus de déploiement d’applications distribuées, car chaque ordinateur client doit être touché pour rendre les entrées de Registre appropriées.

Ces problèmes sont principalement causés par la description d’un composant qui est séparé du composant lui-même. En d’autres termes, les applications ne décrivent ni elles-mêmes ni elles-mêmes.

Caractéristiques de la solution

Les .NET Framework doivent fournir les fonctionnalités de base suivantes pour résoudre les problèmes décrits :

  • Les applications doivent être auto-décrivant. Les applications qui décrivent automatiquement suppriment la dépendance sur le Registre, en activant l’installation sans impact et en simplifiant la désinstallation et la réplication.
  • Les informations de version doivent être enregistrées et appliquées. La prise en charge du contrôle de version doit être intégrée à la plateforme pour s’assurer que la version appropriée d’une dépendance est chargée au moment de l’exécution.
  • Doit se souvenir de « dernier bon connu ». Lorsqu’une application s’exécute correctement, la plateforme doit mémoriser l’ensemble des composants, y compris leurs versions, qui ont fonctionné ensemble. En outre, les outils doivent être fournis qui permettent aux administrateurs de rétablir facilement les applications à cet état « dernier bon connu ».
  • Prise en charge des composants côte à côte. Permettre à plusieurs versions d’un composant d’être installées et exécutées simultanément sur l’ordinateur permet aux appelants de spécifier quelle version ils souhaitent charger au lieu d’une version « forcée » sur le point d’être inconnaissable. Le .NET Framework prend côte à côte une étape plus loin en permettant à plusieurs versions de l’infrastructure elle-même de coexister sur un seul ordinateur. Cela simplifie considérablement l’histoire de mise à niveau, car un administrateur peut choisir d’exécuter différentes applications sur différentes versions de l'.NET Framework si nécessaire.
  • Isolation de l’application. Le .NET Framework doit faciliter l’écriture d’applications qui ne peuvent pas être affectées par les modifications apportées à l’ordinateur pour le compte d’autres applications.

Assemblys : blocs de construction

Les assemblys sont les blocs de construction utilisés par le .NET Framework pour résoudre les problèmes de contrôle de version et de déploiement décrits simplement. Les assemblys sont l’unité de déploiement pour les types et les ressources. De nombreuses façons, un assembly équivaut à une DLL dans le monde actuel; en essence, les assemblys sont des « DLL logiques ».

Les assemblys sont auto-décrivant par le biais de métadonnées appelées manifeste. Tout comme .NET utilise des métadonnées pour décrire des types, elle utilise également des métadonnées pour décrire les assemblys qui contiennent les types.

Les assemblys sont beaucoup plus nombreux que le déploiement. Par exemple, le contrôle de version dans .NET est effectué au niveau de l’assembly: rien de plus petit, comme un module ou un type, est versionné. En outre, les assemblys sont utilisés pour partager du code entre les applications. L’assembly dans lequel un type est contenu fait partie de l’identité du type.

Le système de sécurité d’accès au code utilise des assemblys au cœur de son modèle d’autorisations. L’auteur d’un enregistrement d’assembly dans le manifeste, l’ensemble d’autorisations requises pour exécuter le code, et l’administrateur accorde des autorisations au code en fonction de l’assembly dans lequel le code est contenu.

Enfin, les assemblys sont également essentiels au système de type et au système d’exécution dans lequel ils établissent une limite de visibilité pour les types et servent d’étendue d’exécution pour résoudre les références aux types.

Manifestes d’assembly

Plus précisément, un manifeste inclut les données suivantes sur l’assembly :

  • L’identité. L’identité d’un assembly se compose de quatre parties : un nom de texte simple, un numéro de version, une culture facultative et une clé publique facultative si l’assembly a été créé pour le partage (voir la section sur les assemblys partagés ci-dessous).
  • Liste de fichiers. Un manifeste inclut une liste de tous les fichiers qui composent l’assembly. Pour chaque fichier, le manifeste enregistre son nom et un hachage de chiffrement de son contenu au moment de la génération du manifeste. Ce hachage est vérifié au moment de l’exécution pour vous assurer que l’unité de déploiement est cohérente.
  • Assemblys référencés. Les dépendances entre les assemblys sont stockées dans le manifeste de l’assembly appelant. Les informations de dépendance incluent un numéro de version utilisé au moment de l’exécution pour vous assurer que la version correcte de la dépendance est chargée.
  • Types et ressources exportés. Les options de visibilité disponibles pour les types et les ressources incluent « visible uniquement dans mon assembly » et « visibles pour les appelants en dehors de mon assembly ».
  • Demandes d’autorisation. Les demandes d’autorisation d’un assembly sont regroupées en trois ensembles : 1) ceux requis pour que l’assembly s’exécute, 2) ceux qui sont souhaités, mais l’assembly aura toujours certaines fonctionnalités même si elles ne sont pas accordées, et 3) celles que l’auteur ne souhaite jamais accorder à l’assembly.

L’outil SDK IlAssembleer (Ildasm) est utile pour examiner le code et les métadonnées dans un assembly. La figure 1 est un exemple de manifeste affiché par Ildasm. La directive .assembly identifie l’assembly et les directives d’extern .assembly contiennent les informations sur les autres assemblys sur lesquels cela dépend.

Figure 1. Exemple de manifeste affiché par le désassembleur IL

Structure d’assembly

Jusqu’à présent, les assemblys ont été décrits principalement comme un concept logique. Cette section permet de rendre les assemblys plus concrets en décrivant comment ils sont représentés physiquement.

En général, les assemblys se composent de quatre éléments : les métadonnées d’assembly (manifeste), les métadonnées décrivant les types, le code IL (Intermediate Language) qui implémente les types et un ensemble de ressources. Tous ces éléments ne sont pas présents dans chaque assembly. Seul le manifeste est strictement requis, mais les types ou ressources sont nécessaires pour donner à l’assembly toutes les fonctionnalités significatives.

Il existe plusieurs options pour la façon dont ces quatre éléments peuvent être « empaquetés ». Par exemple, la figure 2 montre une seule DLL qui contient l’assembly entier : le manifeste, les métadonnées de type, le code IL et les ressources.

Figure 2 : DLL contenant tous les éléments d’assembly

Vous pouvez également répartir le contenu d’un assembly sur plusieurs fichiers. Dans la figure 3, l’auteur a choisi de séparer du code utilitaire dans une DLL différente et de conserver un fichier de ressources volumineux (dans ce cas un JPEG) dans son fichier d’origine. Une raison pour laquelle cela peut être effectué consiste à optimiser le téléchargement du code. Le .NET Framework télécharge un fichier uniquement lorsqu’il est référencé. Par conséquent, si l’assembly contient du code ou des ressources qui sont accessibles rarement, les cassant dans des fichiers individuels, augmente l’efficacité du téléchargement. Un autre scénario courant dans lequel plusieurs fichiers sont utilisés consiste à créer un assembly qui se compose de code à partir de plusieurs langages. Dans ce cas, vous allez générer séparément chaque fichier (module), puis les regrouper dans un assembly à l’aide de l’outil Assembly Linker fourni dans le Kit de développement logiciel (SDK) .NET Framework (al.exe).

Figure 3. Éléments d’assembly répartis sur plusieurs fichiers

Contrôle de version et partage

L’une des principales causes de DLL Hell est le modèle de partage actuellement utilisé dans les systèmes basés sur des composants. Par défaut, les composants logiciels individuels sont partagés par plusieurs applications sur l’ordinateur. Par exemple, chaque fois qu’un programme d’installation copie une DLL dans le répertoire système ou inscrit une classe dans le Registre COM, ce code aura potentiellement un effet sur d’autres applications s’exécutant sur l’ordinateur. En particulier, si une application existante a utilisé une version précédente de ce composant partagé, cette application commence automatiquement à utiliser la nouvelle version. Si le composant partagé est strictement compatible descendant, cela peut être correct, mais dans de nombreux cas, la maintenance de la compatibilité descendante est difficile, si ce n’est pas impossible. Si la compatibilité descendante n’est pas maintenue ou ne peut pas être maintenue, cela entraîne souvent des applications qui sont interrompues en tant qu’effet secondaire d’autres applications installées.

Une directive de conception de principe dans .NET est celle des composants isolés (ou des assemblys). L’isolation d’un assembly signifie qu’un assembly ne peut être accessible qu’à une seule application: il n’est pas partagé par plusieurs applications sur l’ordinateur et ne peut pas être affecté par les modifications apportées au système par d’autres applications. L’isolation donne un contrôle absolu au développeur sur le code utilisé par son application. Les assemblys isolés ou privés d’application sont la valeur par défaut dans les applications .NET. La tendance vers les composants isolés a démarré dans Microsoft Windows 2000 avec l’introduction du fichier .local. Ce fichier a été utilisé pour que le chargeur de système d’exploitation et COM recherchent d’abord dans le répertoire de l’application lorsque vous essayez de localiser le composant demandé. (Consultez l’article associé dans MSDN Library, implémentation du partage de composants côte à côte dans les applications.)

Toutefois, il existe des cas où le partage d’un assembly entre les applications est nécessaire. Il ne serait clairement pas judicieux pour chaque application de porter sa propre copie de System.Windowns.Forms, System.Web ou un contrôle Web Forms commun.

Dans .NET, le partage de code entre les applications est une décision explicite. Les assemblys partagés ont des exigences supplémentaires. Plus précisément, les assemblys partagés doivent prendre en charge côte à côte afin que plusieurs versions du même assembly puissent être installées et exécutées sur le même ordinateur, ou même dans le même processus, en même temps. En outre, les assemblys partagés ont des exigences de nommage plus strictes. Par exemple, un assembly partagé doit avoir un nom global unique.

Le besoin d’isolation et de partage nous amène à penser à deux « types » d’assemblys. Il s’agit d’une catégorisation plutôt libre dans laquelle il n’y a pas de différences structurelles réelles entre les deux, mais plutôt dans la façon dont ils seront utilisés : s’il s’agit d’une application privée à une application ou partagée entre plusieurs.

assemblys Application-Private

Un assembly privé d’application est un assembly qui n’est visible qu’à une seule application. Nous nous attendons à ce qu’il s’agit du cas le plus courant dans .NET. Les exigences d’affectation de noms pour les assemblys privés sont simples : les noms d’assembly ne doivent être uniques que dans l’application. Il n’y a pas besoin d’un nom global unique. La conservation des noms uniques n’est pas un problème, car le développeur d’applications a un contrôle complet sur les assemblys isolés de l’application.

Les assemblys privés d’application sont déployés dans la structure d’annuaire de l’application dans laquelle elles sont utilisées. Les assemblys privés peuvent être placés directement dans le répertoire d’application ou dans un sous-répertoire de celui-ci. Le CLR recherche ces assemblys par le biais d’un processus appelé probing. La détection est simplement un mappage du nom de l’assembly au nom du fichier qui contient le manifeste.

Plus précisément, le CLR prend le nom de l’assembly enregistré dans la référence d’assembly, ajoute « .dll » et recherche ce fichier dans le répertoire de l’application. Il existe quelques variantes sur ce schéma où le runtime recherche dans les sous-répertoires nommés par l’assembly ou dans les sous-répertoires nommés par la culture de l’assembly. Par exemple, un développeur peut choisir de déployer l’assembly contenant des ressources localisées en allemand dans un sous-répertoire appelé « de » et en espagnol dans un répertoire appelé « es ». (Consultez le guide du kit de développement logiciel (SDK) .NET Framework pour plus d’informations.)

Comme décrit simplement, chaque manifeste d’assembly inclut des informations de version sur ses dépendances. Ces informations de version ne sont pas appliquées pour les assemblys privés, car le développeur a un contrôle complet sur les assemblys déployés dans le répertoire d’application.

Assemblys partagés

Le .NET Framework prend également en charge le concept d’un assembly partagé. Un assembly partagé est un assembly utilisé par plusieurs applications sur l’ordinateur. Avec .NET, le partage de code entre les applications est une décision explicite. Les assemblys partagés ont des exigences supplémentaires visant à éviter les problèmes de partage que nous rencontrons aujourd’hui. En plus de la prise en charge de côte à côte décrivent précédemment, les assemblys partagés ont des exigences de nommage beaucoup plus strictes. Par exemple, un assembly partagé doit avoir un nom global unique. En outre, le système doit fournir la « protection du nom », autrement dit empêcher une personne de réutiliser le nom de l’assembly d’un autre. Par exemple, dites que vous êtes un fournisseur d’un contrôle de grille et que vous avez publié la version 1 de votre assembly. En tant qu’auteur, vous avez besoin d’assurance que personne d’autre ne peut libérer un assembly qui prétend être version 2 ou votre contrôle grille. Le .NET Framework prend en charge ces exigences d’affectation de noms par le biais d’une technique appelée noms forts (décrite en détail dans la section suivante).

En règle générale, un auteur d’application n’a pas le même degré de contrôle sur les assemblys partagés utilisés par l’application. Par conséquent, les informations de version sont vérifiées sur chaque référence à un assembly partagé. En outre, le .NET Framework permet aux applications et aux administrateurs de remplacer la version d’un assembly utilisé par l’application en spécifiant des stratégies de version.

Les assemblys partagés ne sont pas nécessairement déployés en privé sur une application, bien que cette approche soit toujours viable, en particulier si le déploiement xcopy est requis. En plus d’un répertoire d’application privée, un assembly partagé peut également être déployé dans le Global Assembly Cache ou sur n’importe quelle URL tant qu’une base de code décrivant l’emplacement de l’assembly est fournie dans le fichier de configuration de l’application. Le cache d’assembly global est un magasin à l’échelle de l’ordinateur pour les assemblys utilisés par plusieurs applications. Comme décrit, le déploiement dans le cache n’est pas obligatoire, mais il existe certains avantages à ce faire. Par exemple, le stockage côte à côte de plusieurs versions d’un assembly est fourni automatiquement. En outre, les administrateurs peuvent utiliser le magasin pour déployer des correctifs de bogues ou des correctifs de sécurité qu’ils souhaitent que chaque application sur l’ordinateur utilise. Enfin, il existe quelques améliorations des performances associées au déploiement dans le global assembly cache. Le premier implique la vérification des signatures de noms forts, comme décrit dans la section Nom fort ci-dessous. La deuxième amélioration des performances implique un ensemble de travail. Si plusieurs applications utilisent simultanément le même assembly, le chargement de cet assembly à partir du même emplacement sur le disque tire parti du comportement de partage de code fourni par le système d’exploitation. En revanche, le chargement du même assembly à partir de plusieurs emplacements différents (répertoires d’applications) entraîne le chargement de nombreuses copies du même code. L’ajout d’un assembly au cache sur l’ordinateur d’un utilisateur final est généralement accompli à l’aide d’un programme d’installation basé sur le programme d’installation Windows ou sur une autre technologie d’installation. Les assemblys ne se terminent jamais dans le cache en tant qu’effet secondaire de l’exécution d’une application ou de la navigation vers une page Web. Au lieu de cela, l’installation d’un assembly dans le cache nécessite une action explicite sur la partie de l’utilisateur. Windows Installer 2.0, fourni avec Windows XP et Visual Studio .NET, a été amélioré pour comprendre pleinement le concept d’assemblys, le cache d’assembly et les applications isolées. Cela signifie que vous serez en mesure d’utiliser toutes les fonctionnalités du programme d’installation Windows, telles que l’installation à la demande et la réparation d’application, avec vos applications .NET.

Il n’est souvent pas pratique de créer un package d’installation chaque fois que vous souhaitez ajouter un assembly au cache sur les machines de développement et de test. Par conséquent, le Kit de développement logiciel (SDK) .NET inclut certains outils permettant d’utiliser le cache d’assembly. Le premier est un outil appelé gacutil qui vous permet d’ajouter des assemblys au cache et de les supprimer ultérieurement. Utilisez le commutateur /i pour ajouter un assembly au cache :

gacutil /i:myassembly.dll 
See the .NET Framework SDK documentation for a full description of the 
      options supported by gacutil.

Les autres outils sont une extension Shell Windows qui vous permet de manipuler le cache à l’aide de l’Explorateur Windows et de l’outil de configuration .NET Framework. L’extension Shell est accessible en accédant au sous-répertoire « assembly » sous votre répertoire Windows. Vous trouverez l’outil de configuration .NET Framework dans la section Outils d’administration du Panneau de configuration.

La figure 4 montre une vue du Global Assembly Cache à l’aide de l’extension Shell.

Figure 4. Global Assembly Cache

Noms forts

Les noms forts sont utilisés pour activer les exigences de nommage plus strictes associées aux assemblys partagés. Les noms forts ont trois objectifs :

  • Unicité du nom. Les assemblys partagés doivent avoir des noms globalement uniques.
  • Empêcher l’usurpation de noms. Les développeurs ne veulent pas qu’une autre personne publie une version ultérieure de l’un de vos assemblys et prétend faussement qu’elle provient de vous, soit par accident, soit intentionnellement.
  • Fournissez une identité sur la référence. Lors de la résolution d’une référence à un assembly, les noms forts sont utilisés pour garantir que l’assembly chargé provient de l’éditeur attendu.

Les noms forts sont implémentés à l’aide du chiffrement à clé publique standard. En général, le processus fonctionne comme suit : l’auteur d’un assembly génère une paire de clés (ou utilise un fichier existant), signe le fichier contenant le manifeste avec la clé privée et rend la clé publique disponible pour les appelants. Lorsque des références sont faites à l’assembly, l’appelant enregistre la clé publique correspondant à la clé privée utilisée pour générer le nom fort. La figure 5 décrit le fonctionnement de ce processus au moment du développement, notamment la façon dont les clés sont stockées dans les métadonnées et la façon dont la signature est générée.

Le scénario est un assembly appelé « Main », qui fait référence à un assembly appelé « MyLib ». MyLib a un nom partagé. Les étapes importantes sont décrites comme suit.

Figure 5. Processus d’implémentation de noms partagés

  1. Le développeur appelle un compilateur en passant une paire de clés et l’ensemble de fichiers sources pour l’assembly. La paire de clés est générée avec un outil sdk appelé SN. Par exemple, la commande suivante génère une nouvelle paire de clés et l’enregistre dans un fichier :

    Sn –k MyKey.snk
    The key pair is passed to the compiler using the custom attribute 
            System.Reflection.AssemblyKeyFileAttribute as follows:
    
       <assembly:AssemblyKeyFileAttribute("TestKey.snk")>
    
  2. Lorsque le compilateur émet l’assembly, la clé publique est enregistrée dans le manifeste dans le cadre de l’identité de l’assembly. L’inclusion de la clé publique dans le cadre de l’identité est ce qui donne à l’assembly un nom global unique.

  3. Une fois l’assembly émis, le fichier contenant le manifeste est signé avec la clé privée. La signature résultante est stockée dans le fichier.

  4. Lorsque Main est généré par le compilateur, la clé publique de MyLib est stockée dans le manifeste de Main dans le cadre de la référence à MyLib.

Au moment de l’exécution, il existe deux étapes que l'.NET Framework prend pour s’assurer que les noms forts donnent au développeur les avantages requis. Tout d’abord, la signature de nom fort de MyLib est vérifiée uniquement lorsque l’assembly est installé dans le Global Assembly Cache , la signature n’est pas vérifiée à nouveau lorsque le fichier est chargé par une application. Si l’assembly partagé n’est pas déployé dans le Global Assembly Cache, la signature est vérifiée chaque fois que le fichier est chargé. La vérification de la signature garantit que le contenu de MyLib n’a pas été modifié depuis la génération de l’assembly. La deuxième étape consiste à vérifier que la clé publique stockée dans le cadre de la référence principale à MyLib correspond à la clé publique qui fait partie de l’identité de MyLib. Si ces clés sont identiques, l’auteur de Main peut être sûr que la version de MyLib chargée provient du même éditeur que la version de MyLib avec laquelle Main a été créée. Cette vérification d’équivalence de clé est effectuée au moment de l’exécution, lorsque la référence de Main à MyLib est résolue.

Le terme « signature » amène souvent Microsoft Authenticode® à l’esprit. Il est important de comprendre que les noms forts et Authenticode ne sont pas liés de quelque manière que ce soit. Les deux techniques ont des objectifs différents. En particulier, Authenticode implique un niveau de confiance associé à un éditeur, tandis que les noms forts ne le font pas. Aucun certificat ni aucune autorité de signature tierce n’est associée à des noms forts. En outre, la signature de nom fort est souvent effectuée par le compilateur lui-même dans le cadre du processus de génération.

Une autre considération à noter est le processus de « signature différée ». Il est souvent vrai que l’auteur d’un assembly n’a pas accès à la clé privée nécessaire pour effectuer la signature complète. La plupart des entreprises conservent ces clés dans des magasins bien protégés qui ne sont accessibles qu’à quelques personnes. Par conséquent, le .NET Framework fournit une technique appelée « signature différée » qui permet à un développeur de créer un assembly avec uniquement la clé publique. Dans ce mode, le fichier n’est pas réellement signé, car la clé privée n’est pas fournie. Au lieu de cela, le fichier est signé ultérieurement à l’aide de l’utilitaire SN. Pour plus d’informations sur l’utilisation de la signature différée d’un assembly dans le kit sdk .NET Framework, consultez Le délai de signature.

Stratégie de version

Comme décrit, chaque manifeste d’assembly enregistre des informations sur la version de chaque dépendance à laquelle il a été créé. Toutefois, il existe certains scénarios dans lesquels l’auteur ou l’administrateur de l’application peut souhaiter s’exécuter avec une version différente d’une dépendance au moment de l’exécution. Par exemple, les administrateurs doivent être en mesure de déployer des versions de correctif de bogues sans exiger que chaque application soit recompilée pour récupérer le correctif. En outre, les administrateurs doivent être en mesure de spécifier qu’une version particulière d’un assembly ne doit jamais être utilisée si un trou de sécurité ou un autre bogue grave est détecté. La .NET Framework permet cette flexibilité dans la liaison de version via des stratégies de version.

Numéros de version de l’assembly

Chaque assembly a un numéro de version en quatre parties dans le cadre de son identité (autrement dit, la version 1.0.0.0 de certains assemblys et la version 2.1.0.2 sont des identités complètement différentes en ce qui concerne le chargeur de classe). L’inclusion de la version dans le cadre de l’identité est essentielle pour distinguer les différentes versions d’un assembly à des fins côte à côte.

Les parties du numéro de version sont principales, mineures, de build et de révision. Aucune sémantique n’est appliquée aux parties du numéro de version. Autrement dit, le CLR ne déduit pas la compatibilité ou toute autre caractéristique d’un assembly en fonction de la façon dont le numéro de version est attribué. En tant que développeur, vous êtes libre de modifier n’importe quelle partie de ce nombre comme vous le voyez. Même s’il n’existe aucune sémantique appliquée au format du numéro de version, les organisations individuelles trouveront probablement utile d’établir des conventions sur la façon dont le numéro de version est modifié. Cela permet de maintenir la cohérence au sein d’une organisation et facilite la détermination des éléments tels que la génération d’un assembly particulier. Une convention classique est la suivante :

Majeur ou mineur. Les modifications apportées à la partie principale ou mineure du numéro de version indiquent une modification incompatible. Dans cette convention, la version 2.0.0.0 est considérée comme incompatible avec la version 1.0.0.0. Par exemple, une modification incompatible serait une modification apportée aux types de certains paramètres de méthode ou à la suppression d’un type ou d’une méthode complètement.

Génération : Le numéro de build est généralement utilisé pour faire la distinction entre les builds quotidiennes ou les versions plus petites compatibles.

Révision. Les modifications apportées au numéro de révision sont généralement réservées à une build incrémentielle nécessaire pour corriger un bogue particulier. Vous entendez parfois ce que l’on appelle le numéro de « correctif de bogue d’urgence » dans la mesure où la révision est souvent modifiée lorsqu’un correctif à un bogue spécifique est expédié à un client.

Stratégie de version par défaut

Lors de la résolution des références à des assemblys partagés, le CLR détermine la version de la dépendance à charger lorsqu’elle se trouve dans une référence à cet assembly dans le code. La stratégie de version par défaut dans .NET est extrêmement simple. Lors de la résolution d’une référence, le CLR prend la version à partir du manifeste de l’assembly appelant et charge la version de la dépendance avec le même numéro de version exact. De cette façon, l’appelant obtient la version exacte qu’il a été construit et testé contre. Cette stratégie par défaut a la propriété dont elle protège les applications du scénario où une autre application installe une nouvelle version d’un assembly partagé dont dépend une application existante. Rappelez-vous qu’avant .NET, les applications existantes commencent à utiliser le nouveau composant partagé par défaut. Toutefois, dans .NET, l’installation d’une nouvelle version d’un assembly partagé n’affecte pas les applications existantes.

Stratégie de version personnalisée

Il peut arriver que la liaison à la version exacte avec laquelle l’application a été fournie n’est pas ce que vous voulez. Par exemple, un administrateur peut déployer un correctif de bogue critique sur un assembly partagé et souhaiter que toutes les applications utilisent cette nouvelle version, quelle que soit la version avec laquelle elles ont été générées. En outre, le fournisseur d’un assembly partagé peut avoir expédié une version de service à un assembly existant et souhaite que toutes les applications commencent à utiliser la version de service au lieu de la version d’origine. Ces scénarios et d’autres sont pris en charge dans le .NET Framework par le biais de stratégies de version.

Les stratégies de version sont indiquées dans les fichiers XML et sont simplement une demande de chargement d’une version d’un assembly au lieu d’une autre. Par exemple, la stratégie de version suivante dirige le CLR pour charger la version 5.0.0.1 au lieu de la version 5.0.0.0 d’un assembly appelé MarineCtrl :

 <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly
   <assemblyIdentity name="MarineCtrl" publicKeyToken="9335a2124541cfb9" />
      <bindingRedirect oldVersion="5.0.0.0" newVersion="5.0.0.1" />   
  </dependentAssembly>
</assemblyBinding>

En plus de rediriger d’un numéro de version spécifique vers un autre, vous pouvez également rediriger d’une plage de versions vers une autre version. Par exemple, la stratégie suivante redirige toutes les versions de 0.0.0.0 à 5.0.0.0 de MarineCtrl vers la version 5.0.0.1 :

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly
   <assemblyIdentity name="MarineCtrl" publicKeyToken="9335a2124541cfb9" />
      <bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.1" />   
  </dependentAssembly>
</assemblyBinding>

Niveaux de stratégie de version

Il existe trois niveaux auxquels la stratégie de version peut être appliquée dans .NET : stratégie spécifique à l’application, stratégie d’éditeur et stratégie à l’échelle de l’ordinateur.

Stratégie spécifique à l’application. Chaque application dispose d’un fichier de configuration facultatif qui peut spécifier la volonté de l’application de se lier à une version différente d’un assembly dépendant. Le nom du fichier de configuration varie en fonction du type d’application. Pour les fichiers exécutables, le nom du fichier de configuration est le nom de l’exécutable + une extension « .config ». Par exemple, le fichier de configuration pour « myapp.exe » serait « myapp.exe.config ». Les fichiers de configuration des applications ASP.NET sont toujours « web.config ».

stratégie Publisher. Bien que la stratégie spécifique à l’application soit définie par le développeur ou l’administrateur de l’application, la stratégie d’éditeur est définie par le fournisseur de l’assembly partagé. Publisher stratégie est la déclaration de compatibilité du fournisseur concernant différentes versions de son assembly. Par exemple, supposons que le fournisseur d’un contrôle de Windows Forms partagé envoie une version de service qui contient un certain nombre de correctifs de bogues pour le contrôle. Le contrôle d’origine était la version 2.0.0.0 et la version de la version du service est 2.0.0.1. Étant donné que la nouvelle version contient simplement des correctifs de bogues (aucune modification de l’API cassant), le fournisseur de contrôle émettrait probablement une stratégie d’éditeur avec la nouvelle version qui entraîne l’utilisation des applications existantes qui utilisaient la version 2.0.0.0.1. Publisher stratégie est exprimée en XML tout comme l’application et la stratégie à l’échelle de l’ordinateur, mais contrairement à ces autres niveaux de stratégie, la stratégie d’éditeur est distribuée en tant qu’assembly lui-même. La principale raison de cela est de s’assurer que l’organisation qui publie la stratégie pour un assembly particulier est la même organisation que celle qui a publié l’assembly lui-même. Pour ce faire, vous devez exiger que l’assembly d’origine et l’assembly de stratégie reçoivent un nom fort avec la même paire de clés.

Stratégie à l’échelle de l’ordinateur. Le niveau de stratégie final est une stratégie à l’échelle de l’ordinateur (parfois appelée stratégie d’administrateur). La stratégie à l’échelle de l’ordinateur est stockée dans machine.config qui se trouve dans le sous-répertoire « config » sous le répertoire d’installation .NET Framework. Le répertoire d’installation est %windir%\microsoft.net\framework\%runtimeversion%. Les instructions de stratégie effectuées dans machine.config affectent toutes les applications s’exécutant sur l’ordinateur. La stratégie à l’échelle de l’ordinateur est utilisée par les administrateurs pour forcer toutes les applications sur un ordinateur donné à utiliser une version particulière d’un assembly. Le scénario de commentaire le plus utilisé est le moment où une sécurité ou un autre correctif de bogue critique a été déployé sur le Global Assembly Cache. Après avoir déployé l’assembly fixe, l’administrateur utilise la stratégie de version à l’échelle de l’ordinateur pour s’assurer que les applications n’utilisent pas l’ancienne version rompue de l’assembly.

Évaluation de a stratégie

La première chose que fait le CLR lors de la liaison à un assembly fortement nommé est de déterminer la version de l’assembly à lier. Le processus commence par lire le numéro de version de l’assembly souhaité qui a été enregistré dans le manifeste de l’assembly faisant la référence. La stratégie est ensuite évaluée pour déterminer si l’un des niveaux de stratégie contient une redirection vers une autre version. Les niveaux de stratégie sont évalués pour commencer par la stratégie d’application, suivis par l’éditeur et enfin l’administrateur.

Une redirection trouvée à n’importe quel niveau remplace toute instruction effectuée par un niveau précédent. Par exemple, supposons que l’assembly A référence l’assembly B. La référence à B dans le manifeste d’A est la version 1.0.0.0.0. En outre, la stratégie d’éditeur fournie avec l’assembly B redirige la référence de 1.0.0.0 vers 2.0.0.0.0. En outre, la stratégie de version est le fichier de configuration à l’échelle de l’ordinateur qui dirige la référence à la version 3.0.0.0.0. Dans ce cas, l’instruction effectuée au niveau de l’ordinateur remplace l’instruction effectuée au niveau de l’éditeur.

Contournement de la stratégie de Publisher

En raison de l’ordre dans lequel les trois types de stratégie sont appliqués, la redirection de version de l’éditeur (stratégie d’éditeur) peut remplacer la version enregistrée dans les métadonnées de l’assembly appelant et toute stratégie spécifique à l’application qui a été définie. Toutefois, forcer une application à toujours accepter la recommandation d’un éditeur concernant le contrôle de version peut revenir à DLL Hell. Après tout, DLL Hell est principalement dû à la difficulté de maintenir la compatibilité descendante dans les composants partagés. Pour éviter davantage le scénario où une application est interrompue par l’installation d’une nouvelle version d’un composant partagé, le système de stratégie de version dans .NET permet à une application individuelle de contourner la stratégie d’éditeur. En d’autres termes, une application peut refuser la recommandation de l’éditeur concernant la version à utiliser. Une application peut contourner la stratégie d’éditeur à l’aide de l’élément « publisherPolicy » dans le fichier de configuration de l’application :

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

<publisherPolicy apply="no"/>

</assemblyBinding>

Définition de stratégies de version avec l’outil de configuration .NET

Heureusement, le .NET Framework est fourni avec un outil d’administration graphique, ce qui vous évite d’avoir à vous soucier de la modification manuelle des fichiers de stratégie XML. L’outil prend en charge la stratégie de version à l’échelle de l’application et de l’ordinateur. Vous trouverez l’outil dans la section Outils d’administration de Panneau de configuration. L’écran initial de l’outil d’administration ressemble à la figure 6 :

Figure 6. Outil d’administration

Les étapes suivantes décrivent comment définir une stratégie spécifique à l’application :

  1. Ajoutez votre application au nœud Applications dans l’arborescence. Cliquez avec le bouton droit sur le nœud Applications, puis cliquez sur Ajouter. La boîte de dialogue Ajouter affiche une liste d’applications .NET à choisir. Si votre application ne figure pas dans la liste, vous pouvez l’ajouter en cliquant sur Autre.

  2. Choisissez l’assembly pour lequel vous souhaitez définir la stratégie. Cliquez avec le bouton droit sur le nœud Assemblys configurés, puis cliquez sur Ajouter. L’une des options consiste à sélectionner un assembly dans la liste des assemblys référencés par l’application et à afficher la boîte de dialogue suivante, comme illustré dans la figure 7. Sélectionnez un assembly, puis cliquez sur Sélectionner.

    Figure 7. Choix d’un assembly

  3. Dans la boîte de dialogue Propriétés , entrez les informations de stratégie de version. Cliquez sur l’onglet Stratégie de liaison et entrez les numéros de version souhaités dans le tableau, comme illustré dans la figure 8.

    Figure 8. Onglet Stratégie de liaison

Déploiement

Le déploiement implique au moins deux aspects différents : empaqueter le code et distribuer les packages aux différents clients et serveurs sur lesquels l’application s’exécutera. L’objectif principal du .NET Framework est de simplifier le déploiement (en particulier l’aspect de la distribution) en rendant possible l’installation sans impact et le déploiement xcopy. La nature auto-décrivant des assemblys nous permet de supprimer notre dépendance sur le Registre, ce qui simplifie considérablement l’installation, la désinstallation et la réplication. Toutefois, il existe des scénarios où xcopy n’est pas suffisant ou approprié en tant que mécanisme de distribution. Dans ce cas, le .NET Framework fournit des services de téléchargement de code complets et une intégration avec le programme d’installation Windows.

Packaging

Il existe trois options d’empaquetage disponibles dans la première version du .NET Framework :

  • As-built (DLL et EXEs). Dans de nombreux scénarios, aucun empaquetage spécial n’est nécessaire. Une application peut être déployée dans le format produit par l’outil de développement. Autrement dit, une collection de DLL et d’EXEs.
  • Fichiers cab. Les fichiers Cab peuvent être utilisés pour compresser votre application pour des téléchargements plus efficaces.
  • packages du programme d’installation Windows. Microsoft Visual Studio .NET et d’autres outils d’installation vous permettent de générer des packages Windows Installer (fichiers .msi). Le programme d’installation Windows vous permet de tirer parti de la réparation des applications, de l’installation à la demande et d’autres fonctionnalités de gestion des applications microsoft Windows.

Scénarios de distribution

Les applications .NET peuvent être distribuées de différentes façons, notamment xcopy, téléchargement de code et via le programme d’installation de Windows.

Pour de nombreuses applications, y compris les applications web et les services web, le déploiement est aussi simple que la copie d’un ensemble de fichiers sur le disque et leur exécution. La désinstallation et la réplication sont tout aussi simples : supprimez simplement les fichiers ou copiez-les.

Le .NET Framework fournit une prise en charge complète du téléchargement de code à l’aide d’un navigateur Web. Plusieurs améliorations ont été apportées dans ce domaine, notamment :

  • Aucun impact. Aucune entrée de Registre n’est effectuée sur l’ordinateur.
  • Téléchargement incrémentiel. Les éléments d’un assembly sont téléchargés uniquement à mesure qu’ils sont référencés.
  • Téléchargez isolément l’application. Le code téléchargé pour le compte d’une application ne peut pas affecter d’autres personnes sur l’ordinateur. L’objectif principal de notre prise en charge du téléchargement de code est d’empêcher le scénario où un utilisateur télécharge une nouvelle version d’un composant partagé en tant qu’effet secondaire de la navigation sur un site Web particulier et d’avoir cette nouvelle version affecter négativement d’autres applications.
  • Aucune boîte de dialogue Authenticode. Le système de sécurité d’accès au code est utilisé pour permettre au code mobile de s’exécuter avec un niveau de confiance partiel. Les utilisateurs ne seront jamais présentés avec des boîtes de dialogue leur demandant de décider s’ils approuvent le code.

Enfin, .NET est entièrement intégré au programme d’installation Windows et aux fonctionnalités de gestion des applications de Windows.

Résumé

Le .NET Framework active l’installation sans impact et traite DLL Hell. Les assemblys sont les unités de déploiement autodéscriptibles et pouvant être utilisées pour activer ces fonctionnalités.

La possibilité de créer des applications isolées est cruciale, car elle permet aux applications d’être générées qui ne seront pas affectées par les modifications apportées au système par d’autres applications. Le .NET Framework encourage ce type d’application par le biais d’assemblys privés d’application déployés dans la structure de répertoires de l’application.

Côte à côte est une partie principale de l’article de partage et de contrôle de version dans .NET. Côte à côte permet d’installer et d’exécuter simultanément plusieurs versions d’un assembly sur l’ordinateur, et permet à chaque application de demander une version spécifique de cet assembly.

Le CLR enregistre les informations de version entre les éléments d’une application et utilise ces informations au moment de l’exécution pour garantir que la version appropriée d’une dépendance est chargée. Les stratégies de version peuvent être utilisées par les développeurs d’applications et les administrateurs pour fournir une certaine flexibilité en choisissant la version d’un assembly donné chargée.

Il existe plusieurs options d’empaquetage et de distribution fournies par le .NET Framework, notamment le programme d’installation Windows, le téléchargement de code et la simple xcopy.