Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Atteint de JavaScriptite Aiguë [Cyril Durand]

Expert ASP.net Ajax et WCF, Cyril Durand parle dans son blog de point techniques sur ASP.net, ASP.net Ajax, JavaScript, WCF et .net en général. Cyril est également consultant indépendant, n'hésitez pas à le contacter pour de l'assistance sur vos projets

Actualités

  • Blog de Cyril DURAND, passionné de JavaScript, Ajax, ASP.net et tout ce qui touche au developpement Web Client-Side.

    N'hésitez pas à me contacter pour vos projets .net : architecture, accompagnement, formation, ...

    View Cyril Durand's profile on LinkedIn
    hit counters


    Expertise Commerce server et BizTalk

ASP.net – comment avoir de l’intellisense dans les propriétés des controles ASP.net

On m’a récemment demandé comment rajouter de l’intellisense sur les valeurs d’une propriété personnalisée d’un contrôle ASP.net. C’est à dire faire en sorte d’avoir l’exemple ci dessous :

image

Pour cela, au niveau du contrôle, il suffit de déclarer la propriété avec un type enum.

image

Ainsi, l’éditeur de page ASP.net proposera automatiquement les différentes valeurs de l’enum possible. Cette astuce fonctionne aussi bien avec des UserControl (ascx) ou des contrôles personnalisés (CustomControl).

Le principal avantage de l’enum par rapport à un string ou à un entier c’est que vous êtes sur que vous ne pourrez entrer que les valeurs possibles.

Signer des projets .net avec une clé unique – éviter les doublons de clé snk dans une solution

Lorsque vous avez des projets .net complexes et que certains des projets doivent être signés, il est préférable d’utiliser une seule clé pour tous les projets de la solution.

image

Malheureusement, lorsque vous signez votre projet avec Visual Studio, celui-ci va dupliquer la clé à l’intérieur de votre projet, vous vous retrouvez alors avec plusieurs fichiers snk identique dans votre projet.

image

Afin de contourner ce problème, il faut modifier directement le XML du projet. Pour cela, il faut décharger le projet : click droit sur le projet puis “unload project

image

Puis, il faut l’éditer : click droit sur le projet et “Edit

image

Cela permet ainsi d’éditer le fichier XML contenant le projet, il vous faut alors rechercher ou ajouter l’élément AssemblyOriginatorKeyFile celui-ci indique le chemin relatif vers la clé partagé

image

Puis, il faut recharger le projet et supprimer le snk que Visual Studio nous a copié.

image

Les propriétés du projet indiquent alors le chemin vers le bon fichier.

image

Et vous, avez vous des problèmes avec la signature de vos projets ?

ASP.net – Mise en place d’un serveur de session – à quoi correspond aspnet_state.exe

Par défaut, ASP.net stocke les sessions au sein de son propre processus, c’est à dire w3wp.exe ou aspnet_wp.exe pour IIS 5.1 et inférieur. Lorsque le pool d’application est recyclé ou redémarré les sessions sont alors perdues. Cela peut rapidement causer quelques soucis : perte de la connexion, perte du panier, ...

Afin de ne plus avoir ce problème, ASP.net propose différents moyens de stocker la session. Ces moyens sont configurables via l’attribut mode de la section system.web/sessionState du web.config. Les différentes valeurs possibles sont :

  • Off : Désactive complètement la session. Dans ce cas, une erreur sera remontée si on tente d’accéder à la session.
  • InProc : Valeur par défaut, stocke la session au sein du processus courant.
  • StateServer : Stocke la session dans un serveur de session. L’adresse du serveur de session se configure via l’attribut stateConnectionString.
  • SQLServer : Stocke la session sur un serveur SQL. La chaine de connexion vers le serveur se configure via l’attribut sqlConnectionString.
  • Custom : Utilise le type spécifié via l’attribut SessionIDManagerType. Ce type doit implementer ISessionIDManager

Afin d’utiliser le mode StateServer, il est nécessaire de changer le mode de démarrage du service aspnet_state. Ce service est automatiquement installé lorsque l’on installe ASP.net, c’est lui qui stockera la session ASP.net. Une fois lancé, on retrouve le service dans le gestionnaire de tâche sous le nom de aspnet_state.exe

image

Il faut ensuite modifier le fichier de configuration ainsi :

<configuration> <system.web> <sessionState mode="StateServer" stateConnectionString="tcpip=127.0.0.1:42424" /> </system.web> </configuration>

Désormais, les sessions sont stockés dans le process aspnet_state.exe et non w3wp.exe.

Par défaut, le serveur de session n’est pas accessible de l’extérieur, si vous utilisez plusieurs serveurs frontaux et que vous voulez monopoliser le serveur de session, il faut modifier la clé de registre suivante :

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\aspnet_state\Parameters\AllowRemoteConnection

Si la valeur de cette clé est 0 alors le service windows sera accessible seulement pour la machine local. Sinon, si la valeur est 1, alors le service windows sera accessible depuis d’autres serveurs. Attention, pour des raisons de performances, il n’y pas d’authentification, il faut donc impérativement que ce port ne soit pas visible de l’extérieur.

image

J’ai déjà parlé plus précisément sur les mécanismes internes des sessions en ASP.net, vous pouvez consulter l’article : “Partager la session entre plusieurs sous-domaines avec http ou https” pour en savoir plus sur les sessions ASP.net

Avez-vous déjà mis en place le serveur de sessions ? Si oui, avez vous rencontrer des soucis particuliers ?

OutputCache et les fichiers .browser – ou comment un UserAgent peut nuire à votre site web

Afin d’optimiser les performances d’une application web, une des bonnes pratiques consiste à rajouter du cache sur les pages ou contrôle utilisateur via la directive @OutputCache. Cette directive permet de mettre le rendu HTML de la page en cache.

Après avoir fait cette opération et passé le site en production, de temps en temps, de façon aléatoire, le HTML que retournait le serveur ne contenait pas les scripts JavaScript propres à ASP.net : fonction __doPostBack , champs cachés __EVENTTARGET, webResources et scriptResource … Je n’ai jamais réussi à reproduire ce comportement sur une plate-forme autre que la production.

Après une séance de décompilation dans Reflector, je me suis rendu compte que ces scripts étaient inclus en fonction du navigateur exécutant la page (Request.Browser).

// pseudo code internal void BeginFormRender(HtmlTextWriter writer, string formUniqueID){ if (this.ClientSupportsJavaScript){ this.RenderPostBackScript(writer, formUniqueID); } } internal bool ClientSupportsJavaScript { get { if (!this._clientSupportsJavaScriptChecked) { this._clientSupportsJavaScript = (this._request != null) && (this._request.Browser.EcmaScriptVersion >= JavascriptMinimumVersion); this._clientSupportsJavaScriptChecked = true; } return this._clientSupportsJavaScript; } }

ASP.net génère la variable Browser à partir du UserAgent du navigateur ainsi que des fichiers .browser de votre site web. Par défaut, un site web ASP.net “hérite” des fichiers .browser du dossier %Windows%\Microsoft.NET\Framework\v2.0.50727\CONFIG\Browsers. Pour certains UserAgent (principalement les robots/crawler), la propriété ClientSupportsJavaScript retourne false.

Du coup, si votre cache est généré à partir d’une requête provenant d’un UserAgent ne supportant pas JavaScript, les scripts ne seront pas inclut pour cette requête. Cependant le rendu HTML de la page sera ajouté au cache et renvoyé pour tous les navigateurs.

Afin de résoudre ce problème, la solution la plus simple est de faire varier votre cache en fonction du type de navigateur. Pour cela, nous pouvons utiliser l’attribut VaryByCustom de la directive OutputCache. Lorsque cet attribut est renseigné, ASP.net va interroger la méthode GetVaryByCustomString du global.asax, cette méthode doit retourner un string servant de clé de cache pour la valeur envoyée.

L’implémentation par défaut contient déjà du code répondant au paramètre browser :

// code provenant de Reflector public class HttpApplication { // ... public virtual string GetVaryByCustomString(HttpContext context, string custom) { if (StringUtil.EqualsIgnoreCase(custom, "browser")) { return context.Request.Browser.Type; } return null; } }

Ainsi, si l’on souhaite faire varier le cache en fonction du navigateur il suffit de rajouter la valeur browser à l’attribut VaryByCustom de la directive OutputCache.

<%@ OutputCache Duration="60" VaryByParam="none" VaryByCustom="browser" %>

Le cache sera alors différent en fonction du navigateur : IE, Firefox, mobile, … Si ce comportement ne vous satisfait pas, vous pouvez surcharger la méthode GetVaryByCustomString du global.asax et réécrire le code pour avoir un cache unique sur les principaux navigateurs.

Comme expliqué sur mon précédent poste (OutputCache et ScriptManager – Attention, seul le rendu est caché), l’attribut OutputCache ne fait que cacher le rendu HTML, ASP.net possède un mécanisme permettant de faire varier le HTML en fonction du navigateur, à nous alors de faire attention à ce que l’on met en cache et comment le cache doit varier.

Et vous, avez vous déjà rencontré des problèmes similaires ?

OutputCache et ScriptManager – Attention, seul le rendu est caché

Lorsque j’utilise un UserControl et que j’ai besoin d’inclure des scripts JavaScript, j’utilise un ScriptManagerProxy. L’avantage du ScriptManagerProxy est qu’il n’inclut le script qu’une seule fois, qu’importe si le UserControl se répète ou si l’on inclut le même script via un autre ScriptManagerProxy.

Récemment j’ai eu besoin d’activer du cache ASP.net sur un tel UserControl. J’ai donc utilisé la directive @OutputCache afin de mettre en place ce cache. Mon UserControl ressemblait alors à ceci :

<%@ Control Language="C#" %> <%@ OutputCache Duration="60" VaryByParam="none" %> <asp:ScriptManagerProxy runat="server"> <Scripts> <asp:ScriptReference Path="~/js/pouet.js" /> </Scripts> </asp:ScriptManagerProxy> <%= DateTime.Now.ToLongTimeString() %>

Lors du premier affichage d’une page contenant cet UserControl, tout se passe bien, le script est correctement chargé. Par contre si j’actualise cette page, alors mon UserControl provient du cache mais le script JavaScript, lui, n’est pas chargé.

Pourquoi ?

Afin de comprendre d’où vient le problème, il faut comprendre ce que fait la directive OutputCache. Lorsque ASP.net rencontre une telle directive, il va alors créer un contrôle héritant de PartialCachingControl. Ce contrôle va alors se charger de gérer le cache : si le rendu du contrôle est présent dans le cache ASP.net va utiliser le rendu HTML du UserControl présent en cache, sinon il va utiliser le UserControl.

C’est ici qu’est le problème : lorsqu’ASP.net utilise le cache, il n’exécute pas les différents événements du UserControl (Init, Load, …). Dans notre cas, le ScriptManagerProxy ajoute les scripts dans le ScriptManager de la page lors de son événement Load.

Ce problème est présent avec le ScriptManagerProxy, mais il se retrouve dès que vous modifier le HTML à l’extérieur du UserControl à partir de celui-ci. Si vous utilisez par exemple les fonctions du ScriptManager ou du ClientScript comme le RegisterClientScript, vous serez alors aussi confrontés à ce souci.

Comment faire ?

Il n’y a pas de solution à ce problème, il s’agit d’un “bug” by design. Si l’on souhaite rajouter du cache sur un UserControl utilisant un ScriptManagerProxy, il faut déplacer le ScriptManagerProxy au niveau de la page.

L’ajout de cache sur un UserControl n’est donc pas une chose bénigne, il faut bien s’assurer que les événements du UserControl ne modifie pas le HTML de la page.

Et vous, avez vous déjà rencontré de tel soucis ?

Retour sur le “Business Integration RoadShow” – BizTalk et AppFabric

J’ai assisté mardi 1er décembre au “Business Integration RoadShow” dans les locaux de Microsoft à Paris. Il s’agissait d’un événement présentant des retours d’expérience sur l’utilisation de BizTalk.

IMAG0051

Voici l’agenda de la journée

image

Une centaine de personnes étaient présentes dans la salle. La journée a commencée par un keynote animé par Sriram Balasubramian : directeur R&D BizTalk Server. Il nous a présenté les fonctionnalités de BizTalk : son utilité, ses avantages … Il nous a ensuite expliqué que BizTalk était au coeur des principaux produits Microsoft.

IMAG0054IMAG0055

Nous avons ensuite eu quelques chiffres quand à l’utilisation de BizTalk dans le monde.

IMAG0059

Puis Sriram nous a présenté AppFabric et comment BizTalk s’inscrivait dans ce projet.

IMAG0063IMAG0066

Je n’ai pas trouvé les explications très claires, Sriram nous a présenté AppFabric comme un composant de BizTalk. Suite à une question du public, Eric Ortiz, Product Manager BizTalk en France, nous a expliqué qu’AppFabric est un produit répondant à des besoins différents, ces 2 produits sont complémentaires. Selon moi, AppFabric est un BizTalk light. Il n’a pour l’instant pas les fonctionnalités de BizTalk comme la persistance transparente des messages, les nombreux connecteurs tierces, le monitoring (Business Activity Monitoring), les règles métier (Business Rules Engine), la mise en cluster, … Il ne cible pas le même public : pour simplifier on peut dire que BizTalk répond au besoin des grandes entreprises alors que AppFabric couvrira les besoins des PME.

IMAG0080IMAG0082 

Pour en savoir plus sur AppFabric, je vous invite à lire le compte rendu de vlabz sur AppFabric : PDC 2009 : Workflow Services et Windows Server AppFabric

Sriram nous a ensuite montré la roadmap BizTalk : on peut voir qu’il y aura une version 2009 R2 de BizTalk avec peu de nouvelles fonctionnalités, principalement la possibilité d’utiliser Visual Studio 2010. La prochaine version de BizTalk “vNext” aura des connecteurs pour AppFabric. Le message est clair : AppFabric ne remplacera pas BizTalk, BizTalk n’est pas mort.

IMAG0068

Les trois sessions suivantes ont été des retours d’expérience sur des projets BizTalk.

Premier retour : la FNAC avec Logica

La FNAC utilisait Seebeyond eGate de Sun. Sun n’assurant plus de support, ils sont en train de basculer leurs 160 flux sur BizTalk. La Fnac a fait le choix de BizTalk pour sa perennité, ce produit existe depuis plusieurs années et le support sera assuré par Microsoft pendant encore plusieurs années. Dans un premier temps, ils ont sélectionnés 17 flux représentatifs. Après 6 mois d’études et de développements, 6 flux sont actuellement en production.

IMAG0069

Secon retour : SCC (ex Allium) avec Codit

Ils nous ont présentés ce que BizTalk leur permet de gérer. SCC est un prestataire logistique, ils gèrent 15 000 commandes / jour. Outre le fait de pouvoir gérer toutes leurs commandes avec Biztalk, ils ont aussi mis en place un process leur permettant de dématérialiser les factures. En effet, à cause des contraintes de coût, il est plus avantageux de passer au tout numérique. “une facture papier coûte beaucoup plus cher qu’une facture électronique” : génération de la facture papier, acheminement de la facture papier, archivage... 

 IMAG0072

Dernier retour : JMBruneau avec Expertime

La dernière présentation de la matinée était un retour d’expérience de JMBruneau sur l’utilisation de AppFabric en collaboration BizTalk. JMBruneau a récemment refondé complètement son infrastructure, ils ont utilisé AppFabric pour tous les échanges internes et BizTalk pour la communication avec l’extérieur. Ce projet est de loin le plus intéressant, il fait intervenir de nombreux acteurs et progiciels : BizTalk, AppFabric, Commerce Server, Dynamics AX, …

IMAG0073IMAG0076IMAG0077 IMAG0078

Faute de temps, je n’ai pu être présent que le matin. Cette matinée a été enrichissante, les retours d’expérience sur BizTalk sont rares et toujours appréciables.

WebTest Plugin – ignorer les urls externe lors d’un test web Visual Studio

Lorsque l’on fait un test web avec Visual Studio, celui-ci charge les dépendances de la page : images, scripts, css, etc. Dans certains cas, il est possible que les images ne soient pas sur le serveur sur lequel on fait les tests, nous ne voulons alors pas récupérer ses fichiers.

image

Afin d’exclure les requêtes pointant vers une url externe, il est nécessaire de créer un plugin au test web. Un plugin consiste en une classe héritant de WebTestPlugin. Cette classe contient plusieurs méthodes virtuelles, par exemple les méthodes PreRequest et PostRequest qui, comme leurs noms l’indiquent, s’exécutent avant et après la requête.

public class IgnoreExternalFileFilter : WebTestPlugin { public override void PreRequest(object sender, PreRequestEventArgs e) { } public override void PostRequest(object sender, PostRequestEventArgs e) { } }

Au niveau de l’eventArgs de la méthode PreRequest, il est possible de récupérer l’instance de l’objet Request et donc l’url de la requête. Il est également possible de définir l’action pour cette requête : est-ce que l’on continue ou est-ce que l’on ignore la requête.

Dans l’exemple ci dessous, j’exclus les requêtes qui ne pointent pas vers le serveur sur lequel on fait les tests.

public override void PreRequest(object sender, PreRequestEventArgs e) { if (!IsUrlValid(e.Request.Url)) e.Instruction = WebTestExecutionInstruction.Skip; else base.PreRequest(sender, e); }

L’événement PostRequest survient après la requête mais avant le chargement des dépendances. Il est possible de manipuler ses dépendances avant leurs chargements. Ci dessous, je supprime les dépendances qui ne m’intéresse pas.

public override void PostRequest(object sender, PostRequestEventArgs e) { for (int i = e.Request.DependentRequests.Count - 1; i > 0; i--) { WebTestRequest request = e.Request.DependentRequestsIdea; if (!IsUrlValid(request.Url)) { e.Request.DependentRequests.RemoveAt(i); } } base.PostRequest(sender, e); }

Au niveau de la méthode IsUrlValid, rien de bien compliqué :

private String _hostName; private Boolean IsUrlValid(String urlToValidate) { Uri uriToValidate = new Uri(urlToValidate); return String.Equals(uriToValidate.Host, _hostName, StringComparison.InvariantCultureIgnoreCase); }

Je récupère la propriété _hostName à partir des informations de contexte du test web lors de l’événement PreRequest.

private const String WebServerParameterName = "WebServerName"; public override void PreRequest(object sender, PreRequestEventArgs e) { this._hostName = new Uri((String)e.WebTest.Context[WebServerParameterName]).Host; // ... }

Comment utiliser ce plugin ?

La première chose est d’inclure la classe dans votre projet de test. Puis, il faut associer votre plugin à votre test web, pour cela, faites un clic droit sur votre projet web puis “Add Web Test Plugin”

image

Une fenêtre vous indique alors les différents plugins disponible ainsi que leurs propriétés.

image

Enfin, il faut renseigner la propriété de contexte WebServerName qui correspond à l’uri du serveur sur lequel vous voulez faire vos tests. Pour cela, il y a un bouton “Parameterize Web Servers”

image

Il faut alors renseigner la propriété WebServerName avec l’url que vous souhaitez.

image

Au niveau du test web, vous retrouvez l’ensemble des propriétés de contexte.

image

Vous pouvez retrouver l’ensemble du code ici : WebTestPlugin - Ignorer les urls externe lors des test web Visual Studio

Et vous, vous utilisez les outils de test Visual Studio ? Si oui, utilisez-vous des plugins ?

TeamBuild MSB5004 - The solution file has two projects named "localhost". Comment compiler avec msbuild une solution contenant 2 sites web IIS ?

Récemment, j’ai eu besoin de compiler une solution contenant 2 sites web pointant sur IIS avec MsBuild.

La solution ressemblait à peu près à ça :

image

Malheureusement lorsque je lance le build, msbuild m’indique l’erreur suivante :

Solution3.sln : Solution file error MSB5004: The solution file has two projects named "localhost".

image

En effet, dans le solution explorer, la seule différence entre les 2 websites est le numéro de port. Malheureusement il n’est pas possible de modifier dans Visual Studio les noms de ces sites web.

J’ai donc ouvert via notepad le fichier sln, j’ai trouvé ces lignes :

Project("{GUID}") = "localhost", "http://localhost:10171", "{GUID}" Project("{GUID}") = "localhost", "http://localhost:10172", "{GUID}"

On voit clairement que l’on a deux projets avec le même nom. J’ai alors modifié le nom des ces deux projets puis relancer mon build et tout s’est bien déroulé.

Le problème est que si l’on édite le fichier sln via Visual Studio, ces valeurs seront automatiquement écrasées par localhost. J’ai donc créé 2 fichiers sln : un pour les développeurs et une copie corrigée pour msbuild.

J’ai remonté le souci à Microsoft via connect, ils m’ont confirmé qu’il s’agit d’un bug et que cela sera corrigé dans VS2010.

Hi Cyril,
Thanks for the feedback. I confirm this is a bug. […] his bug should be fixed in the final version of VS2010.
Dan

Il existe une autre solution, plutôt que d’utiliser les numéros de port pour différencier les sites web, on peut utiliser les nom d’hotes. Au niveau de mon fichiers hosts j’ai rajouté 2 entrées vers 127.0.0.1 

image

Avec cette configuration, la solution compile. Malheureusement, je n’ai pas pu utiliser cette astuce, mon vrai fichier .sln contient une 30aine de site web et une 20aine de personnes travaillent actuellement sur cette solution.

Pour en savoir plus, voir d’autres workaround de la part de Microsoft ou bien voter pour ce bug, c’est ici : [Connect] error MSB5004 with MsBuild when using two IIS website

ASP.net – partager des fichiers (pages, UserControl) entre plusieurs site web – ajouter un fichier lié (Add As Link)

Dans certains cas, il est interessant de partager des fichiers entre plusieurs sites web : des pages web (.aspx), des UserControls (.ascx), etc …

Si l’on utilise un projet de type WebApplication et non un site web, il est possible d’ajouter un fichier lié. Pour cela, dans le solution explorer, lorsque vous faites un “Add existing item”

image

Vous pouvez sélectionner des fichiers d’un autre emplacement. Afin d’éviter que Visual Studio copie le fichier, vous pouvez l’ajouter en tant que fichier lié (Add As Link):

imageAinsi, votre solution contiendra bien une référence vers un fichier externe. Visual Studio nous affiche le fichier via l’icone “raccourci” dans l’explorateur de solution.

image

A l’utilisation cela ne fait aucune différence, il n’y aucun soucis pour éditer le fichier, compiler/publier le site web, ou réaliser des check-in (il faut que le fichier partagé fasse partie du workspace TFS).

Si vous utilisez un site web, on ne peut pas utiliser un fichier lié, en effet ceux ci nécessitent un fichier .csproj (ou .vbproj). En fonction des besoins, vous pouvez utilisez ces 3 astuces

  • Si vous utilisez IIS, vous pouvez utiliser un répertoire virtuel
  • Si vous n’avez pas besoin des fichiers pour le développement et que vous utilisez Team Build pour faire vos déploiements, vous pouvez utiliser une tache qui copiera les fichiers dans votre site web.
  • (non testé) Le système de fichier NTFS permet de faire des liens symboliques, il est donc possible de simuler le comportement des fichiers liés directement au niveau NTFS. Je n’ai pas testé cette solution, si l’un d’entre vous test, je suis curieux d’avoir son feedback, notamment au niveau de l’interaction avec TFS.  
    ==> How to create a symbolic link
    ==> junction utility (sysinternals)

Bien sur ces solutions ne sont pas aussi efficaces et simple que les fichiers liés.

Et vous ? Comment faites vous pour partager des fichiers entre 2 sites/applications web ?

Partager des ressources (resx) entre 2 applications - resx et visibilité public/internal (PublicResXFileCodeGenerator Vs ResXFileCodeGenerator)

J’ai récemment eu besoin de partager une ressource (resx) entre 2 sites web. J’ai alors créé une ressource dans une assembly partagée.

image

Malheureusement, je n’ai pas pu accéder à cette ressource depuis mes sites web. En effet, par défaut, les ressources génèrent du code avec une visibilité internal.

image

Bien sur, j’aurais pu utiliser la classe ResourceManager et accéder à mes ressource par ce biais, cette solution est cependant moins pratique que d’avoir un accès direct.

Par défaut les resx utilisent l’outil personnalisé nommé “ResXFileCodeGenerator” afin de générer le code. Il existe un autre outil permettant de générer des ressources avec une visibilité public : PublicResXFileCodeGenerator.

image

Ainsi vous pouvez accéder à vos ressources directement depuis les autres assemblies.

ASP.net 2.0 nous propose le ResourceExpressionBuilder, cela nous permet d’utiliser le code suivant :

<asp:Literal runat="server" Text="<%$ Resources : Shared, Hello %>" />

La solution proposée plus haut ne permet pas d’utiliser cette expression. En effet le ResourceExpressionBuilder recherche les ressources seulement dans le dossier App_GlobalResources.

Si vous souhaitez avoir le même comportement en utilisant des ressources partagés, il faut son votre propre ExpressionBuilder. J’ai expliqué comment faire un ExpressionBuilder ici : Dynamic Expression personnalisée : ProfileExpressionBuilder avec ASP.net 2.0

Et vous ? Avec vous eu déjà besoin de partager vos ressources entre plusieurs applications ?

Team Build : error MSB3554 - The fully qualified file name must be less than 260 characters

Lorsque l’on utilise Team Build pour compiler nos projets, il se peut que l’on tombe sur le problème suivant :

error MSB3554: Cannot write to the output file XXX. The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.

Il ne s’agit en aucun cas d’un bug de TFS ou de Visual Studio, mais d’une limitation de windows. En effet, l’interface de windows ne permet pas d’avoir un fichier dont le nom complet a une taille supérieur à 260 caractères (File Names, Paths, and Namespaces (MSDN)).
Par défaut, lorsque l’on configure un serveur de build, le workspace utilisé est C:\Documents and Settings\tfsBuild\Local Settings\Temp\ProjectName\, si l’on a un projet avec des namespaces conséquent, on arrive rapidement à cette taille limite.

La solution est donc de changer le chemin du workspace par défaut. Pour cela dans Team Explorer, faites un click droit sur le noeud “Builds” puis “Manage Build Agents …

image  

image

Dans le champ “Working Directory”  au lieu de $(Temp)\$(BuildDefinitionPath) utilisez C:\Build\$(BuildDefinitionId). Ainsi, le workspace sera c:\Build\123 au lieu de C:\Documents and Settings\tfsBuild\Local Settings\Temp\ProjectName\ soit une cinquantaine de caractères de gagnés.

Pour information, voici la description des 2 variables utilisées.

  • $(BuildDefinitionId) - a 32-bit identifier for a definition that uniquely identifies it for the Team Foundation Server
  • $(BuildDefinitionPath) - a path of the form <Team Project>\<Definition Name>

Vous pouvez également utiliser n’importe quelle variable d’environnement Windows.

Localisation et JavaScript – utilisation des ScriptResources et WebResources – ASP.net Ajax

Lorsque l’on fait une application web contenant du JavaScript, tôt ou tard se pose la question de la localisation, c’est à dire l’affichage des textes en différentes langues. ASP.net Ajax propose une solution utilisant les ressources .net, les fichiers resx.

Afin de mettre en place cette solution, plusieurs étapes sont nécessaires. Tout d’abord il faut utiliser un projet de type “Class Library” puis ajouter un fichier JavaScript que l’on inclut en “Embedded Resource”, il faut également rajouter les fichiers de ressources contenant les différents textes.

image

Il faut ensuite indiquer que notre fichier JavaScript est une WebResource, nous allons pour cela utiliser l’attribut WebResource. Le premier argument, correspond au nom de votre ressource, il est composé du namespace et du nom du fichier, vous pouvez retrouver ce nom en utilisant Reflector.

[assembly: WebResource("ClassLibrary1.Pouet.js", "application/x-javascript")]

Il est également nécessaire d’indiquer que notre WebResource est un ScriptResource, on utilise pour cela l’attribut ScriptResource. C’est ici que l’on associe le fichier Javascript avec notre fichier de ressource. Le premier argument correspond au nom de la WebRessource, le second au nom de la ressource contenant le texte, le dernier au nom de l’objet contenant le texte coté client.

[assembly: ScriptResource("ClassLibrary1.Pouet.js", "ClassLibrary1.Pouet", "Pouet.Res")]

Il ne reste plus qu’à ajouter votre fichier JavaScript à votre page, pour cela 2 possibilités :

public class MyControl : ScriptControl { // ... protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors() { yield break; } protected override IEnumerable<ScriptReference> GetScriptReferences() { yield return new ScriptReference("ClassLibrary1.Pouet.js", typeof(MyControl).Assembly.FullName); } }
  • Soit vous ajoutez votre script via le ScriptManager :

<asp:ScriptManager runat="server"> <Scripts> <asp:ScriptReference Assembly="ClassLibrary1" Name="ClassLibrary1.Pouet.js" /> </Scripts> </asp:ScriptManager>

Dans les 2 cas, votre page pointera vers un nouveau fichier JavaScript :

image

On peut voir qu’un nouvel objet à été créé côté client. Cet objet contient les différents textes localisés. Il est alors possible d’accéder aux ressources via :

alert(Pouet.Res.Hello)

Comment est géré le choix de la langue à utiliser ?

Tout comme ASP.net, le choix de la ressource à utiliser est sélectionné en fonction de la culture du thread courant.

Il est possible de spécifier cette culture au niveau du web.config via la section system.web/globalization. Les valeurs possibles sont “auto” pour utiliser la langue du navigateur ou une culture existante par exemple “FR-fr”

<configuration> ... <system.web> ... <globalization uiCulture="auto"/> </system.web> </configuration>

Et vous ? Comment gérez-vous la localisation en JavaScript ?

Convertir un int en String avec Linq to Entity – Utilisation de Entity SQL - LINQ to Entities does not recognize the method 'System.String ToString()' method

Dans certains cas, on a besoin de faire une recherche dans la base afin de savoir si un nombre contient un certain fragment. Par exemple si vous avez une table contenant des grands entiers, vous aimerez pouvoir rechercher toutes les lignes dont le nombre possède 23 (123, 242354, …)

La requête SQL est simple :

SELECT * FROM dbo.Person WHERE Num LIKE '%12%';

Avec Linq To Entity, la requête devrait ressembler à :

IEnumerable<Person> persons = entities.Person .Where(p => p.Num.ToString().Contains("12"));

Afin de reproduire le comportement du Like, on est obligé de convertir notre entier en String et d’utiliser la méthode Contains. Si l’on exécute ce code, alors on obtient une exception :

LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression.

Malheureusement, je n’ai pas trouvé de solution pour convertir un entier en String avec Linq To Entities. Une des solutions est de convertir notre IQueryable en IEnumerable, c’est à dire de récupérer l’ensemble des éléments coté .net puis de faire le where en Linq to Object, niveau performance ce n’est pas jouable !

L’autre solution est d’utiliser du entity SQL :

IEnumerable<Person> persons = entities.Person .Where("cast(it.Num as System.String) like '%' + @nb + '%'", new ObjectParameter("nb", 10.ToString()));

Ainsi, la requête générée par le moteur Linq To Entities est correctement traduite en SQL.

Je trouve étonnant que l’équipe de Linq To Entities, ne propose pas de solution simple pour résoudre ce problème. Lors du parsing de l’expression, il me semble simple de traduire int.ToString en cast( … as System.String), cela pose peut être quelques problèmes au niveau des cultures, mais je ne pense pas que cela soit bloquant.

Je n’ai pas testé avec EF4 (Entity Framework de .net 4), quelqu’un sait si ce cas sera plus simple avec EF4 ? une autre solution ?

Utiliser l’attribut XmlSchemaProvider

Récemment j’ai eu besoin de retourner un objet non serializable via un WebService. Afin de réaliser cela, j’ai implémenté l’interface IXmlSerializable.

Prenons par exemple ce service :

[ServiceContract] public interface IMyService { [OperationContract] Person Get(int personId); } [DataContract] public class Person : IXmlSerializable { // ... public void WriteXml(XmlWriter writer) { writer.WriteStartDocument(); writer.WriteStartElement("Person", "http://schemas.itelios.com/sample/Person"); writer.WriteAttributeString("PersonID", this._personId.ToString()); writer.WriteElementString("FirstName", this._firstName); writer.WriteEndElement(); } }

Si l’on rajoute une référence à ce service et que l’on ne partage pas le type Person alors le type de retour sera DataSet !

image

Si l’on regarde le WSDL (WebService Description Language) c’est à dire la description du service, alors on voit ceci :

<xs:complexType name="Person"> <xs:sequence> <xs:element ref="xs:schema" /> <xs:any /> </xs:sequence> </xs:complexType>

L’élément xs:any indique que le noeud que l’on retourne peut contenir n’importe quel élément XML.

Ce comportement est normal, nous spécifions nulle part le schéma du XML que l’on retourne. Avant de spécifier un schéma nous allons créer le type XSD que l’on va retourner :

<?xml version="1.0" encoding="utf-8"?> <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://schemas.itelios.com/sample/Person" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:complexType name="Person"> <xs:sequence> <xs:element name="FirstName" type="xs:string" /> </xs:sequence> <xs:attribute name="PersonID" type="xs:int" use="required" /> </xs:complexType> </xs:schema>

Puis, utilisons l’attribut XmlSchemaProviderAttribute, cet attribut prend en paramètre le nom d’une méthode static devant avoir la signature suivante :

public static XmlQualifiedName XXX(XmlSchemaSet xss)

L’objet de type XmlSchemaSet est la collection de schema associée à notre WebService, nous allons rajouter notre schema dans cette collection. Il reste alors à indiquer quel type XSD notre objet va retourner, c’est le but du XmlQualifiedName.

public class Person { // ... public static XmlQualifiedName GetPersonSchema(XmlSchemaSet xss) { // on stock notre fichier XSD en ressource de notre assembly using (Stream fs = Assembly.GetExecutingAssembly() .GetManifestResourceStream("Contract.Person.xsd")) { XmlSchema schema = XmlSchema.Read(fs, (sender, e) => { Console.WriteLine(e.Message); }); xss.Add(schema); } // nom du type XSD et namespace de l'objet que notre serialization va utiliser return new XmlQualifiedName("Person", @"http://schemas.itelios.com/sample/Person"); } }
Ainsi, notre schéma sera correctement ajouté au niveau du WSDL et le type retourné sera associé avec le type Person du namespace spécifié.

image

Cet attribut fonctionne aussi bien pour les services WCF que pour les services web classique (asmx).

Pour information, j’ai déjà expliqué comment utiliser l’interface IXmlSerializable ici : WCF : serialiser un objet non serialisable lorsque l'on n'a pas accès au type

WCF et JsonP – faire des requetes AJAX cross domain facilement

Je vous avais parlé il y a quelques temps de JSONP, une solution permettant de faire communiquer un navigateur avec un serveur autre que le serveur d’origine de la page actuelle : une sorte de requête XmlHttpRequest cross domain.

J’ai recemment eu besoin de mettre en place cette solution avec un service WCF. Après quelques recherches, je suis tombé sur un exemple de Microsoft implémentant JsonP sous forme d’un behavior WCF : JSON with Padding (AJAX) [MSDN] 

Arès avoir téléchargé l’exemple, vous pouvez alors décorer votre méthode de l’attribut JSONPBehavior

[ServiceContract(Namespace = "")] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class WSPouet { [WebGet] [OperationContract] [JSONPBehavior(callback = "jsonp")] public PouetData GetData(key url) { } }

Au niveau de la configuration, vous devez avoir ceci :

<system.serviceModel> <behaviors> <endpointBehaviors> <behavior name="WSPouetAspNetAjaxBehavior"> <enableWebScript/> </behavior> </endpointBehaviors> </behaviors> <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/> <services> <service name="WSPouet"> <endpoint address="" behaviorConfiguration="WSPouetAspNetAjaxBehavior" binding="customBinding" bindingConfiguration="jsonpBinding" contract="WSPouet"/> </service> </services> <bindings> <customBinding> <binding name="jsonpBinding"> <jsonpMessageEncoding/> <httpTransport manualAddressing="true"/> </binding> </customBinding> </bindings> <extensions> <bindingElementExtensions> <add name="jsonpMessageEncoding" type="MyCompany.ServiceModel.JsonpBindingExtension, MyCompany.ServiceModel.JsonpExtension"/> </bindingElementExtensions> </extensions> </system.serviceModel>

L’argument callback de l’attribut JsonPBehavior permet de spécifier le nom de l’argument contenant la fonction de callback. Ainsi vous pouvez accéder à votre service en allant sur l’url WSPouet.svc/MyMethod?jsonp=mycallback&key=mykey.

Si vous utilisez jQuery, côté client vous pouvez accèder à votre service en utilisant :

$.getJSON('http://pouet.com/WSPouet.svc/MyMethod?key=pouet&jsonp=?', function(data) { alert(data.d); });

Pour en savoir plus sur JsonP (JavaScript Object Notation with Padding), je vous invite à lire l’article suivant : JSONP : comment faire des requete JSON cross-domain

ASP.net – Partager la session entre plusieurs sous-domaines avec http ou https

Dans certains cas il peut être intéressant de pouvoir partager une session entre plusieurs sous-domaines. Cela nous permet par exemple d’avoir une session unique pour les urls fr.monsite.com et en.monsite.com ou http://www.monsite.com et https://secure.monsite.com.

Afin de bien comprendre notre problème, intéressons nous au fonctionnement des sessions.

Tout d’abord une session est partagée entre les pages d’un site via un cookie. Par défaut, ce cookie a pour nom ASP.NET_SessionId et contient une suite de 24 caractères alpha numériques aléatoires.

Lorsque l’on fait une requête vers ASP.net, un HttpContext est instancié. Ce contexte passe au travers de différents HttpModule dont le SessionStateModule, c’est ce module qui va se charger d’orchestrer les différentes étapes de la création / sauvegarde des variables de session. Dans un premier temps, ce module va récupérer un ID de session via le SessionIDManager, ce manager va regarder dans la requête s’il y a ou non le cookie de session contenant l’ID. S’il n’est pas présent, il va alors générer et retourner un ID unique. Le module va ensuite interroger le SessionStateStoreProvider afin de récupérer l’état de la session pour l’assigner au contexte de la requête.

Le traitement de la page s’exécute alors. Viens ensuite l’événement ReleaseRequesState qui va sauvegarder la session dans le SessionStateStoreProvider puis persister le SessionID dans la réponse renvoyé au client.

image

Deux classes sont responsables du fonctionnement des sessions, le SessionIDManager et le SessionStateStoreProvider. Le SessionStateStoreProvider permet de stocker les variables de sessions alors que SessionIDManager permet de sauvegarder côté client l’identifiant de session. ASP.net nous permet de personnaliser ces 2 classes via des interfaces.

Dans notre cas, nous avons besoin de créer notre propre SessionIDManager, en effet c’est lui qui est responsable de la création du cookie. Afin de simplifier l’implémentation, nous allons encapsuler le SessionIDManager natif, lors de la méthode SaveSessionID nous allons modifier la propriété Domain du cookie de session.


public class CustomSessionIDManager : ISessionIDManager { private ISessionIDManager _innerSessionIDManager; public CustomSessionIDManager() { this._innerSessionIDManager = new SessionIDManager(); } public void SaveSessionID(HttpContext context, string id, out bool redirected, out bool cookieAdded) { this._innerSessionIDManager.SaveSessionID(context, id, out redirected, out cookieAdded); // POC : not the best solution if (cookieAdded) { context.Response.Cookies["ASP.NET_SessionId"].Domain = "local.test"; } } // ... }

L’enregistrement de notre SessionIDManager se fait en renseignant la propriété sessionIDManagetType de la section sessionState du fichier de config.

<configuration> <system.web> <sessionState sessionIDManagerType="CustomSessionIDManager, App_Code" /> </system.web> </configuration>

Cette solution n’est pas optimum, en effet le nom du cookie est écrit en dur ainsi que le domaine. Le nom du cookie est spécifié via la propriété cookieName de la section sessionState du web.config, il nous est alors facilement récupérable. Pour le nom du domaine, nous avons 2 solutions, soit l’on réussit à le faire transiter du fichier de config vers notre SessionIDManager, soit on le calcul via le domaine courant. Malheureusement, on ne peut pas ajouter de paramètres au SessionIDManager via la section sessionState. Nous allons utiliser les AppSettings et le calcul si la clé n’est pas définit.

public class CustomSessionIDManager : ISessionIDManager { private String _cookieName; private ISessionIDManager _innerSessionIDManager; public CustomSessionIDManager() { this._innerSessionIDManager = new SessionIDManager(); } public void Initialize() { this._innerSessionIDManager.Initialize(); SessionStateSection sessionStateSection = (SessionStateSection)ConfigurationManager.GetSection("system.web/sessionState"); this._cookieName = sessionStateSection.CookieName; } public void SaveSessionID(HttpContext context, string id, out bool redirected, out bool cookieAdded) { this._innerSessionIDManager.SaveSessionID(context, id, out redirected, out cookieAdded); if (cookieAdded) { String domainName = ConfigurationManager.AppSettings["CustomSessionIDManager.CookieDomain"]; if (String.IsNullOrEmpty(domainName)) { domainName = context.Request.Url.Host; String[] domainParts = domainName.Split('.'); if (domainParts.Length > 1) domainName = String.Join(".", domainParts .Skip(domainParts.Length - 2) .ToArray()); } context.Response.Cookies[_cookieName].Domain = domainName; } } // ... }

Ainsi, nous pouvons accéder à la session entre plusieurs sous-domaines.

Bien sur, il faut que le SessionStateStoreProvider utilisé soit le même entre les différents sites. Si vous avez qu’un seul site définit pour les 2 urls au niveau de IIS, il n’est pas nécessaire de modifier le sessionStateStoreProvider. Par contre, si vous avez 2 sites différents il vous faut utiliser un serveur de session ou un serveur SQL afin de transiter les sessions entre vos 2 sites web. Pour plus d’informations sur la configuration d’un serveur de session, vous pouvez lire l’article ASP.NET Session State 

Pour plus de détails sur le fonctionnement interne des sessions, je vous invite à consulter l’article Fast, Scalable, and Secure Session State Management for Your Web Applications de Michael Volodarsky.

WCF – Interception des operations – comment rajouter du code lors de l’appel de méthode WCF – IOperationBehavior et IOperationInvoker

Lorsque l’on utilise WCF, dans certains cas, on aimerait pouvoir exécuter du code lorsque certaines méthodes sont appelées.

J’ai récemment eu ce besoin. Je travaillais sur un “Ajax-enabled WCF service” accessible depuis JavaScript. Pour différentes raisons j’utilisais l’attribut [WebGet] afin que les méthodes soient directement disponible via la méthode GET.

Malheureusement, lorsque je debuggais mon service, mes appels étaient mis en cache par le client, je devais donc vider mon cache afin de pouvoir rappeler mes méthodes. J’ai donc décidé de rajouter le code suivant au niveau de mes méthodes :

HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);

Mes appels n’étaient alors plus mis en cache!

Cependant je ne trouvais pas cette solution très élégante. J’ai donc cherché une solution me permettant d’intercepter tous les appels à mes méthodes.

Je me suis tourné vers la création d’un attribut implémentant IOperationBehavior. Cette interface possède 4 méthodes :

  • AddBindingParameters méthode permettant d’envoyer des données personnalisées aux bindings lors de l’exécution.

  • ApplyClientBehavior méthode permettant de modifier, examiner ou insérer des extensions lors de l'exécution de l’opération du côté du client.
    ==> on utilise cette méthode lorsque l’on veut faire un Behavior qui s’exécutera coté client.

  • ApplyDispatchBehavior : méthode permettant de modifier, examiner ou insérer des extensions dans l'exécution de l’opération du côté du service.
    ==> on utilise cette méthode lorsque l’on veut faire un Behavior qui s’exécutera coté serveur.

  • Validate pour confirmer qu'un OperationDescription remplit les conditions requises. Elle permet d'assurer qu'une opération dispose d'un certain paramètre de configuration activé, qu'elle prend en charge une fonctionnalité particulière et d'autres spécifications.

Ces méthodes seront appelées seulement lors de la création du service. Afin d’avoir la main sur l’exécution de chaque méthode/opération nous devons créer notre propre inspector. Nous allons pour cela créer une classe implémentant IParameterInspector.

Cette interface possède 2 méthodes aux noms explicites : AfterCall et BeforeCall.

Voici l’implémentation de mon OperationBehavior :

public class PouetAttribute : Attribute, IOperationBehavior { public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) { } public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation) { dispatchOperation.ParameterInspectors.Add(new PouetInspector()); } public void Validate(OperationDescription operationDescription) { IOperationBehavior webGetOperationBehavior = operationDescription.Behaviors .FirstOrDefault(operationBehavior => operationBehavior is WebGetAttribute) as WebGetAttribute; if (webGetOperationBehavior == null) throw new NotSupportedException("WebGetAttribute is required for PouetAttribute"); } }

Ainsi que l’implémentation de mon ParameterInspector

public class PouetInspector : IParameterInspector { public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState) { if (HttpContext.Current != null) HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache); } public object BeforeCall(string operationName, object[] inputs) { return null; } }

Ainsi, au niveau de mon service, il ne me reste plus qu’a décorer ma méthode de l’attribut Pouet.

[Pouet] [WebGet] [OperationContract] public String Hello(String s) { // won't stay in client cache return s + " - " + DateTime.Now.ToLongTimeString(); }

Nous avons vus comment se greffer à l’appel de nos operations via un ParameterInspector. Les méthodes BeforeCall et AfterCall nous renseignent également sur les paramètres envoyés à notre opération ainsi que son retour. Il n’est cependant pas possible de faire des modifications. Un inspecteur ne permet que d’analyser, regarder, ce qu’il se passe. Si l’on souhaite modifier le comportement de l’opération, il faut alors passer par un OperationInvoker

Bug IE8 – Invalid viewstate lors de chargement des fichiers ScriptResource.axd et WebResource.axd

Depuis quelques temps, on m’a remonté des soucis avec les fichiers ScriptResource.axd ou WebResource.axd.

L’erreur que l’on obtient est “System.Web.HttpException: Invalid viewstate

[HttpException (0x80004005): Invalid viewstate.] System.Web.UI.Page.DecryptStringWithIV(String s, IVType ivType) +2648697 System.Web.UI.Page.DecryptString(String s) +30 System.Web.Handlers.ScriptResourceHandler.DecryptParameter(NameValueCollection queryString) +81 System.Web.Handlers.ScriptResourceHandler.ProcessRequestInternal(HttpResponse response, NameValueCollection queryString, VirtualFileReader fileReader) +45 System.Web.Handlers.ScriptResourceHandler.ProcessRequest(HttpContext context) +184 System.Web.Handlers.ScriptResourceHandler.System.Web.IHttpHandler.ProcessRequest(HttpContext context) +7 System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +181 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +75

L’url posant problème était toujours composée de la clé identifiant le script à charger ainsi que de données aléatoires ressemblant à un submit d’un formulaire avec la méthode GET ou d’autres valeurs provenant de fonctions JavaScript. Par exemple :

/WebResource.axd?d=D-wd7RbHCvS/../../images/icons/Ico_resize.gif')}}function%20ShowFilter_Manufacturer(){var%20div.......

Jusque la, je me suis dit qu’il s’agissait surement d’un navigateur ou d’un robot mal codé. Mais récemment on m’a remonté ce problème sur un intranet avec un panel d’utilisateurs très restreint, de plus le problème survient de plus en plus souvent et sur différents sites.

J’ai réussit à le reproduire une fois, le problème ne survient sans raison particulière, très rarement. Puisqu’il s’agit d’une erreur lors du chargement d’un fichier externe, aucune erreur n’est remontée au client. Cependant lorsque cela se produit, le navigateur est figé lors du chargement de la page, il faut alors la rafraichir manuellement.

Après quelques recherches, j’ai trouvé d’autres personnes rencontrant le même souci. Il semblerait que le problème provient d’un bug de IE8 ! Dans certains cas IE8, construirait mal les urls de fichiers externes !

Le bug a été remonté à Microsoft via Connect, je vous invite tous à voter pour le bug afin que l’on obtienne une correction rapide de IE8 : [Connect] Feedback : Invalid Webresource.axd parameters being generated 


Mais à quoi sert ces fichiers ScriptResource.axd et WebResource.axd ?

Ces handlers permettent de retourner une ressource stockée dans une assembly.

Par exemple, si vous construisez un contrôle, il est possible que vous ayez besoin d’images ou de fichiers JavaScript. Afin de faciliter le déploiement, vous pouvez inclure ces ressources directement dans une assembly, celles ci seront alors accessible via les handler ci-dessus. Pour cela, ces handlers nécessitent une clé contenant le nom de la ressource et de l’assembly chiffré en base64. L’algorithme de chiffrement et de déchiffrement utilisé et le même que celui du viewstate.
Le code de l’algorithme a été conçut en pensant que seule le viewstate l’utiliserait. Lorsqu’un problème survient lors du déchiffrage de la clé, une exception parlant de viewstate est remonté.
Malgré que le message d’erreur parle de viewstate, il est en aucun cas question de viewstate ! Le problème provient du déchiffrage de la clé de ressource.

Et vous, avez vous rencontré le même genre de bug sur vos sites web ? Toujours avec IE8 ?

WCF – erreur 404.3 – lors d’un accès à un fichier .svc avec IIS7

Lorsque vous configurez un service WCF avec IIS (via un fichier .svc), il se peut que vous ayez l’erreur suivante lorsque vous tentez d’accéder au fichier .svc

HTTP Error 404.3 - Not Found
The page you are requesting cannot be served because of the extension configuration. If the page is a script, add a handler. If the file should be downloaded, add a MIME map.

Cette erreur survient car le fichier de config ne possède pas de handler configuré pour ce type de fichier.

Depuis IIS7, les handlers et modules sont directement gérés par IIS et non ASP.net, il s’agit de l’une des caractéristiques du pipeline mode integrated. Si votre fichier de config contient une entrée pour les handlers au niveau de la section system.web/httpHandlers, il est conseillé de migrer cette config vers la section system.webServer/httpHandlers propre à IIS7.

Pour cela, on peut utiliser l’outil de migration via la commande

c:\windows\System32\inetsrv\appcmd migrate config “PouetSiteName/”

Si l’erreur persiste ou si votre fichier original ne contient pas le handler requis, il faut le rajouter manuellement.

<configuration> <system.webServer> <handlers> <add name="ServiceHandler" verb="*" path="*.svc" type="System.ServiceModel.Activation.HttpHandler, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> </handlers> </system.webServer> <configuration>

Cette erreur n’est pas lié à WCF, il se peut que vous obtenez la même erreur avec un autre type de fichier. Dans ce cas, il faut rajouter le handler associé à la ressource demandé. N’hésitez pas à me contactez via les commentaires si vous ne savez pas quel handler utiliser pour d’autres types de fichiers.

FastStone Capture – un outil agréable pour faire des captures d’écran

Rédigeant de plus en plus de documentations, formations, … J’ai souvent besoin de faire des captures d’écran. Pour cela, j’utilise simplement les fonctionnalités de Windows via la touche “imprim écran” ce qui envoie la capture dans le presse papier, je la modifie ensuite via Paint.net si besoin. Il m’arrive aussi d’utiliser le Snipping tool de Vista lorsque je me sers de ce système d’exploitation.

Il existe de nombreuses autres solutions permettant de faire des captures d'’écran, j’en ai essayé quelques unes mais aucun ne m’a vraiment convaincu :

  • SnagIT : une des références en la matière
    • Beaucoup trop lourd pour ce que j’en ai besoin
    • Propose beaucoup trop d’options inutiles
    • etc …
  • Snipping Tool de windows Vista : plutôt sympa
    • Pas besoin d’installation
    • Pas possible de faire une capture tout en défilant la page
      • très utile pour une page web ou un menu de configuration un peu trop long
    • Disponible seulement sur vista
      • J’ai travaillé très peu de temps sous Vista au profit de server 2008 et maintenant seven

image 

Un collègue m’a récemment fait découvrir FastStone Capture, j’ai été très réticent avant de l’essayer. J’ai finalement craqué et j’en suis plutôt satisfait.

  • Très léger
  • Fais seulement ce qu’on attend de lui
  • Peut se lancer au démarrage de la machine
  • Pas d’installation requise
  • Peut faire des captures d’une fenêtre tout en défilant la page.
  • Permet de sélectionner seulement une partie de l’écran
    • via un zoom agréable
  • Possibilité d’envoyer la capture vers plusieurs endroits
    • Editeur
    • Presse papier
    • Fichier – l’outil nous demande alors le nom du fichier à chaque capture
    • Fichier - enregistrement automatique
    • Imprimante
    • Email
    • Powerpoint
    • FTP
  • Reduisable dans le systray
  • touche de raccourci par défaut très pratique
  • Possibilité d’enregistrer sous forme de film dans la derniere version.

image

Je ne suis généralement pas fan de ce genre d’outils. Celui ci m’a vraiment plus, cela fait maintenant quelques semaines que je l’utilise et je n’ai encore rien trouvé à lui reprocher.

Pour le tester c’est par ici : FastStone Capture Download n’oublier pas qu’aucune installation n’est requise.

Et vous, quels outils utilisez vous pour vos captures d’écran ?



Les 10 derniers blogs postés

- Créer un périphérique Windows To Go 10 ! par Blog de Jérémy Jeanson le 11-21-2014, 04:54

- RDV à Genève le 12 décembre pour l’évènement “SharePoint–Office 365 : des pratiques pour une meilleure productivité !” par Le blog de Patrick [MVP Office 365] le 11-19-2014, 10:40

- [IIS] Erreurs web personnalisées par Blog de Jérémy Jeanson le 11-19-2014, 00:00

- BDD/TDD + Javascript par Fathi Bellahcene le 11-16-2014, 16:57

- Sécuriser sans stocker de mots de passe par Blog de Jérémy Jeanson le 11-15-2014, 08:58

- Où télécharger la preview de Visual Studio 2015 ? par Blog de Jérémy Jeanson le 11-13-2014, 21:33

- Les cartes sont partout ! par Le blog de Patrick [MVP Office 365] le 11-13-2014, 17:26

- [ #Office365 ] Courrier basse priorité ! par Le blog de Patrick [MVP Office 365] le 11-12-2014, 08:56

- [Oracle] Fichier oranfsodm12.dll absent du package client par Blog de Jérémy Jeanson le 11-10-2014, 20:44

- [ #Office365 ] Le chapitre 1 des Groupes est écrit, et alors ? par Le blog de Patrick [MVP Office 365] le 11-10-2014, 20:23