Mettez des Add-ins dans votre application avec le Framework 3.5

Florian Casabianca

Rendre une application extensible passe par la possibilité d'y ajouter des add-ins. Plus facile à dire qu'à faire. De nombreux points sont à prendre en compte et les problèmes à résoudre ne manquent pas. L'arrivée de la version 3.5 du Framework .Net et du nouvel espace de noms System.AddIn va permettre une prise en charge beaucoup plus facile de ce genre de fonctionnalité au sein de nos applications.

N'hésitez pas à commenter cet article ! Commentez ♪

- Titre : Mettez des Add-ins dans votre application avec le Framework 3.5

- Auteur : Florian Casabianca

- Parution : 2 janvier 2008

- Mise à jour : 2 janvier 2008

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2008 Florian Casabianca. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.

Sommaire

- Introduction

- I. Un peu de théorie

- I-A. Vous avez dit Add-in ?

- I-B. Les grands principes de développement

- I-B-1. Trois types de développeurs

- I-C. Aperçu des fonctionnalités

- I-C-1. Découverte

- I-C-2. Activation

- I-C-3. Isolation

- I-C-4. Déchargement

- I-C-5. Sandboxing

- II. L'architecture du pipeline

- II-A. Les composants

- II-B. L'architecture des répertoires

- III. Exemple de création d'un add-in

- III-A. Présentation de l'exemple

- III-B. Architecture utilisée

- III-C. Organisation de la solution

- III-D. Le contrat

- III-E. Côté Add-in

- III-E-1. La vue

- III-E-2. L'add-in

- III-E-3. L'adaptateur

- III-F. Côté Hôte

- III-F-1. La vue

- III-F-2. L'adaptateur

- III-G. L'hôte

- III-G-1. L'application

- III-G-2. Découverte d'add-ins

- III-G-2-a. La mise en cache des informations

- III-G-2-b. Recherche d'add-ins

- III-G-3. Activation

- III-G-4. Utilisation

- III-G-5. Déchargement

- IV. Compatibilité ancien add-in, nouvel hôte

- IV-A. Présentation de la mise à jour

- IV-B. Nouveau contrat

- IV-C. Modifications côté hôte

- IV-C-1. La vue

- IV-C-2. L'adaptateur

- IV-C-3. L'hôte

- IV-D. Création d'un add-in V2

- IV-D-1. La vue

- IV-D-2. L'add-in

- IV-D-3. L'adaptateur

- IV-E. Compatibilité descendante avec un ancien add-in

- IV-E-1. État des lieux

- IV-E-2. L'adaptateur

- IV-F. Test de l'application

- V. Conclusion

- VI. Liens

- Sources

- Remerciements

- Contact

Introduction

Rendre une application extensible passe par la possibilité d'y ajouter des add-ins. Plus facile à dire qu'à faire. De nombreux points sont à prendre en compte et les problèmes à résoudre ne manquent pas. L'arrivée de la version 3.5 du Framework .Net et du nouvel espace de noms System.AddIn va permettre une prise en charge beaucoup plus facile de ce genre de fonctionnalité au sein de nos applications.

I - Un peu de théorie

I-A - Vous avez dit Add-in ?

Un add-in est un fragment de code (une assembly) chargé et activé de façon dynamique par une application hôte dans certaines circonstances (démarrage de l'hôte, demande de l'utilisateur, etc.). Un add-in est généralement écrit par une personne différente de celle qui a conçu l'application. Cela implique que l'add-in n'a pas forcément été testé de façon poussée. Ainsi, l'application hôte doit pouvoir utiliser un add-in tout en étant protégée de tout problème survenant du côté de l'add-in (instabilité, sécurité, etc.).

Les scénarios d'utilisation d'add-ins peuvent se regrouper dans trois catégories :

- Fournir un service. Ici l'add-in apporte un service dont l'hôte va se servir. C'est par exemple le cas d'un add-in qui permettrait à un logiciel de traitement de texte d'exporter un fichier dans un nouveau format ;

- L'automation. Ici l'add-in définit la façon dont va se comporter l'hôte. C'est par exemple le cas avec les add-ins Office ou les add-ins Windows Live Messenger dont un article se trouve ici ;

- Le « parasitage ». L'add-in se comporte ici un peu comme un parasite et utilise l'hôte simplement comme support. Il ne contrôle pas l'hôte et ne lui offre pas véritablement un service. Il offre des fonctionnalités à l'utilisateur en s'intégrant à l'hôte, mais pourrait tout aussi bien fournir les mêmes fonctionnalités s'il été intégré à un hôte différent. C'est le cas par exemple des barres d'outils qui s'installent dans les navigateurs ou dans la barre des tâches.

Dans l'exemple de création d'un add-in que nous verrons un peu plus tard, nous concevrons un add-in se situant dans la première catégorie.

I-B - Les grands principes de développement

Le « versionning » et l'isolation des composants sont des points critiques pour la stabilité à long terme d'une application. Une des conséquences importantes est qu'un type (au sens objet) appartenant à un add-in ne devra jamais être chargé dans le domaine d'application de l'hôte.

L'application hôte et ses add-ins doivent pouvoir évoluer de façon indépendante (aucune référence directe de l'un sur l'autre). Cela signifie par exemple qu'une nouvelle version de l'hôte doit pouvoir (c'est une possibilité, pas une obligation) utiliser des add-ins développés pour l'ancienne version de l'application (compatibilité descendante). La compatibilité ascendante (capacité pour des add-ins écrits pour une version V2 d'une application de fonctionner avec une version V1) peut aussi être assurée (mais difficile à obtenir).

« Design for success ». La mise en place de l'architecture proposée par Microsoft pour le support d'add-ins va engendrer du travail supplémentaire pour la première version de votre application. Mais le respect de ce modèle permettra d'assurer le succès du développement des futures versions, notamment dans la prise en compte du « versionning » ou de l'isolation des composants. Il est donc important de comprendre qu'une application bien construite dès le départ (ici pour la prise en compte d'add-ins) vous permettra de la faire évoluer sans que la nouvelle version casse la compatibilité avec l'ancienne.

I-B-1 - Trois types de développeurs

Le modèle de développement proposé permet de découper le travail à effectuer en trois parties (trois types de tâches distinctes). Chaque partie sera prise en charge par un développeur différent (ce n'est pas une obligation, juste une possibilité de fractionner le travail). Vous aurez donc, par ordre de difficulté croissante.

Le développeur d'add-in. Un accent a été mis sur le besoin de faciliter au maximum le travail du développeur de compléments. Son seul souci doit être de coder les fonctionnalités de son add-in, pas de s'occuper des mécanismes de « versionning », d'isolation, etc. Ainsi, la création d'un add-in peut se résumer à ce bout de code :

[AddIn("mon addin", Version = "1.0.0.0",)]
public class monAddin : VueAddIn

Deux choses sont à noter. La première est l'ajout d'un tag AddIn (permettant de spécifier quelques métadonnées telles qu'une description, un numéro de version, etc.). La deuxième est qu'un add-in doit dériver d'une vue (Interface ou classe abstraite). Cette vue représente la façon dont l'add-in est vu par l'hôte (la façon dont l'hôte va se servir de l'add-in).

Le développeur de l'hôte. Celui-ci n'a pas un travail très difficile non plus. Il lui suffit de quelques lignes de codes pour découvrir, activer puis utiliser un add-in. Voici à peu près le code à utiliser :

IList<AddInToken> tokens = AddInStore.FindAddIns(typeof(AddInType), addinPath);

foreach(AddinToken token in tokens)
{
AddInType addinInstance = token.Activate<AddInType>(AddInSecurityLevel.FullTrust);
}

Le développeur du pipeline. C'est ce dernier qui aura la tâche la plus délicate. Il devra développer un modèle permettant à l'hôte et à ses add-ins de communiquer entre eux, indépendamment de la version de l'un ou l'autre. Nous allons dans la prochaine partie détailler les différents composants du pipeline.

I-C - Aperçu des fonctionnalités

Voici un petit récapitulatif des fonctionnalités offertes par cette nouvelle API.

I-C-1 - Découverte

- Via des vues/types (au sens objet) et répertoires spécifiques.

- Lecture des métadonnées sans avoir besoin de charger ou activer l'add-in.

- Aucune déclaration (contrairement aux objets COM) n'a besoin d'être faite sur la machine pour ajouter un add-in à un hôte. Un simple copier/coller peut suffire.

- Le pipeline et les add-ins sont physiquement séparés (répertoires différents).

I-C-2 - Activation

- Sélection du contexte d'isolation (utilisation d'un domaine d'application personnalisé ou création d'un nouveau / utilisation d'un processus différent).

- Création du pipeline en chargeant les assemblies correspondantes.

- Instanciation de l'add-in.

- L'add-in est retourné à l'hôte sous forme de vue.

I-C-3 - Isolation

- Entre l'hôte et l'add-in.

- Entre les add-ins.

I-C-4 - Déchargement

- L'hôte peut à tout moment demander à décharger un add-in.

- Déchargement automatique lorsqu'il n'existe plus aucune référence à l'add-in dans l'hôte.

I-C-5 - Sandboxing

- Les add-ins doivent s'exécuter avec des permissions égales ou inférieures à celle de l'hôte.

- Une énumération permet de spécifier le niveau de sécurité accordé à l'add-in (Full Trust, Internet, Intranet).

- Possibilité de spécifier un ensemble personnalisé de permissions.

II - L'architecture du pipeline

II-A - Les composants

Nous allons ici présenter les différents composants du pipeline permettant la communication entre l'hôte et l'add-in. C'est sans doute la partie la plus délicate à comprendre (et à expliquer.).

Voici un schéma présentant l'architecture générale du pipeline :

Deux points sont essentiels à noter :

- le premier est que chaque composant est en fait matérialisé par une assembly différente ;

- le deuxième est la direction des dépendances statiques. L'hôte et l'add-in ne dépendent seulement que d'une vue. Et cette vue n'a de dépendance sur aucun autre composant.

Les vues hôte et complément (add-in) sont des assemblies ne contenant rien d'autre que des classes abstraites (ou des interfaces) représentants la façon dont l'hôte et l'add-in se voient mutuellement. L'hôte ne connait de l'add-in que ce qui est défini dans la vue hôte, et l'add-in ne connait de l'hôte que ce qui est défini dans la vue complément.

Les contrats sont des interfaces définissant le protocole permettant de communiquer à travers la frontière séparant l'hôte et l'add-in. Les contrats doivent hériter de IContract, doivent être des interfaces (au sens POO) et ne peuvent être « versionnés ». Ils n'ont de références sur aucun autre composant. Les types contenus dans l'assembly des contrats sont les seuls à être chargés des deux côtés de la limite d'isolement. Ni l'hôte ni l'add-in ne font directement référence au contrat, on utilise une couche intermédiaire, les adaptateurs.

Les adaptateurs ont un rôle stratégique. Ils ont pour objectif de convertir les vues en contrat et les contrats en vue. C'est dans ces adaptateurs que sera écrit le code permettant d'assurer une compatibilité entre nouvelle et ancienne version de l'hôte et de l'add-in. Pour mieux comprendre ce qu'est un adaptateur, vous êtes invité à lire l'article suivant : Comprendre le design Pattern Adaptateur.

Comme vous le voyez, l'hôte ne possède jamais de référence au contrat ni de référence à l'adaptateur lui-même (idem pour l'add-in). Ainsi, nous avons la possibilité d'avoir une nouvelle version du contrat, les adaptateurs s'occupant de maintenir une compatibilité avec les anciens composants.

Le schéma suivant présente ce principe :

Nous avons ici une nouvelle version de l'hôte (V2). Un nouveau contrat a été défini afin de ternir compte de ces nouveautés (le contrat V1 n'existe plus). Comment un add-in développé pour la version 1 de l'hôte va-t-il pouvoir continuer à fonctionner avec la version 2 ? Grâce aux adaptateurs. Un adaptateur (V1-> V2) a été développé et dont le but est de convertir le nouveau contrat (V2) en une vue (V1) compréhensible pour l'add-in (V1). Bien sûr, les add-ins spécialement développés pour l'hôte V2 utilisent une nouvelle vue (V2) et un adaptateur (V2) suivant le schéma traditionnel.

Vous aurez aussi compris que pour qu'une nouvelle version de l'hôte arrête de supporter les anciennes versions d'un add-in il lui suffit de ne pas proposer l'adaptateur adéquat.

Il devient aussi possible de différer le support d'anciens add-ins au sein d'une nouvelle version de l'hôte. Étant donné que les adaptateurs se trouvent dans des assemblies séparés de l'hôte (et des add-ins), il suffit de rajouter (dans le dossier des adaptateurs) l'assembly contenant l'adaptateur adéquat pour assurer le support d'une ancienne version d'un add-in (le tout sans devoir retoucher à l'hôte).

II-B - L'architecture des répertoires

Afin que le Framework puisse découvrir les différents composants du pipeline et activer les add-ins, ceux-ci doivent être placés dans des répertoires ayant une arborescence et des noms spécifiques.

Le schéma suivant montre l'arborescence requise :

Répertoire

Description

Pipeline root

C'est le répertoire racine du pipeline qui contiendra tous les autres répertoires listés ci-dessous. Il n'y a pas de restriction concernant son nom ni l'endroit où il se trouve (mais il est généralement dans le même répertoire que l'hôte).
Exemple : ..\Pipeline

AddIns

Optionnel. Ce répertoire contient un ou plusieurs autres répertoires. Chacun des ces sous-répertoires contient un add-in.Ce répertoire doit être appelé AddIns.
Exemple : ..\Pipeline\AddInsIl est aussi possible d'avoir des add-ins se trouvant ailleurs dans le système.

AddInSideAdapters

Ce répertoire contient tous les adaptateurs côté add-in.Ce répertoire doit être appelé AddInSideAdapters.
Exemple : ..\Pipeline\AddInSideAdapters

AddInViews

Ce répertoire contient toutes les vues côté add-in.Ce répertoire doit être appelé AddInViews.
Exemple : ..\Pipeline\AddInViews

Contracts

Ce répertoire contient les contrats.Ce répertoire doit être appelé Contracts.
Exemple : ..\Pipeline\Contracts

HostSideAdapters

Ce répertoire contient les adaptateurs côté hôte.Ce répertoire doit être appelé HostSideAdapters.
Exemple : ..\Pipeline\HostSideAdapters

III - Exemple de création d'un add-in

III-A - Présentation de l'exemple

Après la théorie, place à la pratique. Nous allons concevoir ici une petite application permettant d'être étendue via l'ajout d'add-ins. Nous verrons dans un premier temps le développement de l'hôte, du pipeline et d'un add-in, le tout en version 1. Puis nous étudierons les modifications à apporter afin d'assurer une compatibilité entre une nouvelle version de l'hôte et la première version de l'add-in.

Le concept de l'application exemple est assez simple. L'hôte sera représenté par un petit éditeur de texte. La sauvegarde dans un fichier du texte saisi ne sera pas directement prise en compte par l'hôte, mais par un add-in. L'hôte transmettra à l'add-in un objet String contenant le texte saisi par l'utilisateur et l'add-in se chargera de le sauvegarder dans un fichier sous un format spécifique. L'utilisateur aura juste à choisir l'add-in qu'il veut utiliser pour la sauvegarde. Ainsi nous aurons des add-ins permettant d'enregistrer au format texte, XML, PDF, OpenXML, etc.

Voici à quoi ressemblera l'application :

Une liste déroulante sera remplie avec la liste des add-ins disponibles. L'utilisateur choisira un add-in et cliquera sur le bouton Enregistrer afin de sauvegarder le texte saisi dans un format pris en charge par l'add-in.

III-B - Architecture utilisée

Avant de nous jeter dans le code, il nous faut avant tout comprendre comment celui-ci sera structuré. Nous avons vu précédemment des notions de contrat, vue, adaptateur et les références qu'ils avaient les uns sur les autres. Voyons maintenant concrètement comment tout cela va être organisé au niveau code, par rapport à notre exemple de départ.

Le diagramme ci-dessous présente les différentes classes utilisées dans le pipeline et leurs imbrications les unes avec les autres. N'hésitez à revenir y jeter un coup d'œil de temps en temps afin de mieux comprendre la suite de l'article.

Nous retrouvons les différents composants (vue, adaptateur, contrat) présentés précédemment.

III-C - Organisation de la solution

Nous allons tout d'abord créer une nouvelle solution vide dont la structure sera la suivante :

Créez les trois dossiers en faisant un clic droit sur la solution, Add, New Solution Folder.

La création des dossiers n'est pas obligatoire, mais permettra de mieux organiser notre code.

Tout au long du déroulement de l'exemple, nous ferons référence aux assemblies System.AddIn et System.AddIn.Contract. Vous les trouverez dans l'onglet .NET de la fenêtre d'ajout de référence :

III-D - Le contrat

Créez un nouveau projet de type Bibliothèque de classes dans le dossier Contracts et nommez-le Contrats.

Dans les propriétés du projet sous l'onglet Build, définissez le chemin de sortie sur ..\output\Pipeline\Contracts. Attention, si vous définissez ce chemin en mode Debug, il faudra le redéfinir en mode Release (idem pour les autres projets).

Le contrat sera défini par une interface héritant de IContract et qui contiendra les opérations que l'hôte fera sur l'add-in. Dans notre cas il n'y aura qu'une seule méthode EnregistrerDoc qui prendra en paramètre l'emplacement où l'on souhaite enregistrer le fichier ainsi que la chaine de caractères à sauvegarder.

N'oubliez pas la référence à System.AddIn.Contract ainsi que l'attribut AddInContract.

Définition du contrat

using System.AddIn.Contract;
using System.AddIn.Pipeline;

namespace Contrats
{
/// Définit le contrat entre l'hôte et l'addin.
[AddInContract]
public interface IContratSauverDoc : IContract
{
/// Enregistre le String passé en paramètre dans un fichier
bool EnregistrerDoc(String chemin, String texte);
}
}

III-E - Côté Add-in

III-E-1 - La vue

Créez un nouveau projet de type Bibliothèque de classes dans le dossier Partie_AddIn et nommez-le Addin.Vue.

Dans les propriétés du projet sous l'onglet Build, définissez le chemin de sortie sur ..\output\Pipeline\AddInViews.

La vue sera représentée par une classe abstraite et décorée par l'attribut AddInBase. Ajoutez une référence à System.AddIn.

Définition de la vue côté add-in

using System.AddIn.Pipeline;

namespace Addin.Vue
{
// Classe de base pour notre addin.
// Définit comment l'addin reçoit les appels de l'hôte
[AddInBase]
public abstract class VueAddInSauverDocCotéAddIn
{
public abstract bool EnregistrerDoc(String cheminFichier, String texte);
}
}

Cette classe représente la classe de base des add-ins. Toute personne souhaitant développer un add-in pour notre éditeur devra faire dériver son add-in de cette classe. Tout ce que l'add-in connait de l'hôte se trouve dans cette classe. L'add-in sait simplement que l'hôte (via le pipeline) va l'appeler via la fonction EnregistrerDoc.

III-E-2 - L'add-in

Chaque add-in dérive de la vue lui correspondant et sera déployé dans son propre répertoire.

Créez un nouveau projet de type Bibliothèque de classes dans le dossier Partie_AddIn\AddIns et nommez-le AddIn.TXT.

Dans les propriétés du projet sous l'onglet Build, définissez le chemin de sortie sur ..\output\Pipeline\AddIns\AddIn.TXT.

Attention à bien séparer les chemins de sortie des Add-ins. Si vous créez un deuxième Add-in (par exemple AddIn.PDF) celui -ci devra avoir pour chemin de sortie ..\output\Pipeline\AddIns\AddIn.PDF.

Ajoutez une référence à la vue (AddIn.Vue), mais veillez à mettre sa propriété Copy Local à FALSE (la dll de la vue ne doit pas être déployée avec l'add-in).

Ajoutez aussi une référence à System.AddIn.

Créez donc une classe SauverEnTXT qui dérive de la classe VueAddInSauverDocCotéAddIn et décorée par l'attribut AddIn. Ce dernier vous permet de spécifier les métadonnées de l'add-in. Il ne reste plus qu'à écrire le code de la méthode EnregistrerDoc.

Code de l'add-in SauverEnTXT

using Addin.Vue;
using System.AddIn;
using System.IO;

namespace AddIn.TXT
{
[AddIn("Format texte",
Version = "1.0.0.0",
Description = "AddIn permettant d'enregistrer un document sous format texte",
Publisher = "Badger")]
public class SauverEnTXT : VueAddInSauverDocCotéAddIn
{
public override bool EnregistrerDoc(string cheminFichier, string texte)
{
StreamWriter sw = File.CreateText(cheminFichier);
sw.Write(texte);
sw.Close();
return true;
}
}
}

III-E-3 - L'adaptateur

L'adaptateur côté add-in permet de convertir la vue de l'add-in pour qu'elle corresponde au Contrat.

Créez un nouveau projet de type Bibliothèque de classes dans le dossier Partie_AddIn et nommez-le AddIn.Adaptateurs. Vous y mettrez toutes les classes servant d'adaptateur pour les différentes versions des vues. Dans notre exemple nous n'aurons besoin que d'un seul adaptateur.

Dans les propriétés du projet sous l'onglet Build, définissez le chemin de sortie sur ..\output\Pipeline\AddInSideAdapters.

Ajoutez une référence à la vue (AddIn.Vue) et au contrat (Contrat), mais veillez à mettre leur propriété Copy Local à FALSE.

Ajoutez aussi des références à System.AddIn et System.AddIn.Contract.

Créez une classe AdaptateurVueAddInVersContratSauverDoc dérivant de ContractBase et implémentant le contrat IContratSauverDoc. N'oubliez pas de décorer la classe avec l'attribut AddInAdapter.

Créez ensuite un constructeur prenant en paramètre la vue qu'il est censé convertir (dans notre cas VueAddInSauverDocCotéAddIn) et gardez une référence à cette vue via un champ privé. Il ne reste plus qu'à écrire le code de la méthode EnregistrerDoc provenant du contrat que nous implémentons.

AdaptateurVueAddInVersContratSauverDoc

using System.AddIn.Pipeline;
using Addin.Vue;
using System.AddIn;

namespace AddIn.Adaptateurs
{
// Encapsule une instance de VueAddInSauverDocCotéAddIn
// pour pouvoir l'exposer en tant que IContratSauverDoc
[AddInAdapter]
public class AdaptateurVueAddInVersContratSauverDoc : ContractBase, Contrats.IContratSauverDoc
{
private VueAddInSauverDocCotéAddIn _vue;

// Constructeur avec un seul paramètre (l'addin) obligatoire
public AdaptateurVueAddInVersContratSauverDoc(VueAddInSauverDocCotéAddIn vue)
{
this._vue = vue;
}

Bool EnregistrerDoc(string chemin, string texte)
{
return this._vue.EnregistrerDoc(chemin, texte);
}
}
}

III-F - Côté Hôte

III-F-1 - La vue

La vue côté hôte définit la façon dont l'hôte voit l'add-in. Tout ce que l'hôte connait de l'add-in se trouve dans cette classe. L'hôte ne manipulera finalement que des types vue.

Créez un nouveau projet de type Bibliothèque de classes dans le dossier Partie_Hote et nommez-le Hote.Vue.

Dans les propriétés du projet sous l'onglet Build, définissez le chemin de sortie sur ..\output (au même niveau que l'hôte).

La vue que nous appellerons VueAddInSauverDocCotéHote sera représentée par une classe abstraite contenant les méthodes dont se servira l'hôte.

Définition de la vue côté hôte

namespace Hote.Vue
{
// Définit comment sera vu un addin (de type sauverDoc) par l'hôte
public abstract class VueAddInSauverDocCotéHote
{
public abstract bool EnregistrerDoc(String cheminFichier, String texte);
}

}

III-F-2 - L'adaptateur

Il est chargé de convertir le contrat afin qu'il corresponde à ce que voit l'hôte (c'est-à-dire la vue).

Créez un nouveau projet de type Bibliothèque de classes dans le dossier Partie_Hote et nommez-le Hote.Adaptateurs. Vous y mettrez les différents adaptateurs dont vous aurez besoin. Dans notre cas nous n'en créerons qu'un seul.

Dans les propriétés du projet sous l'onglet Build, définissez le chemin de sortie sur ..\output\Pipeline\HostSideAdapters.

Ajoutez une référence à la vue (Hote.Vue) et au contrat (Contrat), mais veillez à mettre leur propriété Copy Local à FALSE (les dll du contrat et de la vue ne doivent pas être déployées avec l'adaptateur).

Ajoutez aussi des références à System.AddIn et System.AddIn.Contract.

Créez une classe AdaptateurContratSauverDocVersVueHote dérivant de la vue VueAddInSauverDocCotéHote. N'oubliez pas de décorer la classe avec l'attribut HostAdapter.

Créez ensuite un constructeur prenant en paramètre le contrat qu'il est censé convertir (dans notre cas IContratSauverDoc) et gardez-en une référence via un champ privé. Il ne reste plus qu'à implémenter la méthode abstraite EnregistrerDoc provenant de la vue dont nous dérivons.

AdaptateurContratSauverDocVersVueHote

using Hote.Vue;
using Contrats;
using System.AddIn.Pipeline;

namespace Hote.Adaptateurs
{
// Convertit une instance de type IContratSauverDoc afin de la rendre
// accessible à l'hôte
// (qui ne manipule que des objets de type VueAddInSauverDocCotéHote)
[HostAdapter]
public class AdaptateurContratSauverDocVersVueHote : VueAddInSauverDocCotéHote
{
private IContratSauverDoc _contrat;

// Constructeur avec un seul paramètre (l'adaptateur coté l'addin)
// obligatoire
public AdaptateurContratSauverDocVersVueHote(IContratSauverDoc contrat)
{
this._contrat = contrat;
}

public override bool EnregistrerDoc(string cheminFichier, string texte)
{
return this._contrat.EnregistrerDoc(cheminFichier, texte);
}
}
}

III-G - L'hôte

Nous allons faire ici un focus sur l'hôte ainsi que sur les mécanismes permettant la découverte, l'activation et l'utilisation des add-ins.

III-G-1 - L'application

Créez un nouveau projet de type Windows Application dans le dossier Partie_Hote et nommez-le Hote.Adaptateurs.

Dans les propriétés du projet sous l'onglet Build, définissez le chemin de sortie sur ..\output. Définissez de plus ce projet en tant que projet de démarrage.

Ajoutez aussi une référence à System.AddIn ainsi qu'à la vue (Hote.Vue). L'hôte n'a besoin d'aucune autre référence (adaptateur ou add-in).

III-G-2 - Découverte d'add-ins

L'approche consistant à utiliser des attributs afin de définir un add-in a comme avantage la facilité d'utilisation. Le développeur a juste à créer une classe héritant de la classe abstraite de base, à appliquer l'attribut AddIn sur cette classe et c'est parti ! Cela a cependant l'inconvénient d'alourdir le processus de découverte des add-ins. Il faut en effet examiner chaque assembly afin de déterminer s'il s'agit d'un add-in et auquel cas de quel type d'add-in nous avons à faire. Ce processus est trop lourd pour être exécuté par exemple à chaque démarrage de l'application hôte. Il existe donc un système permettant de mettre en cache les informations concernant les add-ins afin d'y accéder sans devoir réinspecter chaque assembly à chaque fois.

III-G-2-a - La mise en cache des informations

La méthode static Update de la classe AddInStore va effectuer tout le travail pour nous. Cette méthode prend en paramètre le chemin du répertoire du pipeline et va l'examiner ainsi que tous ses sous-répertoires à la recherche d'add-ins (type ayant l'attribut AddInAttribute) et de composants du pipeline.

Pour chaque add-in trouvé, elle va enregistrer les informations le concernant dans un fichier de cache nommé AddIns.store qui sera créé dans le répertoire des add-ins.

Chaque composant du pipeline verra aussi ses informations stockées dans un fichier PipelineSegments.store situé à la racine du répertoire spécifié en argument.

AddInStore.Update(addinRoot);

Le processus de découverte s'effectue dans un domaine d'application dédié et les informations concernant chaque add-in sont obtenues en utilisant la réflexion. Cela assure qu'aucun code contenu dans les assemblies étudiées ne sera jamais exécuté (aucun add-in n'est encore actif !) et évite d'encombrer le domaine d'application de l'hôte.

Notez qu'un deuxième appel à cette méthode ne réenclenchera le processus que s'il y a eu des modifications depuis le premier appel (ajout d'un nouvel add-in par exemple).

En plus de la création de ce cache en cours d'exécution de l'hôte, il existe un outil nommé AddInUtil.exe qui peut effectuer ce travail (lors d'une installation personnalisée ou à partir d'un script par exemple).

III-G-2-b - Recherche d'add-ins

Maintenant que les informations concernant les différents add-ins de l'hôte sont en cache, il devient aisé de rechercher un type d'add-in en particulier.

La classe AddInStore propose une méthode static FindAddIns prenant en paramètre le type que l’hôte veut utiliser pour communiquer avec les add-ins (la vue) et un chemin et qui retourne une collection d'objets de type AddInTokens représentant les add-ins trouvés. Cette méthode va bien entendu aller chercher la liste des add-ins dans le fichier de cache. Si celui-ci n'existe pas, la collection renvoyée sera vide. Cette méthode peut être utilisée lors du chargement de l'application. Le fichier de cache est gardé en mémoire afin d'éviter les accès disque (il est bien sûr rechargé si besoin).

// Set the add-ins discovery root directory to be the current directory
string addinRoot = Environment.CurrentDirectory;

// Update the add-ins cache and pipeline components cache.
AddInStore.Update(addinRoot);

// On liste tous les addins de type AddInSauverDocVueHote
Collection<AddInToken> addins =  AddInStore.FindAddIns(typeof(VueAddInSauverDocCotéHote), addinRoot);

Un objet AddInTokens permet d'activer l'add-in qu'il représente. Il contient de plus des métadonnées sur l'add-in que l'hôte peut utiliser (nom, version, description). Ces informations ont été récupérées lors de la création du cache (méthode Update) et la méthode FindAddIns ne fait que les lire. Cela permet à l'hôte d'obtenir des informations sur les add-ins et choisir lesquels activer sans avoir à exécuter un quelconque code contenu dans les add-ins.

Une fois la liste des add-ins disponibles connue, nous pouvons remplir la combobox (dans notre exemple) qui servira à choisir la méthode de sauvegarde pour notre application :

//remplissage de la combobox contenant la liste des addins
this.comboBoxFormats.DataSource = addins;
this.comboBoxFormats.DisplayMember = "Name";

III-G-3 - Activation

Une fois que la liste des add-ins disponibles est chargée et que vous avez sélectionné l'add-in que vous voulez utiliser, il reste une dernière étape avant son utilisation : son activation.

Côté hôte l'activation se fait en une ligne de code et permet de renvoyer un objet de type VueAddInSauverDocCotéHote (qui est la classe représentant la vue de l'add-in par l'hôte) :

//on récupère l'addintoken sélectionné dans la combobox
AddInToken addinToken = this.comboBoxFormats.SelectedItem as AddInToken;

//activation de l'addin correspondant
VueAddInSauverDocCotéHote addinInstance = addinToken.Activate<VueAddInSauverDocCotéHote>(AddInSecurityLevel.Internet);

Nous utilisons la méthode Activate de l'objet AddInTokens (voir plus haut) représentant l'add-in à activer. Il existe trois versions de la méthode Activate prenant en charge des paramètres différents. Mais dans tous les cas, il s'agit de définir les autorisations de sécurité qui seront assignées à l'add-in. Dans l'exemple ci-dessus, nous lui passons en paramètre le niveau de sécurité correspondant au jeu d'autorisations Internet. Il existe d'autres jeux d'autorisations prédéfinis que vous pouvez utiliser :

Si vous ne trouvez pas votre bonheur dans cette liste, sachez qu'une autre version de la méthode Activate permet de passer en paramètre votre propre jeu d'autorisations. Une troisième version permet de fournir votre propre AppDomain.

Mais derrière cette relative simplicité (une ligne de code) se cache un mécanisme quelque peu plus complexe. Vous n'y avez peut-être pas prêté attention, mais si vous regardez le code que nous avons écrit jusqu'à présent vous remarquerez que nous n'avons pas instancié le moindre objet. Nous n'avons créé ni add-in ni adaptateur et pourtant nous nous retrouvons (grâce à la méthode Activate) avec une instance côté hôte que nous allons pouvoir utiliser. Que s'est-il donc passé lors de l'appel à cette méthode ? Voyons un peu plus en détail les différentes étapes par lesquelles est passé le système. N'hésitez pas à vous référer au schéma représentant l'organisation de l'exemple pour mieux comprendre.

- Un domaine d'application (appDomain) est créé avec les permissions spécifiées lors de l'appel à la méthode Activate.

- L'assembly contenant l'add-in est chargée dans cet appDomain à travers un appel à la méthode Assembly.LoadFrom().

- Le constructeur sans argument de l'add-in est appelé (via réflexion) pour en créer une instance dans cet appDomain. Comme l'add-in dérive d'une classe abstraite (dans notre cas VueAddInSauverDocCotéAddIn) se trouvant dans une autre assembly, cette dernière est aussi chargée dans l'appDomain. A ce point nous avons donc une instance de l'add-in s'exécutant dans son propre appDomain. Il nous faut maintenant la connecter à l'hôte.

- Une instance de l'adaptateur côté add-in (dans notre cas AdaptateurVueAddInVersContratSauverDoc) est construite en lui passant en paramètre de son constructeur l'instance de notre add-in (typée en tant que VueAddInSauverDocCotéAddIn). Cet adaptateur implémente le contrat (IContratSauverDoc) et dérive de MarshalByRefObject par l'intermédiaire de la classe ContractBase. Ainsi, il peut « vivre » à la fois dans l'appDomain de l'hôte et dans celui de l'add-in. Tous les appels provenant de l'appDomain de l'hôte passeront au travers d'un proxy et seront exécutés dans l'appDomain de l'add-in.

- Le code d'activation renvoie à l'appDomain de l'hôte l'adaptateur typé en tant que contrat qu'il implémente (dans notre cas IContratSauverDoc).

- Une instance de l'adaptateur coté hôte (AdaptateurContratSauverDocVersVueHote) est construite dans l'appDomain de l'hôte avec l'adaptateur côté add-in (AdaptateurVueAddInVersContratSauverDoc) passé en paramètre de son constructeur.

- Enfin, le code d'activation retourne à l'hôte l'adaptateur côté hôte (AdaptateurVueAddInVersContratSauverDoc) typé en tant que vue (VueAddInSauverDocCotéHote).

III-G-4 - Utilisation

L'activation nous a renvoyé un objet de type VueAddInSauverDocCotéHote (qui est classe représentant la vue de l'add-in par l'hôte). Nous pouvons donc utiliser cet objet et appeler sa méthode qui permet l'enregistrement du texte dans un fichier :

//chemin correspond à l'emplacement de sauvegarde
addinInstance.EnregistrerDoc(chemin, richTextBox1.Text);

En bout de chaine, le code de l'add-in est exécuté et un fichier texte est créé contenant le texte tapé dans la richTextBox. Du mois, en théorie. Car en pratique l'exécution de ce code va provoquer une jolie exception :

Il s'agit d'une exception de sécurité nous informant que le code de notre add-in ne dispose pas des droits pour pouvoir accéder au système de fichier de l'ordinateur. Pour mieux comprendre, remontons un peu en arrière lors de la phase d'activation. Nous avions en effet activé l'add-in avec le jeu de sécurité correspondant à la zone Internet. L'add-in ne possède ainsi que peu de droits, et n'a notamment pas accès au système de fichier de l'ordinateur. Pour résoudre ce problème, nous avons plusieurs solutions. Nous pourrions modifier le jeu de sécurité et en choisir un qui accorde plus de droits à l'add-in :

AddInSecurityLevel.FullTrust

Cependant l'add-in se retrouverait ici avec plus de droits que nécessaire.

La meilleure méthode (tout est relatif) est de construire un jeu personnalisé de permissions :

Jeu personnalisé de permissions

//création du jeu de permission
PermissionSet perms = new PermissionSet(PermissionState.None);
perms.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
perms.AddPermission(new FileIOPermission(FileIOPermissionAccess.AllAccess, chemin));

try
{
//activation de l'addin
VueAddInSauverDocCotéHote addinInstance = addinToken.Activate<VueAddInSauverDocCotéHote>(perms);

//utilisation de l'addin
addinInstance.EnregistrerDoc(chemin, richTextBox1.Text);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}

Pour cela nous créons un objet PermissionSet qui contiendra les différentes permissions accordées à l'add-in (plus exactement au domaine d'application de l'add-in). Puis nous y ajoutons deux permissions.

La première est une permission que l'on oublie parfois (et l'on reste ainsi longtemps à se demander pourquoi le code ne fonctionne pas), mais qui est indispensable dans notre cas : c'est la permission donnée au code de s'exécuter. Sans cette autorisation, le code managé ne peut pas s'exécuter. Vous pouvez donc donner toutes les autorisations que vous voulez, si l'autorisation d'exécution n'est pas présente cela ne servira à rien.

Une fois l'autorisation d'exécution donnée, il faut donner à l'add-in le droit en écriture afin qu'il puisse créer le fichier texte demandé. C'est ce que nous faisons en créant un objet FileIOPermission et en lui passant en paramètre le type d'accès de fichier demandé (dans notre cas, tous les accès) et le fichier sur lequel s'exercent ces autorisations. Nous donnons ainsi ici un accès total seulement à l'emplacement où l'on veut sauvegarder le fichier.

Et cette fois-ci le code s'exécute sans problème. Vous pouvez donc jouer très finement avec les autorisations qui seront accordées aux add-ins de vos applications.

III-G-5 - Déchargement

Il est possible à l'hôte de décharger l'add-in après utilisation. Pour cela, une ligne de code suffit :

// Récupération de l'AddInController pour l'add-in et fermeture de celui-ci
AddInController.GetAddInController(addinInstance).Shutdown();

La méthode ShutDown coupe le pipeline communication entre l'add-in et l'hôte. Elle se charge aussi de décharger le domaine d'application de l'add-in.

IV - Compatibilité ancien add-in, nouvel hôte

Nous venons donc de voir comment mettre en place un mécanisme de gestion d'add-in dans une application. Comme indiqué au début de l'article, la création du pipeline a nécessité un certain travail pour la première version de l'application. Mais le fait d'avoir pris la peine de développer la gestion d'add-in suivant ce modèle va maintenant nous être bénéfique.

Nous allons en effet effectuer une mise à jour de notre éditeur et voir comment rendre les anciens add-ins (développés pour la version 1) compatibles avec cette nouvelle version.

IV-A - Présentation de la mise à jour

La modification apportée sur la version 2 de l'application va être très simple, il s'agit ici simplement de montrer le mécanisme permettant la compatibilité descendante avec les add-ins.

Dans la première version de l'application, nous voulions que les add-ins permettent l'enregistrement d'une chaine de caractères dans un fichier. C'était là le seul service que l'hôte demandait à ses add-ins. Dans la version 2 de l'application, nous souhaitons que les add-ins puissent en plus renvoyer sous forme de chaine de caractères l'extension correspondante au format du fichier créé. Par exemple, un add-in permettant de sauvegarder sous format PDF devra renvoyer « PDF ».

Vous devez surement vous douter du problème que nous allons devoir traiter. Effet, l'add-in de sauvegarde au format texte que nous avons développé n'offre absolument pas cette fonctionnalité. Nous allons donc étudier comment continuer tout de même à utiliser cet add-in (sans le modifier) avec la version 2 de l'application.

IV-B - Nouveau contrat

Le contrat ne devant pas être « versionné », vous ne pouvez pas le modifier. Vous êtes obligés d'en créer un nouveau et de supprimer l'ancien.

Dans le projet Contrats supprimez le contrat initial (IContratSauverDoc) et créez-en un nouveau (nouvelle classe) que vous appellerez IContratSauverDocV2 :

ContratV2

using System.AddIn.Contract;
using System.AddIn.Pipeline;

namespace Contrats
{
// Définit le contrat entre l'hôte et l'addin.
[AddInContract]
public interface IContratSauverDocV2 : IContract
{
// Enregistre le String passé en paramètre dans un fichier
bool EnregistrerDoc(String chemin, String texte);

// Revoit l'extension du fichier qui sera créé
String GetExtentionDoc();
}
}

N'oubliez pas de définir son chemin de sortie sur ..\output\Pipeline\Contracts\.

IV-C - Modifications côté hôte

IV-C-1 - La vue

L'hôte attend une nouvelle fonctionnalité de l'add-in. Il faut donc refléter ce changement dans la façon dont l'hôte voit l'add-in, c'est-à-dire dans la vue VueAddInSauverDocCotéHote.

Pour cela, nous allons modifier la vue et lui rajouter la méthode GetExtentionDoc qui permettra à l'hôte d'utiliser cette nouvelle fonctionnalité.

namespace Hote.Vue
{
/// <summary>
/// Définit comment sera vu un addin (de type sauverDoc) par l'hôte
/// </summary>
public abstract class VueAddInSauverDocCotéHote
{
public abstract bool EnregistrerDoc(String cheminFichier, String texte);

public abstract String GetExtentionDoc();
}
}

IV-C-2 - L'adaptateur

Nous modifions aussi l'adaptateur côté hôte.

using Contrats;
using System.AddIn.Pipeline;
using Hote.Vue;

namespace Hote.Adaptateurs
{
/// <summary>
/// Convertit une instance de type IContratSauverDoc afin de la rendre accessible à l'hôte
/// (qui ne manipule que des objets de type VueAddInSauverDocCotéHote)
/// </summary>
[HostAdapter]
public class AdaptateurContratSauverDocVersVueHote : VueAddInSauverDocCotéHote
{
private IContratSauverDocV2 _contrat;

/// <summary>
/// Constructeur avec un seul paramètre (l'adaptateur coté l'addin) obligatoire
/// </summary>
/// <param name="contrat"></param>
public AdaptateurContratSauverDocVersVueHote(IContratSauverDocV2 contrat)
{
this._contrat = contrat;
}

public override bool EnregistrerDoc(string cheminFichier, string texte)
{
return this._contrat.EnregistrerDoc(cheminFichier, texte);
}

public override string GetExtentionDoc()
{
return this._contrat.GetExtentionDoc();
}
}
}

IV-C-3 - L'hôte

Au sein de l'hôte, nous n'effectuerons que très peu de changements. Après l'appel à la méthode EnregistrerDoc nous affichons une boite de dialogue indiquant le format d'enregistrement :

MessageBox.Show("Texte sauvegardé sous format: "+addinInstance.GetExtentionDoc());

Comme vous le voyez, les changements dans le pipeline côté hôte se résument dans notre cas à la modification des classes afin de prendre en compte la nouvelle fonctionnalité.

IV-D - Création d'un add-in V2

Nous allons voir ici comment créer un add-in spécialement conçu pour la version 2 de l'application. Les étapes seront pratiquement les mêmes que celles vues en début d'article. La partie suivante sera consacrée à la compatibilité entre la nouvelle version de l'application et une ancienne d'un add-in.

IV-D-1 - La vue

Dans le dossier Partie_AddIn créez un nouveau projet de type bibliothèque de classes et nommez-le AddIn.VueV2.

Créez une classe VueAddInSauverDocCotéAddInV2 :

using System.AddIn.Pipeline;

namespace Addin.Vue
{
/// <summary>
/// Classe de base pour notre addin. Définit comment l'addin reçoit les appels de l'hôte
/// </summary>
[AddInBase]
public abstract class VueAddInSauverDocCotéAddInV2
{
public abstract bool EnregistrerDoc(String cheminFichier, String texte);

public abstract String GetExtentionDoc();
}
}

Cette classe définit la façon dont l'add-in sera appelé par l'application version 2.

N'oubliez pas de définir le chemin de sortie sur ..\output\Pipeline\AddInViews.

IV-D-2 - L'add-in

Nous allons créer un nouvel add-in qui sera directement compatible avec la version 2 de l'application. Cet add-in permettra de sauvegarder une chaine de caractères dans un fichier XML.

Dans le dossier add-in créez un nouveau projet

Add-in SauverEnXML

using Addin.VueV2;
using System.AddIn;
using System.Xml.Linq;

namespace AddIn.XML
{
[AddIn("Format xml",
Version = "1.0.0.0",
Description = "AddIn permettant d'enregistrer un document sous format xml",
Publisher = "Badger")]
public class SauverEnXML : VueAddInSauverDocCotéAddInV2
{

public override bool EnregistrerDoc(string cheminFichier, string texte)
{
XDocument doc = new XDocument();

XElement element = new XElement("root",
new XElement("data", texte)
);

doc.Add(element);

doc.Save(cheminFichier);

return true;
}

public override string GetExtentionDoc()
{
return "XML";
}
}
}

Dans les propriétés du projet sous l'onglet Build, définissez le chemin de sortie sur ..\output\Pipeline\AddIns\AddIn.XML.

Ajoutez une référence à la vue (AddIn.VueV2), mais veillez à mettre sa propriété Copy Local à FALSE (la dll de la vue ne doit pas être déployée avec l'add-in).

IV-D-3 - L'adaptateur

Il ne reste plus que l'adaptateur côté add-in. Créez une nouvelle classe dans le projet AddIn.Adaptateurs (qui existe déjà) et nommez-la AdaptateurVueAddInVersContratSauverDocV2.

Définissez le chemin de sortie sur ..\output\Pipeline\ Add-inSideAdapters.

Ajoutez une référence à la vue (AddIn.VueV2) et au contrat, mais veillez à mettre leur propriété Copy Local à FALSE.

AdaptateurVueAddInVersContratSauverDocV2

using System.AddIn.Pipeline;
using Addin.VueV2;
using System.AddIn;

namespace AddIn.Adaptateurs
{
/// <summary>
/// Encapsule une instance de VueAddInSauverDocCotéAddInV2 pour pouvoir l'exposer en tant que IContratSauverDocV2
/// </summary>
[AddInAdapter]
public class AdaptateurVueAddInVersContratSauverDocV2 : ContractBase, Contrats.IContratSauverDocV2
{
private VueAddInSauverDocCotéAddInV2 _vue;

/// <summary>
/// Constructeur avec un seul paramètre (l'addin) obligatoire
/// </summary>
/// <param name="vue"></param>
public AdaptateurVueAddInVersContratSauverDocV2(VueAddInSauverDocCotéAddInV2 vue)
{
this._vue = vue;
}

#region IContratSauverDocV2 Members

bool Contrats.IContratSauverDocV2.EnregistrerDoc(string chemin, string texte)
{
return this._vue.EnregistrerDoc(chemin, texte);
}

public string GetExtentionDoc()
{
return this._vue.GetExtentionDoc();
}

#endregion
}
}

Comme vous avez pu le constater, la création d'un add-in spécialement dédié à la version 2 de l'application se fait exactement de la même façon que pour la version 1. Il faut simplement créer une nouvelle vue et un nouvel adaptateur correspondant au nouveau contrat.

IV-E - Compatibilité descendante avec un ancien add-in

Nous allons aborder maintenant la façon de rendre compatible notre add-in SauverEnTXT avec la nouvelle version de l'application.

IV-E-1 - État des lieux

Faisons un petit état des lieux de notre nouveau pipeline afin de mieux comprendre les modifications à effectuer.

L'add-in SauverEnTXT implémente la vue VueAddInSauverDocCotéAddIn alors que les add-ins directement conçus pour la version 2 de l'application implémentent la vue VueAddInSauverDocCotéAddInV2. Nous ne pouvons pas modifier le code de l'add-in. Nous devons pouvoir le réutiliser tel quel. Il nous faut donc garder la vue VueAddInSauverDocCotéAddIn dans le pipeline.

Le contrat IContratSauverDoc n'existe plus. Un nouveau contrat a fait son apparition : IContratSauverDocV2. L'adaptateur AdaptateurVueAddInVersContratSauverDoc qui implémentait le contrat IContratSauverDoc va donc devoir disparaître.

Le défi est donc de transformer l'ancienne vue VueAddInSauverDocCotéAddIn afin qu'elle corresponde au nouveau contrat IContratSauverDocV2. Pour cela, nous allons créer un adaptateur AdaptateurVueAddInVersContratSauverDocV1toV2.

IV-E-2 - L'adaptateur

Dans le projet AddIn.Adaptateur (qui existe déjà), commencez par supprimer l'ancien adaptateur AdaptateurVueAddInVersContratSauverDoc.

Créez ensuite un nouvel adaptateur AdaptateurVueAddInVersContratSauverDocV1toV2 implémentant le contrat IContratSauverDocV2.

Ajoutez une référence à l'ancienne vue (AddIn.Vue) et au contrat, mais veillez à mettre leur propriété Copy Local à FALSE.

AdaptateurVueAddInVersContratSauverDocV1toV2

using Addin.Vue;
using System.AddIn;

namespace AddIn.Adaptateurs
{
// Encapsule une instance de VueAddInSauverDocCotéAddIn
//pour pouvoir l'exposer en tant que IContratSauverDocV2
[AddInAdapter]
public class AdaptateurVueAddInVersContratSauverDocV1toV2 : ContractBase, Contrats.IContratSauverDocV2
{
private VueAddInSauverDocCotéAddIn _vue;

// Constructeur avec un seul paramètre (l'addin) obligatoire
public AdaptateurVueAddInVersContratSauverDocV1toV2(VueAddInSauverDocCotéAddIn vue)
{
this._vue = vue;
}

#region IContratSauverDocV2 Members

public boolEnregistrerDoc(string chemin, string texte)
{
return this._vue.EnregistrerDoc(chemin, texte);
}

// on adapte car l'addin V1 n'a pas de méthode renvoyant une extension
public string GetExtentionDoc()
{
return "Inconnu";
}

#endregion
}
}

Le point intéressant se trouve dans la méthode GetExtentionDoc. L'add-in ne prenant pas en charge cette fonctionnalité, l'adaptateur est obligé de ruser. Il renvoie simplement la chaine de caractères « Inconnu ».

L'adaptateur fait donc croire à l'hôte que l'add-in implémente la fonctionnalité permettant de renvoyer l'extension du fichier. Lorsque l'hôte voudra obtenir l'extension sur un add-in ne prenant pas compte cette fonctionnalité, l'adaptateur interceptera cette demande et renverra une chaine de caractères quelconque. Ainsi, même un ancien add-in pourra fonctionner avec la nouvelle version de l'application, c'est là toute la force des adaptateurs au sein du pipeline.

IV-F - Test de l'application

Sur la version 2 de l'application, l'utilisation de l'add-in SauverEnXML affiche le message suivant :

L'utilisation de l'ancien add-in SauverEnTXT fonctionne toujours. Le message apparaissant à l'écran est le suivant :

Dans les deux cas, le texte est bien sauvegardé dans un fichier avec un format spécifié par l'add-in.

V - Conclusion

L'arrivée de ce nouveau modèle de conception va faciliter le développement d'add-ins ainsi que la prise en compte des problématiques liées (sécurité, isolation, versionning, etc.).

Il a de plus été pensé pour ne pas compliquer la tâche du développeur d'add-in, tous les mécanismes du système se trouvant dans le pipeline.

VI - Liens

Le blog de l'équipe CLR Add-In

Blog de Guy Burstein

MarshalByRefObject

Comprendre le design Pattern Adaptateur

Add-ins and Extensibility sur MSDN

Sources

Téléchargez les sources de la solution donnée en exemple.

Remerciements

J'adresse ici tous mes remerciements à l'équipe de rédaction de « developpez.com » pour le temps qu'ils ont bien voulu passer à la correction et à l'amélioration de cet article.

Contact

Si vous constatez une erreur dans le tutoriel, dans les sources, dans la programmation ou pour toutes informations, n'hésitez pas à me contacter par le forum.