Simplification du déploiement et résolution des problèmes liés aux DLL avec le .NET Framework

 

Steven Pratschner
Microsoft Corporation

Mise à jour de novembre 2001

Résumé: Cet article présente le concept d’assembly et décrit comment .NET Framework utilise les 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 : les 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 destinées à simplifier le déploiement d’applications et à résoudre l’enfer des DLL. 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 survenir avec les systèmes basés sur des composants d’aujourd’hui. Par exemple, pratiquement tous les utilisateurs finaux ont installé une nouvelle application sur leur ordinateur, pour constater qu’une application existante cesse mystérieusement de fonctionner. La plupart des développeurs ont également passé du temps avec Regedit, essayant de maintenir la cohérence de toutes les entrées de Registre nécessaires afin d’activer une classe COM.

Les instructions 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 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 des 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 (pour plus d’informations, voir How To Build and Service Isolated Applications and Side-by-Side Assemblyes for Windows XP ).

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

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

Énoncé du problème

Gestion de version

Du point de vue du client, le problème de contrôle de version le plus courant est ce que nous appelons DLL Hell. En termes simples, DLL Hell fait référence à l’ensemble des problèmes provoqué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 rétrocompatible avec la version déjà présente sur l’ordinateur. Bien que l’application qui vient d’être installée fonctionne correctement, il se peut que les applications existantes qui dépendaient d’une version précédente du composant partagé ne fonctionnent plus. Dans certains cas, la cause du problème est encore plus subtile. Par exemple, considérez 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 qui étaient présentes sur l’ordinateur. Si une application qui a été installée sur l’ordinateur utilise 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 souvenir d’avoir installé 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 disponibles pour aider l’utilisateur (ou la personne de support qui l’aide) à déterminer ce qui ne va pas.

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 affectent généralement toutes les applications sur l’ordinateur. Il n’est pas facile de créer une application aujourd’hui complètement isolée des modifications.

L’une des raisons pour lesquelles 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 avec la rétrocompatibilité, sinon ils risquent de casser les applications existantes lorsqu’ils installent un nouveau composant. Dans la pratique, l’écriture d’un code toujours rétrocompatible est extrêmement difficile, voire impossible. Dans .NET, la notion de côte à côte est au cœur de l’histoire du 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 sur le système.

La séparation entre les entrées dans le 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 faible. Ces entrées incluent souvent des entrées pour les coclasses, les interfaces, les typeslibs et les ID d’application DCOM, pour ne pas mention les entrées effectuées pour inscrire des extensions de document ou des catégories de composants. Souvent, vous finissez par les synchroniser manuellement.

Enfin, cet encombrement du Registre est requis 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 créer les entrées de Registre appropriées.

Ces problèmes sont principalement dus à la description d’un composant séparé du composant lui-même. En d’autres termes, les applications ne sont ni auto-décrivantes ni autonomes.

Caractéristiques de la solution

Le .NET Framework doit fournir les fonctionnalités de base suivantes pour résoudre les problèmes décrits précédemment :

  • Les applications doivent être auto-décrites. Les applications qui s’auto-décrivent suppriment la dépendance vis-à-vis du Registre, ce qui permet l’installation sans impact et simplifie 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 garantir 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, des outils doivent être fournis pour permettre aux administrateurs de rétablir facilement les applications à cet état « dernier bon connu ».
  • Prise en charge des composants côte à côte. Autoriser l’installation et l’exécution simultanée de plusieurs versions d’un composant sur l’ordinateur permet aux appelants de spécifier la version qu’ils souhaitent charger au lieu d’une version « forcée » sans le savoir. Le .NET Framework fait un pas de plus en plus loin en permettant à plusieurs versions du framework lui-même de coexister sur un seul ordinateur. Cela simplifie considérablement l’histoire de la mise à niveau, car un administrateur peut choisir d’exécuter différentes applications sur différentes versions du .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 à la machine pour le compte d’autres applications.

Assemblys : les 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 qui viennent d’être décrits. Les assemblys sont l’unité de déploiement pour les types et les ressources. À bien des égards, un assembly équivaut à une DLL dans le monde d’aujourd’hui ; en substance, les assemblys sont des « DLL logiques ».

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

Les assemblys sont bien plus que le déploiement. Par exemple, le contrôle de version dans .NET s’effectue au niveau de l’assembly: rien de plus petit, tel qu’un module ou un type, n’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 du code utilise des assemblys au cœur de son modèle d’autorisations. L’auteur d’un assembly enregistre dans le manifeste le jeu d’autorisations requis 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, car ils établissent une limite de visibilité pour les types et servent d’étendue d’exécution pour la résolution des 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 création du manifeste. Ce hachage est vérifié au moment de l’exécution pour s’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, qui est utilisé au moment de l’exécution pour garantir 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 « visible pour les appelants en dehors de mon assembly ».
  • Demandes d’autorisation. Les demandes d’autorisation pour un assembly sont regroupées en trois ensembles : 1) celles requises pour l’exécution de l’assembly, 2) celles qui sont souhaitées, mais l’assembly disposera toujours de certaines fonctionnalités même si elles ne sont pas accordées, et 3) celles que l’auteur ne souhaite jamais que l’assembly soit accordé.

L’outil sdk Ildasm (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 extern .assembly contiennent les informations sur les autres assemblys dont celui-ci 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 de langage intermédiaire (IL) 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 des types ou des ressources sont nécessaires pour donner à l’assembly des 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’ensemble de l’assembly : le manifeste, les métadonnées de type, le code IL et les ressources.

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

Le contenu d’un assembly peut également être réparti sur plusieurs fichiers. Dans la figure 3, l’auteur a choisi de séparer un code d’utilitaire en une autre DLL et de conserver un fichier de ressources volumineux (dans ce cas, un fichier JPEG) dans son fichier d’origine. L’une des raisons pour lesquelles cela peut être effectué est d’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 rarement consultés, leur fractionner en 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 générez chaque fichier (module) séparément, puis regroupez-les 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

Gestion des versions 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 a 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 rétrocompatible, cela peut être correct, mais dans de nombreux cas, le maintien de la compatibilité descendante est difficile, voire 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 de l’installation d’autres applications.

Une règle de conception principale dans .NET est celle des composants isolés (ou assemblys). L’isolement d’un assembly signifie qu’un assembly n’est 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 au développeur un contrôle absolu 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 aux composants isolés a commencé 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 lors de la recherche du composant demandé. (Consultez l’article connexe 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 applications est nécessaire. Il n’est clairement pas logique que chaque application contienne sa propre copie de System.Windowns.Forms, System.Web ou un contrôle Web Forms commun.

Dans .NET, le partage de code entre 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 globalement unique.

Le besoin d’isolation et de partage nous amène à penser à deux « types » d’assemblys. Il s’agit d’une catégorisation assez lâche en ce qu’il n’y a pas de véritables différences structurelles entre les deux, mais la différence réside plutôt dans la façon dont ils seront utilisés: qu’ils soient privés à une application ou partagés entre plusieurs.

assemblys Application-Private

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

Les assemblys privés d’application sont déployés dans la structure de répertoires de l’application dans laquelle ils sont utilisés. Les assemblys privés peuvent être placés directement dans le répertoire de l’application ou dans un sous-répertoire de celui-ci. Le CLR recherche ces assemblys par le biais d’un processus appelé sondage. Le sondage 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 dans 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 ». (Pour plus d’informations, consultez le Guide du KIT de développement logiciel (SDK) .NET Framework.)

Comme décrit précédemment, 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 dispose d’un contrôle total sur les assemblys déployés dans le répertoire de l’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 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 la description côte à côte précédemment, les assemblys partagés ont des exigences de nommage beaucoup plus strictes. Par exemple, un assembly partagé doit avoir un nom globalement unique. En outre, le système doit prévoir la « protection du nom », c’est-à-dire empêcher une personne de réutiliser le nom d’assembly d’un autre. Par exemple, supposons que vous êtes 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’être assuré que personne d’autre ne peut libérer un assembly prétendant être la version 2 ou votre contrôle de grille. Le .NET Framework prend en charge ces exigences de nommage 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 obligatoire. En plus d’un répertoire d’application privé, un assembly partagé peut également être déployé dans le Global Assembly Cache ou dans n’importe quelle URL tant qu’un codebase décrivant l’emplacement de l’assembly est fourni dans le fichier de configuration de l’application. Le global assembly cache 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 cela présente certains avantages. 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 veulent que chaque application de l’ordinateur utilise. Enfin, quelques améliorations des performances sont associées au déploiement dans le global assembly cache. La première implique la vérification des signatures de nom fort, comme décrit dans la section Nom fort ci-dessous. La deuxième amélioration des performances implique l’ensemble de travail. Si plusieurs applications utilisent le même assembly simultanément, 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 s’effectue généralement à l’aide d’un programme d’installation basé sur Windows Installer ou une autre technologie d’installation. Les assemblys ne se retrouvent jamais dans le cache comme un 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 de la part de l’utilisateur. Windows Installer 2.0, qui est fourni avec Windows XP et Visual Studio .NET, a été amélioré pour bien comprendre le concept des assemblys, du cache d’assemblys et des applications isolées. Cela signifie que vous pourrez utiliser toutes les fonctionnalités de Windows Installer, telles que l’installation à la demande et la réparation d’applications, 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 des 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’assemblys. 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 Windows Shell qui vous permet de manipuler le cache à l’aide de Windows Explorer et de l’outil de configuration .NET Framework. Vous pouvez accéder à l’extension Shell en accédant au sous-répertoire « assembly » sous votre répertoire Windows. L’outil de configuration .NET Framework se trouve 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 :

  • Nom unicité. Les assemblys partagés doivent avoir des noms globalement uniques.
  • Empêcher l’usurpation de noms. Les développeurs ne veulent pas que quelqu’un d’autre publie une version ultérieure de l’un de vos assemblys et prétend faussement qu’elle vient de vous, soit par accident, soit intentionnellement.
  • Fournissez l’identité sur 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 du serveur de publication 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 en utilise une existante), signe le fichier contenant le manifeste avec la clé privée et met la clé publique à la disposition des 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 comment 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 qui transmet 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, le .NET Framework effectue deux étapes pour s’assurer que les noms forts donnent au développeur les avantages requis. Tout d’abord, la signature de nom fort de MyLib n’est vérifiée que 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 de Main à 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 qui a été chargée provient du même éditeur qui a créé la version de MyLib avec laquelle Main a été créé. Cette équivalence de clé case activée est effectuée au moment de l’exécution, lorsque la référence de Main à MyLib est résolue.

Le terme « signature » rappelle souvent Microsoft Authenticode®. Il est important de comprendre que les noms forts et Authenticode ne sont en aucun cas liés. Les deux techniques ont des objectifs différents. En particulier, Authenticode implique un niveau de confiance associé à un éditeur, contrairement aux noms forts. Aucun certificat ou autorité de signature tierce n’est associé à des noms forts. En outre, la signature de nom forte 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 arrive souvent que l’auteur d’un assembly n’ait 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 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 de développement logiciel (SDK) .NET Framework.

Stratégie de version

Comme décrit précédemment, chaque manifeste d’assembly enregistre des informations sur la version de chaque dépendance sur 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 autre version d’une dépendance au moment de l’exécution. Par exemple, les administrateurs doivent être en mesure de déployer des versions de correctifs 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 n’est jamais utilisée si un problème de sécurité ou un autre bogue grave est détecté. Le .NET Framework offre 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 classes). 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 majeures, mineures, build et révision. Aucune sémantique n’est appliquée aux parties du numéro de version. Autrement dit, le CLR n’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 bon vous semble. Même si aucune sémantique n’est 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 tout au long d’un organization et de déterminer plus facilement d’où provient 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. En vertu de cette convention, la version 2.0.0.0 serait considérée comme incompatible avec la version 1.0.0.0. Les exemples d’une modification incompatible sont une modification des types de certains paramètres de méthode ou la suppression d’un type ou d’une méthode.

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

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 entendrez parfois ce numéro appelé « correctif de bogue d’urgence » dans la mesure où la révision est souvent modifiée lorsqu’un correctif à un bogue spécifique est envoyé à 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’il se trouve sur 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 du manifeste de l’assembly appelant et charge la version de la dépendance avec exactement le même numéro de version. De cette façon, l’appelant obtient la version exacte sur laquelle il a été construit et testé. Cette stratégie par défaut a la propriété qui 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 commençaient à 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é livrée ne correspond 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é créées. En outre, le fournisseur d’un assembly partagé peut avoir envoyé 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 .NET Framework via des 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 indique au CLR de 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 à partir 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 autre version 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 d’éditeur. Alors que la stratégie spécifique à l’application est 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é. La stratégie d’éditeur est la déclaration de compatibilité du fournisseur concernant les différentes versions de son assembly. Par exemple, supposons que le fournisseur d’un contrôle 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 uniquement des correctifs de bogues (aucune modification d’API cassant), le fournisseur de contrôle émettrait probablement une stratégie d’éditeur avec la nouvelle version, ce qui oblige les applications existantes qui utilisaient la version 2.0.0.0.0 à utiliser maintenant la version 2.0.0.1. La stratégie d’éditeur 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 elle-même. La raison principale est de s’assurer que la organization la publication de la stratégie pour un assembly particulier est la même organization qui a libéré l’assembly lui-même. Pour ce faire, vous devez 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 la stratégie à l’échelle de l’ordinateur (parfois appelée stratégie 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 du .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 en cours d’exécution 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 le plus commenté dans lequel cela est utilisé est lorsqu’un correctif de sécurité ou d’autres bogues critiques a été déployé dans 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 cassée 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 à quelle version de l’assembly se 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 dans l’ordre en commençant par la stratégie d’application, puis 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 fait référence à l’assembly B. La référence à B dans le manifeste de A est à la version 1.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. 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. 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 d’éditeur

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 fois 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 prise de 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 afin que vous n’ayez pas à 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 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 à partir de laquelle 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 une stratégie. Cliquez avec le bouton droit sur le nœud Assemblys configurés, puis cliquez sur Ajouter. L’une des options consiste à choisir un assembly dans la liste des assemblys référencés par l’application, et il affiche 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’un des principaux objectifs du .NET Framework est de simplifier le déploiement (en particulier l’aspect distribution) en rendant l’installation sans impact et le déploiement xcopy réalisables. La nature auto-décrivant les assemblys nous permet de supprimer notre dépendance vis-à-vis du 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 à Windows Installer.

Packaging

Trois options d’empaquetage sont disponibles dans la première version du .NET Framework :

  • As-built (DLL et EXE). Dans de nombreux scénarios, aucun empaquetage spécial n’est requis. Une application peut être déployée au format produit par l’outil de développement. Autrement dit, une collection de DLL et d’EXE.
  • Fichiers cab. Les fichiers cab peuvent être utilisés pour compresser votre application pour des téléchargements plus efficaces.
  • Packages Windows Installer. Microsoft Visual Studio .NET et d’autres outils d’installation vous permettent de créer des packages Windows Installer (.msi fichiers). Windows Installer vous permet de tirer parti de la réparation d’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 manières, notamment xcopy, le téléchargement de code et par le biais de Windows Installer.

Pour de nombreuses applications, y compris les applications web et les services web, le déploiement est aussi simple que de copier un ensemble de fichiers sur le disque et de les exécuter. La désinstallation et la réplication sont tout aussi simples : il suffit de supprimer les fichiers ou de les copier.

Le .NET Framework offre une prise en charge étendue 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échargement isolé dans l’application. Le code téléchargé pour le compte d’une application ne peut pas affecter les autres sur l’ordinateur. L’un des principaux objectifs 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 que cette nouvelle version affecte 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 confrontés à des boîtes de dialogue leur demandant de décider s’ils font confiance au code.

Enfin, .NET est entièrement intégré à Windows Installer et aux fonctionnalités de gestion des applications de Windows.

Résumé

Le .NET Framework permet l’installation sans impact et traite DLL Hell. Les assemblys sont les unités de déploiement auto-décrivant et pouvant faire l’objet d’une version, utilisées pour activer ces fonctionnalités.

La possibilité de créer des applications isolées est essentielle, car elle permet de créer des applications 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 essentielle de l’histoire du partage et du 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 parties d’une application et utilise ces informations au moment de l’exécution pour s’assurer que la version appropriée d’une dépendance est chargée. Les stratégies de version peuvent être utilisées à la fois par les développeurs d’applications et les administrateurs pour offrir une certaine flexibilité dans le choix de la version d’un assembly donné qui est chargée.

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