« .NET »

[VS Extensibility] Part 1 : Comment étendre Microsoft Visual Studio

2 février 2009

image La plupart des développeurs sur plateforme Microsoft/.NET ont bien l’habitude d’ouvrir leur Visual Studio pour le développement de leurs applications. Lancé en 1997, la 1ère version de Visual Studio (ou VS) nommé tout naturellement Visual Studio 97, a voulu réunir dans un seul et même IDE plusieurs langages qui étaient à l’époque le VB 5.0, VC++, InterDev, VJ++ et FoxPro. Depuis 2002 et l’arrivée de la plateforme .NET, Visual Studio est devenu l’IDE de référence permettant le développement d’application WinForm, ASP.NET (WebForm), Mobile, WPF, Services Web/WCF, workflow, etc… supportant plusieurs langages comme VB.NET, C#, VC++.net et bien d’ autre encore sous forme d’extension.

image La grande qualité de Visual Studio est sa capacité à être étendue pour l’enrichir de nouvelles fonctionnalités (Add-in), de nouveaux langages, designers (DSL), etc.. Dans la version 2008 il est aussi possible de se servir du Visual Studio Shell permettant l’utilisation du socle Visual Studio pour développer un IDE spécifique basé sur VS.

Au fil du temps, les API d’extensibilité de VS se sont de plus en plus ouvertes rendant plus accessible et plus facile le développement d’extension. Ouvrons donc notre Visual Studio 2008 pour y créer un nouveau projet ! Vous retrouverez quelques templates de projets d’extensibilité dont les principaux sont :image

  • VS Addin : pour créer un module/extension dans Visual Studio
  • VS Shell Isolated : pour créer un nouvel IDE basé sur Visual Studio
  • VS Language Package : pour ajouter un nouveau langage dans Visual Studio en spécifiant les analyseurs lexical/syntaxique, le compilateur, la coloration syntaxique pour le langage, etc…
  • VS Package : pour créer un package (visible dans le Splash screen de VS) contenant un ensemble d’éléments graphique, de services, de templates de projets, d’ éditeurs, ou encore de designers.
  • DSL : pour la création de designers dans Visual Studio

Dans cet article nous verrons comment créer un Add-in dans votre Visual Studio 2005 ou 2008 en parcourant les principales fonctionnalités que l’on voudra doter à notre Add-in (création de fenêtre, écriture dans les Window Pane, interaction avec la StatusBar ou Properties Window, etc…). Je mettrai aussi en avant les principaux problèmes et leur solutions que l’on rencontre couramment dans le développement de tel add-in.

Création du projet de type « VS Add-in » :

Commençons par créer un projet de type « Visual Studio Add-in » que nous nommerons « MonAddinDemo » ! Afin simplifier la création d’ un tel projet, Visual Studio vous lancera un assistant (wizzard) permettant de préparer votre projet de type Add-in :

image  image  image  image  image  image

  1. Choix du langage : ici en C#
  2. Choix de l’hôte qui chargera notre Add-in : ici nous créons un add-in compatible VS2005 et 2008 en mode normal et Macro (pour l’ édition de Macro)
  3. Choix du nom et description de notre Add-in
  4. Choix des options : ici nous sélectionnons le fait que nous voulons ajouter automatique notre add-in dans le menu Tools de VS.
  5. Choix des informations sur le « About »
  6. Résumé !

Une fois validée toutes les informations recueillies vont permettre à Visual Studio de créer votre projet d’ Add-in en personnalisant votre fichier .addin et Connect.cs en fonction des choix effectués dans l’ assistant.

Voici à quoi ressemble votre projet après création :

image

On y retrouve :

  • AssemblyInfo.cs : contenant les attributs sur notre assembly
  • CommandBar.resx : contenant les traductions des menus de niveau 1 (Fichier, Édition,  …) de Visual Studio dans toutes les cultures
  • Connect.cs : classe de démarrage de notre add-in (qui implémente IDTExtensibility2)
  • MonAddinDemo – For Testing.AddIn : contenant la description de l’ Add-in au format XML (pour le test !)
  • MonAddinDemo.Addin : idem mais pour le packaging !

Sans plus attendre, voyons ce que tout cela rend. Tapez F5 pour compiler et exécuter notre projet ! Une nouvelle instance de Visual Studio se lancera et en cliquant sur le menu Tools (ou Outils en français) nous retrouverons notre add-in avec une jolie icône :)

image

Fichiers .AddIn et la classe Connect – comment ca marche ?

Fichiers .AddIn

Les fichiers de type .AddIn sont en fait des fichiers XML de description d’un addin. Chaque addin a donc son fichier .AddIn permettant de le décrire en deux sections XML différentes : Les HostApplication(s) et la section Addin.

Les HostApplication(s) vont décrire quelles sont les applications qui hébergeront notre addin. Dans notre « MyAddinDemo » nous avons souhaité héberger notre addin au sein de VS 2005 (8.0) et 2008 (9.0) en mode normal et macro.

Le XML correspondant est donc :

image

La section Addin contiendra les propriétés de l’addin en question comme par exemple :

  • FriendlyName : Le nom de notre addin tel qu’il sera affiché dans le Gestionnaire d’Add-in de Visual Studio
  • Description : la description de notre addin.
  • AboutBoxDetail et AboutBoxIcon : les informations (détail et icône) pour la boite A Propos comme nous l’ avons spécifié dans l’ étape 5 de l’ assistant
  • Assembly : le fichier DLL de l’assembly .NET contenant notre addin
  • FullClassName : le nom complet de la classe Connect contenu dans notre Assembly (ici : MonAddinDemo.Connect)

Extrait de notre section Addin :

image

Au démarrage, Visual Studio parcourra les répertoire ci-dessous (plus de détails sur MZ-Tools) pour charger tous fichiers .AddIn qu’il trouvera.

  • %ALLUSERSPROFILE%\Application Data\Microsoft\MSEnvShared\AddIns
  • %VSCOMMONAPPDATA%\AddIns
  • %ALLUSERSDOCUMENTS%\Microsoft\MSEnvShared\AddIns (seulement pour VS 2008)
  • %APPDATA%\Microsoft\MSEnvShared\AddIns
  • %VSAPPDATA%\AddIns
  • %VSMYDOCUMENTS%\AddIns

Comme nous l’avons vu un peu plus haut, avec ces fichiers XML, Visual Studio sera capable de charger l’assembly et d’ instancier la classe de connexion (notre MonAddinDemo.Connect).

Vous avez remarqué la présence de deux fichiers .AddIn dans notre solution : un normal et un pour « testing ». En fait, un fichier <mon projet>.AddIn est créé pour décrire l’add-in tel que l’on utilisera lors du packaging. Le 2ème « For Testing » est en fait un raccourci vers un fichier .AddIn présent dans le répertoire « %VSMYDOCUMENTS%\AddIns« . Il permet en fait d’avoir notre d’addin lancé au démarrage de Visual Studio pour nos tests ! Mais attention, si un 2ème VS est ouvert, vous risquerez de ne plus pouvoir compiler car il sera impossible de modifier votre assembly .DLL si elle est chargée par une autre instance de VS.

La classe Connect

Notre classe Connect est une implémentation de l’interface IDTExtensibility2 et IDTCommandTarget pour l’ajout d’une commande dans le menu. C’est elle qui est référencée dans notre fichier .AddIn et c’est donc elle qui sera instanciée par Visual Studio pour le chargement de l’ addin.

Comme nous le montre notre Object Browser :

image 

… cette classe implémentera les méthodes suivantes :

  • IDTExtensibility2.OnConnection : appelée au début du chargement de l’ addin par VS.
  • IDTExtensibility2.OnDisconnection : appelée au début du déchargement de l’ addin par VS.
  • IDTExtensibility2.OnAddInsUpdate : appelée quand la liste des addins est modifiée .
  • IDTExtensibility2.OnStartupComplete : appelée en fin de chargement de l’ addin.
  • IDTExtensibility2.OnBeginShutdown : appelée lorsque Visual Studio se ferme.
  • IDTCommandTarget.QueryStatus : appelée pour retourner l’état actuel (activée, désactivée, masquée, etc.) de la commande nommée spécifiée.  
  • IDTCommandTarget.Exec : appelée pour exécuter la commande nommée spécifiée. 

Comme vous le remarquait l’interface IDTExtensibility2 définie les méthodes de chargement/déchargement de notre addin. L’interface IDTCommandTarget  permet quant à elle de créer des commandes nommées qui est ici notre élément dans le menu Tools avec le petit smiley :)

Analysons maintenant le code de notre classe Connect générée par Visual Studio ! Vous remarquerez que seules les méthodes OnConnection, QueryStatus et Exec contiennent du code. Libre à vous d’implémenter une logique pour la fermeture de votre add-in si cela est nécessaire.

Lors de l’appel de la méthode OnConnection, l’argument connectMode va permettre de savoir le mode de connexion de l’addin. Dans le code générée par VS, le code de la méthode connexion est executé si le connectMode == ext_cm_UISetup. Comme nous l’indique la MSDN :

La valeur ext_cm_UISetup indique au complément qu’il s’agit de sa première exécution. Dans ce cas, le complément peut ajouter ses commandes personnalisées au menu et à la barre d’outils. Sinon, il peut ignorer cette étape.

Dans d’autre cas, on préféra utiliser les modes AfterStartup ou Startup pour permettre la création de fenêtres au démarrage ou initialisation de la logique de notre application au démarrage de VS.

La suite du code montre un « try..catch » (qu’il conviendrai d’externaliser dans une méthode privée) permettant la récupération du nom du menu « Tools ». En effet comme l’indique le commentaire, ce code permet de rechercher dans le fichier CommandBar.resx quel est le nom du menu Tools en fonction de la culture de VS. En version anglaise (EN), le nom est bien sûr « Tools » mais en version française (FR) cela devient « Outils », et cela change pour les versions JP, ES, IT, RU,  etc….

Une fois le nom récupéré on pourra récupérer la CommandBarPopup par le code :

1
2
3
4
5
6
7
//Place the command on the tools menu.
//Find the MenuBar command bar, which is the top-level command bar holding all the main menu items:
Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["MenuBar"];
 
//Find the Tools command bar on the MenuBar command bar:
CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];
CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;

Et ainsi ajouter notre bouton :

1
2
3
4
5
6
//Add a command to the Commands collection:
Command command = commands.AddNamedCommand2(_addInInstance, "MonAddinDemo", "MonAddinDemo", "Executes the command for MonAddinDemo", true, 59, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported+(int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);
 
//Add a control for the command to the tools menu:
if((command != null) && (toolsPopup != null))
	command.AddControl(toolsPopup.CommandBar, 1);

Vous remarquerez dans l’appel de la méthode AddNamedCommand2 les différents paramètres pour notre bouton comme par exemple son nom, son titre et son tooltip. Mais où est donc spécifié notre image (smiley) ? C’est en fait le « 59″ qui détermine l’icône de notre bouton !

Il s’agit en effet du n° de FaceID dont vous retrouverez la liste complète sur la page http://www.kebabshopblues.co.uk/2007/01/04/visual-studio-2005-tools-for-office-commandbarbutton-faceid-property/. On y retrouve notre n° 0059 représentant notre smiley jaune.

Nous verrons dans un autre post comment définir sa propre icône !

Une fois notre addin « connecté » et le bouton ajouté dans le menu Tools, voyons l’implémentation de l’interface IDTCommandTarget et de ses deux méthodes : QueryStatus et Exec.

La méthode QueryStatus permet de définir le status de notre bouton dans le menu. Nous pourrions par exemple implémenter une logique où le bouton serait grisé si l’addin est déjà lancé par exemple.

Dans le code actuel il n’en n’est rien. Le bouton est toujours activé et visible comme l’indique le code :

1
2
3
4
5
6
7
8
if(neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
{
	if(commandName == "MonAddinDemo.Connect.MonAddinDemo")
	{
		status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported|vsCommandStatus.vsCommandStatusEnabled;
		return;
	}
}

La méthode Exec quant à elle implémente la logique lors du click du bouton. Il convient d’abord de vérifier quel bouton est cliqué en testant le paramètre commandeName.

Dans le code généré par Visual Studio, aucune action n’est effectuée. Pour notre démo nous allons ajouter la référence vers System.Windows.Forms pour afficher une MessageBox. Voici le code complet de notre méthode Exec :

1
2
3
4
5
6
7
8
9
10
11
12
13
public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
{
	handled = false;
	if(executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
	{
		if(commandName == "MonAddinDemo.Connect.MonAddinDemo")
		{
			handled = true;
			System.Windows.Forms.MessageBox.Show("Mon 1er Add-in Visual Studio :)");
			return;
		}
	}
}

Relançons maintenant notre projet (F5) et observez le résultat lors du click sur notre bouton :

image

Conclusion

Vous voilà maintenant introduit dans le développement d’add-in pour Visual Studio 2005/2008. Un addin est donc une assembly .NET contenant une classe nommée généralement Connect qui implémente l’interface IDTExtensibility2 et est responsable de la « Connexion » et « déconnexion » de l’addin à Visual Studio. Chaque add-in étant accompagné d’un fichier de description au format XML (.AddIn) placé dans un des répertoires de Visual Studio pour préciser les informations sur l’add-in (nom, fichier de l’assembly, nom de la classe Connect, etc…) et ainsi être chargé au démarrage de l’IDE Visual Studio.

Dans la prochaine partie nous verrons comment créer des fenêtres dans Visual Studio.

Historique .NET : dates et versions (Fx, CLR, IDE et langages)

29 janvier 2009

En préparant du contenu pour des sessions sur le futur de .NET (VS2010, .NET 4.0, C# 4.0 ou encore VB 10), j’ ai été amené à faire un petit tableau récapitulatif des dates et versions des principaux éléments de .NET : le framework, la CLR, l’ IDE Visual Studio et les deux principaux langages .net (C# et VB.net).

Un petit copier/coller pour vous en faire profiter :

image

A bientôt :)

[WPF] PlayAnimations ou comment jouer plusieurs animations les unes après les autres

8 janvier 2009

Lors du développement de SmartCooking (notre projet Imagine Cup) en Mai 2008, j’ ai été confronté à quelques détails techniques avec WPF. Parmi eux : les animations !

Notre application se découpe en plusieurs écrans (menu principal, visualisation du stock, ajout de produits, etc…) tous liés entre eux par de petites animations pour y jouer des transitions entre chaque écran.

image image image image

Plus globalement, si j’ ai trois écrans A, B et C, je crée quelques animations (storyboards) que je nomme ‘StoryboardOpenScreenA’, ‘StoryboardCloseScreenA’, ‘StoryboardOpenScreenB’, etc… Avec donc, à chaque fois, une animation de fermeture et une autre pour l’ ouverture de mon écran !

Il me suffit donc, dans mon code ou par l’ intermédiaire de Triggers, qu’ a jouer ces animations pour réaliser les transitions d’ un écran à l’ autre. Oui mais voila, si je place un trigger jouant plusieurs animations, du type :

1
2
3
4
5
6
<window.triggers> 
    <eventtrigger routedevent="ButtonBase.Click" sourcename="bt1"> 
        <beginstoryboard storyboard="{StaticResource animation1}" /> 
        <beginstoryboard storyboard="{StaticResource animation2}" /> 
    </eventtrigger> 
</window.triggers>

… ou directement dans le code, par appel de la fonction BeginStoryboard, mes deux animations se joueront en même temps !! Et croyez-moi, cela n’ est pas très joli surtout quand il y a plus de deux animations en même temps !

Ce que je veux donc, c’ est jouer les animations les unes après les autres ! Pour cela deux façons à ma connaissance :

  • Soit, prévoir dans les storyboards des « blancs » pour démarrer l’ animation après quelques secondes (calé sur le temps de la précédente animation !). Mais de suite je vous arrête car cette méthode n’ est pas du tout convenable !
  • Soit s’ abonner à l’ événement Completed de l’ objet Storyboard pour être notifié lors de la fin de l’ animation et ainsi lancer les animations suivantes !

Je me suis donc mis à la tache pour simplifier l’ utilisation de cette méthode par la création d’ une méthode d’ extension sur l’ objet Window (System.Windows.Window) permettant de jouer X animation(s) dans l’ ordre en précisant simplement leurs noms dans l’ ordre voulu !

On pourra ensuite, par exemple, dans notre Window et en une ligne de code, jouer les animations AnimA, AnimB et AnimC les une après les autres :

1
this.PlayAnimations("AnimA", "AnimB", "AnimC");

Le nom des animations étant un « params string[] » on pourra y jouer autant d’ animation que l’ on veut (une seule, deux, trois, … dix, … ou plus).

Voici le code de cette extension :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public static class MyWpfExtensions
{
    public static void PlayAnimations(this Window window, params string[] animationsName)
    {
        // If animationsName is empty: return
        if (animationsName.Length <= 0) return;
        // For each animation
        for (int i = 0; i < animationsName.Length; i++)
        {
            // Get Storyboard object
            var anim = window.GetStoryBoard(animationsName[i]);
            if (anim != null)
            {
                // If not the last animation,
                if (i < (animationsName.Length - 1))
                {
                    // Add handler on the Completed event of the current storyboard to play the te next animation
                    anim.Completed += new EventHandler(
                        new StoryboardPlayNextHandler(window, anim, animationsName[i + 1]).OnHandler);
                }
            }
        }
        // Finally, play the 1st animation
        window.BeginStoryboard(window.GetStoryBoard(animationsName[0]));
    }
 
    public static Storyboard GetStoryBoard(this Window window, string name)
    {
        if (window.Resources.Contains(name))
            return (Storyboard)window.Resources[name];
        else
            return null;
    }
 
    public class StoryboardPlayNextHandler
    {
        private string _nextAnimationName;
        private Storyboard _actualStoryBoard;
        private Window _window;
 
        public StoryboardPlayNextHandler(Window window,
            Storyboard actualStoryBoard, string nextAnimationName)
        {
            this._actualStoryBoard = actualStoryBoard;
            this._nextAnimationName = nextAnimationName;
            this._window = window;
        }
 
        public void OnHandler(object sender, EventArgs e)
        {
            if (_actualStoryBoard != null)
            {
                // Remove the current handler on the completed event !
                _actualStoryBoard.Completed -= new EventHandler(this.OnHandler);
                // Launch next storyboard
                _window.BeginStoryboard(_window.GetStoryBoard(_nextAnimationName));
            }
        }
    }
}

En espérant que cela pourra vous être utile :)

Code complet de ma classe en cliquant ici !

MyWordPressUrlRewriteModule (Part 1) et comment héberger WordPress 2.3 sur IIS 7.0

19 février 2008

Retrouvez la version 2 de cet article sur la MSDN à l’adresse http://msdn.microsoft.com/fr-fr/architecture/cc512087.aspx

IIS7-logo L’ installation de WordPress sur un serveur IIS 7.0 est un peu différente de ce que l’ on pouvait faire avant !

En effet sous IIS6.0, nous avons le choix d’ exécuter notre PHP au travers des CGI, ISAPI ou alors, ce que j’ utilise actuellement en production et qui est le plus performant : le Fast-CGI pour IIS6.0 qui n’ est ni plus ni moins qu’ un autre filtre ISAPI utilisant de manière optimisée le processus "php-cgi.exe" ! (Pour les intéressés, une documentation pour l’ installation de PHP en Fast-CGI sur IIS6.0 ici)wordpress-icon

De plus, afin de pouvoir profiter des permalink sous toutes ses formes et sans le "/index.php/" nous devions utiliser soit une astuce grâce aux erreurs 404 ou soit (et pour être plus propre) un module d’ URL Rewriting comme celui de Jon Tackabury que j’ utilise pour ce blog et disponible sur IIS.net. A ce sujet, vous retrouverez sur le site de WordPress plus d’ informations sur les permalink et IIS.

Windows Server 2008 étant sortie en RTM il y a maintenant dix jours, la migration de mes serveurs perso est imminente et j’ ai donc testé l’ hébergement d’ un site WordPress sous IIS7.0.

1/ Installation de PHP 5 sous IIS7.0

1.1 / Préparation de IIS pour Fast-CGI

Tout d’ abord, sous IIS 7.0 nous disposerons de deux modules natifs qui nous intéressons fortement nommé "CgiModule" et "FastCgiModule" qui vous faudra activer lors de l’ installation du rôle Web Server en sélectionnant entre autre "CGI" :

role-webserver

Les deux modules devraient être activés dans le fichier de configuration de IIS (dans %systemroot%\system32\inetsrv\config\applicationHost.config) sous la section globalModules (étant donné que ce sont des modules natifs ;) ).

1
2
3
4
5
6
7
8
9
<configuration>
    ....
    <system.webserver>
        ....
        <globalmodules>
            ......
            <add image="%windir%\System32\inetsrv\cgi.dll" name="CgiModule" />
            <add image="%windir%\System32\inetsrv\iisfcgi.dll" name="FastCgiModule" />
            ......

La commande "appcmd list modules" devrait d’ ailleurs vous indiquer le bon chargement de ces deux modules :

1
2
3
4
5
6
C:\Users\Administrator>%systemroot%\system32\inetsrv\appcmd list modules
.....
MODULE "CgiModule" ( native, preCondition: )
MODULE "FastCgiModule" ( native, preCondition: )
.....
C:\Users\Administrator>
1.2/ Installation de PHP et configuration Fast-CGI pour PHP

Il vous faudra ensuite configurer le module Fast-CGI avec PHP. Pour cela :

  1. Télécharger PHP 5 (en ZIP) que vous décompresserez dans c:\PHP\.
  2. Créer une copie du fichier php.ini-recommanded et le renommer en php.ini.
  3. Éditer ce fichier est redéfinir extension_dir = "C:\PHP\ext" qui nous servira un peu plus tard pour activer quelques extension de base de PHP. Pour plus’ informations sur l’ installation et la configuration de PHP je vous renvoi sur le site PHP.net ou sur cet article.
  4. Rajouter le bout de XML suivant dans votre fichier applicationHost.config dans la section system.webServer pour configurer le Fast-CGI au niveau de votre serveur :
1
2
3
4
5
6
7
8
<configuration>
    ....
    <system.webserver>
        ......
        <fastcgi>
            <application fullpath="C:\PHP\php-cgi.exe" />
        </fastcgi>
        .......

Vous pouvez aussi réaliser cette manipulation par la simple commande :

1
appcmd set config /section:system.webServer/fastCGI /+[fullPath='c:\php\php-cgi.exe']
1.3/ Ajout du handler sur les extensions PHP

Une fois le module de Fast-CGI installé et configuré, nous allons ajouter un handler sur l’ extension PHP afin que le module Fast-CGI exécute nos pages PHP ! Vous pouvez ajouter ce handler directement au niveau de votre fichier applicationHost (au niveau "serveur") afin que tous vos sites IIS enfants héritent de cette configuration ou alors définir ce handler dans vos fichier web.config au niveau de votre "site", "application" ou même "répertoire virtuel".

Dans notre cas, nous ajouterons ce handler au niveau de notre serveur. Pour cela trois méthodes :

  • Dans le fichier configuration applicationHost.config, ajouter la ligne ci-dessous sous la section system.webServer dans <location path=""> (qui regroupe la configuration de tous les sites IIS de notre serveur)
1
2
3
4
5
6
7
8
9
10
<configuration>
    ....
    <location overridemode="Allow" path="">
        ....
        <system.webserver>
            ......
            <handlers accesspolicy="Read, Script">
                ......
                <add name="PHP-FastCGI" path="*.php" resourcetype="Either" scriptprocessor="c:\php\php-cgi.exe" modules="FastCgiModule" verb="*" /> 
                ......
  • Par AppCmd en tapant la commande :
1
appcmd set config /section:system.webServer/handlers /+[name='PHP-FastCGI',path='*.php',verb='*',modules='FastCgiModule',scriptProcessor='c:\php\php-cgi.exe',resourceType='Either']
  • Par la console d’ administration IIS, en sélectionnant notre serveur puis sur la page des features sélectionner "Handler Mapping" pour ajouter un nouveau handler et compléter le formulaire comme sur la capture ci-dessous :

 console iis - ajout handler php

Nous pouvons dès maintenant créer un fichier .PHP dans l’ un de vos sites IIS avec un simple <?php echo "Hello PHP" ?> pour s’ assurer que votre PHP est correctement exécuté par le serveur IIS.

2/ Installation de WordPress

2.1 / Les pré-requis : extensions PHP, MySql et phpMyAdmin

Tout d’ abord revenons dans le fichier de configuration de PHP (C:\PHP\php.ini dans notre cas) et dé-commenter les lignes suivantes afin d’ activer ces extensions pour le bon fonctionnement de WordPress et phpMyAdmin:

1
2
3
4
5
6
extension=php_curl.dll
extension=php_gd2.dll
extension=php_mbstring.dll
extension=php_mcrypt.dll
extension=php_mysql.dll
extension=php_mysqli.dll

Nous allons ensuite installer un serveur MySql pour héberger notre base WordPress. Vous retrouverez l’ installeur de MySql pour Windows ici. Rien de bien compliqué, suivant, suivant, suivant :)

Ensuite, installer simplement un phpMyAdmin et créer la base WordPress ainsi qu’ un utilisateur :

  1. Télécharger phpMyAdmin
  2. Le décompresser dans un de nos sites IIS maintenant prêts pour accueillir du PHP
  3. Naviguer avec votre navigateur sur l’URL de cette application pour lancer la configuration (temporairement donner les droits CT dans votre dossier phpMyAdmin afin que l’ application PHP puisse créer le fichier de configuration config.inc.php)
  4. Créer ensuite un utilisateur (sur la page "Privilèges" > "Ajouter un utilisateur") en donnant un login et mot de passe et sélectionner "Créer une base portant son nom et donner à cet utilisateur tous les privilèges sur cette base" comme montré sur la capture ci-dessous :

phpmyadmin - ajout utilisateur

2.2/ Installation de WordPress 2.3.3 sur IIS

Il ne nous reste plus qu’ a installer WordPress (à télécharger ici) avec La Célèbre Installation en 5 Minutes ! Mais attention la version actuelle de WordPress, version 2.3.3, ne fonctionnera pas sur un Windows/IIS au niveau de l’ installation dû a des erreurs d’ insertion SQL de ce type :

1
WordPress database error Table 'wpdemo.wp_options' doesn't exist for query SELECT option_value FROM wp_options WHERE option_name = 'siteurl' made by is_blog_installed

Pour résoudre ce problème remplacer le code du fichier wp-db.php du dossier wp-includes par le code publié sur Pastebin de wpforums – Fix for CGI Error – message n° 893007.

3/ Configuration des Permalink avec MyWordPressUrlRewriteModule

Maintenant notre WordPress fonctionnel sur notre serveur IIS7, intéressons-nous aux URL de notre blog, appelés aussi permalink (liens permanents).

Par défaut les liens sont sous la forme : http://www.monblog.com/?p=123 ce qui n’ est très explicite sur le contenu de cette page ! WordPress nous permet alors dans l ‘interface d’ administration sous "Options > Permaliens" de redéfinir la structure de ces liens pour avoir des URLs personnalisées du genre http://www.monblog.fr/index.php/2008/02/19/sample-post/ ce qui est déjà plus sexy !!

Mais encore une chose me dérange : le "/index.php/" dans l’ URL indispensable pour que WordPress puisse traiter la requête ! Comme je l’ évoquais en introduction, nous pouvons alors jouer avec les erreurs 404 ou utiliser des filtres ISAPI dans un pipeline en mode classique mais là n’ est pas notre but ! Nous allons en effet utiliser tout l’ intérêt de IIS 7.0, à savoir le mode intégré d’ un pool d’ application qui permet l’ exécution de modules natifs et managés dans notre pipeline IIS.

Nous allons donc ôter notre casquette d’IT et lancer notre Visual Studio pour créer notre module IIS :)

3.1/ Création du module IIS

Sous Visual Studio, Ouvrir > Site Web > Local IIS et sélectionner votre site où ce trouve votre blog WordPress. Ajouter alors une classe que l’ on nommera MyWordPressUrlRewriteModule.cs qui viendra se placer dans le dossier App_Code (créé automatiquement par VS).

Implémenter ensuite l’ interface IHttpModule et abonnez-vous à l’ événement  BeginRequest de notre "context" (de type HttpApplication) dans la méthode Init. A l’ appel de cette événement nous allons simplement tester si le PhysicalPath de notre Request est un fichier ou dossier existant au quel cas nous ne ferons rien ! A l’ inverse, nous appellerons la méthode RewritePath en rajoutant ~/index.php devant notre RawUrl.

Le code de notre module sera donc très simple :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using System.IO;
using System.Web;
 
public class MyWordPressUrlRewriteModule : IHttpModule
{
    public void Dispose()  { }
 
    public void Init(HttpApplication context)
    {
        context.BeginRequest += new EventHandler(context_BeginRequest);
    }
 
    void context_BeginRequest(object sender, EventArgs e)
    {
        HttpApplication context = (HttpApplication)sender;
        string physicalPath = context.Request.PhysicalPath;
        if (!(File.Exists(physicalPath) || Directory.Exists(physicalPath)))
        { 
            context.Context.RewritePath("~/index.php" + context.Request.RawUrl);
        }
    }
}

La méthode RewritePath permet simplement de redéfinir un chemin d’ accès interne. Prenons ces quelques exemples :

Url démandée Dossier ou fichier existant Url interne
/index.php Oui inchangée
/wp-login.php Oui inchangée
/wp-admin/xxx Oui inchangée
/wp-content/xxx Oui inchangée
/2008/02/19/sample-post NON /index.php/2008/0219/sample-post
/a-propos NON /index.php/a-propos

Comme nous le voyons, les fichiers wp-login.php, site d’ administration dans le répertoire wp-admin ou vos thèmes, plugins et médias de vos posts/pages dans le dossier wp-content ne seront pas affectés par notre module car ces derniers sont des fichiers physiquement existants par rapport avec l’URL demandée. Par contre les URLs du type http://www.monblog.fr/2008/02/19/sample-post/ n’ existe pas physiquement et donc seront "rewrités" par notre module sur l’URL http://www.monblog.fr/index.php/2008/02/19/sample-post/ qui sera ensuite attrapée et interprétée par WordPress.

3.2/ Activation du module dans le pipeline IIS

Une fois notre module développé nous allons l’ activer au niveau de notre site IIS hébergeant WordPress. Pour cela depuis la console d’ administration IIS, sur le site IIS de WordPress, entrer dans la feature "Modules" et ajouter depuis le panneau Action un module managé. IIS compilera dynamiquement votre site et détectera votre module que nous pourrons ensuite sélectionner comme "Type" comme le montre la capture ci-dessous :

console iis - ajout d'un module manage

Nous pouvons aussi ajouter manuellement dans le fichier web.config au niveau de la racine de votre site IIS (créé par Visual Studio lors de l’ ouverture du site) une balise add contenant la définition de notre module :

1
2
3
4
5
6
7
8
<configuration>
    ....
    <system.webserver>
        ....
        <modules>
            ......
            <add name="MyWpUrlRewrite" type="MyWordPressUrlRewriteModule" />
            ......
3.3/ Configuration des permalinks dans WordPress

Maintenant notre module installé, il ne nous reste plus qu’ a personnaliser nos permalinks dans l’ interface d’ administration de WordPress dans "Options > Liens permanents" et personnaliser l’URL comme par exemple : /%year%/%monthnum%/%day%/%postname%/

image

Et le tour est joué, en naviguant sur une des pages de notre blog nous verrons alors des URLs du genre http://www.monblog.fr/2008/02/19/sample-post/ comme nous l’ avons spécifié dans la structure de nos "permaliens" !

3.4/ Module local ou partagé ?

N’ oubliez pas que nous n’ avons pas compilé notre module, il est simplement activé de manière locale sur notre site IIS WordPress. Mais il se peut (comme moi) que vous ayez plusieurs sites IIS hébergeant un site WordPress et par conséquent autant de sites IIS qui ont besoin de ce module.

Pour cela, plutôt que de recopier le code de votre module un peu partout, nous allons compiler la classe de notre module dans une assembly que l’ on placera dans la GAC.

Ouvrez donc Visual Studio et créer un projet de type "Bibliothèque de classes" dans lequel vous placerez la classe de notre module. Dans les propriétés de votre projet, dans l’ onglet "Signature", activer la signature de votre assembly :

proprietes-projet-csharp

Toujours dans la page des propriétés de votre projet, dans l’ onglet "Événements de génération", entrez la commande suivante afin de placer votre assembly signé dans la GAC afin de partager votre module sur votre serveur.

1
2
call "%VS90COMNTOOLS%\vsvars32.bat" > NULL
gacutil.Exe /if "$(TargetPath)"

Mais attention à chaque compilation, votre module sera ré-installé dans la GAC et donc déployé surtout vos sites (attention donc si vous êtes sur un serveur en prod ;) ).

Dans la console d’ administration IIS, en ajoutant un nouveau module vous trouverez dans les modules disponibles votre module avec un nom beaucoup long (le strongname de votre assembly) de type : MyWordPressUrlRewrite.MyWordPressUrlRewriteModule, MyWordPressUrlRewriteService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=54ce8ebb28415bb0 !

1
2
3
4
5
6
7
8
<configuration>
    ....
    <system.webserver>
        ....
        <modules>
            ......
            <add name="WpUrlRewrite" type="MyWordPressUrlRewrite.MyWordPressUrlRewriteModule, MyWordPressUrlRewriteService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=54ce8ebb28415bb0" />
            ......

Conclusion

A partir de maintenant vous ajouterez donc votre module sur chacun de vos sites IIS utilisant WordPress depuis la console d’ administration IIS ou dans le fichier de configuration de votre site IIS (web.config) manuellement en XML. Mais attention ne l’ activez pas au niveau de votre serveur (applicationHost.config) car cela risquerait de perturber vos applications non WordPress.

Pour remédier à ce problème nous pourrions par exemple étendre le schéma de configuration IIS afin de rajouter une section pour savoir si le module doit rewriter ou non ! Cela permettrait de charger le module au niveau du serveur (applicationHost.config) et de l’ activer « plus bas » au niveau du site, application ou même répertoire dans un fichier web.config par une balise du type <MyWordPressUrlRewrite enabled="true" /> par exemple.

Étant donné que l’ on aura étendu notre schéma de configuration IIS nous pourrions même étendre notre console d’ administration IIS afin d’ y ajouter une simple feature permettant de configurer l’activation de notre rewriting dans notre fichier web.config de maniere graphique (ce qui fera fort plaisir aux ITs ^^)

La suite dans une partie 2 ;)

MySimpleWatch : mesurer un temps d’ exécution simplement avec StopWatch

14 février 2008

Introduite dans la version 2.0 du framework, la classe StopWatch (dans l’ espace de nom System.Diagnostic) permet de mesurer facilement et d’ une grande précision de l’ ordre de la microseconde (0.000001 seconde).

Cette classe n’ est qu’ une abstraction de ce que l’ on devait faire avant sous .NET 1.x en utilisant la méthode QueryPerformanceCounter via un DllImport de kernel32.dll, simplifiant ainsi la mesure de temps. (En quelque sorte, une amélioration de ma classe PerfTimer que j’ avais utilisé pour mesurer mon algo de résolution de Mastermind il y a déjà quelques années ^^).

L’ utilisation de StopWatch est très simple. On crée notre objet StopWatch pour pouvoir appeler les méthodes Start et Stop qui vont respectivement permettre de démarrer et arrêter notre timer et on récupère le temps écoulé via les propriétés Elapsed, ElapsedMilliseconds, ou ElapsedTicks. (documentation MSDN de StopWatch).

Exemple très simple :

1
2
3
4
5
StopWatch stopWatch = new StopWatch();
stopWatch.Start();
// -- Code à mesurer ici --
stopWatch.Stop();
Console.WriteLine("Temps écoulé en ms : " + stopWatch.ElapsedMilliseconds.ToString());

Maintenant introduit à l’ objet StopWatch pour ceux qui ne connaissaient pas, revenons sur MySimpleWatch ! Historiquement, j’ avais réalisé l’ an passé une petite classe, enfin du moins une simple méthode dans un de mes projets, permettant grâce aux délégués et méthodes anonymes introduits dans C#2.0 d’ exécuter du code avant et après mon délégué. Plus récemment, mardi dernier, pendant une session sur « LINQ avancé » aux TechDays 2008, Mitsu nous donnait dans la même lignée une astuce pour démarrer/arrêter un StopWatch, ce qui m’ a donné l’ envie de récrire cela dans une belle classe que je pourrais utiliser un peu partout dans mes développements.

L’ idée est très simple : avoir une méthode prenant en paramètre une Action (code à exécuter) que l’ on mesurera. En résumé :

1
2
3
stopWatch.Start();
action();
stopWatch.Stop();

De plus, j’ ai ajouté la possibilité de passer un deuxième délégué dans cette méthode de type Action<Stopwatch> qui sera exécuté à la fin pour traiter le résultat du timer (par exemple pour écrire le résultat dans un log, Trace, etc…).

Le code de ma classe étant très simple, je ne rentre pas plus sur son explication, mais voyons plutôt son utilisation dans notre code.

MySimpleWatch contient trois méthodes publiques :

  1. SimpleWatch : permet simplement d’ exécuter un bout de code en nous renvoyant un StopWatch contenant le temps d’ exécution de ce code.
  2. DisplayWatch : exécute le code de l’Action et affiche le résultat dans la Console. (avec possibilité de donner un nom à la tache – utile si plusieurs résultats sont affichés dans la console !)
  3. ProcessWatch : exécute le code de l’Action comme toujours puis exécute le deuxième délégué pour traiter la réponse en passant en paramètre le StopWatch du résultat (en clair un Action<Stopwatch>)

Rien ne vaut de bons exemples :

1) Afficher simplement la mesure dans la console :

1
2
3
4
5
6
7
MySimpleWatch watch = new MySimpleWatch();
 
watch.DisplayWatch(delegate
{
    // Code à mesurer ici ! Ex :
    Thread.Sleep(1000);
});

2) Obtenir le StopWatch d’un bout de code :

1
2
3
4
5
6
7
8
9
10
MySimpleWatch watch = new MySimpleWatch();
 
var result = watch.SimpleWatch(delegate
{
    // Code à mesurer ici ! Ex :
    Thread.Sleep(1000);
});
 
// Exemple de traitement du resultat :
Trace.WriteLine("Elapsed time in ms : " + result.ElapsedMilliseconds.ToString());

3) Exécuter du code pour traiter le résultat :

1
2
3
4
5
6
7
8
9
10
11
12
13
MySimpleWatch watch = new MySimpleWatch();
 
watch.ProcessWatch(delegate
{
    // Code à mesurer ici ! Ex :
    Thread.Sleep(1000);
},
delegate(Stopwatch w)
{ 
    // Code pour traiter le resultat ici !
    // Exemple:
    Trace.WriteLine("Elapsed time in ms : " + w.ElapsedMilliseconds.ToString());
});

En utilisant les expressions lambda introduitent dans C# 3.0, nous pouvons aussi simplifier le code par :

1
2
3
4
5
6
7
MySimpleWatch watch = new MySimpleWatch();
 
watch.ProcessWatch(delegate
{
    // Code à mesurer ici ! Ex :
    Thread.Sleep(1000);
}, (w => Trace.WriteLine("Elapsed time in ms : " + w.ElapsedMilliseconds.ToString())));

Voilà, j’ espère que cela pourra vous être utile :) Le code de la classe MySimpleWatch est disponible ici.