Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Kévin Gosse

Clair, .NET, et précis

Certifications beta .NET 4

Les inscriptions pour les certifications beta .NET 4 ont commencé. L’inscription est offerte pour les examens suivants :

- 71-511, TS: Windows Applications Development with Microsoft .NET Framework 4

- 71-515, TS: Web Applications Development with Microsoft .NET Framework 4

- 70-513: TS: Windows Communication Foundation Development with Microsoft .NET Framework 4

- 70-516: TS: Accessing Data with Microsoft .NET Framework 4

- 70-518: Pro: Designing and Developing Windows Applications Using Microsoft .NET Framework 4

- 70-519: Pro: Designing and Developing Web Applications Using Microsoft .NET Framework 4

Si vous aviez l’intention de passer l’une d’elle, c’est une occasion à saisir. Le passage devra se faire entre le 5 et le 30 avril.

Plus d’informations : http://blogs.technet.com/betaexams/archive/2010/03/17/register-for-visual-studio-2010-beta-exams.aspx

[WP7] L’émulateur Windows Phone 7 est multitâche

Une question qui reste en suspend concernant Windows Phone 7 est de savoir si le système permettra à plusieurs applications de s’exécuter simultanément, ou si un système de mise en pause similaire à celui de l’iPhone sera adopté.

Même si cela ne permet pas de tirer de conclusions, notamment par l’absence de gestionnaire de tâches, cette petite expérience montre que l’émulateur WP7 fourni avec le SDK est clairement multitâche :

 

Affaire à suivre.

[WP7] Le SDK de Windows Phone 7 est disponible !

Comme le titre l'indique, une première CTP du kit de développement pour Windows Phone 7 est disponible. Basé sur Visual Studio 2010 Express, il propose de développer des applications pour WP7 en Silverlight ou XNA 4. Un émulateur est bien entendu de la partie pour tester les applications.

Le tout est téléchargeable sur cette page : http://www.microsoft.com/express/Downloads/#2010-Visual-Phone

Lien direct : http://download.microsoft.com/download/D/9/2/D926FB38-BB43-4D87-AE5A-1A3391279FAC/VM_BOOT/vm_web.exe

 

WP7

Silverlight 3 + Vidéo : Détection et injection de contenu

Vous souvenez vous de cette démonstration des possibilités du couple Firefox 3 / HTML5 ? Récemment, quelqu’un m’a soutenu sur un forum que HTML5 était actuellement la seule technologie permettant de faire cela aussi facilement. De là est né un petit défi, à savoir de reproduire la démonstration avec Silverlight 3. Et c’est chose faite !

Le principe consiste ici à effectuer le rendu de la vidéo dans un WriteableBitmap. A partir de là, vu qu’on a accès à la valeur des pixels, il n’y a plus qu’à appliquer l’algorithme de détection (j’ai repris à peu de choses près celui de la page de démonstration) et à positionner un élément par dessus la vidéo. Les transformations (rotation, agrandissement…) ne posent pas de problème vu que Silverlight fournit tout le nécessaire.

Vous pouvez voir le résultat ici, et vous trouverez également pour ceux qui le désirent le code source.

Preuve s’il en est qu’on n’a pas fini de s’amuser avec Silverlight.

Silverlight 3 : WriteableBitmap et DataUri

Rappels sur WriteableBitmap :

Silverlight 3 apporte la classe WriteableBitmap, qui permet d’effectuer le rendu d’un contrôle dans une image. Son utilisation est très simple. On instancie l’objet en lui passant en paramètre le contrôle dont on souhaite effectuer le rendu, puis on assigne le WriteableBitmap à une image pour voir le résultat :

   1: WriteableBitmap wb = new WriteableBitmap(this.btnTest, null);
   2:  
   3: image1.Source = wb;

Là où les choses deviennent plus intéressantes, c’est que depuis la version finale de Silverlight 3, il est possible d’accéder à la valeur des pixels du rendu. On peut ainsi, par exemple, dessiner par dessus le contrôle :

   1: WriteableBitmap wb = new WriteableBitmap(this.btnTest, null);
   2:  
   3: int pixelsPerLine = wb.PixelWidth / wb.PixelHeight;
   4: int horizontalIndex = 0;
   5:  
   6: for (int i = 0; i < wb.PixelHeight; i++)
   7: {
   8:     for (int j = 0; j < pixelsPerLine; j++)
   9:     {
  10:         wb.Pixels[(i * wb.PixelWidth) + j + horizontalIndex] = 255 << 24;
  11:     }
  12:  
  13:     horizontalIndex += pixelsPerLine;
  14: }
  15:  
  16: image1.Source = wb;

Attention toutefois, il n’est possible d’accéder à la valeur des pixels que si le contrôle n’affiche pas des données provenant d’un autre domaine (si vous chargez une image depuis un autre site par exemple, cela ne marchera pas).

Maintenant, voyons comment s’amuser un peu avec cette nouvelle classe.

HTML 5 : les data URI

Les data URI font leur apparition avec HTML5. Cette fonctionnalité est supportée par Internet Explorer 8, donc on peut espérer qu’elle se répande rapidement. Le principe est de placer les données binaires d’une ressource externe directement dans le code HTML. Ainsi, au lieu de référencer une image externe, on peut écrire les données directement dans la page.
Une data URI est composée du type MIME, du texte “base64”, puis des données binaires proprement dites encodées en base 64.

Par exemple :

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABl
BMVEUAAAD/ //+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U g9
C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC

Et en collant les deux ensemble…

Nous avons donc d’un côté une classe permettant d’effectuer le rendu d’un contrôle dans une image, et de l’autre un moyen d’intégrer une image directement dans la source d’une page web. A partir de là, pourquoi ne pas essayer d’effectuer le rendu d’un contrôle directement dans la page web ?

Pour cela, il y a quand même une difficulté, c’est qu’il faut pouvoir convertir la valeur des pixels du rendu dans un format d’image. Pour cela, j’ai donc choisi le bmp, qui est probablement un des formats d’image les moins complexe.

Un coup d’oeil sur Wikipedia nous apprend qu’une image bmp est composée d’un en-tête, puis de la valeur des pixels. Il nous faut donc tout d’abord une fonction pour créer cet en-tête :

   1: private void WriteBitmapHeader(BinaryWriter bw, int width, int height)
   2: {
   3:     // Magic Number
   4:     bw.Write('B');
   5:     bw.Write('M');
   6:  
   7:     // Size of bitmap
   8:     bw.Write((UInt32)0);
   9:  
  10:     // Unused
  11:     bw.Write((UInt32)0);
  12:  
  13:     // Offset where bitmap data starts
  14:     bw.Write((UInt32)54);
  15:  
  16:     // Number of bytes in the header up to this point
  17:     bw.Write((UInt32)40);
  18:  
  19:     // Width of the bitmap (in pixels)
  20:     bw.Write((UInt32)width);
  21:  
  22:     // Height of the bitmap (in pixels)
  23:     bw.Write((UInt32)height);
  24:  
  25:     // Number of color planes being used.
  26:     bw.Write((Byte)0x1);
  27:     bw.Write((Byte)0x0);
  28:  
  29:     // The number of bits/pixel.
  30:     bw.Write((Byte)0x18);
  31:     bw.Write((Byte)0x0);
  32:  
  33:     // No compression used
  34:     bw.Write((UInt32)0);
  35:  
  36:     // The size of the raw BMP data (after this header)
  37:     bw.Write((UInt32)0);
  38:  
  39:     // The horizontal resolution of the image
  40:     bw.Write((UInt32)2835);
  41:  
  42:     // The vertical resolution of the image
  43:     bw.Write((UInt32)2835);
  44:  
  45:     // Number of colors in the palette
  46:     bw.Write((UInt32)0);
  47:  
  48:     // Means all colors are important
  49:     bw.Write((UInt32)0);
  50: }
 

Il s’agit ici de l’application bête et méchante de l’exemple donné par Wikipedia, au détail près que la taille du bitmap n’est pas renseignée (car pénible à calculer et de toutes manières pas utilisée par le navigateur).

Il nous faut ensuite une fonction pour écrire le contenu de l’image. Un petit piège cette fois : les pixels sont énumérés de bas en haut dans une image bmp, alors qu’ils le sont de haut en bas dans le WritableBitmap. Il faut donc penser à faire la transposition. Il faut également ajouter des données à la fin de chaque ligne pour que la taille de la ligne en octets soit un multiple de 4.

   1: private void WriteBitmapContent(BinaryWriter bw, WriteableBitmap bitmap)
   2: {
   3:     int totalPixels = bitmap.PixelHeight * bitmap.PixelWidth;
   4:  
   5:     int paddingSize = (bitmap.PixelWidth * 3) % 4;
   6:  
   7:     for (int y = totalPixels - bitmap.PixelWidth; y >= 0; y -= bitmap.PixelWidth)
   8:     {
   9:         for (int x = 0; x < bitmap.PixelWidth; x++)
  10:         {
  11:             int color = bitmap.Pixels[x + y];
  12:  
  13:             Byte[] pixel = System.BitConverter.GetBytes(color);
  14:  
  15:             for (int i = 0; i < 3; i++)
  16:             {
  17:                 bw.Write(pixel[ i ]);
  18:             }
  19:         }
  20:  
  21:         for (int i = 0; i < paddingSize; i++)
  22:         {
  23:             bw.Write((Byte)0x0);
  24:         }
  25:     }
  26: }

A partir de là, il n’y a plus qu’à coller les morceaux ensemble :

   1: WriteableBitmap wb = new WriteableBitmap(this.btnTest, null);
   2:  
   3: MemoryStream ms = new MemoryStream();
   4: BinaryWriter bw = new BinaryWriter(ms);
   5:  
   6: this.WriteBitmapHeader(bw, wb.PixelWidth, wb.PixelHeight);
   7: this.WriteBitmapContent(bw, wb);
   8:  
   9: bw.Flush();
  10:  
  11: string uri = System.Convert.ToBase64String(ms.ToArray());
  12:  
  13: var img = HtmlPage.Document.CreateElement("img");
  14: img.SetProperty("src", "data:image/bmp;base64," + uri);
  15: img.SetStyleAttribute("position", "absolute");
  16: img.SetStyleAttribute("left", "500px");
  17: img.SetStyleAttribute("top", "500px");
  18:  
  19: HtmlPage.Document.Body.AppendChild(img);

Le rendu n’est pas parfait car le WriteableBitmap gère un canal alpha ce qui n’est pas le cas du bmp. On aurait donc un résultat plus correct en utilisant un format plus complet, comme png.

Si vous voulez voir le rendu final, c’est ici que ça se passe.

Bien sûr, en l’état, tout ceci ne sert pas à grand chose. Mais avec un peu d’imagination, je pense qu’il y a possibilité d’en tirer quelque chose.

A suivre…

Silverlight 3 : Communication et multicast

Envoi et réception de messages

Il y a une fonctionnalité apportée par Silverlight 3 dont j'ai très peu entendu parler, à ma grande surprise : l'envoi et la réception de messages entre applications Silverlight côté client.

Sur la forme, c'est extrêmement simple. Tout gravite autour du namespace System.Windows.Messaging. Afin de ne pas interférer avec d'autres applications SL, les communications se font sur un canal nommé. La première étape pour recevoir des messages est donc de créer ce canal :

var receiver = new System.Windows.LocalMessageReceiver("channel");
receiver.MessageReceived += new EventHandler<MessageReceivedEventArgs>(Receiver_MessageReceived);
receiver.Listen();

Ce code créé un nouveau LocalMessageReceiver et l'assigne au canal "channel" (évidemment, dans la pratique, je vous suggère d'utiliser un nom fort pour éviter qu'un autre développeur n'utilise le même nom que vous). La fonction Receiver_MessageReceived est ensuite assignée à l'objet pour indiquer l'action à effectuer lors de la réception d'un message. Dans mon exemple, la fonction se contente de rediriger le contenu du message vers la console :
 
private void Receiver_MessageReceived(object sender, MessageReceivedEventArgs e)
{
        Console.WriteLine(e.Message);
}

Enfin, la méthode Listen permet d'indiquer que tout est prêt et ouvre le canal de communication. Rien de bien compliqué donc.

Pour l'envoi de messages, c'est encore plus simple. Il faut cette fois se servir de la classe LocalMessageSender. L'envoi de messages se fait de manière asynchrone.

var sender = new System.Windows.Messaging.LocalMessageSender("channel");
sender.SendAsync("Hello world !");

A noter que la classe LocalMessageSender implémente un événement SendCompleted, utile par exemple si vous voulez savoir de manière précise quand le message a été reçu.

Bien entendu, dans cet exemple, les codes d'envoi et de reception de message peuvent être placés dans des applications Silverlight différente, c'est tout l'intérêt de la chose.

 

Multicast

La première chose que j'ai tenté de faire pour tester cette nouvelle API est l'envoi de messages à plusieurs applications, en imaginant déjà une sorte de Live Mesh où les applications pourraient directement interagir entre elles. Problème : le multicast n'est pas prévu par cette API. En cas d'ouverture de deux LocalMessageReceiver sur le même canal, une exception est levée avec le message d'erreur "Error HRESULT_E_FAIL has been returned from a call to a COM component". Il faut donc s'y prendre autrement.

Le modèle qui vient tout de suite à l'esprit est un modèle client/serveur, où les clients envoient les messages au serveur, qui s'occupe de les transmettre aux autres clients. La méthode "facile" consisterait à créer une application Silverlight marquée comme serveur "en dur", mais je souhaitais une solution moins contraignante. Je me voyais donc déjà me lancer dans un système d'élection avec tous les problèmes d'accès concurrent qui en découlent, mais fort heureusement il est possible de faire plus simple.

Je ne connais pas les détails d'implémentation, mais malgré toutes mes tentatives je ne suis pas parvenu à créer deux LocalMessageReceiver simultanément sur le même canal. Il semblerait donc que le framework se charge déjà des accès concurrents, ce qui retire une belle épine du pied. A partir de là, il suffit de tenter d'ouvrir le même canal sur toutes les applications, et se baser sur l'exception levée pour déterminer si l'application sera cliente ou serveur :

bool isServer = false;
var receiver = new System.Windows.Messaging.LocalMessageReceiver("masterChannel"); 
receiver.MessageReceived += new EventHandler<MessageReceivedEventArgs>(Receiver_MessageReceived);  
try
{
       receiver.Listen();
       isServer = true;
}
catch (Exception)
{ }
bool isServer = false; 
 

Maintenant que nous avons notre serveur, il faut encore implémenter toute la logique derrière. Pour que le serveur puisse transmettre les messages, il faut qu'il conserve la liste des clients, et le nom de leur canal de communication respectif. Il faut donc assigner un nom unique à chaque application (pour cela, le générateur de GUID fera tout à fait l'affaire), et gérer un protocole permettant au client de signaler son existence :

if (!isServer)
{
      this.Id = Guid.NewGuid().ToString();
      receiver = new System.Windows.Messaging.LocalMessageReceiver(this.Id);
      receiver.MessageReceived += new EventHandler<MessageReceivedEventArgs>(Receiver_MessageReceived);
      receiver.Listen();
      var sender = new System.Windows.Messaging.LocalMessageSender("masterChannel");
      sender.SendAsync("REGISTER|" + this.Id);
}

Et le traitement des messages proprement dit :

private void Receiver_MessageReceived(object sender, MessageReceivedEventArgs e)
{
    if (e.Message.StartsWith("REGISTER|"))    
    {
        LocalMessageSender child = new LocalMessageSender(e.Message.Remove(0, 9));
        this.ChildSenders.Add(child);    
    }
    else
    {
        if (this.IsMaster)
        {
            foreach (var child in this.ChildSenders)
            {
                child.SendAsync(e.Message);
            }
        }
 
        // Traiter le message    
    }
}

C'est évidemment une implémentation sommaire. Pour une utilisation "réelle", j'ai codé une petite librairie qui permet de rendre une application Silverlight communicante. Pour cela, il suffit d'instancier la classe depuis l'application :

var communicationHandler = new MulticastChannel.CommunicationHandler(); 
communicationHandler.MessageReceived += new EventHandler<MessageReceivedEventArgs>(communicationHandler_MessageReceived); 
communicationHandler.Initialize();

Créer une méthode contenant le code à exécuter lors de la réception d'un message :

private void communicationHandler_MessageReceived(string message)
{
    Console.WriteLine(message);
}

Et enfin, envoyer à chaque fois que nécessaire un message via la méthode SendMessage :

communicationHandler.SendMessage("Hello world !");

Si vous souhaitez y jeter un oeil, j'ai mis en ligne la librairie et son code source.

En l'état, la librairie ne peut pas être utilisée dans un environnement multi-fenêtrée. En effet, il n'y a rien de prévu pour nommer un nouveau serveur si la fenêtre contenant l'ancien a été fermée par l'utilisateur. Pour gérer cela, il faudrait se baser sur l'évenement déclenché par la méthode LocalMessageSender.SendAsync, qui permet de savoir si une erreur s'est produite lors de l'envoi. A partir de là, un nouveau serveur peut être déterminé en reprenant la méthode du try/catch. Je mettrai peut être la librairie à jour prochainement à ce sujet.

Bien entendu, ce billet pourra sombrer dans les oubliettes du bidouillage si la version finale de Silverlight 3 implémente nativement le multicast pour l'envoi de messages :)

Linq to SQL + Web deployment : Fail ?

Sous ce titre un rien provocateur se cache un petit problème qui m’a valu une journée de perdue et quelques cheveux en moins.

Il s’agit d’une application web en apparence ordinaire, en .NET 3.5, avec dans la solution un projet de déploiement web (vdproj) servant à générer le package MSI utilisé pour l’installation du site.
Là où les choses commencent à devenir étranges, c’est que la compilation du projet de déploiement s’achève systématiquement sur le message « 7 succeded, 1 failed », alors qu’aucune erreur n’est affichée par le compilateur. Jusque là, personne ne s’en était vraiment soucié vu que le package MSI est quand même généré.

Les choses sont devenues plus gênantes quand je suis arrivé lundi avec pour tâche d’industrialiser les builds à l’aide de Team Foundation Server. Car bien évidemment, en détectant le message d’échec remonté par le compilateur, TFS arrête le processus de build et lève une erreur.

J’ai donc été forcé de me documenter sur le sujet, et, d’autres personnes s’étant trouvées dans la même situation, j’ai pu en trouver le coupable.

Il s'agit du module de validation des fichiers dbml générés par Linq to SQL. Le bug est d’ailleurs signalé sur Microsoft Connect : https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=317870. Notons ici le professionnalisme de Microsoft qui a fermé le rapport de bug sans donner de véritable solutions.

Les recherches sur divers blogs et forums m’ont permis de trouver deux solutions de contournement au problème :

La première, ajouter le paramètre /ResetSkipPkgs au lancement de Visual Studio. C’est le plus simple, mais ça n’a hélas pas marché dans mon cas.

Le second, modifier le fichier .csproj du projet contenant le diagramme dbml, et en retirer les lignes suivantes :
<ItemGroup>
     <Service Include="{3259AA49-8AA1-44D3-9025-A0B520596A8C}" />
</ItemGroup>

Je me suis donc retrouvé avec trois possibilités :

- Appliquer la solution de contournement en modifiant le fichier .csproj

- Chercher un autre moyen de désactiver le composant incriminé

- Inclure au processus de build un script qui lance la génération des MSI, détecte s’ils ont bien été créés, et renvoie un code d’erreur approprié à TFS

La première possibilité était pour moi exclue d’office, car je ne voulais pas introduire de régressions. Les forums semblent dire qu’il n’y a aucun impact, mais je suis présent sur le projet depuis trop peu de temps pour pouvoir m’en assurer.

Je me suis donc attardé sur la seconde possibilité, et je me suis rendu compte que le problème pouvait être corrigé par la suppression d’une clé dans la base de registre. Cela empêche le chargement du composant incriminé et revient donc au final au même que la première solution, mais cette fois la modification se limite au serveur de builds, et n’impacte donc pas l’ensemble de l’équipe de développement.

La clé à supprimer est : HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Services\{3259AA49-8AA1-44d3-9025-A0B520596A8C}

Je recommande bien entendu d’en faire une sauvegarde avant, pour pouvoir la restaurer rapidement en cas de problème.

A partir de là, il n’y a plus qu’à surveiller de près la qualité des builds en attendant un correctif officiel.

Dernière journée des Techdays 09

Cela ayant plutôt bien marché la veille, la dernière journée de ces Techdays a été pour moi une journée sans plénière.
C’est donc avec la conférence sur Visual Studio 2010 que tout commence, présentée par Gregory Renard et Blaise Vignon. Je craignais qu’elle soit un peu redondante avec la conférence sur Team Test de la veille, mais heureusement il n’en fut rien.
La grande nouveauté est évidemment l’arrivée de WPF au sein de l’IDE. Les avantages n’ont pas été beaucoup mis en avant, mais on trouve notamment une personnalisation possible de la « start page », écrite en XAML. Le MEF (Managed Extensibility Framework) est intégrée à cette nouvelle version de Visual Studio, et devrait permettre une plus grande extensibilité de l’environnement. A noter également l’arrivée d’une fonction de recherche rapide, permettant de rechercher une classe, méthode, ou fichier, rapidement en affichant les résultats au fur et à mesure de la frappe. La recherche peut également se faire sur les initiales des mots capitalisés. Ainsi, on pourra par exemple se contenter de taper MEF pour chercher managedExtensibilityFramework.
Egalement, on pourra souligner le support natif des projets WiX, ainsi qu’un « Architecture Explorer » qui permet de centraliser et générer rapidement des diagrammes de classe, de dépendances, ou autres. Le « rapidement » sous-entendant bien sûr que vous n’utilisez pas la même VM que ce pauvre Redo, qui a bien du mérite pour avoir réussi à maintenir le rythme de la conférence malgré les freezes à répétition.

P2120009

Après un sandwich agrémenté d’une pomme moisie (!), direction la conférence sur Live Mesh, présentée par Pierre Couzy et Davy Frontigny.
Rien de bien extraordinaire à signaler : la session a consisté en une présentation des différentes API Live et aux moyens disponibles pour y accéder. Virtual Earth, Live Search, Live Spaces, Live Mesh, autant de plateformes qui n’attendent que le bon vouloir des développeurs.

P2120024

Retour à des sujets plus « touchy » avec la conférence sur la programmation fonctionnelle, animée par Mitsuru Furuta et Michel Perfetti.
La session commence avec un tour d’horizon de différentes manières d’apporter de la programmation fonctionnelle au sein d’un code C# classique. Le mot d’ordre étant plus ou moins : décrire le comportement fonctionnel plutôt que de piloter la machine. Après la présentation de ces différentes méthodes (LINQ, utilisation de génériques, tuples…) et de leurs limites inhérente au langage utilisé, la conférence s’est conclue sur une brève démonstration de F#.

P2120035

La journée se termine avec une nouvelle conférence sur Live Mesh, présentée par David Rousset et Gregory Renard.
Contrairement à la précédente, cette session s’est focalisée uniquement sur Live Mesh, ce qui a permit d’avoir une vision plus claire des possibilités. L’idée étant d’arriver à une convergence entre les multiples périphériques que nous sommes amenés à utiliser au quotidien : ordinateur de salon, ordinateur portable, smartphone…
Au-delà du simple partage de données, des possibilités de synchronisation sont offertes, ainsi que de pilotage à distance dans le cas des PC. Live Mesh sert également de support aux applications Silverlight, qui peuvent être exécutées aussi bien sur le bureau virtuel en ligne, que directement sur la machine en mode déconnecté. De nombreuses possibilités offertes aux développeurs, avec toutefois un bémol pour ma part : il semblerait qu’il n’y ait aucun socle de prévu pour la communication entre les applications. Il va donc falloir ruser un peu avant de changer tout ça en une plateforme de jeux multijoueurs !

P2120041

C’est ainsi que s’achève ces Techdays 2009. Les trois jours furent courts mais extrêmement instructifs, et je tiens à remercier tous les orateurs pour leur travail d’approfondissement de ces sujets qui nous passionnent.
Il est hélas temps de revenir aux réalités : demain le boulot reprend, sans distributeur de popcorns dans les locaux.

TechDays 09 : Seconde journée

Pour éviter le coup de barre de fin de journée de la veille, j’ai volontairement laissé mon édredon prendre la priorité sur la conférence plénière. Du coup, rien à dire à ce sujet.

C’est donc un peu avant 11 heures que je me présente pour la conférence sur C# et Linq, avant de constater l’impressionnante file d’attente et d’appliquer la méthode agile sur mon planning en me rabattant sur la conférence sur Hyper-V.

Un choix plutôt bon car la conférence était très intéressante, avec deux excellents orateurs (Christophe Dubos et Fabrice Meillon). La présentation s’est centrée d’abord sur les différents scénarios pouvant justifier l’usage de la virtualisation, puis sur les fonctionnalités apportées par Hyper-V : administration centralisée des VM, failover, bascule rapide des VM d’une machine sur l’autre, administration possible par Powershell… Que des choses bien alléchantes en somme.

P2110008

Un petit sandwich et direction Visual Studio, avec la conférence « Visual Studio Team Test » présentée par Etienne Margraff et Florent Santin.
Conférence très intéressante, commençant par rappeler les possibilités offertes par VS2008 en termes de tests, avant de présenter les nouveautés de l’édition 2010. La plus prometteuse est sans conteste Camano, un outil de test destiné aux profils non techniques. Cette application permet de créer des tests fonctionnels, d’y associer des cas d’utilisation, et de les planifier. Lors du passage des tests, une check-list reste présente à l’écran pour rappeler chaque étape, et marquer au fur et à mesure le résultat. Le déroulement du test peut être enregistré de trois manières : dans un fichier texte décrivant les actions effectuées, dans une vidéo, ou dans un « fichier événements » permettant de reproduire les actions.
Cerise sur le gâteau, en cas d’échec du test il est possible de créer une fiche de bug pré remplie, avec le récapitulatif des étapes et la possibilité d’ouvrir directement la vidéo correspondant à une étape bien précise.
Un autre produit a été annoncé, mais il est hélas trop peu avancé pour que nous puissions avoir une démonstration : Team Labs. Il s’agit d’une application centralisant des VM de différentes configuration (matériel, OS…) pour faciliter les tests sur un vaste éventail de machines. On pourra en prime sauvegarder un snapshot pour revenir à un état bien précis (qui a dit : problème non reproductible ?).

P2110037

La conférence suivante concerne les nouveautés de WCF dans .NET 4, et est animée par Pascal Belaud.
Après un rappel sur ce qu’est WCF, les nouveautés proprement dîtes ont été présentées, à savoir : prise en charge du REST (disponible également sous .NET 3.5 à l’aide du WCF REST Starting Kit), et ajout du support de WS Discovery (qui permet aux services hébergés sur différentes machines de se découvrir mutuellement en faisant du broadcast UDP). Pas grand-chose, mais la longue et didactique démonstration a permit de bien remplir la séance tout en nous donnant une vision claire du sujet.

P2110047

Trois orateurs attendent, manipulant fébrilement des téléphones portables, pour la présentation sur Windows Mobile. Au moins, on est tout de suite dans l’ambiance.
Ambiance très fun donc, avec présentation d’applications mobiles entrecoupée de lancer de goodies par les trois orateurs : Pierre Cauchois, Frédéric Brandt, et David Cohen. Une seule déception : je ne suis pas parvenu à gagner un des cinq téléphones mobiles mis en jeu.

P2110049

On reste dans le mobile et on termine la journée sur la création d’interfaces riches pour mobiles, présentée par Fabien Decret et Stéphane Sibué. Quelques pistes ont été données : utilisation intensive de GDI, code HTML hébergé dans un contrôle WebBrowser, ou encore application Flash, mais rien de bien palpitant. On pourra également regretté que la conférence n’ait pas parlé de moyens d’interactions avec l’interface (reconnaissance de gestes par exemple). La conférence s’est terminée sur une timide démonstration de Silverlight 2 sur Windows Mobile, pour lequel aucune date n’a été annoncée.

P2110059

En résumé, une journée moins technique mais non moins intéressante que la veille. J’espère que le dernier jour restera au même niveau.

Résumé de la première journée aux Techdays 09

La journée commence dans un grand amphi comble par la conférence plénière. Sans grande surprise, on nous rappelle les sujets incontournables du moment : Windows 7, Surface, Visual Studio 2010, et bien sur Azure.
Petite découverte quand même pour ma part : VS.PHP, un plugin de Visual Studio permettant l’écriture et le débugage de code PHP. Je ne manquerai pas de me documenter à ce sujet au cours des prochaines semaines.

P2100021

Le choix de la séance suivante fut difficile, mais mon choix s’est finalement porté sur Azure, conférence présentée par Pierre Lagarde et David Rousset.
Je pense que l’essentiel de ce sujet a déjà été expliqué dans les blogs, je ne m’attarderai donc pas trop dessus. La démonstration a connu quelques ratés en raison du stress des deux orateurs, mais le message est passé.

P2100028

Ensuite, c’est la programmation dynamique, par Simon Ferquel et Mitsu Furuta, qui a retenu mon attention. La conférence était très intéressante, mais j’ai été un peu pris de court : je m’attendais à un point sur le DLR et les langages de script portés sur la plateforme .NET (IronPython, IronRuby). A la place, nous avons eu le droit à un récapitulatif des différentes manières d’exécuter dynamiquement du code en .NET, de l’émission d’IL par réflexion à une parenthèse sur le mot clé dynamic en .NET 4, en passant par la compilation d’arbres d’expressions en LINQ. Je tâcherai de faire plus attention à la description la prochaine fois !

P2100035

Vient la conférence que j’attendais le plus, à savoir C# 4, là encore présentée par Mitsu, qui décidemment fait preuve d’une endurance remarquable.
Comme Azure, le sujet a déjà été discuté maintes fois dans les blogs, je ne vais donc pas entrer dans le détail. On retrouve donc le mot clé dynamic, les paramètres optionnels, la co et contra variance…
J’ai appris deux petites choses quand même : tout d’abord la possibilité de créer un objet hérité de DynamicElement, pour personnaliser le binding des membres avec le mot clé dynamic. L’exemple consistait en une application qui lit un fichier XML, et se sert d’une classe dérivée de DynamicElement pour faire le lien dynamiquement entre les membres appelés et les balises XML. Ainsi, l’appel à monObjet.Nom renvoie le contenu de la balise XML <nom>, alors que celle-ci n’était pas connue au moment de la compilation. J’ai conscience d’expliquer cela très mal, je vous invite donc vivement à jeter un œil aux codes qui ont servi à la démonstration, quand ils seront disponibles.
Ensuite, l’ouverture future du compilateur, qui sera écrit en code managé. Il sera donc possible d’en contrôler le comportement, et appeler ses fonctionnalités depuis une application .NET. Avec en prime une épatante démonstration d’une console capable d’interpréter du code C#, le tout en une poignée de lignes de code.

P2100038

La conférence qui suit porte sur l’optimisation d’applications ASP.NET et ASP.NET AJAX. Là il faut bien avouer une chose : une session de niveau « 300 : confirmé » où on nous montre qu’il existe un cache dans lequel on peut placer des objets, il y a dû y avoir un cafouillage quelque part.

P2100048

La journée se finit sur le développement avancé en Silverlight 2, par Pierre Lagarde, Simon Ferquel, Thierry Bouquain, et Julien Frelat. C’était un peu la conférence de trop (comprenez par là que j’étais trop fatigué pour retenir quoi que ce soit). Au programme : binding, gestion des moteurs de recherche, et démonstration de Quakelight, un Quake en Silverlight.

P2100061

 

En bref, excellente journée à l'exception de la conférence sur l'optimisation ASP.NET qui m'a vraiment deçu. Vivement demain !

Accéder aux outils Sysinternals par un partage réseau

Pour ceux qui utilisent régulièrement les outils de Sysinternals (Process Monitor et PsExec m'ont sauvé la vie plus d'une fois), j'ai appris il y a peu qu'ils étaient récupérables directement depuis un dossier partagé. Ce dossier est accessible en entrant l'adresse \\live.sysinternals.com dans l'explorateur de fichiers. Le principal intérêt est de pouvoir exécuter les outils sans avoir à les copier sur le disque dur (pratique quand vous n'êtes pas sur votre propre machine !).

C'est un peu lent à l’usage mais j'aime beaucoup le principe. Ce serait fort sympathique si ce mode de distribution pouvait se répandre à l'avenir.

MAJ : Comme on me le fait si justement remarquer, j'ai six mois de retard sur le blog de Gaël Covain. Sincères excuses !

UAC sous Windows 7 - Un pas en avant, deux en arrière ?

Non content d'avoir mis en évidence un moyen de désactiver l'UAC quand il est réglé à son niveau par défaut sous Windows 7, l'auteur du blog Within Windows a trouvé un moyen d'obtenir une élévation de droits sans approbation de l'utilisateur :

http://www.withinwindows.com/2009/02/04/windows-7-auto-elevation-mistake-lets-malware-elevate-freely-easily/

Manifestement, Microsoft se base sur un système de signature numérique pour déterminer quelles applications peuvent s'exécuter avec des droits administrateur sans nécessiter une confirmation de l'utilisateur. Le problème est que certaines de ces applications (en l'occurrence, rundll32.exe) peuvent être détournées pour exécuter du code tiers. C'est donc tout le système d'élévation automatique des droits qui est remis en cause.

Je ne peux plus qu'espérer que Microsoft se penchera sérieusement sur la question avant la sortie finale de Windows 7 pour, pourquoi pas, rétablir par défaut le réglage de sécurité maximal de l'UAC.

C# - Déterminer si un point est à l'intérieur d'un triangle

M'amusant à coder un petit jeu en 2D avec des formes géométriques simples, j'ai rapidement été confronté à un problème simple en apparence : comment déterminer si un point se situe à l'intérieur d'un triangle ? Cela paraît évident et immédiat visuellement, mais c'est autrement plus tordu à coder.

Après quelques recherches peu fructueuses, j'ai fini par tomber sur le site suivant : http://www.mochima.com/articles/cuj_geometry_article/cuj_geometry_article.html

Je passe les explications mathématiques, pour m'attarder sur le point qui nous intéresse ici. Pour trois points P1(x1, y1), P2(x2, y2), et P3(x3, y3), on peut appliquer la formule suivante :
z = x1 (y2 - y3) + x2 (y3 - y1) + x3 (y1 - y2)
Le signe de z permet de déterminer si P3 est à droite ou à gauche du segment P1->P2.

A partir de là, ça devient simple : en considérant un triangle ABC et un point P, le point P est au centre du triangle s'il est toujours même côté lorsque l'on parcourt chacun des segments A->B, B->C, et C->A (droite ou gauche selon le sens dans lequel on le parcourt). Il faut donc appliquer la formule précédente sur chacun de ces segments, et vérifier que le signe de z est toujours le même.

Au niveau du code, cela peut s'exprimer par :

internal class Utils
{
  private static float ComputeZCoordinate(Point p1, Point p2, Point p3)
  {
    //x1 (y2 - y3) + x2 (y3 - y1) + x3 (y1 - y2)

    return p1.X * (p2.Y - p3.Y) + p2.X * (p3.Y - p1.Y) + p3.X * (p1.Y - p2.Y);
  }
  
  public static bool IsPointInsideTriangle(Point[] triangle, Point point)
  {
    float z1 = ComputeZCoordinate(triangle[0], triangle[1], point);
    float z2 = ComputeZCoordinate(triangle[1], triangle[2], point);
    float z3 = ComputeZCoordinate(triangle[2], triangle[0], point);
    
    return (z1 > 0 && z2 > 0 && z3 > 0) || (z1 < 0 && z2 < 0 && z3 < 0);
  }
}

Faire de l'AJAX sans restrictions de domaine

Les développeurs de la librairie Javascript Dojo ont mit au point un moyen sécurisé d'envoyer des requêtes AJAX (via une iframe) à l'aide de la propriété window.name, ce qui a pour principal intérêt de contourner les restrictions de domaine imposées par le navigateur.

Ça se passe ici, et c'est vraiment épatant.

Présentation sur Velocity

Une petite présentation PowerPoint sympathique sur Velocity a été mise sur le blog du projet. Elle a le mérite de recentrer les choses et d'indiquer quelles sont les ambitions de ce projet. Je vous conseille donc vivement de la lire si vous êtes intéressé par les problématiques de cache au sein d'un cluster.

A noter qu'il y a également un podcast de la présentation pour ceux qui aiment ce genre de média ;)

Google AJAX Libraries API

Prototype, script.aculo.us, jQuery… Autant de librairies Javascript dont le nom doit être familier aux oreilles de ceux qui s’intéressent au développement Web 2.0, de part l’éventail de fonctionnalité qu’elles offrent et leur popularité croissante. Cependant, leur puissance à un cout : leur poids, de plusieurs dizaines de kilo-octets, a un impact significatif sur le temps de chargement des pages, le rendu de celles-ci étant en général dépendant de l’exécution des scripts.

Les librairies se retrouvent en général dans le cache du navigateur après le premier chargement, mais ce cache n’étant pas partagé entre les différents sites, l’utilisateur se retrouve à télécharger x copies identiques de la même librairie, là où une aurait suffit. Alors pourquoi ne pas tenter de factoriser ce cout ?

Et c’est là qu’intervient « AJAX Libraries API » de Google. Le principe est simple : proposer des liens vers les principales librairies Javascript utilisées, hébergées sur les serveurs de Google pour qu’elles soient toujours chargées depuis la même adresse, et donc au final utiliser au mieux le cache des navigateurs quand l’utilisateur surfe d’un site à l’autre.

Loin de s’arrêter là, Google nous propose également un système simple mais bien pensé de versionning. En effet, l’adresse vers les librairies est de la forme http://ajax.googleapis.com/ajax/libs/[Nom de la librairie]/[Version]/[Fichier javascript].js
Ainsi, pour jQuery en version 1.2.6, on aura : http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.js

L’astuce est qu’il est possible de ne spécifier qu’une partie de la version. Si par exemple vous souhaitez la dernière version dans la branche 1.2 de jQuery, il suffit d’aller chercher à l’adresse : http://ajax.googleapis.com/ajax/libs/jquery/1.2/jquery.js

Ou encore http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.js si vous voulez la dernière version en date.

« AJAX Libraries API » est donc un moyen simple d’améliorer le confort de l’utilisateur en améliorant l’efficacité du cache de son navigateur, tout en permettant une mise à jour automatique des librairies utilisées. Bref, un outil à connaître et à garder sous le coude.

Vous pourrez trouver plus d’informations et la liste des librairies supportées à l’adresse suivante : http://code.google.com/apis/ajaxlibs/documentation/index.html

Récupération de la version des Assemblies référencées par une application

Après avoir eu plusieurs fois des erreurs dues au déploiement de mauvaises versions des fichiers, j’ai voulu ajouter une page de test vérifiant la version des DLL utilisées par l’application. J’ai donc écris le code suivant :

string version = "Version des librairies : <br/><ul>";

Assembly currentAssembly = Assembly.GetExecutingAssembly();

version += "<li>" + currentAssembly.FullName.ToString() + "</li>";

AssemblyName[] assemblyNames = currentAssembly.GetReferencedAssemblies();

foreach (AssemblyName assemblyName in assemblyNames)

version += "<li>" + assemblyName.FullName + "</li>";

Après exécution du code, la version de toutes les DLL référencées s’affiche, mission accomplie.

… C’est du moins ce que je pensais avant de remplacer une des DLL par une version plus ancienne. Et là, surprise : le script continue de me renvoyer le même numéro de version.

Après investigation, il se trouve que la fonction Assembly.GetReferencedAssemblies ne va pas vérifier la version des Assemblies référencées, mais se contente de renvoyer les informations qu’elle trouve dans les métadonnées de l’Assembly sur laquelle on exécute la fonction. Elle nous renvoie donc la version des DLL qu’elle s’attend à trouver, et non la version qu’elle trouve effectivement.

Pour pallier au problème sans pour autant avoir à parcourir les fichiers sur le disque ou recharger les assemblies, il est possible d’utiliser la fonction Assembly.GetAssembly, qui prend en paramètre un type et renvoie l’assembly dans laquelle le type est défini.

Le code devient donc quelque chose comme :
 

string version = "Version des librairies : <br/><ul>";

Assembly currentAssembly = Assembly.GetExecutingAssembly();

version += "<li>" + currentAssembly.FullName.ToString() + "</li>";

version += "<li>" + Assembly.GetAssembly(typeof(Type1)).FullName + "</li>";

version += "<li>" + Assembly.GetAssembly(typeof(Type2)).FullName + "</li>";

version += "<li>" + Assembly.GetAssembly(typeof(Type3)).FullName + "</li>";

Où Type1, Type2, et Type3, sont des types définis chacun dans une librairie à tester.

C’est clairement moins élégant, on perd le coté dynamique, et cela créé des dépendances supplémentaires entre les librairies, mais ça marche. Il existe d’autres méthodes mais il faut à chaque fois s’assurer que l’on ne va pas charger inutilement une copie de l’assembly en mémoire, ce qui pourrait vite devenir gênant pour l’application.

MAJ : Comme le fait remarquer Cyril, il est aussi possible de faire la même chose de manière générique en passant pas l'AppDomain :

foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())

{

Console.WriteLine(assembly.FullName);

}

Posted: mercredi 28 mai 2008 20:03 par KooKiz | 6 commentaire(s)
Classé sous : ,
Coupler Basic Auth et formulaire au sein d'une application ASP.NET

J'ai pu être confronté dans le cadre de mon travail à une application devant présenter deux modes d'authentification : une première, classique, par formulaire, et une seconde, pour les utilisateurs accédant à l'application depuis l'intranet, par fenêtre Basic Auth, remplie automatiquement à l'aide d'un système de SSO (Single Sign On).

Prit séparément, ces deux systèmes ne présentent aucune difficulté particulière pour leur implémentation, mais les cumuler présente un certain nombre de difficultés, comme par exemple la configuration du serveur : si on active l'accès anonyme, la boite d'authentification Basic Auth n'apparait pas. En revanche, si on active l'authentification Basic Auth, les utilisateurs ne peuvent atteindre la page de saisie du login et mot de passe (pour l'authentification par formulaire) sans voir la popup d'authentification.

Après quelques recherches, notamment sur des blogs de développeurs ayant rencontré le même problème, j'ai pu trouver trois solutions.

Solution numéro 1 : Simuler un Basic Auth.

La première solution consiste à tout faire soi-même. Le principe est donc de désactiver l'authentification dans le web.config, activer l'accès anonyme dans IIS, et déterminer à l'aide de l'URL d'où vient l'utilisateur. S'il vient d'internet, on vérifie qu'il est authentifié, et si ce n'est pas le cas on le redirige vers la page de login. A partir de là, on retrouve un fonctionnement par formulaire classique, à ceci près qu'il faut gérer certains mécanismes soit même vu que l'authentification est désactivée dans le web.config. Si l'utilisateur vient de l'intranet, on renvoie un code d'erreur 401 pour déclencher l'affichage de la popup d'authentification sur le navigateur. Une fois que l'utilisateur entre ses informations, il est possible de récupérer le login et le mot de passe dans l'en-tête de la requête http (respectivement Request.ServerVariables["LOGON_USER"] et Request.ServerVariables["AUTH_PASSWORD"]). Il ne reste plus qu'à authentifier l'utilisateur.
Bien que pouvant être très couteuse en termes de temps de développement et de test (simuler l'authentification Basic Auth est plus difficile qu'il n'y parait), cette solution est de mon point de vue la plus souple. Il faut aussi garder à l'esprit que tout gérer soi-même expose considérablement l'application, le moindre bug pouvant se transformer en faille critique de sécurité.

Solution numéro 2 : Séparer le module d'authentification du reste de l'application

La seconde solution consiste à isoler le code d'authentification de l'application dans une nouvelle application (ou plus exactement : deux nouvelles applications). Une gérant le Basic Auth, et l'autre l'accès par formulaire. En fonction de l'URL, l'utilisateur est dirigé vers l'une ou l'autre. A partir de là, il faut effectuer une authentification classique, à ceci près qu'il faut ensuite rediriger l'utilisateur vers l'application principale. Cela implique donc de désactiver l'authentification sur cette dernière, et mettre en place un système couplant cookies et informations de sessions stockées dans la base de données pour transmettre les informations d'authentification d'une application à l'autre. L'avantage de cette solution est d'avoir une configuration cohérente entre le serveur et les applications, pas de difficulté particulière pour le développement (si ce n'est la transmission des informations de l'utilisateur), et un système robuste en termes de sécurité (utilisation des modes d'authentification de la manière prévue par .NET). Le principal problème est le surcout engendré par le déploiement et la maintenance de deux applications, même limitées au processus d'authentification.

Solution numéro 3 : Utiliser deux répertoires virtuels

La troisième et dernière solution consiste à créer dans IIS deux répertoires virtuels pointant vers la même application. L'un sera configuré en accès anonyme, et l'autre en accès Basic Auth. L'application est quant à elle configurée en formulaire dans le web.config. Il faut ensuite distinguer d'où vient l'utilisateur, soit à l'aide de l'URL, ou de manière plus sûre à l'aide du nom du répertoire virtuel. Si l'utilisateur vient d'internet, on se retrouve dans une authentification par formulaire classique. Si l'utilisateur vient de l'intranet, IIS se chargera de lui afficher la popup d'authentification Basic Auth pour qu'il saisisse ses identifiants, et vérifiera leur validité auprès de l'Active Directory. Il suffit ensuite de récupérer le login dans l'application. Attention toutefois, l'application étant configurée en mode formulaire dans le web.config, les informations de l'utilisateur ne sont pas renseignées dans la propriété CurrentPrincipal comme dans une authentification Windows classique. Par chance, après l'authentification de l'utilisateur, IIS transmet à l'application la requête HTTP telle qu'elle a été reçue. Il est donc possible de récupérer le login dans les en-têtes HTTP, comme dans le cas de la première solution. Je conseille d'ailleurs de récupérer dans la foulée le mot de passe pour effectuer une double validation (des fois qu'un utilisateur malveillant parvienne à faire croire à l'application qu'il vient de l'intranet alors qu'il est passé par le répertoire virtuel en accès anonyme).
Cette solution a l'avantage d'être la plus simple des trois. Elle a par contre un inconvénient majeur : si deux répertoires virtuels pointent vers les mêmes fichiers, cela implique que la même application soit chargée deux fois en mémoire, entrainant une forte surconsommation et une augmentation de la fragmentation. De plus, il faut faire attention à ce que les deux applications n'utilisent pas par exemple les mêmes fichiers de log, sous peine de voir l'une d'elles bloquée par le verrouillage en écriture de l'autre.

En bref

Chacune des trois solutions a ses avantages et ses inconvénients, aussi chaque situation est à étudier pour déterminer celle qui convient le mieux. Cependant, dans la plupart des cas, je recommanderai la seconde, car c'est celle qui respecte le mieux le cadre prévu des fonctions d'authentification de .NET. Cette liste de solution n'étant pas forcément exhaustive, je vous invite à réagir si vous envisageriez d'autres solutions pour faire cohabiter une authentification formulaire et Basic Auth au sein de la même application.

ASP.NET : Memory Pressure et fragmentation

Je me propose ici de décrire le mécanisme de Memory Pressure et de commencer à introduire un problème qui peut survenir sur la plupart des applications ASP.NET un tant soit peu gourmandes en mémoire : la fragmentation.

Malgré tous les mécanismes de récupération de la mémoire s'exécutant en arrière plan, il arrive que les besoins en mémoire d'une application se mettent à dépasser les capacités de la machine l'exécutant. Pour pallier à ce genre de situations et tenter de préserver la stabilité de l'application, ASP.NET utilise quelques procédés, à savoir :

- Appel forcé du Garbage Collect

- Libération du cache (System.Web.Caching)

- Recyclage du processus (relance automatique de l'application)

Pour savoir quand faire appel à ces mécanismes, .NET se base sur deux indicateurs : « Memory Pressure » et « Memory Limit ».

Memory Pressure et Memory Limit

Régulièrement durant le cycle de vie de l'application, .NET exécute un algorithme pour déterminer la marge de manœuvre de l'application vis-à-vis de la mémoire restante.

Cet algorithme est très simple, et répond à la formule suivante :

Memory Pressure = (Mémoire totale - Mémoire disponible) / Mémoire totale

La « Memory Limit » est un paramétrage situé dans le fichier Machine.Config. Il s'agit d'une valeur indiquant le pourcentage de la mémoire physique total de la machine que l'application ne doit pas dépasser. Sa valeur par défaut est de 60.

Si la valeur de Memory Pressure dépasse 90, ASP.NET tente alors de libérer des entrées du cache (System.Web.Caching) et déclenche un Garbage Collect. Si cela ne suffit pas, et que la consommation mémoire de l'application continue d'augmenter jusqu'à dépasser la « Memory Limit », ASP.NET déclenche le recyclage du processus, qui consiste ni plus ni moins en un arrêt-relance. La conséquence pour l'utilisateur est une indisponibilité de quelques secondes et la perte de la session si elle n'est pas stockée sur un serveur tiers.

Avec ce système, il est en théorie impossible que ASP.NET renvoie une des redoutées « OutOfMemory Exception ». Et pourtant, ces erreurs apparaissent et deviennent un fléau sur nombre d'applications. Pourquoi ? A cause justement d'un effet retords de ce système.

Tout système ayant ses limites…

Petit rappel : sur une machine disposant d'un système d'exploitation 32 bits, la taille maximale de mémoire adressable pour un processus est de 4 go. Windows se réserve 2 go sur cet espace, il ne reste donc plus que 2 go par processus.

Considérons maintenant l'exemple d'une machine disposant de 4 go de ram, et reprenons les conditions utilisées :

- La Memory Pressure atteint 90 % => 90% de 4 go représentent 3,6 go, taille que le processus ne pourra jamais atteindre

- L'occupation totale en mémoire de l'application est à moins de 10 % de la « Memory Limit » => La Memory Limit est fixée à 60 % de la mémoire totale, soit 2,4 go. L'application sera donc en Memory Pressure si elle consomme au moins 2,16 go, taille que le processus ne pourra jamais atteindre

Résultat, ASP.NET ne se met jamais en état d'alerte, et des exceptions se mettent à apparaitre un peu partout, avec les conséquences néfastes que l'on peut facilement imaginer.

Et sur un serveur possédant 3 go de ram ? En faisant rapidement le calcul, on déduit que le processus est recyclé si la consommation de mémoire excède 1,8 go. Pas de problème alors ? Et pourtant, là encore les « OutOfMemory » guettent. Comment est-ce possible ?

La mémoire virtuelle et ASP.NET

Plongeons un peu dans les mécanismes d'allocations de la mémoire utilisés par .NET. Le processus ASP.NET a une quantité de mémoire qui lui est réservée, appelée mémoire virtuelle. Le nom de virtuelle lui vient du fait qu'elle ne représente pas forcément de la mémoire physique : Windows la gère comme bon lui semble, gardant certains morceaux à divers endroits de la RAM, plaçant d'autres morceaux sur le disque dur dans le fichier d'échange (swap)… Comme ce remue-ménage serait difficile à gérer de la part des applications qui s'exécutent, Windows leur fait croire qu'elles disposent d'un espace mémoire en RAM contigu, et s'occupe de faire le lien avec la mémoire physique (qui peut tout à fait ressembler à un gruyère) de manière transparente. A charge pour l'application de préserver son espace de mémoire virtuelle contigüe, Windows ne pouvant quand même pas s'occuper de tout.

De son côté, ASP.NET stocke les objets par piles, en général de 64 mo. Si un nouvel objet est alloué, et que la pile correspondante est pleine, ASP.NET parcourt la mémoire à la recherche d'un espace contigu de 64 mo pour créer une nouvelle pile. S'il ne trouve pas, il fait appel à Windows, qui accepte gracieusement de lui agrandir son espace de mémoire virtuelle. Mais progressivement, à force d'allocations et de libérations, la mémoire finit par se fragmenter : des espaces de mémoire libres apparaissent, trop petits pour créer une nouvelle pile. Ce phénomène est aggravé par le mode de chargement des Assemblies, qui peuvent se positionner dans la mémoire à leur adresse favorite, au lieu de se positionner bien sagement en bloc au début de l'espace mémoire (je ne critique pas, je constate). Si la fragmentation de la mémoire n'est pas maitrisée, ASP.NET va finir par atteindre la limite des 2 go, et Windows va refuser d'agrandir encore la mémoire virtuelle. N'ayant pas d'alternative (l'espace mémoire réellement occupé n'a pas atteint la « Memory Limit »), ASP.NET renvoie une erreur « OutOfMemory » alors qu'il reste de la mémoire libre.

En conclusion ?

Même s'il ne concerne pas toutes les applications, et qu'ilpeut être réduit par un ensemble de bonnes pratiques, le phénomène de fragmentation peut nuire à la stabilité d'une application, et le programmeur doit donc apprendre à composer avec. Alors comment régler la « Memory Limit » ? Chaque cas est unique, et cela dépend donc de la propension de l'application à fragmenter. Mais d'une manière générale, je recommanderai de ne pas dépasser les 1,4 go, voire 1,2 go. Donc n'oubliez pas, si l'on vous confie une application ASP.NET tournant sur un système d'exploitation 32 bits, n'oubliez surtout pas de vérifier ce paramétrage dans le fichier machine.config.

La gestion de m&#233;moire en .NET

Vu que je pense consacrer une large partie de mes prochains billets aux problématiques de mémoire, je pense qu'il est intéressant de commencer par un très bref rappel de la gestion de la mémoire dans .NET.

Le Garbage Collector

Je suppose que la majorité des gens qui lisent ces lignes savent déjà que .NET est une machine virtuelle. Au sein de cette machine virtuelle, toutes les allocations et désallocations de mémoire effectuées par du code managé (depuis le code .NET donc) sont tracées par le Garbage Collector (GC). Il est ainsi capable de déterminer quels objets maintiennent une référence vers un autre objet, et sait donc quand un objet n'est plus utilisé. Il peut alors libérer la mémoire qu'il occupait.

Quand un objet n'est plus référencé, il n'est pas libéré tout de suite. La libération de la mémoire par le GC étant couteuse en temps processeur, elle n'est effectuée que de temps en temps, selon plusieurs critères que je détaillerai par la suite. Quand le GC fait un "Garbage Collect", il parcourt la liste des objets qui ont été alloués à la recherche de ceux qui ne sont plus référencés, et les libère.  Pour optimiser ce traitement, un système de "génération" est mis en place :

A leur création, tous les objets sont alloués dans un heap dit "Generation 0". Au cours d'un garbage collect, les objets qui ne sont pas libérés sont déplacés vers un autre heap, dit "Generation 1". Au garbage collect suivant, s'ils ne sont toujours pas libérés, ils sont à nouveau déplacés vers un autre heap, appelé "Generation 2". Arrivés là, ils ne bougeront plus tant qu'ils seront référencés.

Quel est l'intérêt de ce système ?

Les concepteurs du Garbage Collector de .NET ont ici considéré que les objets utilisés dans une application ont soit une durée de vie très courte, soit très longue. Ce qui à l'usage se révèle en général vrai : les objets temporaires déclarés dans une fonction sont libérés dès la fin de l'exécution de celle-ci (durée de vie courte), tandis que certaines classes, comme les Form, peuvent être utilisés pendant presque toute la durée d'exécution de l'application (durée de vie longue).

A partir de ce constat, le Garbage Collector nettoie souvent la génération 0 (dont la plupart des objets seront libérés), et beaucoup moins souvent la génération 2 (qui contient les objets à longue durée de vie, et donc qui ont peu de chance d'être libérés). Le temps processeur utilisé par le Garbage Collector est ainsi réduit.
Au passage, la génération 2 est un très bon indicateur pour diagnostiquer la présence de fuites de mémoire : si le nombre d'objet en génération 2 (ce nombre peut être surveillé par des compteurs de performance) ne cesse d'augmenter au cours de l'exécution d'une application, il y a de fortes probabilités que des objets ne soient pas libérés quand il le faudrait.

Lors des Garbage Collect, en plus de libérer les objets qui ne sont plus utilisés, le GC en profite pour compacter la mémoire : il déplace et regroupe les objets restants en mémoire de manière à créer des segments de mémoire contigus les plus larges possibles. Si vous avez du mal à vous représenter ce remue-ménage, c'est exactement ce que font la plupart des défragmenteurs de disque : déplacer les fichiers dans les premiers secteurs du disque, pour créer un seul bloc d'espace libre qui s'étend jusqu'à la fin du disque.

Le Large Object Heap

La dégragmentation présentée précédemment, aussi utile soit-elle pour garantir une utilisation optimale de la mémoire, présente un inconvénient majeur : si les objets en mémoire sont très gros, elle peut devenir très couteuse en temps processeur. C'est là que fait son entrée le Large Object Heap.

Aux trois générations du Garbage Collector se rajoute ce quatrième heap un peu particulier.Il a deux particularités :

- Il stocke uniquement les objets de taille importante (supérieure à 85 ko)

- Il n'est jamais défragmenté

Après mon explication précédente, son intérêt devient immédiat : il évite que le Garbage Collector se mette à déplacer des objets volumineux en mémoire, ce qui aurait un impact négatif sur les performances. La contrepartie est que ce heap est très sensible à la fragmentation. Il faut donc que le développeur veille à libérer le plus vite possible les objets volumineux pour ne pas l'encombrer inutilement.

Déclenchement du "Garbage Collect"

Attardons nous maintenant sur le déclenchement du Garbage Collect. Il se produit lorsqu’une des trois conditions suivantes est remplie :

- Un appel à la fonction GC.Collect() est fait depuis le code ou depuis une fonction .NET native

- Le nombre d'objets dans la génération 0 dépasse une certaine limite (je ne saurais vous dire combien)

- Le système est dit en état de "Memory Pressure" (Il s'agit d'un algorithme utilisé par .NET pour déterminer quand les besoins en mémoire de l'application commencent à dépasser ce que la machine peut lui fournir)

Il est très rare qu'un développeur ait à appeler manuellement la fonction GC.Collect, ce qui est par ailleurs formellement déconseillé dans une application ASP.NET : le "Garbage Collect" étant succeptible de déplacer des objets en mémoire, tous les threads sont suspendus durant son exécution.

Une gestion de la mémoire maitrisée ?

Tout ce beau monde tourne sans que le développeur n'ait à gérer quoi que ce soit, et dans la grande majorité des cas la gestion de la mémoire de l'application .NET ne posera pas de problème et saura parfaitement se faire oublier. Mais (parce qu'il y a toujours un "mais"), aussi rodé ce système soit-il, il existe des situations ou des pièges qui feront que cette gestion de la mémoire se retournera contre le développeur. Et ceux sont quelques unes de ces situations que je me propose de vous exposer dans mes prochains billets.

Plus de Messages Page suivante »


Les 10 derniers blogs postés

- [MIX10] Keynote deuxième journée – Internet Explorer 9, Html5, Visual Studio 2010, OData par Atteint de JavaScriptite Aiguë [Cyril Durand] le il y a 18 heures et 58 minutes

- Certifications beta .NET 4 par Kévin Gosse le il y a 19 heures et 4 minutes

- [Mix 2010] – Microsoft Translator Technology Preview V2 par RedoBlog - The .NET Gentleman !!! le il y a 19 heures et 44 minutes

- Lancement en Preview de Cyclone lors des TechDays 2010! par Blog de Frédéric Queudret le il y a 22 heures et 8 minutes

- [WP7] Je ne veux pas d’un nouvel iPhone par Le blog de FremyCompany le 03-17-2010, 13:11

- [WF4] Pourquoi utiliser le ContentPresenter dans l’ActivityDesigner? par Blog de Jérémy Jeanson le 03-17-2010, 07:54

- [Mix 2010] – Telechargez Internet Explorer 9 en Preview ! par RedoBlog - The .NET Gentleman !!! le 03-17-2010, 00:58

- [MIX 2010] – Keynote Day 2 online : Windows Internet Explorer 9, jQuery, OData et Dallas CTP2 ! par RedoBlog - The .NET Gentleman !!! le 03-17-2010, 00:18

- [Mix 2010] – Retour d’expérience développement Seesmic sur Windows Phone 7 par RedoBlog - The .NET Gentleman !!! le 03-16-2010, 23:44

- [Mix 2010] – Le Keynote Day 1 disponible online ! par RedoBlog - The .NET Gentleman !!! le 03-16-2010, 23:21