SharePoint : Détecter un changement de valeur de propriété Personalizable

Dans le cadre du développement d'une évolution (gestion du VB.NET) pour la CodeTesterWebPart, je me suis rendu compte que le modèle objet de la WebPart ne permet pas de savoir directement, sur le set d'une propriété personalizable, si ce set correspond à une action utilisateur (changement de valeur) ou à l'initialisation du contrôle (affectation de la valeur sauvegardée).

N'ayant pas trouvé de documentation sur le sujet, j'ai remonté les deux callstack :

  • Dans le cadre de l'initialisation du contrôle, la propriété est affectée par le SPWebPartManager sur l'événement PageInitComplete de la page.
  • Dans le cadre d'un changement de valeur de propriété par l'utilisateur, le set de la propriété est appellé depuis le RaisePostBackEvent de la page (après le load donc). D'ailleurs, si nous sommes dans ce cas, il s'agît toujours d'une requête POST.

Dans une requête POST, la méthode CreateChildControls() de la WebPart est appellé juste avant le Load de la page, c'est à dire après que la propriété soit initialisée, mais avant qu'elle puisse être changée.

Partant de cette observation, j'utilise la propriété ChildControlsCreated pour déterminer s'il s'agit d'une initialisation ou d'un changement de valeur :

Sharepoint : La CodeTesterWebPart en pratique

Lorsque l'on développe sous Sharepoint, à chaque fois que l'on modifie son code il faut compiler, packager, déployer, recycler son application pool, et attendre que Sharepoint s'initialise. On peut alors vérifier l'éxécution du code si on a la chance d'avoir un bouton qui le déclenche directement, mais celui-ci peut aussi être éxécuté en fin de workflow ou lors d'évènements plus ou moins évidents à reproduire.

Tester du code dans son contexte d'exécution est donc souvent une tâche longue et fastidieuse pour le développeur Sharepoint.

La CodeTesterWebPart permet la compilation et l'éxécution instantanée de code C#. Elle est disponible depuis quelques jours sur CodePlex : http://www.codeplex.com/CodeTesterWebPart

On dispose d'une boite de texte dans laquelle on saisit le code que l'on souhaite, et d'un bouton "Run that method!" qui permet de l'éxécuter instantanément. On peut modifier la liste des assemblies référencées (en spécifiant leur chemin sur le disque) ainsi que les usings :

Le bouton "Save Inputs" permet de sauvegarder les références, usings, et le code grâce au concept de Personnalization.

Si il y a des erreurs de compilation, elles sont affichées :

La méthode de test dispose de deux arguments permettant de tester du code d'interface graphique : la page courante et un placeholder qui est situé juste en dessous du corps de la webpart. Ici, on joue avec un diagramme de gantt et on injecte du javascript dans la page :

Autre avantage, le code s'exécute avec le contexte courant. On peut donc tester tout ce qui concerne les droits. Par exemple, avec un compte visiteur, si on tente de modifier le titre du site courant avec la CodeTesterWebPart, on est redirigé vers la page Access Denied. On se rend alors compte qu'il faut utiliser  SPSecurity.RunWithElevatedPrivileges, le code suivant fonctionne sans problème :

On pourrait se dire que les limites sont atteintes dès lors que l'on veut tester du code récursif. Non, c'est possible aussi ! Il suffit de tricher un peu sur le parenthèsage. Après tout, le code est compilé tel qu'il est rendu à l'écran.

Mais comment ca marche ?!

Le namespace System.CodeDom contient des classes qui permettent de compiler dynamiquement une assembly à partir de code source sous forme de chaine de caractères. La CodeTesterWebPart compile ainsi une assembly en mémoire, il n'y a pas d'écriture sur le disque. La méthode TestMethod est ensuite appellée par reflection, de manière tout à fait classique. Pour les plus curieux, le code source est disponible sur CodePlex.

Sharepoint : Développez plus vite avec la CodeTesterWebPart

Et si on pouvait vérifier que notre code s'exécute comme prévu avant même de le déployer?

Voici la CodeTesterWebPart :

Téléchargement : http://www.codeplex.com/CodeTesterWebPart

Conçue dans ce but, j'espère que cette Web part vous fera gagner du temps lors de vos prochains développements :)

Sharepoint : Le contrôle ScriptLink

Le contrôle ScriptLink de Sharepoint permet d'effectuer l'enregistrement d'un fichier javascript dans une page. Voici les avantages de ce contrôle par rapport au ClientScript.RegisterClientScriptInclude() d'ASP.NET :

  • Gestion de la localisation
  • Gestion du cache
  • Le script est toujours référencé dans le header

Les deux choses importantes à savoir avant de l'utiliser sont que le chemin à spécifier doit être relatif au répertoire LAYOUTS (si utilisation sans localisation), et que si le fichier n'est pas trouvé une erreur sera levée.

Dans le cadre d'une utilisation avec localisation, Sharepoint cherchera la version qui correspond à la langue du site dans LAYOUTS/XXXX, où XXXX est le LCID du site courant (1033 pour l'anglais, 1036 pour le français).

Voyons un exemple d'utilisation avec une web part. Chacun des fichiers .js contient une méthode HelloWorld qui lance une alerte avec un message different. Voici la structure et le code du projet :

Dans ce cas c'est le fichier LAYOUTS/SLDemo/ScriptLinkDemo.js qui est référencé :

Maintenant essayons avec nos versions localisées, en changeant juste la valeur de Localizable :

Résultat à l'éxécution avec un site en francais (LCID 1036):

C'est la version qui correpspond au LCID du site qui est utilisée, on peut le vérifier en examinant le code HTML de la page:

Mais que se passe-t-il si il n'y a pas de fichier pour ce LCID ? Essayons. J'efface le fichier SLDemo/1036/ScriptLinkDemo.js, un coup de iisreset.exe, et je recharge ma page :

On remarque que ni la version neutre ni la version anglaise n'ont été substituées par Sharepoint, et que le message d'erreur n'indique pas le chemin exact attendu. Attention donc à deployer une version par langue susceptible d'être utilisée.


Classé sous ,

.NET 3.5 : Les certifications

Ca y est, il est possible de passer certaines des certifications du Framework .NET 3.5 !

Attention, seules les certifications relatives aux 3 briques majeures du Framework 3.0 sont prêtes, à savoir :

Les autres sont pour bientôt :

Pour préparer ces examens, on peut commencer par télécharger le training kit de Microsoft : Visual Studio 2008 and .NET Framework 3.5 Training Kit

Bonnes révisions à tous ;)

Technorati Profile

Classé sous ,

Sharepoint 2007 et Framework .NET 3.5 : Intégration d'AJAX en pratique

J'ai souhaité tester moi-même l'intégration d'AJAX dans Sharepoint. Disposant d'un Visual Studio 2008, j'en ai profité pour vérifier que le SP1 de Sharepoint règle le problème de compatibilité avec l'UpdatePanel d'AJAX.

Pré-requis, donc, un WSS mis à jour avec le Service Pack 1, et le Framework .NET 3.5.

Etape 1 : Mise à jour du Web.Config

AJAX requiert des changements assez lourds dans le Web.config de chaque application dans laquelle on l'utilise. Je me suis souvenu être tombé, il y a quelques temps, sur une feature Sharepoint permettant de mettre à jour le web.config : AJAX.Config, disponible dans le package Sharepoint Features 2007 (qui contient d'ailleurs de petits bijoux). Malheureusement, cette feature ne prend pas en compte les nouveaux numéros de version des assemblies du Framework 3.5. L'activer rend inutilisable l'application web à moins que l'ancienne version AJAX ne soit installée.

Il faut donc (à ce jour) mettre à jour le web.config manuellement, suivant la procédure de Microsoft, sans oublier de remplacer tous les numéros de version par 3.5.0.0. Hormi cela, aucun problème : notre application web Sharepoint est maintenant compatible AJAX !

Etape 2 : Modification de la master page

Pour pouvoir utiliser AJAX dans une page, il faut un et un seul ScriptManager. Il est donc recommandé de l'ajouter dans la master page du site. Sur son blog traitant du sujet, Mike suggère d'éditer manuellement le fichier master via un chemin WEBDAV (\\server\<pathtosite>\_catalogs\masterpage), et de placer la balise ScriptManager juste après la balise SPWebPartManager. Facile :

Ca y est, notre site est prêt pour accueillir des contrôles AJAX !

On pourrait aussi éditer le master directement dans le répertoire 12, mais puisqu'il s'agît d'un des fichiers par défaut utilisé par Sharepoint, cette manipulation n'est pas supportée par Microsoft. Le mieux en pratique est donc d'utiliser un master personnalisé.

Développement d'une WebPart AJAX

On notera dans le post de Mike le besoin d'utiliser du code qui enregistre un block de script permettant de patcher le javascript de Sharepoint. Dans une configuration WSS 3.0 SP1 + Framework 3.5, ceci est sensé être inutile. J'ai réalisé une toute petite WebPart utilisant un UpdatePanel pour prouver cette théorie :

Le tour est joué, ça fonctionne !

Le test est vraiment basique et je compte bien aller plus loin très prochainement. Cela fera surement l'objet d'un autre post.


Classé sous , ,

Design d'un simulateur de poker performant grâce à Linq

Dans le cadre du développement d’un robot capable de jouer au Texas Hold’em, j’ai créé une classe RoundInfo qui centralise toutes les informations sur la main en cours. Ce post est dédié à son design car il est amusant, performant en consommation mémoire, et basé entièrement sur Linq.

 

Le projet en quelques mots

Un des gros challenges quand on développe un robot de poker est la performance. A l’ère Bruel ^^, lorsque j’ai développé mon premier robot (en appliquant point par point la méthode de la RACHE), et une fois que suffisamment d’ « intelligence » fut implémentée, il arrivait parfois qu’il mette plus de 20 secondes à réagir lorsque son tour venait. Résultat, il se faisait kicker de la table. Le proof of concept était validé, mais le design méritait d'être revu. Par manque de temps, j'ai alors abandonné le projet.

Quelques générations de micro-processeurs plus tard, lorsque j'ai souhaité tester les nouveautés du Framework 3.5, la motivation était toute trouvée pour reprendre refaire le projet. Cette fois la priorité serait mise sur la performance. Pas seulement au niveau des traitements, mais aussi au niveau mémoire car l’IA devrait conserver en mémoire toutes les mains jouées contre chacun des adversaires afin de mieux les « lire ». 

La solution comporte 4 projets :

·         Un projet de type console application qui sert de point d’entrée

·         HoldemSimulator : librairie maison permettant de simuler une table de poker et la partie qui s’y déroule, ainsi que deux implémentations sommaires de IPlayer

·         HoldemInterface : contient l’interface IPlayer à implémenter pour réaliser un robot compatible avec HoldemSimulator

·         Nono le petit robot : implémentation vicieuse de IPlayer

·         HandEvaluator : une librairie C# incontournable lorsque l’on effectue des calculs de probabilité dans le contexte du Poker.

 

Les classes EventInfo

Une main de poker peut se résumer en quelques méta-datas (qui est dans la partie et à quelle place, quels sont les montants des blinds, etc.) ainsi qu’une suite ordonnée d’événements/actions (tel joueur mise tant, tel autre joueur relance, telle carte est découverte au turn, etc.). Tous ces éléments, et leur ordre, définissent une main de poker. Voici pour exemple le log d’une main jouée sur un site de poker en ligne :

Starting Hand #204916231

Game Type: Hold'em

Limit Type: No Limit

Table Type: Ring

Money Type: PLAY MONEY

Blinds are now $50/$100

Button is at seat 10

Seat 3: novembertango - $63757

Seat 4: MoneA - $10000

Seat 6: polak33 - $9000

Seat 7: Kheops - $6000

Seat 9: raceman69 - $0 (away from table)

Seat 10: john83 - $0 (away from table)

Moving Button to seat 7

novembertango posts small blind ($50)

MoneA posts big blind ($100)

Shuffling Deck

Dealing Cards

Dealing [2d Ah] to Kheops

polak33 calls $100

Kheops calls $100

novembertango calls $100

MoneA checks

Dealing Flop [As 5s Qs]

novembertango checks

MoneA checks

polak33 checks

Kheops bets $5900 (all-in)

novembertango folds

polak33 laughs his *** off.

MoneA folds

polak33 folds

Kheops shows [2d Ah]

Kheops has One Pair: Aces

Kheops wins $400

End Of Hand #204916231

 

Afin de décrire chacune de ces informations qui définissent une main, j’ai créé une classe abstraite EventInfo dont héritent tous les types d’information. Ce sont des classes toutes simples composées de rares propriétés et d'une méthode ToString(). C'est la hiérarchie des classes et le typage fort qui sont pertinents. Je vous invite à cliquer sur le diagramme de classes ci-dessous pour en voir une version lisible.

 

 

 

 

 

La classe RoundInfo

 

Dans le contexte d’une utilisation dans le simulateur, cette classe est utilisée par le Dealer (objet qui distribue les cartes), par les 9 instances FishPlayer qui sont les adversaires de Nono, et par Nono en personne. Elle dispose de propriétés permettant de savoir en temps réel combien il y a dans le pot, qui est à la table, qui doit jouer, combien doit-il miser pour suivre, de combien est la relance minimum, etc. Cette classe est donc centrale et de ses performances dépend le nombre de mains que le simulateur peut traiter par seconde. C’est là qu’intervient Linq !

 

La classe est composée d’un seul accesseur, privé, une collection List<EventInfo> _events. En pratique, pour chaque méta-data et à chaque fois qu’une action a lieu à la table, le dealer appelle la méthode AddEventInfo(EventInfo info) en passant une instance d'EventInfo. De son coté, chaque robot fait de même pour maintenir sa propre instance de RoundInfo.

 

Grâce à Linq, cette unique collection permet de fournir une multitude de propriétés. En voici un tout petit extrait :

 

 

        public virtual Int32 BigBlind {

            get { return _events.OfType<PlayerBlindEventInfo>().Where(pm => pm.Type == BlindType.Big).Select(pm => pm.Chips).FirstOrDefault(); }

        }

 

        public virtual String Board {

            get { return _events.OfType<BoardEventInfo>().Select(be => be.Board).LastOrDefault() ; }

        }

 

        public virtual String[] Players {

            get { return _events.OfType<PlayerStartEventInfo>().OrderBy(ps => ps.Seat).Select(ps => ps.PlayerId).ToArray(); }

        }

 

        public virtual StepName CurrentStep {

            get { return _events.OfType<BoardEventInfo>().Select(be => be.Step).LastOrDefault(); }

        }

 

        public virtual String[] RemainingPlayers {

            get { return Events.OfType<PlayerStartEventInfo>().Where(ps => !HasFolded(ps.PlayerId)).OrderBy(ps => (ps.Seat - ButtonSeat - 1 + Utils.SeatsPerTable) % Utils.SeatsPerTable).Select(ps => ps.PlayerId).ToArray(); }

        }

 

        public virtual String[] AllInPlayers {

            get { return Events.OfType<PlayerBetEventInfo>().Where(pb => pb.AllIn).Select(pb => pb.PlayerId).Distinct().ToArray(); }

        }

 

        public virtual String NextPlayer {

            get { return GetNextPlayer(); }

        }

 

        public virtual Int32 ButtonSeat {

            get { return Events.OfType<ButtonEventInfo>().Select(b => b.Seat).Single(); }

        }

 

Le diagramme des classes touffu d'EventInfo prend tout son sens lorsque l'on voit l'utilisation massive de de la méthode OfType<T>() qui ne retourne que les objets du type T .

 

Les rares méthodes privées de la classe sont elles aussi basées sur Linq :

 

 

        private Boolean HasFolded(String playerId) {

 

            return Events.OfType<PlayerFoldEventInfo>().Select(pf => pf.PlayerId).Contains(playerId);

 

        }

 

        private String GetNextPlayer() {

 

            if (Events.Last() is BoardEventInfo)

                return RemainingPlayers.First();

 

            //Last player that moved

            String lastPlayerName = Events.OfType<PlayerMoveEventInfo>().Except(Events.OfType<PlayerFoldEventInfo>().ToArray()).Select(pm => pm.PlayerId).LastOrDefault();

 

            //if null, the next player is the small blind

            if (String.IsNullOrEmpty(lastPlayerName))

                return (RemainingPlayers.Length == 2) ? RemainingPlayers.LastOrDefault() : RemainingPlayers.FirstOrDefault();

 

            //Seat of the last player that moved

            Int32 lastPlayerSeat = Events.OfType<PlayerStartEventInfo>().Where(ps => ps.PlayerId == lastPlayerName).Select(ps => ps.Seat).FirstOrDefault();

 

            String result = Events.OfType<PlayerStartEventInfo>().Where(ps => !HasFolded(ps.PlayerId) && !IsAllIn(ps.PlayerId)).OrderBy(ps => (ps.Seat - lastPlayerSeat - 1 + Utils.SeatsPerTable) % Utils.SeatsPerTable).Select(ps => ps.PlayerId).First();

 

            return result;

 

        }

 

Terminé les 38 collections (souvenir de ma V1) à maintenir en permanence, on requête dynamiquement une seule et unique collection! D’un point de vue mémoire c’est du tout bon car on a l’historique et le statut en temps réel, le tout grâce à une seule collection. Et d’un point de vue maintenance c’est vraiment agréable, le moindre problème s'identifie très facilement.

Certes, la même approche aurait pu être envisagée sans Linq, mais le code aurait été beaucoup trop volumineux et incompréhensible pour que tout cela soit réaliste.

Les seuls reproches que je fais à RoundInfo sont le fait que la classe ne soit pas thread-safe, et le coût processeur injustifié lors d’accès répétés à une même propriété entre deux appels à AddEventInfo().

 

Une brève revue de code met en évidence que ce cas ne se présente qu’au moment ou le robot doit prendre une décision. C’est là qu’intervient la classe RoundSnapShot.

 

 

La classe RoundSnapShot

 

Au moment de la prise de décision par le robot (implémentée sous la forme d’un réseau de neurones travaillant en asynchrone), tous les neurones accèderont aux propriétés de l'objet RoundInfo, ce qui peut conduire à une grosse déception côté performance. On va donc utiliser RoundSnapShot, une image de RoundInfo à un instant donné obtenue avec la méthode publique GetSnapShot() de la classe RoundInfo. RoundSnapShot hérite de RoundInfo et surcharge chacune de ses propriétés de la manière suivante :

 

        private Int32? _smallBlind;

 

        public override Int32 SmallBlind {

            get {

                lock (_lockSmallBlind)

                   if (_smallBlind == null)

                       _smallBlind = base.SmallBlind;

                return (Int32)_smallBlind;

            }

        }

 

        private String[] _allInPlayers;

 

        public override String[] AllInPlayers {

            get {

                lock (_lockAllInPlayers)

                   if (_allInPlayers == null)

                      _allInPlayers = base.AllInPlayers;

                return _allInPlayers;

            }

        }

        //et ainsi de suite..

 

J’ai déclaré un objet lock différent pour chaque propriété car les différents neurones accèdent à plusieurs propriétés en même temps, donc un seul objet lock partagé par toutes les propriétés aurait ralenti inutilement la prise de décision.

 

 

Un log simple à implémenter

 

Afin de pouvoir étudier le comportement du robot, et tout simplement débugger le simulateur, j’avais besoin d’un moyen de générer un log similaire à celui d’un site de poker en ligne décrit plus haut, afin de voir l'historique de la main d'un simple coup d'oeil. Avec ce design, rien de plus simple ! J’ai surchargé la méthode ToString() de chaque classe héritant de EventInfo.

 

Exemples :

 

         //BoardEventInfo

        public override string ToString() {

            if(Step == StepName.Flop)

                return String.Format("Flop : [{0}]", Board);

            else

                return String.Format("{0} : [{1}]", Step, Board.Split(' ').Last());

        }

 

        //PlayerRaiseEventInfo

        public override string ToString() {

            return String.Format("{0} raises {1}{2}", PlayerId, Chips, AllIn ? " and is all-in" : null);

        }

 

Pour atteindre mon but final, j’ai aussi surchargé ToString() dans RoundInfo :

 

        public override string ToString() {

            StringBuilder builder = new StringBuilder();

            foreach (EventInfo info in Events)

                builder.AppendLine(info.ToString());

            return builder.ToString();

        }

 

Il n’y a plus qu’à utiliser les classes Trace et Debug du framework pour écrire le log de la main dans un fichier texte ou dans la console de debug.

 

 

Promis, la prochaine fois je parlerai Sharepoint :)


Classé sous , , , ,

Sharepoint : Développement d'un template de site contenant une colonne lookup

Les colonnes lookup permettent aux éléments d'une liste de référencer un ou plusieurs éléments d'une autre liste, afin de s'approcher du comportement d'une base de données relationnelle dans WSS 3.0. Créer une colonne lookup dans un site éxistant n'est pas particulièrement difficile, mais dans le contexte d'un template de site, on coince car une colonne lookup ne peut être crée que si la liste cible existe : lors de la définition d'un template de site accompagné de templates de listes on ne peut pas connaitre les identifiants qu'auront ses listes une fois créées.

La technique que nous allons voir consiste en une feature qui définit et instancie les 2 listes, puis créé la colonne lookup.

Nous allons réaliser un template de site permettant le suivi des résultats de parties de poker, contenant une liste Parties et une liste Résultats. Un résultat est le gain net d'un joueur (utilisateur membre du site) lors d'une partie, on voudra donc une colonne lookup dans la liste Résultats. On utilisera un projet de type WSPBuilder (installé avec WSPBuilder Extensions) afin de ne pas se préoccuper du packaging et du déploiement, et Sharepoint Site Generator disponible dans le package Windows SharePoint Services 3.0 Tools: Visual Studio 2005 Extensions.

Créons un site vierge avec l'interface classique de Sharepoint, et ajoutons-y les listes et colonnes que l'on souhaite avoir dans notre template. Utilisons ensuite Sharepoint Site Generator pour générer les templates de ces deux listes : Les répertoires Parties et Resultats sont créés.

Création de la feature des templates de liste

Ajoutons à notre projet une feature vierge (via WSPBuilder) de scope Web pour nos templates de liste et copions y les 2 répertoires correspondants fraichement générés. En observant Resultats/schema.xml, on se rend bien compte du problème : la colonne lookup n'a pas pu être définie automatiquement :

Bien que la colonne Partie n'existe pas, les vues (éléments View) la référencent. Gardez cela en mémoire, nous en reparlerons lors de la dernière étape.

Afin que nos deux templates de listes soient inclus dans la feature et instanciés lors de son activation, il faut remplir elements.xml comme ceci :

ListTemplate : Nous devons décider d'un identifiant Type pour chacun de nos templates de liste. Ceux-ci doivent être uniques dans la feature dans laquelle ils sont déclarés. Prenons donc 1 et 2. Hidden="TRUE" permet de ne pas proposer nos templates de liste dans l'écran accessible via le bouton "Create" de WSS. Cliquez ici pour plus d'informations sur l'élément ListTemplate.

ListInstance : L'outil CreateGuid de visual studio nous permet de générer des identifiants pour nos deux instances. la valeur de FeatureId doit correspondre à l'identifiant de la feature générée par WSPBuilder (visible dans feature.xml), et celle de Type doit correspondre celle du ListTemplate correspondant. Cliquez ici pour plus d'informations sur l'élément ListInstance.

Création d'un template de site

Afin de créer un template de site vierge, copions le contenu du template de site de base "sts" (répertoire C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\SiteTemplates) dans notre projet sous 12/TEMPLATE/SiteTemplates/PokerLookup. Ouvrons le fichier onet.xml et dans le noeud <Configuration ID="1" ..> ajoutons une référence vers notre feature :

Ainsi, à chaque fois qu'une instance de notre template de site sera créée, la feature PokerLookup y sera activée, ce qui aura pour résultat la création de nos deux listes.

Le template est prêt, reste à informer Sharepoint qu'il existe. Pour ce faire, Ajoutons à notre projet un fichier 12/TEMPLATE/XXXX/XML/WEBTEMP_PokerLookup.xml, où XXXX est le LCID de la langue dans laquelle le template doit être référencé (1033 pour l'anglais, 1036 pour le francais, etc.). Sharepoint cherche la liste des templates de site à chaque fois qu'il redémarre dans les fichiers WEBTEMP_*.xml de tous les répertoires 12/TEMPLATE/XXXX/XML.

Il faut ensuite référencer la configuration 1 du template de site PokerLookup dans notre fichier WEBTEMP_PokerLookup.xml:

Notre template de site est prêt pour un premier test. A ce stade, la structure du projet est la suivante :

Grâce au menu contextuel offert par WSPBuilder, on peut packager et déployer notre solution. Il est alors possible de créer un site avec notre template de site : les 2 listes apparaissent correctement. Cependant, il manque la colonne lookup Partie dans la liste Résultats pour finaliser notre template.

Ajout de la colonne lookup

Il existe deux moyen de procéder : par code avec un SPFeatureReceiver, et en manipulant le fichier schema.xml.

Ajout d'une colonne lookup par code

Le SPFeatureReceiver permet d'éxécuter du code après qu'une feature soit activée. Créons une classe PokerLookup_Lists, héritant de SPFeatureReceiver, et ajoutons-y le code suivant :

Indiquons dans feature.xml que cette classe est branchée à notre feature :

On notera l'attribut Hidden="TRUE" dont le but est d'éviter que des administrateurs puissent réactiver cette feature, ce qui pourrait poser problème puisque le code de FeatureActivated n'est prévu pour être exécuté qu'une seule fois (afin de ne pas s'éloigner du sujet de ce post).

Si l'on redéploie notre solution et que l'on créé une nouvelle instance de notre template de site, nous constaterons que la colonne Partie est correctement crée. Cependant, elle est présente en double dans les vues définies par notre template de liste. Lors d'une telle implémentation, si on a utilisé Sharepoint Site Generator pour créer les templates de liste, il faut donc retirer "à la main" toutes les références à la colonne lookup dans le fichier schema.xml pour éviter ce problème.  

Ajout d'une colonne lookup par XML

Créer un SPFeatureReceiver juste pour ajouter une colonne lookup fait forcément froncer les sourcils au développeur Sharepoint, il existe heureusement une solution XML. La définition du champ Partie (dans Resultats/schema.xml, qui est commentée par Sharepoint Site Generator, est en fait utilisable si on utilise l'attribut List pour spécifier l'url de la liste cible (au lieu de son Id dans son contexte d'origine) :

Bien que le SDK spécifie d'utiliser l'attribut List pour spécifier le nom de la liste cible, il faut bel et bien spécifier l'url. Cliquez ici pour plus d'informations sur l'élément Field.

Conclusion

Les colonnes lookup, indépendantes du concept de template de site, sont plus difficiles à intégrer aux templates de site que les colonnes classiques. Le plus pratique est alors d'utiliser une feature regroupant les deux listes (et leurs templates) concernées.

Déclarer la colonne dans le fichier schema.xml du template de liste permet de centraliser toutes les déclarations de colonnes et rend le template plus maintenable, ne nécessitant pas le déploiement d'une assembly. Mais dans des cas plus complexe où l'url de la liste cible n'est pas toujours connue (colonne lookup cross-site), ou autres cas tordus, il peut s'avérer utile d'utiliser la déclaration par code qui offre plus de possibilités et de souplesse.


Classé sous , ,

Salut Codes-Sources !

Bonjour à tous !

Ceci étant mon premier message, voici la traditionnelle présentation : Je travaille en tant que développeur-consultant .NET chez Winwise depuis bientôt 2 ans, et depuis quelques mois au sein du pôle collaboratif. 

Je ne prévois pas de limiter ce blog à Sharepoint 2007, et compte bien poster quelques billets au sujet de mes futures découvertes sur WCF, Linq, WPF, et autres technologies incontournables du monde .NET. Joueur occasionnel, il est possible que certains de mes exemples de code aient comme un parfum de poker ;)

Je tiens à remercier Phil, Gribouillon, Azra, Daniel, et bien sûr Cyril, qui ont tous contribué à l'ouverture de ce blog (Faut pas croire, c'est pas aussi facile que ca en a l'air ^^).

A bientôt sur codes-sources,

Arnault


Les 10 derniers blogs postés

- Et je mets le son.... par Pierrick's Blog le il y a 33 minutes

- SharePoint : Comment interdire l’accès à un utilisateur pour tous les sites d’une Web Application par Blog Technique de Romelard Fabrice le il y a 17 heures et 38 minutes

- VPC - Reset de la position de la console par Blog technique de Nicolas Boonaert le il y a 20 heures et 14 minutes

- Un bug dans IE rendra cette page… non-imprimable ! par Le blog de FremyCompany le il y a 21 heures et 10 minutes

- Quelques retours sur Google Protocol Buffers par Julien Chable le 07-18-2008, 11:10

- SharePoint et le multilinguisme : Comment gérer la traduction des propriétés dans les toolbars des WebParts par The Mit's Blog le 07-18-2008, 10:53

- [Open XML] Les liens de la semaine 14/07/2008 par Julien Chable le 07-18-2008, 10:29

- Une nouvelle version des TFS Power tools est disponible par Michel Perfetti [Miiitch] le 07-18-2008, 09:37

- EF et le testeur fou par Matthieu MEZIL le 07-18-2008, 07:34

- VPC SharePoint/WSS clonable par Le blog technique de Loïc Bar le 07-17-2008, 16:32