Skip to main content

Transformez l’application Family.Show afin d’afficher l’organigramme d’une entreprise issu de l’Active Directory

Par David Rousset, Microsoft France.

David travaille actuellement dans l'équipe PTC (Partner Technical Consultant). A ce titre, il suit un certain nombre de clients ISV/éditeurs de logiciels. Sa mission consiste à délivrer du conseil sur les technologies de développement Microsoft et à agir en tant que point de contact principal pour faire l'interface avec nos équipes de support lorsque nécessaire. http://blogs.msdn.com/dev

Dans le cadre de mon auto-formation sur la technologie WPF issue du Framework 3.0/3.5, j’ai eu envie de travailler sur une petite application concrète qui pourrait avoir un intérêt à être utilisée au sein d’une entreprise. Mon idée de départ était un outil de « networking » permettant de découvrir ses collègues et leurs équipes d’une manière plus sympathique et parlante qu’en parcourant un ensemble de propriétés un peu austères issues de l’Active Directory… Je voulais donc plutôt récupérer la tête de mes collègues, la hiérarchie de management associée et rendre le tout dynamique et très visuel. J’ai l’habitude de développer autour de l’Active Directory et d’Exchange. Je savais donc que j’allais passer la plupart de mon temps de développement sur le rendu graphique. Plutôt que de partir de zéro et de réinventer la roue, j’ai préféré récupérer le code de Family.Show de Vertigo (disponible ici : http://www.codeplex.com/familyshow) et de refondre le code pour en faire ce que j’en souhaitais. Cela m’a donc permis de mieux comprendre les notions fondamentales de WPF comme le binding, le modèle de threading, XAML et de jouer un peu avec les outils de la gamme Expression.

Je vous propose donc de découvrir le chemin que j’ai parcouru pour arriver à ce que j’ai nommé ActiveDirectory.Show.

Pré-requis :

Le lecteur devra avoir une connaissance préalable des outils de développement de la gamme Visual Studio 2008 ainsi que du Framework 3.5 associé.

Comprendre l’architecture de l’application Family.Show

La 1ère étape évidente fut la nécessité de comprendre comment l’application Family.Show était constituée. Vertigo a eu la bonne idée de publier une petite documentation sur leur code pour dégrossir le sujet. Je vous invite donc à la lire si vous souhaitez suivre le même chemin que moi. Ensuite, il a fallu comprendre ces 3 briques de base:

1 – La manière dont l’arbre était généré

Family.Show étant une application prévue à l’origine pour afficher un arbre généalogique, la structure de l’arbre est un peu simpliste et fonctionne par une série de lignes sur lesquelles se trouve chaque génération. J’ai repris la même notion sauf que sur chaque ligne se trouve les collaborateurs se situant au même niveau hiérarchique par rapport à un manager donné. Cela n’est pas très adapté à des équipes ayant un nombre important de personnes et il serait intéressant d’améliorer ce point. Malgré tout, mon idée était d’arriver vite à un résultat intéressant. J’ai donc repris tel quelle cette construction d’arbre.

2 – Le modèle objet mis en place

Family.Show est une application très bien écrite et bien structurée. Il est donc facile de parcourir le code et de comprendre les mécanismes permettant la construction des collections, l’ajout d’un nouveau membre, etc.

La logique principale sur la partie non-graphique ayant eu le bon goût de se trouver exportée dans une librairie dédiée appelée FamilyShowLib : définition de la classe Person, type de relation entre les personnes (parents, frères, fils, filles, etc.), gestion des photos, construction des collections.

3 – Les notions de WPF utilisées

Family.Show fait un usage intensif du superbe binding proposé par WPF en utilisant notamment les collections ObservableCollection. La mise à jour d’une collection de personnes entraîne ainsi la mise à jour automatique de l’ensemble des contrôles graphiques associés à cette collection : l’arbre hiérarchique bien sûr, mais également les contrôles secondaires sur le détail d’un utilisateur, le filtrage, etc. Pour quelqu’un qui débute WPF, il faut donc prendre le temps de comprendre la magie mise en place pour que cette mise à jour automatique fonctionne.

J’ai également passé pas mal de temps à comprendre comment l’arbre était dessiné et la manière dont les contrôles étaient utilisés.

Pour terminer, l’application de Vertigo est entièrement basée sur l’utilisation de templates et de styles définis dans 2 thèmes SilverResources.xaml et BlackResources.xaml. Pour quelqu’un qui débute, on peut rapidement se perdre dans ces 2 fichiers de ressources. C’est donc là où j’ai donc moi-même perdu un peu de temps.

Modification de la logique de Family.Show pour s’adapter à l’Active Directory

Tout d’abord, le plus simple : on garde tel quel la façon dont l’arbre est construit et affiché. Ainsi, lorsque l’on ajoutera de nouveaux nœuds au fur et à mesure des retours des requêtes vers l’AD (Active Directory), on se contentera d’utiliser la logique de Vertigo qui fonctionne très bien pour redessiner l’arbre. On verra cependant qu’il y a un petit travail supplémentaire à effectuer pour rendre l’affichage encore plus dynamique en utilisant un thread séparé pour la recherche.

Ensuite, il faut modifier le modèle objet pour retirer tout ce qui ne sert à rien et qui reste spécifique à une application de généalogie : la possibilité d’avoir 2 parents (en général, nous n’avons qu’un seul manager), les relations de types frère/sœur, etc. Une fois les pièces inutiles retirées, il faut ajouter celles nécessaires à un objet de l’annuaire : numéros de téléphone, email, titre, etc. Bref, c’est la partie la plus simple. Au niveau de la sérialisation en XML, pour la sauvegarde et le chargement d’un arbre, il n’y a rien à faire. Le code existant s’accommode très bien de la modification de la classe principale. L’application d’origine gère également la norme GED qui ne me servait plus à rien donc je l’ai purement et simplement retiré.

Enfin, il reste la 3ème brique à modifier : les contrôles WPF. Certains ne vont plus servir à rien comme ceux disponibles pour ajouter un nouveau membre vu que dans mon cas je souhaite récupérer ces données de manière dynamique depuis l’annuaire. J’aurais pu imaginer cependant laisser la possibilité d’ajouter manuellement un nouveau membre de l’équipe pour mettre à jour l’AD via cette application WPF.

Etape suivante : les contrôles restants doivent être modifiés au niveau du binding pour être en accord avec la modification du modèle objet précédent. Expression Blend m’ayant permis de remettre en forme ce que je souhaitais et Visual Studio 2008 m’a aidé à modifier le XAML pour changer le binding dynamique. J’ai donc utilisé les 2 outils tels qu’ils sont prévus : Blend pour modifier le design des contrôles et Visual Studio pour modifier leurs comportements.

Pour terminer la préparation de l’application au niveau graphique, il reste alors la modification de l’arbre en lui-même. En effet, les 2 thèmes/skins définis l’étaient spécifiquement pour la généalogie comme par exemple pour afficher des filles (ou mères), des relations entres époux, etc. Par ailleurs, la photo n’était pas rendue directement à l’écran mais uniquement lorsque l’on passait la souris au dessus de l’un des nœuds. J’ai donc fait le choix de simplifier le skin « Black » pour l’épurer au maximum mais en le conservant quasiment tel qu’issu depuis Family.Show. J’ai ensuite passé un peu plus de temps pour modifier le skin « Silver » afin le rendre plus adapté à un trombinoscope. Le gros du travail se faisant sur un template de type bouton. En effet, chaque nœud est « clickable » car il est dérivé du type bouton auquel on a appliqué un template particulier. Cela donne alors ce type de rendu :

 





au lieu de ça initialement :  

 

En gros, voici un résumé visuel du travail effectué : 

Figure 1

Ce qui rend déjà ce genre de chose :

Figure 2

Se connecter de manière dynamique à l’Active Directory

A ce niveau, nous avons donc bien préparé l’application mais elle reste basée sur des données que l’on peut qualifier de statiques. J’ai scindé la logique de connexion vers l’AD en 2 parties : une première chargée d’effectuer une recherche LDAP à partir d’un nom complet ou d’un login (samAccountName), une autre prenant en entrée cet objet retourné pour parcourir de manière récursive les managés (ou directs) associés. On utilise donc le namespace DirectoryServices.

Le code de recherche vers l’annuaire ressemble à ça :

public class ActiveDirectoryHelper

    {

        private DirectoryEntry objRoot;

        private DirectorySearcher objSrch;

        public bool IsNetworkAvailable;

        public ActiveDirectoryHelper(string GCLdapPath)

        {

           try

            {

                objRoot = new DirectoryEntry(GCLdapPath);

                objRoot.RefreshCache();

                objSrch = new DirectorySearcher(objRoot);

                IsNetworkAvailable = true;

            }

           catch

            {

                IsNetworkAvailable = false;

            }

        }

        public DirectoryEntry ResolveThisUser(string displayName)

        {

           string filter;

            filter = "(&(objectClass=user)(objectCategory=user)(displayName=" + displayName + "))";

           return DoADSearchRequest(filter);

        }

        public DirectoryEntry ResolveThisAlias(string aliasName)

        {

           string filter;

            filter = "(&(objectClass=user)(objectCategory=user)(sAMAccountName=" + aliasName + "))";

           return DoADSearchRequest(filter);

        }

        private DirectoryEntry DoADSearchRequest(string filter)

        {

           SearchResultCollection objResults;

           SearchResult objSR;

           DirectoryEntry objUser = null;

           try

            {

                objSrch.Filter = filter;

                objSrch.SearchScope = SearchScope.Subtree;

                objResults = objSrch.FindAll();

               if (objResults.Count != 0)

                {

                    objSR = objResults[0];

                    objUser = objSR.GetDirectoryEntry();

                }

                objSR = null;

               if (objResults != null)

                {

                    objResults.Dispose();

                    objResults = null;

                }

               return objUser;

            }

           catch (Exception e)

            {

               return null;

           }

        }

    }

Et le code principal de construction de l’équipe dans l’arbre ressemble à ça :

private void AddTeamMembers(PeopleCollection team, string direct, Person manager, int recursionDepth, int maxRecursionDepth)

      {

         string givenName = "";

         string sn = "";

         DirectoryEntry directTeamMember = newDirectoryEntry("LDAP://" + direct);

         try

         {

            givenName = directTeamMember.Properties["givenName"].Value.ToString();

         }

         catch { }

         try

         {

            sn = directTeamMember.Properties["sn"].Value.ToString();

         }

         catch { }

         if (!string.IsNullOrEmpty(givenName) || !string.IsNullOrEmpty(sn))

         {

           Person directPerson = newPerson(givenName, sn);

            FillPersonFromDirectoryEntry(directPerson, directTeamMember);

            team.AddColleagueManager(manager, directPerson, dispatcher);

            directPerson.ManagerInMemory = true;

           if (recursionDepth < maxRecursionDepth)

            {

              try

               {

                 foreach (string subDirect in directTeamMember.Properties["directReports"])

                  {

                     AddTeamMembers(team, subDirect, directPerson, recursionDepth + 1, maxRecursionDepth);

                  }

                  directPerson.DirectsInMemory = true;

                  directPerson.ShowDirects = true;

              }

               catch

               { }

            }

         }

      }

Cette méthode permet ainsi, à partir d’un employé de type « manager », de construire son équipe ainsi que ses n-2, n-3, etc. de manière récursive.

Il faut savoir que j’ai simplement renommé certaines méthodes du code initial de Family.Show, via le « refactoring » de Visual Studio, pour utiliser des noms plus adaptés à la situation comme AddColleagueManager()au lieu de AddParent()ou AddChild().Pour information, la méthode FillPersonFromDirectoryEntry() sert à alimenter un objet de type Person (lié aux différents contrôles par binding) depuis les attributs de l’annuaire : company, department,mail, mobile, telephoneNumber, title, samAccountName, distinguishedName, directReports, manager.

Une fois cette logique ajoutée, il ne me restait plus qu’à proposer une fenêtre supplémentaire pour demander à l’utilisateur l’employé de départ qui va servir à construire l’organigramme :

Cette fenêtre étant appelée lorsque l’on clique sur « New ».

Petite note : si l’employé de départ est une feuille (tout en bas de la hiérarchie), le code s’occupe alors de simplement remonter d’un niveau pour afficher son équipe et son manager.

Récupérer les photos depuis les sites WSS http://my

Il existe plusieurs sources de photos chez Microsoft dont celles stockées dans l’attribut de l’Active Directory thumbnailPhoto et surtout celles mises en place par chaque employé dans son propre espace de partage Sharepoint : My. Exemple sur mon espace :

J’ai donc simplement été chercher ces différentes photos au fur et à mesure de la construction de l’arbre depuis l’Active Directory pour les afficher dans les nœuds de l’arbre. J’ai également mis en place un système de cache pour éviter de devoir recharger systématiquement les photos en cas de nouvelles recherches. Pour récupérer la photo depuis http://my , j’ai utilisé le WebService userprofileservice.asmx exposé par Sharepoint. Voici la méthode qui peut vous intéresser :

     

public string ReturnMySitePicUrl(string alias)

        {

           string picurl = null;

            ProfileService.UserProfileService service = new ProfileService.UserProfileService();

            service.Credentials = CredentialCache.DefaultNetworkCredentials;

           try

            {

                ProfileService.PropertyData[] props = service.GetUserProfileByName(alias);

               foreach (ProfileService.PropertyData prop in props)

                {

                   String value = prop.Values.Length > 0 ? prop.Values[0].Value.ToString() : "";

                   if (prop.Name.ToLower().Equals("pictureurl") && value.Length > 0)

                    {

                        picurl = prop.Values[0].Value.ToString();

                   }

                }

            }

            catch { }

            return picurl;

        }

Etendre dynamiquement l’arbre généré

Maintenant que nous avons tout le code pour se connecter à l’Active Directory, récupérer les attributs qui vont bien et les photos de chaque employé, il serait assez intéressant de parcourir l’arbre et de l’étendre de manière dynamique. Pour cela, il m’a fallut simplement ajouter un menu contextuel au niveau de chaque nœud proposant d’afficher le manager ou ses managés si disponible(s) pour alimenter l’organigramme. Avec ce menu, on peut donc monter ou descendre d’un niveau dans la hiérarchie.

Sachant qu’un nœud, ayant sous lui des managés, dispose de ce motif au dessus de sa tête :

Une fois les membres de l’équipe affichés, on peut éventuellement décider de fermer un nœud pour les faire disparaitre afin de rendre l’organigramme plus exploitable :

Ainsi si l’on reprend le résultat montré à la figure 2 et que l’on demande « Show manager of Murielle Le Goff – Riveron », on obtient le résultat suivant :

Figure 3

Rendre l’application multi-threadée

A ce stade du développement, toute recherche ou parcours dynamique de l’arbre bloque le thread principal de gestion de l’interface utilisateur de WPF. En effet, le code de recherche dans l’AD et de récupération des photos étant effectué dans le thread en cours, WPF et Windows n’ont plus le temps d’écouter les interactions utilisateurs (évènements clavier & souris). L’idée est donc de lancer le traitement bloquant sur un thread séparé. Généralement, pour ce genre de chose, j’ai tendance à utiliser la classe BackgroundWorker proposée depuis le framework 2.0. Cependant, dans notre scénario actuel cela pose problème car nous travaillons principalement avec des collections de type ObservableCollection auxquelles sont abonnés l’organigramme et l’ensemble des autres contrôles. Et alors me direz-vous ? Et bah, il est interdit de tenter de manipuler une collection de ce type depuis un thread séparé du thread principal de WPF sous peine d’avoir une exception de ce type : « This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread ». C’est d’ailleurs relativement logique. Comme vous devez sans doute le savoir, quelque soit le type d’application graphique sous Windows, ne peut modifier les contrôles graphiques que le thread dédié à la gestion de l’interface utilisateur. En effet, notre collection est liée de manière forte à des contrôles graphiques qu’elle notifie des changements de son état via le binding. Modifier son contenu depuis un thread séparé reviendrait donc à tenter de modifier les contrôles graphiques de manière indirecte.

La solution ? Elle consiste à mieux comprendre le modèle de threading de WPF. Pour cela, je vous invite à lire tout simplement la documentation MSDN associée qui m’a semblé la plus limpide et facile à comprendre : http://msdn.microsoft.com/fr-fr/library/ms741870(en-us).aspx . L’exemple de code pour gérer la météo devrait vous permettre de résoudre ce problème si un jour il se pose à vous. Le concept consiste alors à bien lancer un thread séparé pour effectuer une tâche bloquante et de notifier le « dispatcher » de WPF que l’on souhaite mettre à jour la collection. Ce dernier s’occupera alors d’appeler votre code de retour dans le contexte du thread graphique pour avoir le droit de mettre à jour la collection.

Une fois ce travail effectué, on voit alors l’arbre réellement se dessiner au fur et à mesure que les membres de l’équipe sont retrouvés avec leur photo. On peut même exploiter un minimum l’interface de l’application pendant que les données sont récupérées.

Pour permettre d’indiquer à l’utilisateur qu’un traitement est en cours, j’ai alors simplement ajouté un petit contrôle utilisant une barre de progression indiquant sur quel employé on travaille actuellement :

Ce contrôle étant également mis à jour via le « dispatcher » de WPF.

Conclusion

J’espère que cette histoire vous aura donné envie de vous lancer également dans la réalisation d’une application WPF similaire.

Pour information, il m’a fallu environ 1 semaine pour arriver à une 1ère version intéressante connectée à l’annuaire Active Directory mais sans gestion du multi-threading alors que je ne connaissais pas grand-chose sur WPF. Il m’a fallu 3 journées de plus pour gérer correctement le multi-threading. Par ailleurs, au cours du développement, j’ai essayé d’utiliser le Framework 3.5 au lieu du 3.0 retenu initialement. J’ai alors noté une amélioration significative des performances du rendu graphique tout simplement en recompilant. En effet, les performances de WPF ont été améliorées entre la version 3.0 et la 3.5.