| L’auteur: | Arnaud Bessems |
| Technologies utilisées: | C#, MEF |
| Niveau de difficulté: | 200 |
Imaginons que vous développiez un programme permettant de récupérer une donnée d’une façon A et d’une façon B, tout en laissant à l’utilisateur le choix entre ceux-ci. Tout va bien dans le meilleur des mondes mais il apparait que dans un futur proche, il vous sera demandé d’ajouter de nouvelles procédures de récupération telles que C, D, E, … . Le problème est que pour chaque nouvelle méthode, il vous faut ré-ouvrir votre projet, les ajouter, recompiler le tout pour finalement livrer le programme. Travail fastidieux si en plus de cela vous n’êtes pas le seul à développer ces nouvelles méthodes. C’est à ce moment-là que le MEF va vous aider, car voici son principe :
Une fois implémenté et configuré, MEF va chercher des composants externes au projet et les charger au sein de celui-ci de façon automatique. Et là où ça devient intéressant, c’est qu’il n’est pas nécessaire de connaître le nombre de ces composants. Qu’ils soient cinquante, cent ou qu’il n’y en ait pas le moindre, rien ne change dans votre code et aucune exception ne sera levée. Vous pouvez voir son fonctionnement comme un bus faisant sa tournée et qui, lorsqu’il passe devant un arrêt, embarque les passagers quels que soient leur nombre mais continue sa route s’il n’y en a aucun.
Reprenons notre exemple auquel on applique le fonctionnement du MEF. Chaque mĂ©thode de rĂ©cupĂ©ration de donnĂ©es (A, B, C, …) va faire l’objet d’un projet sĂ©parĂ© qui va ĂŞtre compilĂ© pour en obtenir un fichier « .dll ». Ces fichiers seront placĂ©s dans un dossier au sein duquel on spĂ©cifie au MEF qu’il devra chercher. Dès lors, au chargement du programme, le dossier sera inspectĂ© et tout ce qui rĂ©pond aux conditions d’éligibilitĂ©s sera chargĂ©. Au final, on possède un programme qui va lister l’ensemble des mĂ©thodes d’accès stockĂ©es dans les fichiers « .dll » et offrir le choix Ă l’utilisateur.
Mais en quoi c’est intéressant ? Et bien lorsque vous ou un tiers développez une nouvelle méthode d’accès aux données, il suffit de placer la nouvelle librairie dans le dossier et elle sera prise en compte dès le prochain démarrage du programme. Plus besoin de recompiler ou partager les fichiers sources.
Vous possédez donc une application extensible à souhait et ce, sans réfléchir au chargement de ces composants. Votre application dispose d’une porte ouverte et correctement sécurisée pour lui permettre d’évoluer de façon simple et performante.
Le concept peut s’appliquer pour bien des cas de figures où vous souhaitez ajouter des composants de façon dynamique. Pour exemple, il est également possible de charger une interface utilisateur de façon à rajouter des champs, des boutons, des textes, … .
Avant de vous expliquer son fonctionnement, je vais faire un petit historique de manière à vous faire comprendre d’où vient ce framework et où le trouver.
Dans un premier temps, le MEF était un projet Open-source à part entière, mais désormais il fait partie intégrante du Framework .NET 4. Il est également compris dans Silverlight 4.
Actuellement, MEF 2 est en cours de développement et est disponible avec .NET 4.5.
Vous pouvez trouver les dernières versions de MEF sur codeplex: http://mef.codeplex.com/.
Fini les blablas, place aux choses concrètes. Pour commencer, il nous faut ajouter une référence à la librairie System.ComponentModel.Composition. A partir de là , une association au mot clé using au sein de la classe qui va charger les composants va nous permettre d’implémenter le chargement des extensions.
using System.ComponentModel.Composition;
Maintenant mettons en place le chargement des composants. Ceux-ci peuvent se trouver sous formes de fichiers « .dll » dans un dossier :
//Found all directories in the main componant directory
string[] directories = Directory.GetDirectories("Plugins");
//An aggregate catalog that combines multiple catalogs
AggregateCatalog containerMaster = new AggregateCatalog();
foreach (string directory in directories){
containerMaster.Catalogs.Add(new DirectoryCatalog(directory));
}
Ou dans un autre projet au sein du même assembly :
//An aggregate catalog that combines multiple catalogs AggregateCatalog containerMaster = new AggregateCatalog (); //Adds all the parts found in the same assembly as the Program class containerMaster.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));
Après cela, nous posons le code suivant qui va compléter le chargement des composants.
(L’extrait de code suivant est expliqué en détails par la suite)
// Crée un CompositionContainer à partir de tous les composants trouvés
CompositionContainer compositionContainer = new CompositionContainer(containerMaster);
// Donne un paramètre optionnel aux constructeurs des composants
compositionContainer.ComposeExportedValue("myKinect", _myKinect);
// Importe concrètement les composants
try{
compositionContainer.ComposeParts(this);
}
catch (CompositionException compositionException){
Logger.Log(LogLevel.Error,"Composition failed");
}
A ce stade, le chargement des composants est terminé, plutôt simple non ? Il vous suffira d’utiliser le code donné plus haut dans une méthode que vous appelez au sein du constructeur de votre classe et le tour est joué. Il faut maintenant savoir ce que l’on importe, des pommes, des poires ou des scoubidous et pour se faire, il faut déclarer une variable global définie comme suit :
[ImportMany(typeof(IGranyF))] private Lazy<IGranyF, IGrany>[] Applications;
Bien qu’il existe de nombreuses façons d’importer vos composants, je vous propose directement une solution complète et performante que je vous explique. Dans un premier temps [ImportMany] permet d’importer plus d’un composant de ce type et le paramètre typeof(Interface) spécifie que seuls les classes implémentant cette interface seront chargées. Gardez cette ligne en mémoire pour plus loin.
A la ligne suivante, on définit une variable de type Lazy[] ou IEnumarable<Lazy> de façon à avoir un tableau remplis de nos composants. Mais pourquoi Lazy ? Pour une question de performance ! Cette fonction va permettre de charger tous vos composants au démarrage mais de ne les instancier que lorsque vous y faites appel. Pour finir, les paramètres IGranyF et IGrany définissent le type de notre variable. Le premier fait référence à l’interface implémentée par nos composants alors que le second défini les Metadatas disponibles en leur sein, dont nous reparlerons plus tard.
Cet exemple ne vous montre qu’une possibilité d’implémentation, bien des variantes et d’autres options sont disponibles, libre à vous de les parcourir dans la documentation officielle.
Maintenant que nous possédons tout ce qu’il faut pour charger nos composants, intéressons-nous à la façon de les définir. Il ne faut pas oublier que le but est de nous simplifier la vie, les modifications à apporter à ces composants doivent donc être les plus réduites possibles, ce qui est le cas grâce au MEF. Tout ce qu’il est nécessaire de rajouter à nos composants est, dans un premier temps, la référence à System.ComponentModel.Composition et l’import du namespace, et dans un second temps l’ajout de ces quelques lignes en tête de notre classe :
[Export(typeof(IGranyF))]
[ExportMetadata("Description", "Les résultats du Lotto de la semaine")]
[ExportMetadata("Title", "Lotto")]
public partial class MainWindow : IGranyF
La première ligne est indispensable à notre classe vu que c’est elle qui va définir le type de composant que l’on a et donc qui sera, ou non, chargé par le MEF.
Ensuite, viennent les Metadatas, qui sont des informations complémentaires fournies par le composant et pouvant avoir son importance pour l’identifier ou le définir après son chargement. Définis par un titre et un contenu, leur nombre peut être quelconque ou même inexistant. Lorsque les composants sont chargés, les Metadatas définis dans l’interface du second paramètre de Lazy<IGranyF, IGrany>[] sont utilisés. La dite interface est, dans notre cas présent, définie comme ceci :
public interface IGrany
{
string Title { get; } // Plugin's title in Plugins Implement
string Description { get; }
}
Pour information, l’autre interface permet de définir des méthodes que chaque composant devra implémenter et qui pourront donc être utilisées dans le projet chargeant les composants.
public interface IGranyF
{
void LeftAction();
void RightAction();
void UpAction();
void DownAction();
}
A ce stade, tout est en place pour réaliser les actions de chargement des composants au sein du programme principal. Mais une dernière question peut vous tarauder, qui donc va instancier les classes chargées ? La réponse est agréable à entendre car elle signifie qu’il ne vous faut pas le faire, le MEF s’en charge, ce qui donne là le principal avantage de cette technologie.
Comme je vous le disais, il existe d’autres manières d’importer les composants et leurs mĂ©tadonnĂ©es. On peut par exemple dĂ©finir une nouvelle classe hĂ©ritant de ExportAttribute, et dĂ©crivant les mĂ©tadonnĂ©es:
public class ExportGranyMetadata : ExportAttribute
{
public string Name { get; set; }
public string Title { get; set; }
}
Et l’utiliser pour l’exportation:
[ExportGranyMetadata(Name="AName", Title="SomeTitle")]
MEF possède bien des fonctionnalités avancées vous permettant de personnaliser son utilisation en fonction de vos besoins mais au vu de leurs nombres, je vous conseille de vous renseigner d’avantage grâce à la documentation officielle ou d’éventuels ouvrages. Malgré tout, je souhaite vous expliquer une fonctionnalité qui m’a été utile : l’ImportingConstructor qui consiste à retourner le fonctionnement du MEF en envoyant une information du container vers le composant. Cela peut s’avérer très utile pour celui qui a besoin de partager une ressource avec ses composants. Dans le cas présent, nous envoyons l’objet permettant d’interagir avec la Kinect de Microsoft au sein des plugins de façon à leur éviter une seconde implémentation redondante.
Dans un premier temps, nous envoyons notre objet depuis le container via cette simple ligne à rajouter en avant dernière position :
container.ComposeExportedValue("myKinect", _myKinect);
Par la suite, au sein du composant dans lequel on souhaite utiliser cet objet, on rajoute un paramètre au constructeur comme suit :
[ImportingConstructor]
public MainWindow([Import("myKinect")] KinectSensor myKinect)
Notez que cette instruction n’est pas obligatoire. Elle a simplement pour effet de chercher un objet avec la clĂ© spĂ©cifiĂ©e parmi les valeurs exportĂ©es au moment de la composition.
Cette technique doit néanmoins être utilisée avec parcimonie car en donnant accès à des ressources du programme principale vous donnez la possibilité au composant de nuire au bon fonctionnement du reste de l’application, réfléchissez donc bien avant de le faire.
Actuellement dans la première version implémentée dans le .Net Framework, MEF ne compte pas se passer d’évolutions et passera bientôt en version 2.0. Cette version sera comprise dans la version 4.5 du .Net Framework et apportera son lot de nouveautés que vous pouvez découvrir en suivant ce lien : http://msdn.microsoft.com/en-us/library/ms171868%28v=vs.110%29.aspx#core
Néanmoins, les versions bêta et leur code source peuvent être téléchargés sur CodePlex à cette adresse : http://mef.codeplex.com/
Que faut-il retenir de cet article ? Principalement, le concept de chargement dynamique de composants de façon simple et performante. MEF n’est pas le seul framework à proposer ce genre de fonctionnalités mais néanmoins il le fait bien et de façon relativement simple.
Donc dans le cas où vous avec besoin de rajouter, dans un futur proche, des capacités à votre programme, il est fort intéressant de laisser une porte ouverte en implémentant MEF et permettre ainsi une extensibilité de votre application de façon simple et rapide.
http://msdn.microsoft.com/en-us/library/dd460648.aspx
http://channel9.msdn.com/Events/TechDays/TechDays-2011-Belgium/TD029
http://www.amazedsaint.com/2010/06/mef-or-managed-extensibility-framework.html
http://sebastien.warin.fr/2009/02/21/413-net-4-introduction-mef-managed-extensibility-framework/
| Arnaud Bessems, Ă©tudiant en dernière annĂ©e d’ingĂ©nieur industriel en informatique, je me passionne pour les nouvelles technologies et les langages de programmation tels que le C#, le PHP, l’HTML5 en me spĂ©cialisant dans les Frameworks .Net et Zend. Vous pouvez retrouver mes autres articles sur mon blog http://bessemsit.blogspot.com/. |
| @bessemsarnaud |
Copyright © Microsoft® Innovation Center Belgium