Feb.21

[.NET 4.0] Introduction à MEF : Managed Extensibility Framework

image Bientôt disponible dans la nouvelle version de la plateforme .NET de Microsoft, le .NET 4.0 introduira le framework MEF actuellement disponible en code ouvert sur CodePlex.

MEF pour Managed Extensibilty Framework, que vous trouverez sous le namespace System.ComponentModel.Composition, est une librairie .NET facilitant le développement d’application extensible.

C’est le travail résultant de l’équipe « Application Framework Core »  responsable d’harmoniser les frameworks applicatifs (type WinForm, SL, WPF et ASP.NET) de la même façon que le fait l’équipe de la BCL (Base Class Library) au niveau le plus bas de .NET.

Développer avec MEF, comment ca marche ?

Aujourd’hui pour concevoir nos applications de façon extensible, nous sommes souvent obligé de développer notre propre système d’extention. L’arrivée, fin 2007, du .NET 3.5 a ouvert une brèche dans la mise à disposition d’un framework générique pour le développement de telle application. Le System.Addin (encore nommé MAF, Managed Addin Framework) met à disposition des mécanismes simplifiant la mise en place d’un système d’extension basé sur des AddIns. Je traiterai un peu plus bas dans cet article des différences entre MAF et MEF.

MEF offre quant à lui un mécanisme d’import/export assez original. Le concept est de marquer des classes, variables, propriétés ou méthode par l’attribut Export. Dans d’autre classes vous pouvez alors créer des propriétés marquées de l’attribut Import pour venir importer les classes, variables, propriétés ou méthodes exportés.

MEF n’est pas qu’un simple framework d’ IoC (Inversion de contrôle) bien qu’il propose des fonctionnalités similaires comme l’injection de dépendance (comme le fait aussi StuctureMap ou Unity). MEF met surtout le focus sur la découverte de composant et permet à votre application de se composer par elle-même et cela à l’exécution.

Pour mieux comprendre, voyons un peu de code :

Si j’appelle la méthode Part1.Demo(), je verrai apparaître sur l’écran « Enjoy MEF« . Cela vous parait-il correct ?

Ne connaissant pas MEF j’aurais tendance à dire non ! En effet, dans ma classe Part1, qui a instancié ma propriété nommée Part ?

La réponse est MEF. En fait pour être exacte, voici le code de ma classe Main qui effectue l’appel à Part1.Demo() :

Comme vous le constatez, nous créerons ici un container basé sur un AssemblyCatalog. C’est à dire que toutes mes classes de mon assembly (GetExecutingAssembly) marquées par l’attribut Export seront chargées dans mon container.

Ensuite nous créons un CompositionBatch dans lequel nous allons ajouter les classes à « composer » qui dans notre cas est Part1. Le batch est ensuite exécuté (Compose) dans notre container. A ce moment là, les Parts de notre batch sont analysées pour y réaliser les Imports nécessaire.

Pour finir j’appelle la méthode Demo de notre Part1. Celle-ci appelle la méthode WriteLine de la classe importée Part2 pour afficher le texte à l’écran.

L’ Import/Export

Dans l’exemple ci-dessus, l’export est de type Part2 car nous n’avons rien précisé (l’export se fera alors sur le type exporté). Je pourrais aussi préciser le type de l’export dans le constructeur de l’attribut Export. Voyons l’exemple de la calculette :

Dans cet exemple je veux exporter mon Addition comme un ICalculate car je ne connais pas toutes les implémentations qu’il y aura derrière un ICalculate. J’ai donc précisé explicitement le type de l’export.

Du coté de mon Import, je préciserai aussi le type à importer :

MEF introduit aussi le concept de « Naming and Activation service » permettant de récupérer un objet à partir d’un nom (chaîne de caractère). Je pourrais par exemple exporter mon addition sous le nom « MonAddition » par le code :

Et l’importer depuis n’importe quelle classe chargée dans mon container par l’import :

Collection d’exportation

Reprenons l’exemple de la calculette. Je suis susceptible d’exporter plusieurs implémentations d’ICalculate pour la soustraction, multiplication ou division par exemple.

Si je tente de rajouter un export pour la multiplication en ajoutant le code :

Une exception sera levée :

Il est normal que plusieurs classes ne puissent pas « rentrer » dans une propriété. Pour ce faire nous allons simplement typer notre import comme une IEnumerable de ICalculate :

Je pourrais alors jouer le code suivant :

S’afficheront à l’écran les nombres : 5 et 6, pour le résultat de l’addition et de la multiplication de 2 et 3.

Import/Export de membres

Jusqu’ à présent nous nous sommes contenté d’exporter des classes soit à partir d’un nom ou d’un type (implicite ou non). Mais MEF ne s’arrête pas là en proposant la possibilité d’exporter un membre d’une classe :

  • un champ (variable)
  • une propriété
  • une méthode

La visibilité du membre n’a aucune conséquence sur l’export. Vous pouvez en effet exporter un membre déclaré comme « private » par exemple.

Pour les champs ou propriétés cela est très simple, voici un court exemple :

Nous exportons ici une constante privée de type string sous le nom VERSION. Dans une de mes parts, je pourrais alors importer cette constante par le code :

Pour les méthodes, nous devons l’importer sous forme d’un délégué. Dans les cas simples, les délégués Action<> (pour une procédure) et Func<> (pour les fonctions) conviennent parfaitement. Voici un petit exemple :

Ici nous exportons une méthode qui prend deux entier en entrée et retourne la somme des deux. Son délégué peut s’exprimer sous forme d’un Func<int, int, int>. Voici l’importation :

Ne sachant pas forcement si nous aurons dans notre container les exports nécessaires pour satisfaire toutes les importations, nous pouvons préciser si nous acceptons ou pas qu’une importation soit laissée vide par le paramètre AllowDefault :

Lazy-Export et les métadonnées

Le Lazy-Export

Dès lors que mon application devient importante, il se peut que MEF se retrouve avec beaucoup d’importation à satisfaire et le démarrage de mon application en prend un coup coté performance.

MEF propose du « lazy-loading » permettant de réaliser l’ importation quand on tente d’y accéder. Sa mise en place est relativement facile puisse qu’il s’agit, sur l’importation, de déclarer un Export<> sur mon type à importer réellement :

Dans mon code, je n’aurais qu’à appeler la méthode GetExportedObject() pour récupérer l’instance typée de mon import :

Ceci est tout à fait compatible dans le cas d’un IEnumerable. Reprenons notre calculette de démo :

ou plus simplement :

Les métadonnées

Lorsque je marque une classe ou membre pour l’exportation, il se peut que j’ai besoin de donner plus d’information que simplement le type ou nom de l’exportation. Je pourrais par exemple avoir plusieurs composants réalisant la même fonction mais de façon différente. Comment pourrais-je les distinguer s’ils s’exportent de la même façon ?

Reprenons l’exemple de la calculette. Je voudrais afficher dans ma console l’opération ainsi que le résultat. Faut-il encore que je sache quel est le signe de mon opération. Nous déclarons cela comme métadonnées de notre composant. Voyez le code suivant :

Nous avons ici créé une métadonnée nommée « Signe » pour contenir le signe de mon opération.

Du coté de l’importation, nous utiliserons le type Export<ICalculate> (qui assure aussi la fonction de « lazy-loading ») pour permettre d’accéder aux métadonnées sous la propriété Metadata.

La propriété Metadata est en fait un Dictionnary<string, object> permettant de placer autant de métadonnées que l’on veut sur nos composants.

Vous avez également la possibilité au niveau de l’importation, de préciser les métadonnées obligatoire pour satisfaire l’importation par l’attribut ImportRequiredMetadata. Ici nous allons préciser que nous voulons obligatoirement la métadonnée « Signe » sur nos composants :

MEF propose aussi de typer fortement les métadonnées par la création d’attribut personnalisé (simple classe héritant d’Attribute et marquée par l’attribut MetadataAttribute). Voici un exemple simple :

Et du coté de l’importation :

Dans notre cas, avec comme simple métadonnée le signe de l’opération, il est inutile de créer son propre attribut, le ExportMetadata était suffisant mais ceci est dit à titre d’exemple 🙂

Cycle de vie des composants et recomposition dynamique

Lorsqu’on l’on développe ce genre d’application, il est indispensable de comprendre comment sont gérées les instances de nos composants. Il se peut par exemple que je veuille qu’une seule instance d’un composant dans mon container, ou à l’inverse que je veuille autant d’instance que d’importation de mon composant.

Pour cela MEF va mettre à disposition un attribut CompositionOption permettant de définir la CreationPolicy de mon composant. Il y a trois types de CreationPolicy :

  • Shared : une seule et unique instance par container.
  • NonShared : nouvelle instance pour chaque importation.
  • Any : peut être Shared ou NonShared en fonction de ce que demande l’importation ou de ce que propose l’exportation.

Pour indiquer à mon composant de notification par mail d’avoir une seule et unique instance dans mon container, je pourrais écrire :

Du coté de l’importation, je pourrais choisir la CreationPolicy à respecter. Ici nous allons importer un INotify (par exemple) de type Shared :

Si je ne précise rien sur l’export ou l’import, la CreationPolicy par défaut est à Any. Voici le tableau des différentes possibilités :

 Export AnyExport SharedExport NonShared
Import AnySharedSharedNonShared
Import SharedSharedSharedIncompatible
Import NonSharedNonSharedIncompatibleNonShared
La Recomposition

Nous serons peut être amené durant l’exécution de notre application, à ajouter ou supprimer des composants dans notre container. MEF propose alors la possibilité de recomposer automatiquement les importations par le paramètre AllowRecomposition :

De ce fait, dans notre calculette de démo, si on injecte à l’exécution de nouvelle opération, ma collection d’opérations (MesOperations) sera automatiquement mis à jour proposant la nouvelle opération fraîchement injectée. Je pourrais surveiller tout cela en implémentant l’interface INotifyImportSatisfaction pour être averti des importations satisfaites.

Les containers et catalogues

Nous avons survolé dans l’introduction les concepts de containers, catalogues et batchs. Laissez-moi revenir un peu plus en détail sur ces points là.

Pour mettre à disposition nos composants dans le container nous allons nous servir d’un catalogue. Un catalogue contient un ensemble de composants exportés.

Nous avons plusieurs types de catalogue dans MEF actuellement :

  • Assembly Catalog : se base sur une assembly pour découvrir les composants
  • Directory Catalog : se base sur un répertoire pour découvrir les assembly pouvant contenir des composants
  • Aggregating Catalog : agrège plusieurs catalogues en un seul
  • Type Catalog : découvre seulement les types précisés dans le constructeur de ce catalogue

Par exemple, pour créer un catalogue se basant sur l’assembly en cours d’exécution et autres assembly dans le répertoire « MesComposants », nous écrirons :

Une fois notre catalogue créé, nous allons créer notre container sur base de ce catalogue par la ligne :

Je pourrais ensuite exécuter un batch permettant de composer les « Parts » de mon application  :

MEF vs le reste du monde (Unity, StructureMap, MAF, …)

Il est important de ne pas tout mélanger !

MEF et MAF

MAF disponible depuis le .NET Framework 3.5 sous le namespace System.Addin est un framework permettant de simplifier le développement d’application à plugin (addin) par la mise en place de contrats entre l’application hôte et les addins.

Le focus est mis sur l’isolation (via des domaines d’application différents) et sur le versionning d’addin permettant de concevoir facilement des applications extensible par addin.

MAF ne fourni pas des fonctionnalités comme l’injection de dépendance comme MEF ou autre framework d’IoC.

Pour résumer c’est une question de philosophie, concevez-vous une application extensible via des plugins en devant assurer l’isolation et le versionning des différents plugins ou concevez-vous une application architecturée par composant ?

Mais il est aussi possible de concevoir une application exploitant ces deux frameworks (par exemple un composant extensible par des plugins ou un plugin conçu par composant).

MEF et les autres framework d’IoC (Unity, StuctureMap & co)

Bien que MEF assure des fonctionnalités comme on les retrouvent dans des frameworks d’IoC, MEF met surtout l’accent sur la découverte de composant : nous ne connaissons pas à l’avance les composants que l’on aura lors de l’exécution !

En trois mot, MEF : Extensibilité, Découverte et Composition.

Conclusion

Vous voilà désormais introduit au framework MEF. Si tout se passe normalement, MEF devrait être intégré dans la prochaine version de .NET, le framework .NET 4.0 qui devrait arriver fin d’année voir début d’année prochaine !

Vous pouvez retrouver actuellement MEF en version Preview 4 et en code ouvert sur la plateforme CodePlex à l’adresse http://www.codeplex.com/MEF/.

Bonne composition à tous 😉

.NET
Share this Story:
  • facebook
  • twitter
  • gplus

Comments(11)

  1. Pascal Belaud [Microsoft France]
    le 6 mars 2009 à 18:11

    Alors là, je n’ai qu’un mot à dire, BRAVO !!!! Ce post est d’une très grande qualité et tu as très bien fouillé le sujet. Un « Must Read » pour tous ceux qui souhaitent comprendre MEF !

    Pascal

  2. Julien Corioland
    le 22 mai 2009 à 16:04

    Excellent article !!

    Par contre, j’ai suivi ton conseil et cherché l’attribut ImportRequiredMetadata suite à ton commentaire sur mon blog. Il semblerait qu’il ait été retiré dans la béta du .NET Framework 4.0…Ou alors ils l’ont bien caché :]

    A+

    Julien

  3. Sebastien
    le 22 mai 2009 à 16:28

    En effet Julien, je ne l’avais pas remarqué !

    L’attribut ImportRequiredMetadata a été supprimé du la Preview 5 de MEF (dernière en date).

    Pour obtenir le même résultat, il faut préciser le type des métadatas requis dans l’import :

    [Import]
    public Export<IMyContract, IMyCustomMetadata> MyContract { get; set; }

    Voir la discussion sur CP : http://mef.codeplex.com/Thread/View.aspx?ThreadId=53661

  4. Julien Corioland
    le 22 mai 2009 à 17:17

    Ah yes bien vu 😉

    Merci !

  5. amine
    le 21 mars 2010 à 12:59

    salut,

    le lazy loading est désormais effectué en utilisant la class Lazy

    Merci pour cet excellent article

    Amine

  6. yannick
    le 29 mars 2010 à 11:04

    bonjour,
    je fais mes premiers pas en .net et je dois créer une appli qu’on pourrait étendre par la suite par ajout de plug in.
    en faisait mes recherches j’ai trouvé le framework MEF.
    j’ai commencé à lire la doc dessus et le mot « assembly » revient tout le temps à koi est ce qu’il fait reférence.

    thx 4 ur answer

  7. Chételat Bastien
    le 27 juillet 2010 à 10:29

    Hello!

    Merci pour cet article!

    Juste une petite suggestion, une version imprimable des articles seraient franchement pas du luxe ;-).

    +++

  8. Bob
    le 13 juillet 2011 à 10:00

    Bravo Jean-Pierre !

  9. Ghusse
    le 6 septembre 2012 à 09:19

    Merci pour ce super article ! Vraiment très intéressant et très très complet.

    J’essaye de me servir de cet exemple, et je ne trouve pas le type ExportCollection. Est-ce que ça a été remplacé depuis la version beta ?

  10. Ghusse
    le 6 septembre 2012 à 10:35

    J’ai trouvé, il faut utiliser IEnumerable<Lazy> et l’attribut [ImportMany(typeof(IMyContract))]

  11. Trackback: MEF – Managed Extensibility Framework | Microsoft Innovation Center Belgique

Leave a comment

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

Comment