Wilfried Woivré & .Net

Unity : Comment injecter ces données ?

DÉCE2

Il y a quelques temps, dans un article, j’ai présenté l’injection de dépendances via Unity sous C# 3.5, vous êtes bien entendu au courant, que cette technologie n’est pas morte avec C# 4.0.

Il existe donc deux méthodes, on peut soit injecter ces différentes instances via le constructeur de notre classe, ou alors en injectant les différentes propriétés de la classe. Donc si vous débutez avec unity, on peut se demander comment l’on fait cette opération, je vais donc vous présenter ces deux méthodes ci dessous, soit via notre code dans notre application, ou via la configuration XML d’Unity.

 

L’injection via le constructeur, est celle que j’avais utilisé dans une précédente démonstration, on va donc prendre un cas très simple pour montrer comment injecter des données via le constructeur.

Commençons par la gestion d’Unity via le fichier de configuration :

  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
    <alias alias="Interface1" type="Demo.UnityConstructor.Interface1, Demo.UnityConstructor" />
    <alias alias="Interface2" type="Demo.UnityConstructor.Interface2, Demo.UnityConstructor" />
    <alias alias="ITest" type="Demo.UnityConstructor.ITest, Demo.UnityConstructor" />
    <alias alias="ClassImpl1" type="Demo.UnityConstructor.ClassImpl1, Demo.UnityConstructor" />
    <alias alias="ClassImpl2" type="Demo.UnityConstructor.ClassImpl2, Demo.UnityConstructor" />
    <alias alias="TestConstructor" type="Demo.UnityConstructor.TestConstructor, Demo.UnityConstructor" />


    <container>
      <register type="Interface1" mapTo="ClassImpl1" name="classImpl1" />
      <register type="Interface2" mapTo="ClassImpl2" name="classImpl2" />
      <register type="ITest" mapTo="TestConstructor" name="FULL">
        <constructor>
          <param name="interface1" type="Interface1">
            <dependency name="classImpl1" type="ClassImpl1" />
          </param>
          <param name="interface2" type="Interface2">
            <dependency name="classImpl2" type="ClassImpl2" />
          </param>
        </constructor>
      </register>
      <register type="ITest" mapTo="TestConstructor" name="INTERFACE1">
        <constructor>
          <param name="interface1" type="Interface1">
            <dependency name="classImpl1" type="ClassImpl1" />
          </param>
        </constructor>
      </register>
      <register type="ITest" mapTo="TestConstructor" name="INTERFACE2">
        <constructor>
          <param name="interface2" type="Interface2">
            <dependency name="classImpl2" type="ClassImpl2" />
          </param>
        </constructor>
      </register>
      <register type="ITest" mapTo="TestConstructor" name="EMPTY">
        <constructor />
      </register>
    </container>
  </unity>

Donc on peut voir rapidement via ce fichier de configuration, qu’on a 3 interfaces (Interface1, Interface2, ITest) ainsi que les 3 classes qui les implémentent (ClassImpl1, ClassImpl2, TestContructor), de même on peut apercevoir que j’enregistre 4 différentes instances de mon TestConstructor avec différents paramètres dans les constructeurs, puisqu’en effet, ma classe TestConstructor contient différent constructeur comme on peut le voir :

public class TestConstructor : ITest
{
    public TestConstructor()
    {
        Console.WriteLine("Constructeur vide !");
    }

    public TestConstructor(Interface1 interface1)
    {
        Console.WriteLine("Constructeur 1 paramètre : Interface 1");
    }

    public TestConstructor(Interface2 interface2)
    {
        Console.WriteLine("Constructeur 1 paramètre : Interface 2");
    }

    public TestConstructor(Interface1 interface1, Interface2 interface2)
    {
        Console.WriteLine("Constructeur 2 paramètres : Interface 1 & 2");
    }
}

Bon maintenant c’est bien beau, mais comment je récupère ma configuration et l’exploite ! Et bien Unity, c’est simple à utiliser, avec simplement 2 lignes de code vous pouvez récupérer votre configuration

private void ConfigureContainerXml()
{
    var configurationSection = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
    configurationSection.Configure(_containerXml);
}

 

Et pour exploiter nos différentes instances, pareil toujours aussi simple

_containerXml.Resolve<ITest>("FULL");
Console.WriteLine("**************");
_containerXml.Resolve<ITest>("INTERFACE1");
Console.WriteLine("**************");
_containerXml.Resolve<ITest>("INTERFACE2");
Console.WriteLine("**************");
_containerXml.Resolve<ITest>("EMPTY");

On remarquera qu’ici, je suis obligé de passer via un nom pour récupérer mes différentes instances de ITest, ceci est dû au fait, que j’en enregistre plusieurs, mais si j’en enregistre qu’une seule, je peux utiliser un unityContainer.Resolve<MonInterface>(); pour résoudre mon injection.

Bon maintenant qu’on a vu comment le faire via un fichier de config, on va voir comment déclarer nos dépendances via le code, et là pareil rien de bien complexe à mon avis :

private void ConfigureContainer()
{
    _container.RegisterType<Interface1, ClassImpl1>();
    _container.RegisterType<Interface2, ClassImpl2>();
    _container.RegisterType<ITest, TestConstructor>("FULL");
    _container.RegisterType<ITest, TestConstructor>("INTERFACE1", new InjectionConstructor(_container.Resolve<Interface1>()));
    _container.RegisterType<ITest, TestConstructor>("INTERFACE2", new InjectionConstructor(_container.Resolve<Interface2>()));
    _container.RegisterType<ITest, TestConstructor>("EMPTY", new InjectionConstructor());
}

Donc dans ce code, ce que je fais c’est enregistrer mes différentes interfaces avec un simple RegisterType, où je peux nommer mon instance. A noter que je passe par un InjectionConstructor pour fournir mes différents paramètres.

Il faut savoir que par défaut, si l’on ne fournit pas d’information pour le constructeur Unity va résoudre le constructeur qui a le plus de paramètre, c’est donc pour cela que j’utilise un InjectionConstructor sans aucun paramètre.

 

Bon maintenant qu’on a fait avec le constructeur, on se dit “oui, c’est bien, c’est beau, mais on peut le faire avec les propriétés ? “, et bien entendu Unity peut aussi le faire ! Et bien entendu, c’est aussi simple qu’avec l’injection de constructeur.

Donc pour commencer, l’injection via le fichier de configuration :

  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
    <alias alias="Interface1" type="Demo.UnityInjectionProperty.Interface1, Demo.UnityInjectionProperty" />
    <alias alias="Interface2" type="Demo.UnityInjectionProperty.Interface2, Demo.UnityInjectionProperty" />
    <alias alias="ITest" type="Demo.UnityInjectionProperty.ITest, Demo.UnityInjectionProperty" />
    <alias alias="ClassImpl1" type="Demo.UnityInjectionProperty.ClassImpl1, Demo.UnityInjectionProperty" />
    <alias alias="ClassImpl2" type="Demo.UnityInjectionProperty.ClassImpl2, Demo.UnityInjectionProperty" />
    <alias alias="TestInjection" type="Demo.UnityInjectionProperty.TestInjection, Demo.UnityInjectionProperty" />

    <container>
      <register type="Interface1" mapTo="ClassImpl1" name="classImpl1" />
      <register type="Interface2" mapTo="ClassImpl2" name="classImpl2" />
      <register type="ITest" mapTo="TestInjection" name="FULL">
        <property name="Interface1">
          <dependency name="classImpl1" type="ClassImpl1" />
        </property>
        <property name="Interface2">
          <dependency name="classImpl2" type="ClassImpl2" />
        </property>
      </register>
      <register type="ITest" mapTo="TestInjection" name="INTERFACE1">
        <property name="Interface1">
          <dependency name="classImpl1" type="ClassImpl1" />
        </property>
      </register>
      <register type="ITest" mapTo="TestInjection" name="INTERFACE2">
        <property name="Interface2">
          <dependency name="classImpl2" type="ClassImpl2" />
        </property>
      </register>
      <register type="ITest" mapTo="TestInjection" name="EMPTY">
      </register>
    </container>
  </unity>

Bon comme on peut voir, à part les différents paramètres, rien ne change par rapport à la version précédent, il suffit de déclarer les différentes propriétés à instancier ! Et c’est toujours aussi simple ! Bien entendu le reste du code (Construction du container et résolution de l’instance) sont les mêmes !

Maintenant comment enregistrer nos différentes instances via le code :

_container.RegisterType<Interface1, ClassImpl1>();
_container.RegisterType<Interface2, ClassImpl2>();
_container.RegisterType<ITest, TestInjection>("FULL",
    new InjectionProperty("Interface1", _container.Resolve<Interface1>()),
    new InjectionProperty("Interface2", _container.Resolve<Interface2>()));
_container.RegisterType<ITest, TestInjection>("INTERFACE1",
    new InjectionProperty("Interface1", _container.Resolve<Interface1>()));
_container.RegisterType<ITest, TestInjection>("INTERFACE2",
    new InjectionProperty("Interface2", _container.Resolve<Interface2>()));
_container.RegisterType<ITest, TestInjection>("EMPTY");

 

Donc ici, on utilise un InjectionProperty, cependant, c’est légèrement plus verbeux que la version avec les constructeurs, mais bon ce n’est pas encore méchant ! Seul différence, c’est que là pour enregistrer notre classe ayant le plus de propriété il faudra toutes les saisir !

 

Voilà le code source si vous voulez tester la solution :

image

 

Bien entendu, vous pouvez aussi mixer les deux méthodes, si vous avez envie ! Pour ma part, j’utilise principalement la méthode via le constructeur ainsi que via le code, tout simplement parce que je préfère résoudre mes éléments automatiquement et ne pas avoir à déclarer mes différentes propriétés ! De plus via le code, puisque le problème du fichier de configuration, c’est que l’on ne sait pas quand les données de ce fichier ne sont pas correct, enfin du moins avant d’avoir lancer l’application …. Et de plus, il n’y a aucune chance qu’un IT réseau peu compétent fasse du zèle en essayant de modifier seul le fichier de config.

Remonter

Allier MEF et Unity

MARS12

On a vu dans les deux derniers articles comment débuter avec MEF (Managed Extensibility Framework) et Unity, on peut donc maintenant imaginer les possibilités que ceux-ci offrent en terme de développement. Mais il faut avouer que coupler les deux serait encore un plus pour nos architectures. Et bien c’est possible grâce à MEFContrib (http://mefcontrib.codeplex.com, oui Codeplex est une source de framework presque intarissable)

Bon alors imaginons un cas assez concret, comme une application client lourd, celle-ci permet la création (et donc l’ajout) de plugins. Il est bien évident que chacun des plugins aura une tâche particulière. Par exemple, un plugin pourrait afficher des flux RSS, un autre attaquer la base de données de l’application afin d’afficher les différents utilisateurs présent en base. Bref beaucoup de possibilité, on voit donc ici l’intérêt de MEF afin de créer des plugins ayant un traitement spécifique et cependant se liant parfaitement à notre application.

Donc assez de parole, si l’ont crée cette application (tout le code sera fourni à la fin de cet article)

On va donc commencer par créer notre application en utilisant une architecture à base de plugins, voici donc ci-dessous les références entre nos différentes assemblys :

image

Si on détaillait un peu ce qu’il y a dans chaque assembly :

  • Client : Notre application WPF qui hébergera les différents plugins
  • Service : Cette assembly va dans notre cas servir de Repository, elle jouera donc le rôle de base de données.
  • Model : Contient les différentes entités de notre application
  • Contracts : Contient les différentes interfaces nécessaires à notre application
  • Plugin : Contient les différents plugins de notre application

Bon maintenant, qu’on a vu les références dans notre application, si on voyait notre application avec son joli design (ou pas)

image

Je ne vais détaillé tous le code, mais on va tout de même voir les interactions pour MEF. Commençons par notre assembly Contract, elle contient deux interfaces qui sont IModule, et IModuleMetadata, qui sont respectivement, l’interface pour définir un module, et l’interface qui contiendra les metadata de chaque objet.

public interface IModule
{
}
public interface IModuleMetadata
{
string Title { get; }
string Author { get; }
}

Notre première interface va en fait être utilisé pour nos différentes interactions utilisées par MEF, ici, cette interface ne défini aucune méthode puisque nos modules sont indépendants.

Notre deuxième définit un titre un nom d’auteur pour chacun des composants, comme ça on sait sur qui il faut taper quand ça ne marche pas ….

Passons à un module, celui des flux RSS par exemple,

[Export(typeof(IModule))]
[ExportMetadata("Title", "Flux RSS de Wilfried Woivré")]
[ExportMetadata("Author", "Wilfried Woivré")]
public partial class RSSControl : UserControl, INotifyPropertyChanged, IModule

On voit donc ici un export de type IModule, et les différentes metadatas de ce module. Bref, rien de bien compliqué dans notre cas du coup.

Il ne manque plus que notre client WPF qui héberge notre application

public partial class Window1 : Window
{
[ImportMany]
Lazy<IModule, IModuleMetadata>[] modules;

[Export(typeof(Func<List<Person>>))]
private List<Person> GetPersons()
{
return new ServiceMock().GetPerson();
}

On a donc ici notre liste de modules que l’on va chargé grâce à MEF. Dans notre fonction que l’on déclare en mode export, on expose la liste des personnes comprises dans notre application, puisque le module qui affiche ces informations n’a pas accès à l’assembly qui gère cela.

Ici, au terme de cette première étape, on a donc une application modulaire grâce à MEF ! Mais chacun de ces modules est “libre” de faire ce qu’il veut, prenons un cas concret, imaginons que l’on veuille instaurer des logs dans notre application. Comment fait-on ? On a le choix me direz-vous, chacun des modules peut choisir de faire ce qu’il veut, ou alors on essaye d’unifier nos différents logs.

Même autre cas, notre méthode ‘GetPersons()’ qui est exportées par MEF n’est déclarée dans aucune interface, donc il faut rédiger une documentation sur toutes les méthodes disponibles, documentation qui ne sera d’ailleurs peut être même pas lue …

Une solution serait donc de passer via une Factory, en effet, si l’on indique que notre ‘ServiceMock’ implémente une interface IContract, on peut utiliser une seule méthode à exportée qui renverrait une instance de notre ServiceMock, on pourrait donc avoir une seule méthode à exporter qui serait de ce type :

[Export(typeof(Func<IFactory>))]
private IFactory GetPersons()
{
return ServiceMock.GetSingleton();
}

Ainsi, il n’y aurait qu’une fonction principale à répertorier dans notre documentation, pour le reste, l’IntelliSense peut vous aider très facilement, mais bien entendu cela n’empêche pas de faire tout plein de doc !

Bon, vous allez me dire, qu’on arrive enfin à un rendu pas trop mal, mais qu’en est-il d’Unity là dedans …. Grâce à lui nous allons ajouter notre logger dans notre cas.

Alors on sait que MEF est fait pour intégrer des éléments non connus dans notre application, alors qu’Unity travaille sur des éléments connus à l’avance, donc ça peut poser problème au final. Donc heureusement, Codeplex est riche en utilitaire et framework en tout genre, il y a donc comme je l’ai dit le projet MefContrib qui est une bonne base pour créer une application intégrant MEF et Unity.

Pour plus de facilité, j’ai redéfini le Bootstrapper d’Unity afin d’y injecter mes données relatives à MEF.

public class BootStrapper : UnityBootstrapper
{
public CompositionContainer MefContainer { get; private set; }

protected override IUnityContainer CreateContainer()
{
var aggregateCatalog = new AggregateCatalog(new ComposablePartCatalog[] { new DirectoryCatalog(@".\Extensions") });
var unityContainer = new UnityContainer();
MefContainer = unityContainer.RegisterFallbackCatalog(aggregateCatalog);

return unityContainer;
}

protected override void ConfigureContainer()
{
Container.RegisterInstance(MefContainer);
Container.RegisterInstance<ILogger>(new Logger());

base.ConfigureContainer();
}

protected override IModuleCatalog GetModuleCatalog()
{
return new DirectoryModuleCatalog() { ModulePath = @".\Extensions" };
}

protected override System.Windows.DependencyObject CreateShell()
{
var view = Container.Resolve<Window1>();
view.Show();

var mefModules = MefContainer.GetExports<IModule>();
view.LoadModules(mefModules);

return view;
}
}

Ici, on hérite donc de la classe Bootstrapper qui est présente dans Unity, c’est une méthode courante que l’on retrouve souvent lorsqu’on utilise PRISM.

Notre première méthode va donc créer notre container Unity, et va y rajouter un catalogue pointant sur notre dossier de plugin. La deuxième va nous permettre de configurer ce précédent container, et d’y ajouter une instance de type ILogger. La suivante indique le chemin des Module, dans laquelle on va injecter du Unity. Et enfin la dernière va créer notre Shell, et lui affecter les différentes instances de modules.

J’ai de plus redéfini quelques méthodes d’extension pour IUnityContainer que vous pourrez voir dans la source du code, je vous laisse un peu de surprise.

A partir de là, on est presque près, cependant au lancement de l’application on n’a pas les logs actifs, ce qui est un soucis, étant donné que c’est ce qu’on désirait. Afin d’arranger cela, rien de plus simple, il suffit de jouer avec les attributs de MEF en exportant le constructeur prenant une instance de ILogger en entrée.

[ImportingConstructor]
public RSSControl(ILogger logger) : this()
{
Logger = logger;
}

Et là rien de plus, on a bien nos différents logs actifs. On a donc réussi à intégrer MEF avec Unity, grâce à tout plein de librairies…

Bref, pour conclure avant de vous fournir le code source de l’application, l’intégration de MEF avec Unity est un bon moyen de coupler IoC et Extensions, cependant cela ralentit fortement le chargement des applications, et je pense que la maintenance de celles-ci ne doit pas être améliorée étant donné qu’il faut connaitre les deux technologies pour maintenir le Shell. Mais cependant c’est tout de même un beau challenge à réaliser … La suite plus tard, étant donné que mon projet pour la fin de l’année se base sur ces trois derniers articles.

image

Remonter

Débuter avec Unity

MARS5

Maintenant qu’on a vu comme débuter avec MEF (Managed Extensibility Framework), on va passer à Unity.

Unity est un concept de développement, disponible dans les Enterprise Library de Microsoft, en version 4.1 lors de l’écriture de cet article. Unity sert à effectuer de l’IoC (Inversion de contrôle) sur les composants que l’on souhaite.

Avant de commencer, à quoi sert l’IoC ? L’IoC permet d’apporter à vos projets un nouveau niveau d’abstraction à votre code. Le but de l’IoC est que ce soit le contrôle qui appelle qui fournisse les différents accès, classes, instances au contrôle appelé.

Imaginons un cas simple, dans une application nous voulons logger des éléments, nous avons diverses manières de le faire. On peut dans un premier cas, instancier un nouveau logger à chaque fois qu’on en a besoin. La deuxième qui est mieux, et d’utiliser une factory qui va gérée elle-même la ou les instances de logger. La troisième se fait via Unity, ou lors de la création de nos contrôles on lui passe un type ou une instance de ILogger. On va comprendre ça mieux par la suite avec l’exemple.

Déjà installer et récupérer Unity, ça peut être utile histoire de s’amuser avec ! Vous pouvez trouver Unity sur Codeplex à l’adresse http://unity.codeplex.com, de plus il est compris dans les enterprise library que je conseille à tous d’utiliser que ce soit pour Unity ou autres, il y a vraiment plein de choses sympas dedans !

Pour mon exemple, histoire de ne pas faire trop compliqué, je suis reparti d’une présentation de Mike Taulty sur PRISM et Silverlight. Unity fait partie de PRISM, mais il peut très bien tourner sans. Vous pouvez retrouver la vidéo sur Channel 9 http://channel9.msdn.com/posts/mtaulty/Prism--Silverlight-Part-2-Dependency-Injection-with-Unity/

Donc pour ceux qui n’iront pas voir les vidéos de cette saga, il présente les différentes caractéristiques de PRISM. Pour Unity, il réalise une calculette en application Console que je vais vous présenter ci dessous.

Utilisons un peu les possibilités de Visual Studio 2010 pour notre schéma, voici donc la structure éclaté de la solution.

image

La solution est donc divisé en 6 namespaces :

  • InterfacesLibrary contient les différentes interfaces implémentées dans l’application
  • InputOutputLibrary contient les différentes classes gérant les entrées et sorties (Console.WriteLine, Console.ReadLine …)
  • Demo.Unity, c’est le point d’entrée du Program
  • CommonTypesLibrary contient les types communs de l’application (enum d’opération, et une classe Arguments qui contient uniquement 2 int)
  • CalculatorLibrary contient la logique du programme, et oui même pour une addition, il faut que ce soit un peu structuré
  • CalculatorCommandParsingLibrary va se charger de parser toutes les données saisies par l’utilisateur

Le but de notre application est que le cœur du programme utilise les différents éléments tel que le parser, les entrées-sorties uniquement via les interfaces. Suivant le principe d’Unity c’est le point d’entrée de notre Program qui va référencer les différentes instances que notre logique va utiliser.

Si on regarde le schéma des dépendances de nos assembly on obtient ceci avec NDepend :

image

On peut voir que seul Demo.Unity référence les différentes assembly dont nous allons nous servir pour effectuer notre traitement. On remarque aussi, que CalculatorLibrary et Demo.Unity référence Microsoft.Practices.ServiceLocation, et Demo.Unity référence aussi Microsoft.Practices.Unity et Microsoft.Practices.Composite.UnityExtensions, on va voir par la suite, pourquoi ces deux assemblys références cette dll.

Bon si on regardait un peu le code maintenant, je ne vous mets pas tout, je vous fournirais tout le code à la fin de cet article.

Donc en fait dans Unity tout se joue à la création des éléments, on va donc analyser notre classe CalculatorReplLoop et notre Program.cs

Voici notre classe CalculatorReplLoop, enfin une partie :

IInputService inputService;
List<IOutputService> outputServices;
ICalculator calculator;
IInputParserService parsingService;

public CalculatorReplLoop()
{

}

public CalculatorReplLoop(ICalculator calculator, IServiceLocator container, IInputService inputService, IInputParserService inputParserService)
{
this.calculator = calculator;
this.inputService = inputService;
outputServices = new List<IOutputService>(container.GetAllInstances<IOutputService>());
parsingService = inputParserService;
}

Regardons un peu plus en détail notre deuxième constructeur, on voit qu’il prend en paramètre diverses interfaces.

ICalculator, IInputService et IInputParserService sont les différences interfaces implémentées dans notre programme, elles servent respectivement à effectuer les opérations, récupérer les valeurs saisies via la console, parser les différentes commandes saisies par l’utilisateur.

IServiceLocator est un objet qui dans notre cas va encapsuler un UnityContainer qui contient les différents types que nous avons injecté. Vous pouvez retrouver IServiceLocator sur Codeplex : http://compositewpf.codeplex.com/. On reviendra néanmoins sur ce que contient ce container, lorsque je vais vous présenter ce qu’on injecte avec Unity.

Passons maintenant à la partie lancement du programme, puis ce que comme je vous l’ai dit c’est l’appelant qui injecte des données à l’appelé, et non l’appelé qui demande des données.

class Program
{
static void Main(string[] args)
{
UnityContainer container = new UnityContainer();

container.RegisterType<ICalculator, Calculator>();
container.RegisterType<ICalculatorReplLoop, CalculatorReplLoop>();
container.RegisterType<IInputService, ConsoleInputService>();
container.RegisterType<IInputParserService, InputParserService>();

container.RegisterType<IOutputService, ConsoleOutputService>("Consoleoutput");
container.RegisterType<IOutputService, MsgBoxOutputService>("MsgBoxOutput");
container.RegisterInstance<IServiceLocator>(new UnityServiceLocatorAdapter(container));

ICalculatorReplLoop loop = container.Resolve<ICalculatorReplLoop>();
loop.Run();
}
}

Au premier abord, Unity peut faire peur à cause de cela. Mais en fait, la réalisation est assez simple, on utilise un objet de type UnityContainer qui va contenir tous les types et les instances que l’on veut utiliser dans notre application.

Prenons le premier bloc, ici, nous “enregistrons” les types ICalculator, ICalculatorReplLoop, IInputService et IInputParserService associés à des classes qui implémentent ces interfaces. Ensuite, les choses se corsent un peu, en effet pour présenter le plus d’éléments, j’ai décidé d’utiliser différents instances de IOutputService, j’enregistre donc les deux types dans mon container, puis j’enregistre une instance de IServiceLocator qui encapsulera mon UnityContainer, j’utilise pour cela un UnityServiceLocatorAdapter qui est contenu dans l’assembly Microsoft.Practices.UnityExtension disponible sur http://compositewpf.codeplex.com/

Pourquoi cela ? Et bien tout simplement, parce que je ne voulais pas que le composant que j’appelle connaisse Unity, si ça avait été le cas, le composant pourrait vivre sa propre vie sans que l’appelant le contrôle.

Maintenant qu’on a enregistré nos différents types, instances, il va falloir maintenant résoudre tout cela afin d’avoir une application qui fonctionne. Et là Unity est magique pour cela, il n’y a qu’à demander quel type on veut résoudre, et il fait par lui même les associations.

Bon, et si on exécutait un peu l’application pour voir le rendu :

image

On demande une opération Add qui sera reconnue par notre parser, puis on lui fournit deux entiers, qui seront lus puis stocké dans un objet Arguments, et on affiche le résultat de l’opération à la fois dans la Console et dans une MessageBox.

Il existe de nombreuses façon d’utiliser Unity, dans ce cas je l’ai utilisé totalement au niveau du code, cependant il est possible d’enregistrer nos différents types au niveau d’un simple fichier de configuration, et ce sans référencer les différentes assembly. Pour ma part, je ne suis pas très adepte de cette méthode, car pour moi ce qui doit être en configuration est quelque chose qui est voué à changer un jour. Or là Unity est prévue pour la conception des applications qui elle n’est pas vouée à changer.

Et voici comme promis le code source de l’application (sous Visual Studio 2008, je pense à ceux qui n’ont pas encore migré) :

image

Remonter

Débuter avec Managed Extensibility Framework (MEF)

FÉVR18

Actuellement, lors de mes projets personnels, je m’intéresse très particulièrement aux technologies MEF et Unity ainsi que toutes les possibilités qu’elles nous offrent.

Je vais donc vous présenter en trois parties ces deux technologies :

  • Débuter avec Managed Extensibility Framework (MEF)
  • Débuter avec Unity
  • Allier Unity et MEF

Ces différents articles ne parleront pas des spécificités de chacune des technologies, sinon trois articles ne suffiront pas …

Donc, en trois mots, MEF qu’est-ce que c’est : Extensibilité, Découverte et Composition.

Mais avant tout ça, comprenons un peu son but, ce framework a pour vocation à homogénéiser les différents modèles d’application qui utilisent des plugins. En effet, avant MEF, les seuls projets sur lesquels j’ai travaillé avaient leur propre système de gestion de plugin. Prenons un logiciel assez connu, Visual Studio, avant la version 2010, celui-ci n’utilisait pas MEF, et la création des plugins pour celui-ci finissait généralement aux oubliettes, vu qu’on passait plus de temps dans la base de registre que dans la création du plugin elle même. Mais maintenant, le temps de la galère est fini grâce à MEF !

On va donc créer un projet qui nous permettra uniquement de jouer avec les opérations standards de mathématiques. (Oui, les sujets des articles changent, les démonstrations sont toujours les mêmes, addition, personne ….)

Avant de débuter, voici, un petit diagramme des assembly de mon application de démonstration.

image

Comme on peut le voir dans ce schéma, notre application Demo.exe fait référence à notre assembly de contrat, qui est aussi référencer par notre assembly de plugin. Cette dernière regroupe les différentes opérations que l’on exécute dans notre programme.

Le but de notre application de démonstration est quand à lui de charger les différents plugins afin que l’utilisateur puisse interagir avec nos différentes opérations. Ici, notre assembly Contrat joue le rôle d’interface entre notre application et ses extensions, ainsi que d’un SDK puisqu’elle va fournir différentes interfaces à implémenter.

Regardons plus en détails ce que contient cette assembly :

image

Soit ici, deux interfaces, la première exposant les méthodes à implémenter pour notre projet, soit uniquement une méthode Calculate, et la deuxième exposant les Metadata que nous souhaitons, par exemple, dans notre cas uniquement le titre.

Le projet contracts, quand à lui ne référence aucune librairie de MEF, ainsi ce contrat pourrait être utiliser sans ce framework, dans un autre cas d’application par exemple.

Maintenant qu’on a vu le projet de contrat, je vous épargne les 4 lignes de codes de ce projet, nous allons voir plus en détail notre application de démonstration. Puisqu’après tout c’est elle qui va gérer l’import ou non des plugins.

Donc, voici ci dessous la totalité du programme, comme vous pouvez le voir une application Console :

class Program
{
    static void Main(string[] args)
    {
        var p = new Program();
        p.Run();
    }

    [ImportMany]
    Lazy<IOperation, IOperationMetadata>[] operations;

    [Export(typeof(Action<String>))]
    private void Print(string value)
    {
        Console.WriteLine(value);
    }

    [Export(typeof(Func<double>))]
    private double ReadValue()
    {
        Console.WriteLine("Entrez un nombre : ");
        double x = -1;
        Double.TryParse(Console.ReadLine(), out x);
        return x;
    }

    private void Run()
    {
        ComposablePartCatalog catalog = new DirectoryCatalog(@".\Extensions");

        var container = new CompositionContainer(catalog);
        var batch = new CompositionBatch();
        batch.AddPart(this);
        batch.AddExportedValue(container);

        container.Compose(batch);

        Console.WriteLine("Nombre d'opérations dans le batch : {0}", operations.Count());

        // Calcul histoire de tester
        foreach (var operation in operations)
        {
            Console.WriteLine("Operation : {0}", operation.Metadata.Title);
            Console.WriteLine("Le résultat est : {0} ", operation.Value.Calculate());
        }
    }
}

Maintenant que je sais que vous n’avez pas tout lu, si on expliquait ce que le code fait, enfin du moins les parties en rapport avec MEF !

[ImportMany]
Lazy<IOperation, IOperationMetadata>[] operations;

Ce tableau va nous permettre d’importer nos différentes opérations, il faut que ces opérations héritent de IOperation, on mappera de plus tous les attributs de notre classe dans l’interface IOperationMetadata.

[Export(typeof(Action<String>))]
private void Print(string value)
{
    Console.WriteLine(value);
}

[Export(typeof(Func<double>))]
private double ReadValue()
{
    Console.WriteLine("Entrez un nombre : ");
    double x = -1;
    Double.TryParse(Console.ReadLine(), out x);
    return x;

Ces deux méthodes vont être exposés via MEF, grâce à l’attribut Export en spécifiant le type. On pourra donc depuis chacun des plugins y faire appel si on le souhaite.

ComposablePartCatalog catalog = new DirectoryCatalog(@".\Extensions");

var container = new CompositionContainer(catalog);
var batch = new CompositionBatch();
batch.AddPart(this);
batch.AddExportedValue(container);

container.Compose(batch);

Ici, c’est la partie de MEF qui nous permet de lier notre programme à ces plugins.

Premièrement, on va chercher les assemblys où sont références nos plugins, dans ce cas dans un sous dossier du projet généré. On va ensuite ajouter les différentes assemblys dans un batch, afin que MEF puisse réaliser son traitement sur des éléments connus.

MEF va ainsi composer toutes les associations possibles, dans notre cas, notre programme va référencer toutes les classes implémentant IOperation et les ajouter dans notre propriétés operations afin que l’on puisse les utiliser par la suite.

Maintenant que l’on a vu la partie du programme, voyons la conception d’un plugin, par exemple celui de l’Addition (c’est à peu près tous les mêmes)

[Export(typeof(IOperation))]
[ExportMetadata("Title", "Addition")]
public class Addition : IOperation
{
    [Import]
    Action<String> Print { get; set; }

    [Import]
    Func<Double> Read { get; set; }

    #region IOperation Members

    public double Calculate()
    {
        Print("Effectuons l'opération");
        return Read() + Read();
    }

    #endregion
}

On voit ici que l’on déclare un export de type IOperation, ainsi qu’une metadata d’export qui correspond à la metadata de notre interface IOperationMetadata.

On va créer ensuite les différents imports de fonctions que nous avons crée précédemment, on notera par ailleurs que les noms des méthodes peuvent être différents ReadValue // Read.

Pour le reste du traitement, on voit qu’il n’y a aucun changement par rapport à une classe addition standard.

Passons à l’exécution :

image

On voit bien que le résultat attendu est bien celui qu’on a, puisqu’on a bien importé nos 4 opérations, elles sont bien différentes, vu que que le traitement est correct, il me semble que 2 + 4 = 6.

MEF est disponible gratuitement sur codeplex : http://mef.codeplex.com

Si on résume ce que l’on vient de voir, on arrive grâce à MEF à étendre notre programme le plus facilement possible, tout ça grâce à de la réflexion et bien d’autres choses contenus dans MEF. Je pense que ce framework sera présent dans bien des applications utilisant la gestion de plugin au vu de sa simplicité d’utilisation. De plus, histoire de mettre la cerise sur le gâteau, MEF est aussi disponible en version Silverlight !!!

Remonter

WPF : Utiliser des commandes dans un DataTemplate

NOVE21

Après une très longue période sans réutiliser de technologies tel que WPF ou Silverlight, on s’aperçoit que l’on perd très vite la main. C’est donc durant un projet éclair que je me suis décidé de refaire un petit projet en utilisant le MVVM.

C’est donc confiant que je commence mon code, que je me décide à le lancer au bout d’une heure, histoire de voir ce que ça donne en dehors du Designer WPF. Mon interface ressemble donc à ce que je veux, cependant je m’aperçois que certaines de mes commandes ne marchaient pas, je passe donc 10 bonnes minutes pour trouver ce qu’il se passe et enfin corriger.

On va donc voir dans cet article un moyen de contourner la déclaration des commandes dans un DataTemplate, sans pour autant perdre en fonctionnalité.

 

Pour cette démonstration je vais utiliser comme base le template de MVVM qui vient de codeplex (WPF Model-View-ViewModel Toolkit 0.1) que je vais modifier de tel sorte que ViewModelBase dérive de DependencyObject, et implémente deux méthodes abstraites qui sont OnViewReady(), et OnViewDispose(). On obtient donc ceci :

/// <summary>
/// Provides common functionality for ViewModel classes
/// </summary>
public abstract class ViewModelBase : DependencyObject, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public abstract void OnViewReady();
    public abstract void OnViewDispose();

}

Note les modifications apportées ici ne sont pas nécessaire pour la démonstration, mais grâce à cela vous pouvez utiliser les DependencyProperty dans votre ViewModel, et propager les évènements Loaded et Unloaded de vos vues, vers vos ViewModel. Voilà pour l’astuce du jour !

Voici donc  la commande que nous allons utiliser :

#region GestionCommandes
private DelegateCommand<Models.Person> editPersonCommand;

public ICommand EditPersonCommand
{
    get
    {
        if (editPersonCommand == null)
            editPersonCommand = new DelegateCommand<Models.Person>(EditPerson);
        return editPersonCommand;
    }
}

private void EditPerson(Models.Person person)
{
    MessageBox.Show("Ici on éditera la personne", "", MessageBoxButton.OK);
}
#endregion 

Bon maintenant, affichons nos données dans la vue au pas à pas. Après un binding initial on obtient quelque chose du style

<Window x:Class="WpfModelViewApplication3.Views.MainView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:c="clr-namespace:WpfModelViewApplication3.Commands"
    Title="Main Window" Height="400" Width="800">

    <Grid>
        <ListBox ItemsSource="{Binding People}" />
    </Grid>
</Window>

Avec pour interface générée :

image

Bon ceci, n’étant pas très lisible pour un client, on va donc créer un DataTemplate. Or pour des raisons de lecture, nous allons ajouter ce DataTemplate en ressources de la fenêtre.

Donc confiant, j’écris ceci :

<Window.Resources>
    <DataTemplate DataType="{x:Type localModel:Person}">
        <StackPanel Orientation="Horizontal">
            <Label Content="{Binding FirstName}" />
            <Button Content="Modifier" Margin="5 0" Command="{Binding EditPersonCommand}" CommandParameter="{Binding}" />
        </StackPanel>
    </DataTemplate>
</Window.Resources>

<Grid>
    <ListBox ItemsSource="{Binding People}" />
</Grid>

J’ai donc mon interface qui change en conséquence :

image

Le problème se situe en fait lorsque je clique sur le bouton Modifier, en effet rien ne se produit. J’ai donc regardé si lors de la construction de la fenêtre il allait bien me chercher ma commande, mais il ignora mon point d’arrêt dans Visual Studio.

Alors l’astuce facile à réaliser avec ce template est d’utiliser leur CommandReference, de tel sorte :

<Window.Resources>
    <c:CommandReference x:Key="EditPerson" Command="{Binding EditPersonCommand}" />


    <DataTemplate DataType="{x:Type localModel:Person}">
        <StackPanel Orientation="Horizontal">
            <Label Content="{Binding FirstName}" />
            <Button Content="Modifier" Margin="5 0" Command="{StaticResource EditPerson}" CommandParameter="{Binding}" />
        </StackPanel>
    </DataTemplate>
</Window.Resources>

<Grid>
    <ListBox ItemsSource="{Binding People}" />
</Grid>

Là, notre code fonctionne comme on le souhaitait, et l’on récupère bien entendu le bon membre lors du déclenchement de la commande. En fait, ce qui a changé, nous avons déclaré la command, dans l’objet CommandReference. Notre bouton peut donc appeler la commande via le nom qu’on lui a donné. En effet, il passe par une ressource statique qui existe, et non une donnée dynamique comme il était question dans le DataTemplate.

Vous pouvez retrouvez la CommandReference dans le MVVM Toolkit, ce qui nous prouve encore une fois qu’avec des Toolkit bien pensé on optimise fortement notre temps de développement.

En espérant que cet article vous aide dans de futurs développement !

Remonter