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.
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.
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.
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#.
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 !
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.
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.
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 ?).
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.
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.
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.
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.
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.
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é.
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 !
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.
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.
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.
En bref, excellente journée à l'exception de la conférence sur l'optimisation ASP.NET qui m'a vraiment deçu. Vivement demain !
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 !
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.
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); } } |
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.
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 ;)
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
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);
} |
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.
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.
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.
De part ses similitudes avec la structure des données en base, le DataSet (et les classes gravitant autour : DataView, DataTable, DataRow, ...) est un outil de manipulation de données très appréciable. Cependant, son utilisation peut mener à quelques pièges, dont certains peuvent être fatals pour la stabilité de l'application.
Dans une application ASP.NET classique orientée autour d'une base de donnée, nous sommes souvent amenés à afficher des donner dans un tableau. A cet usage, un DataGrid alimenté par un DataSet ou un DataView suffit dans la plupart des cas, et permet de produire un code souple et efficace en peu de temps.
Si la source de données comporte beaucoup de résultats, il devient alors utile d'ajouter un système de pagination. Toujours dans une optique de simplicité, le développeur sera tenté de réaliser cette pagination directement dans le code C#, à l'aide de tous les outils fournis par ASP.NET. Je ne m'attarderais pas aujourd’hui sur les risques d'une telle approche (un indice toutefois : Large Object Heap), pour me concentrer sur un piège plus vicieux et plus dangereux.
Mise en situation
C'est l'histoire d'une fonction de recherche particulièrement lourde (temps d'exécution de l'ordre de la dizaine de secondes) sur une page ASPX appelée "Page Y", d'un ingénieur, et d'un client exigeant. Le client, non satisfait (à raison) de la lenteur de chargement de la page, demande à l'ingénieur de remédier au problème. L'ingénieur, après avoir appliqué toutes les astuces d'optimisation simples sur sa procédure stockée (je vous ferai sans doute un billet sur l'utilisation du plan d'exécution sous SQL Server si ça en intéresse certains), décide que la page Y serait bien plus agréable à utiliser si les données n'étaient pas rechargées systématiquement à chaque opération de l'utilisateur (tri par exemple). Le candidat idéal apparaît alors rapidement : la session. Ni une, ni deux, notre ingénieur place le DataSet stockant le jeu de résultat dans la session, pour le conserver d'un appel à l'autre de notre chère page Y.
Un client heureux
Le problème de performance est corrigé et le client est satisfait, notre ingénieur peut dormir sur ses deux oreilles. Mais d'évolution en évolution, l'application s'embellit, les visiteurs sont de plus en plus nombreux, et les besoins de mémoire vont crescendo. Commencent à apparaître des problèmes graves de mémoire et, à nouveau mécontent, le client refait appel à notre ingénieur.
Celui-ci, après analyse méthodique des logs, découvre que les problèmes de mémoires apparaissent sur la plupart des pages. Une analyse plus poussée montrera une tendance : en début de journée, la page Y est la première à manquer de mémoire, avant que le problème ne s'étende sur le reste de l'application. De plus, les utilisateurs rapportent que se déconnecter et se reconnecter permet de s'affranchir des erreurs pendant un temps.
Mais que se passe-t-il ?
Fort de son expérience, notre ingénieur fait tout de suite le rapprochement entre la session et la déconnexion de l'utilisateur... et retourne donc 'intéresser à la page Y. La taille du DataSet est certes grande (plusieurs dizaines de kilo-octets), mais elle ne suffit pas à expliquer les problèmes de mémoire. En étendant son champs de recherche, l'ingénieur se souvient alors que, cluster de serveurs oblige, la session est stockée dans une base de données. Après quelques recherches pour retrouver dans quelle table est stockée la session, notre ingénieur concocte une petite requête pour déterminer la taille de la session en base, avant de tomber de son siège.
Profitons de ce moment d'hébétement chez notre ingénieur pour comprendre à notre tour ce qu'il se passe. Au début de l'exécution de chaque page, ASP.NET récupère les données de session depuis la base de données, et les renvoie à la source à la fin du traitement. Les données sont stockées en base dans un champ binaire, ASP.NET appelle donc fort logiquement le serializer binaire sur chaque objet avant de les envoyer dans la base, et fait la manipulation inverse avant de les récupérer.
L'étude de l'objet DataSet avec Reflector (ou une petite recherche sur Internet) nous montre que, même par le serializer binaire, le DataSet est stocké en XML. Donc un simple DataSet contenant une colonne "nombre" et cinq lignes peut devenir quelque chose comme :
<NewDataSet>
<Table1>
<nombre>1</nombre>
</Table1>
<Table1>
<nombre>2</nombre>
</Table1>
<Table1>
<nombre>3</nombre>
</Table1>
<Table1>
<nombre>4</nombre>
</Table1>
<Table1>
<nombre>5</nombre>
</Table1>
</NewDataSet>
Fort de cette information, nous pouvons jeter un oeil sur l'écran de notre ingénieur qui se relève péniblement sur sa chaise, et nous constatons que son DataSet occupe la bagatelle de 3 méga-octets dans la base de données. A cela, ajoutons l'espace mémoire supplémentaire nécessaire pour désérialiser puis resérialiser l'objet, multiplions par le nombre moyen de pages visualisées par un utilisateur ayant notre DataSet en session, et nous comprenons rapidement que nous avons un problème.
Que faire alors ?
Déjà, il faut savoir que ce problème a été corrigé dans .NET 2.0 (parait-il en tout cas, j'avoue que je n'ai pas encore vérifié). Donc le problème ne se pose que pour ceux qui utilisent le Framework 1.1. A partir de là, la solution la plus évidente est : ne pas mettre le DataSet en session, et essayer de s'en sortir par des moyens alternatifs (comme le cache, si le jeu de résultats est partagé par tous les utilisateurs). Ce n'est hélas pas toujours possible, et s'il n'est pas possible de s'affranchir de l'utilisation de la session, je recommande l'utilisation de l'objet DataSetSurrogate, téléchargeable à l'adresse suivante :
http://support.microsoft.com/kb/829740
Il s'agit d'un objet récupérant les informations du DataSet, et se sérialisant en binaire. Son utilisation est très simple :
DataSet ds = new DataSet();
// Mise en session
Session["monDataSet"] = new DataSetSurrogate(ds);
// Récupération des données, je passe les contrôles de rigueur
ds = ((DataSetSurrogate) Session["monDataSet"]).ConvertToDataSet();
Un nouveau test dans la base de données montre que le même DataSet que précedememnt occupe maintenant moins de 200 kilo-octets. Ce n'est pas parfait, mais cela devient suffisant pour corriger le problème de mémoire. Le client est à nouveau heureux (je plaisante, c'est un client quand même), et notre ingénieur peut retourner rêver d'un projet en .NET 3.5.
Cet ingénieur, cela aurait pu être moi, et cela aurait pu être vous. Alors ne prenez jamais à la légère l'utilisation des DataSet, surtout en .NET 1.1.
WinDbg couplé à son extension SOS (Son of Strike) est un outil extrêmement puissant (que j'essaierai de vous présenter à l'occasion), utilisable à des fin de déboggage/profiling sur une application .NET. Cependant, les deux moyens classiques pour analyser une application depuis WinDbg sont :
- En réalisant des dumps de la mémoire, ce qui est vite contraignant et prend de la place sur le disque, mais c'est souvent le seul moyen pour une application en production
- En s'attachant au processus, ce qui implique que vous n'ayez pas déjà attaché le debugger de Visual Studio
Pour ceux qui souhaitent utiliser les fonctionnalités de WinDbg/SOS en même temps que le debugger de Visual Studio, sachez qu'il est possible d'en exécuter les commandes directement depuis l'IDE (depuis la version 2005) :
- Dans un premier temps, il faut activer le déboggage du code non managé, dans les propriétés du projet
- Ensuite, lancez votre application et à tout moment appuyez sur le bouton pause (inutile de mettre un point d'arrêt)
- Enfin, dans la fenêtre d'exécution immédiate, tapez :
.load sos
- Et c'est tout !
A noter quand même que l'exécution des commandes s'en trouve fortement ralentie. Donc pour analyser une application occupant plusieurs centaines de méga-octets de mémoire vive, je suggère vivement de continuer à passer par des dumps ;)
En continuation directe de l'angoisse de la page blanche, je
pense que ce premier message sera le plus difficile du lot (je l'espère tout au
moins).
Après des années d'hésitations, j'ai décidé de me lancer dans l'aventure, et
ouvrir ce qui peut être communément appelé "Blog technique". Je
partagerai ici mes expériences et mon point de vue sur ce qui touche à
l'informatique et la programmation, essentiellement autour des technologies de
Microsoft (ce qui ne m'empêchera pas de faire quelques écarts pour aborder le
monde "péri-Billou").
Pour poser les bases, je vais commencer par une rapide présentation de moi même
:
Je m'appelle Kévin (nom pas toujours facile à porter) Gosse (bis repetitae),
jeune diplômé de l'EFREI.
Je suis tombé dans la programmation dès mon plus jeune age, avec ce bon vieux
QBASIC, puis Visual Basic. Vint une période "touche à tout" qui me
permit d'essayer nombre de langages, VB.NET, une longue période PHP, avant de
finalement revenir sur C#, étape actuelle de mon voyage.
Professionnellement, je suis un jeune diplômé de l'EFREI travaillant chez une
SSII répondant au nom de Novedia Solutions (feu Smart-Up). Bien qu'elle sera la
cible de quelques uns de mes billets sur ce blog (surtout les premiers en
fait), je ne vais présenter que brièvement l'application sur laquelle je
travaille :
- .NET 1.1, ASP.NET/C# (ça date mais on a vu pire)
- SQL Server 2000 (sympathique si on ne regarde pas de trop près)
- Commerce Server 2002 (un outil qui doit être très bon couplé avec ASP3, mais
que je déconseille formellement dans un projet .NET...)
Voilà. Je peux ajouter aussi qu'elle souffre de quelques problèmes de
performance/consommation mémoire qui m'ont permit de me faire mes armes sur
plusieurs outils de profiling, expérience que je ne manquerai pas de partager.
Parce qu'il ne faut pas trop en faire dès la première fois, je vais arrêter ici
cette introduction, et vous remercier d'avoir prit le temps de lire ces
quelques lignes. Je remercie au passage Julien Chable (alias Neodante) pour s'être chargé des
formalités de création de ce petit blog, sans qui je serais probablement en
train d'écrire ces lignes dans un petit sous-répertoire perdu d'un serveur web
coupé de la civilisation (GoogleBot est un lecteur assidu mais n'est pas très bavard).
Les 10 derniers blogs postés
-
[Refactoring] ReSharper pour Visual Studio 2010 (Preview) par
Thomas Jaskula le il y a 13 heures et 44 minutes
-
[Refactoring] Analyser vos exceptions avec ReSharper Exceptional par
Thomas Jaskula le il y a 14 heures et 58 minutes
-
SharePoint 2007 : patterns & practices SharePoint Guidance par
Philippe Sentenac [MVP SharePoint] le 07-03-2009, 09:56
-
[Visual Studio 2010] Les tests cases c’est bien, mais je vais devoir tout réécrire ? par
Etienne Margraff le 07-03-2009, 09:00
-
MVP[Gribouillon].AddYear par
The Grib's Lair [Sébastien PICAMELOT - MVP SharePoint] le 07-03-2009, 08:45
-
Clinique INSIA - Projet de fin d’Etudes (Silverlight 3 MVVM et OutOfBrowser, WCF, TFS) - Part 1 par
David REI le 07-02-2009, 23:38
-
C’est la crise ? Bah pourquoi cramer du budget pub alors ? par
Nix's Blog le 07-02-2009, 15:31
-
Soyons MVP ! par
TheSaib .NET blog le 07-02-2009, 12:15
-
SharePoint : Gestion des Erreurs 6398, 7076 et 6482 par
Blog Technique de Romelard Fabrice le 07-02-2009, 11:53
-
EF avec WPF par
Matthieu MEZIL le 07-02-2009, 10:18