Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Actualités

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

    View Cyril Durand's profile on LinkedIn

IIS et ASP.net 3.5 : avoir la bonne version du framework

Mais où est donc passé le framework 3.5 dans IIS ? En effet si l'on regarde dans IIS on voit qu'on peut choisir la version de ASP.net.

Sous IIS6 (Win 2003) :

Untitled2

Sous IIS7 (Vista) :

Untitled

On voit sur les captures que l'on peut choisir entre le framework 1.1 et 2.0 mais pas de framework 3.0 ni de 3.5, pourtant le framework 3.5 est bien installé.

C'est tout à fait normal ! Votre site .net 3.5 tournera très bien avec le framework .net 2.0, il n'y a rien à modifier.

Pour comprendre en quoi c'est normal, il faut d'abord se rappeler ce qu'est le framework 3.0 et 3.5 ; redo nous l'explique ici : qu'est-ce que le framework 3.5. Pour résumer le framework 3.x ne fait qu'apporter de nouvelles assemblies (des dll) à .net 2.0 ainsi que de nouveaux compilos (C#3 et VB9), .net 3.x se repose sur la CLR 2.0.

Lorsque vous créez un site .net 3.5, vous ne faites qu'utiliser ces nouvelles assemblies. Ces assemblies sont installées dans le GAC (Global Assembly Cache) lors de l'installation du framework 3.5, IIS n'aura alors aucun mal à les retrouver.
D'un point de vue IIS, votre site web 3.5, reste un "site 2.0" ayant un web.config un peu spécial (mais parfaitement conforme 2.0). Ce web.config référencie les assemblies de .net 3.5 ainsi qu'un nouveau compilo. Si vous voulez comprendre les différents éléments du web.config d'un site .net 3.5, je ne peux que vous conseillez l'article de Scott Mitchell : Dissecting ASP.NET Version 3.5's Web.config File

Il faut voir la combobox de choix du framework au niveau de IIS comme un choix de la CLR, et non comme le choix du framework !

Nested Type et Ajax-enabled WCF service => bug

Dans le cadre d'un test, j'ai récemment fait un service WCF qui ressemblait à peu près à ça :

[ServiceContract(Namespace = "")] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class Service { [DataContract] public class Person { [DataMember] public String FirstName { get; set; } } [OperationContract] [WebGet] public Person GetPerson() { return null; } }

J'ai donc rajouté une référence vers ce service au niveau de ma page pour que je puise accéder à ce service WCF en JavaScript.

<asp:ScriptManager runat="server"> <Services> <asp:ServiceReference Path="~/Service.svc" /> </Services> </asp:ScriptManager>

Lorsque l'on rajoute une référence vers un service WCF (ou asmx) via le ScriptManager. Lors de la phase de rendering, on ajoute un script vers /service.svc/js ce fichier contient le proxy nous permettant de dialoguer avec le WebService.

Dans le cas d'un nested type, le proxy généré contient ça :

var Service.Person=gtc("Service.Person:http://schemas.datacontract.org/2004/07/");

Ce qui fait évidemment planter le proxy puisque l'on ne peut pas déclarer une variable contenant le signe "."

Ce n'est pas très génant puisqu'on doit très rarement renvoyer des nested type (et je ne trouve pas ça propre). Je viens néanmoins d'ouvrir un bug sur connect : nested type in WCF with JSON Serialization

Mis à niveau d'une assembly VB .net 2.0 vers .net 3.5

Visual Studio 2008 permet de créer des projets pour le framework 2.0, 3.0 et 3.5

Untitled

.net 3.x n'étant que l'addition de quelques nouvelles assemblies, le choix d'utiliser tel ou tel framework au niveau de Visual Studio ne fait que restreindre les assemblies disponible lorsqu'on veut ajouter une référence.

Untitled2

Lorsqu'on a un projet de type C#, on peut facilement mettre à niveau le framework utilisé dans les propriétés du projet :

Untitled3

Par contre en VB, pas moyen de trouver cette option :

Untitled4

La seule solution que j'ai trouvée est de modifier le .vbproj en rajoutant la clé TargetFrameworkVersion

<Project ... > <PropertyGroup> <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>

[Update]

Suite au commentaire de actionthomas, l'option se trouve dans l'onglet "Compile" puis "Advanced Compile Options"

Reflector : la fonction "Analyze" - analyser le fonctionnement d'un code .net

Beaucoup de personnes ne savent pas utiliser le maximum de Reflector, certains ne connaissent même pas cet excellent outil. Pour lutter contre cette ignorance, j'ai écrit il y a quelques temps un tutorial de présentation de cet outil : Reflector : un décompilateur .net 

J'aimerais revenir avec un exemple concret d'utilisation de la fonction "Analyze".

Imaginons que vous avez réussi à obtenir un fichier qu'une application .net utilise (en analysant les accès disques via filemon par exemple). Malheureusement, ce fichier est chiffré, il ne vous est donc pas possible de voir le contenu de celui-ci. Comme vous êtes tenace et curieux, vous décider d'analyser l'application .net avec Reflector.
Il y a deux solutions :

  1. soit vous utilisez le plugin CodeSearch pour trouver la méthode qui écrit le fichier, puis vous analysez cette méthode,
  2. soit vous êtes plus astucieux et vous décidez de vous servir de la fonction "analyze" de reflector.

Qu'est-ce que la fonction "Analyze" de Reflector ? C'est une fonction qui permet de savoir qui utilise une méthode, une classe, voir même une assembly.

Untitled

A partir de là, on peut trouver toutes les méthodes qui utilise une méthode particulière.

Dans notre cas, on tente notre chance avec une classe de chiffrement du framework .net : Rijndaël. On s'aperçoit vite que cette classe est utilisée par notre assembly obfusqué. 

Untitled2

Malheureusement la méthode est obfusqué et Reflector ne parvient pas à convertir le code MSIL de cette méthode vers du C#, on pourrait lire le code MSIL mais avant on va essayer d'être plus astucieux. En effet  nous savons que pour utiliser la classe RijndaelManaged il faut une clé et une "IV" qui sont des Byte[], or on voit dans notre classe qu'il y a deux champs de type Byte[], regardons alors dans le constructeur de cette classe afin de vérifier si ces bytes n'ont pas été initialisé.

Untitled3

Nous avons donc réussit à obtenir très facilement la clé de chiffrement du fichier.

Comment complexifier le travail du vilain pirate ?

  • Essayez de pirater votre code et voyez comment complexifier cette tâche.
  • Utilisez un obfuscateur qui chiffre les strings, cela complexifiera la tache du pirate ; la version pro de dotfuscator le fait
  • N'inscrivez pas la clé en dur dans votre code. Si votre application s'utilise avec un serveur, envoyez la clé seulement lorsque celle-ci est nécessaire !

Je ne suis pas expert en sécurité .net et je n'ai jamais eu cette problématique, si vous connaissez d'autres façon de protéger votre code, merci de partager votre expérience dans les commentaires.

 


Attention, ce post explique comment vérifier si son application est facilement piratable, je ne peux en aucun cas être tenu responsable de vos agissements.

ScriptManager.RegisterDataItem - envoyer des données à travers un UpdatePanel

Je viens de découvrir la méthode ScriptManager.RegisterDataItem, cette méthode permet de transférer des données entre le serveur et le client lors d'un AsyncPostback. Pour ceux qui ne sont pas familier avec les asyncpostback, un asyncpostback reprend le mécanisme d'un postback classique sauf qu'il se fait via Ajax (XMLHttpRequest) et ne rafraichit que certaines parties de la page, ces zones sont définies via les UpdatePanels.

En plus d'envoyer le contenu HTML des UpdatePanels à rafraichir, la méthode RegisterDataItem permet de transférer les données que l'on souhaite. Cette méthode dispose de deux signatures, soit l'on envoie un simple string, soit on envoie un objet complexe sérialiser en JSON.

// permet de renvoyer un simple string associé au ClientID d'un contrôle ScriptManager1.RegisterDataItem(Label1, DateTime.Now.ToString()); // permet d'envoyer des données au format JSON JavaScriptSerializer serializer = new JavaScriptSerializer(); Person p = new Person("Cyril", "Durand"); ScriptManager1.RegisterDataItem(Label2, serializer.Serialize(p), true);

Pour récupérer les données côtés clients, il faut s'abonner à l'événement pageLoading du pageRequestManager :

Sys.WebForms.PageRequestManager.getInstance().add_pageLoading(function(sender, args){ var dataItems = args.get_dataItems(); var serverDate = dataItems['ClientID_Label1']; var person = dataItems['clientID_Label2']; });

Je trouve néanmoins dommage que la clé soit un objet de type Control et non un simple String, en effet, cela nécessite de connaitre le clientID du contrôle ce qui n'est pas toujours facile.

[MSIL] surcharge de méthode avec même signature mais type de retour différents

En C# il n'est pas possible d'avoir deux méthodes ayant une signature identique qui différent seulement par le type de retour.

public class Foo { public int Bar() { return 0; } public String Bar() { return "pouet"; } }

En MSIL c'est possible. Le code ci dessous compile parfaitement.

.assembly Foo{} .module Foo.dll .class public CFoo { .method public static int32 Bar() { ldc.i4.0 ret } .method public static string Bar() { ldstr "Toto" ret } }

Lorsqu'on fait un appel de méthode en MSIL, on spécifie le nom complet de la méthode :

call int32 CFoo::Bar() // ou call string CFoo::Bar()

Que se passe t'il en C# ? Comment spécifier la méthode que l'on veut utiliser ?

Untitled

L'IntelliSense de Visual Studio nous propose la méthode Bar qui retourne un String, mais si nous utilisons cette méthode.

String s = CFoo.Bar();

Nous avons une erreur de la part de compilation :

Untitled2

J'ai essayé en castant la méthode (String)CFoo.Bar() mais même erreur. J'ai tenté VB, l'IntelliSense ne me propose même pas de méthode Bar.

Quelle est alors l'utilité de ce type de surcharge ? J'en ai trouvé qu'une seule, cela permet aux obfuscateurs de complexifier le code. En effet, si notre classe possède des méthodes GetPerson() et GetCompany() qui retournent respectivement une List<Person> et une List<Company>, un obfuscateur pourra utiliser le même nom pour renommer ces méthodes.

Est-ce que C# doit pouvoir utiliser/créer de tels surcharges ?

Techniquement, l'inférence de type nous permettrait de ne pas avoir trop d'ambigüité, si cela ne suffit pas on pourra toujours caster explicitement, afin de séléctionner la bonne méthode.
Conceptuellement, bien que dans certains cas cela puisse être utile, je ne pense pas que cela soit une bonne chose, cela risque de complexifier le code pour peu. L'utilisation des generics, nous permet dans la majorité des cas d'obtenir des solutions alternatives.

Et vous qu'en pensez vous ? Est-ce que le futur compilateur devra prendre ce cas en compte ?

Modification header HTTP d'une requête Ajax vers un service WCF / ASMX Ajax

Lorsque l'on fait une requête vers un service WCF / ASMX "Ajax enabled" il se peut que l'on ait besoin de passer des paramètres qui ne sont pas propres à la méthode, par exemple un ticket d'authentification, ...

Ajout d'un paramètre dans le header de la requête (donc coté client) :

Pour ajouter un paramètre dans le header HTTP de la requête il faut s'abonner à l'événement invokingRequest.

Sys.Net.WebRequestManager.add_invokingRequest(function(){ e._webRequest._headers['CacheID'] = ... });

Côté serveur on peut relire le paramètre via :

[WebMethod] [ScriptMethod(UseHttpGet = true)] public List<Person> GetPersons() { Guid cacheID = Guid.Empty; try { cacheID = new Guid(HttpContext.Current.Request.Headers["CacheID"]); } catch { cacheID = Guid.NewGuid(); } }

Ajout d'un paramètre dans le header de la réponse (donc côté serveur) :

[WebMethod] [ScriptMethod(UseHttpGet = true)] public List<Person> GetPersons() { // ... HttpContext.Current.Response.AddHeader("CacheID", cacheID.ToString()); }

Ensuite pour lire ce paramètre côté client :

DataService.GetPersons(function(persons){ var cacheID = null; try { // ca ressemble à un bug Atlas ... cacheID = this._WSRequest._executor.getResponseHeader('CacheID'); } catch (ex) { } // ... });

Je me suis servi de cette astuce pour gérer une sorte de cache, session cliente afin de ne pas renvoyer les informations que le serveur m'avait déjà envoyé.

Communication grille extjs vers WCF ou WebService ASMX "Ajax" via JSON

Je suis actuellement en train d'utiliser le framework extjs.com. Parmi les contrôles de ce framework il existe un contrôle Ext.grid.GridPanel qui, comme son nom l'indique affiche une grille coté client (démo grid extjs).
Pour ceux qui ne connaissent pas extjs, je vous conseille vivement de faire un tour sur la page de démos de ce framework : démos des contrôles extjs, vous risquez d'être étonné.

Comment binder le contrôle grid avec un service WCF ou ASMX "Ajax Enabled" ?

Pour binder un contrôle à une source de donnée, extjs a un mécanisme de "store", celui-ci s'occupe de rechercher les données, les transformer en une donnée utilisable par les contrôles, etc...
Puisqu'un service WCF est capable de nous générer du JSON, le store qui nous intéresse est le JsonStore. Ce "store" permet de faire une requête XMLHttpRequest vers une url nous retournant du JSON. Le JsonStore effectue une requête GET, par défaut WCF n'autorise pas les requêtes GET, il faut donc rajouter l'attribut [WebGet] au niveau du service WCF afin d'autoriser ce verbe.

Voici à quoi ressemble notre service WCF.

[WebGet] [OperationContract] public List<Person> GetPersons() { return new List<Person>() { new Person(){FirstName = "Cyril", LastName = "Durand", BirthDate = new DateTime(1986, 1, 31), Company = "Freelance"}, new Person(){FirstName = "Toto", LastName="Pouet", BirthDate = new DateTime(1900, 1, 1), Company="Student"} }; }

Si vous utilisez un WebService ASMX classique il faut rajouter le paramètre UseHttpGet=true dans l'attribut ScriptMethod.

[WebMethod] [ScriptMethod(UseHttpGet = true)] public List<Person> GetPersons()

Au niveau du client, il n'y a rien de spécial à configurer, nous n'avons même pas besoin d'utiliser le framework Microsoft Ajax Library ou d'inclure le proxy autogénéré du service WCF (service1.svc/js).
Voici le code nécessaire.

var store = new Ext.data.JsonStore({ url : 'service.svc/GetPersons', root : 'd', // anti CSRF attack fields: [ 'FirstName', 'LastName', 'Company', {name:'BirthDate', type:'date', dateFormat: 'atlas'} ] }); var grid = new Ext.grid.GridPanel({ store: store, columns: [ {header: "Prenom", width: 75, sortable: true, dataIndex: 'FirstName'}, {header: "Nom", width: 75, sortable: true, dataIndex: 'LastName'}, {header: "Date de naissance", width: 155, sortable: true, dataIndex: 'BirthDate', renderer: Ext.util.Format.dateRenderer('m/d/Y')}, {id:'company',header: "Nom de la companie", width: 100, sortable: true, dataIndex: 'Company'} ], loadMask : true, autoExpandColumn: 'company', height:350, width:800 }); grid.render(document.body); store.load();

Si vous voulez renvoyer des données de type DateTime voici une petite astuce permettant de déserializer une Date "WCF" avec extjs.

// petite bidouille pour deserialiser les dates atlas avec extjs Date.parseFunctions['atlas'] = '_parseAtlas'; Date._parseAtlas = function(input){ input = input.replace( new RegExp('/Date\\((-?[0-9]+)(?:[a-zA-Z]|(?:\\+|-)[0-9]{4})?\\)/', 'g'), '(new Date($1))'); return eval(input); }

Cette astuce est à exécuter qu'une seule fois au lancement de votre application.

XmlDatasource et requetes XPath - XPathSelect

Question :

Je suis peut être idiot mais je n'arrive pas à voir comment je peux filtrer via XPath un XMLDataSource afin de remplir une datalist avec le text des Tags name.

Pourriez-vous me donner une piste ?

Réponse :

Les requêtes XPath interviennent à deux endroits lorsqu'on utilise un XMLDataSource. On peut les utiliser soit pour filtrer notre XMLDataSource afin de n'afficher que les enregistrements souhaités, ou alors on peut les utiliser pour sélectionner le texte à afficher.
Voici un exemple simple d'utilisation de requête XPath via un XMLDataSource.

<asp:XmlDataSource runat="server" ID="xmlDSCities" XPath="cities/city[starts-with(postalCode,69)]"> <Data> <cities> <city> <name>Lancie</name> <postalCode>69220</postalCode> </city> <city> <name>Villie morgon</name> <postalCode>69910</postalCode> </city> <city> <name>Macon</name> <postalCode>71000</postalCode> </city> </cities> </Data> </asp:XmlDataSource> <asp:Repeater runat="server" DataSourceID="xmlDSCities"> <ItemTemplate> <%# XPath("name") %> </ItemTemplate> <SeparatorTemplate>-</SeparatorTemplate> </asp:Repeater>

Cet exemple affichera seulement les communes dont le code postal commence par 69. On notera que pour simplifier l'exemple, j'ai mis le contenu XML directement dans le contrôle XMLDatasource; on peut bien sur spécifier un nom de fichier ou une URL via sa propriété DataFile.

Allons plus loin :

On peut aller encore plus loin en utilisant la méthode XPathSelect. Cette méthode permet de binder facilement des repeaters imbriqués.

<asp:XmlDataSource runat="server" ID="xmlDSCities" XPath="cities/city[starts-with(postalCode,69)]"> <Data> <cities> <city> <name>Lancie</name> <postalCode>69220</postalCode> <persons> <person> <firstName>Cyril</firstName> </person> <person> <firstName>Pouet</firstName> </person> </persons> </city> <city> <name>Villie morgon</name> <postalCode>69910</postalCode> </city> <city> <name>Macon</name> <postalCode>71000</postalCode> </city> </cities> </Data> </asp:XmlDataSource> <asp:Repeater runat="server" DataSourceID="xmlDSCities"> <ItemTemplate> <%# XPath("name") %> <br /> <asp:Repeater runat="server" DataSource='<%#XPathSelect("persons/person") %>'> <ItemTemplate> <%# XPath("firstName") %> </ItemTemplate> <SeparatorTemplate>-</SeparatorTemplate> </asp:Repeater> </ItemTemplate> <SeparatorTemplate><hr /></SeparatorTemplate> </asp:Repeater>

 

Les méthodes XPath et XPathSelect sont définit au niveau de la classe XPathBinder [MSDN]

Lire un paramètre du fichier de config | web.config ou app.config

Bien souvent on aimerait connaitre la valeur d'un élément de la configuration actuelle, par exemple la page d'erreur par défaut, la liste des handlers configurés etc...
C'est exactement la question que j'ai vu sur le forum ASP.net tout à l'heure.

L'astuce est simple mais encore faut il la connaitre. Pour récupérer les paramètres stocké dans le web.config (ou app.config), il faut passer par la classe ConfigurationManager et sa méthode statique GetSection.

Par exemple pour obtenir tous les handlers configurés sur l'application web :

HttpHandlersSection section = (HttpHandlersSection)ConfigurationManager.GetSection(@"system.web/httpHandlers"); gvHandler.DataSource = section.Handlers; gvHandler.DataBind();

Ce qui nous génère :

Path Verb Type Validate LockItem
*.rules * System.Web.HttpForbiddenHandler
*_AppService.axd * System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35
ScriptResource.axd GET,HEAD System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35
*.xoml * System.ServiceModel.Activation.HttpHandler, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
*.svc * System.ServiceModel.Activation.HttpHandler, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
trace.axd * System.Web.Handlers.TraceHandler
WebResource.axd GET System.Web.Handlers.AssemblyResourceLoader
*.axd * System.Web.HttpNotFoundHandler
*.aspx * System.Web.UI.PageHandlerFactory
*.ashx * System.Web.UI.SimpleHandlerFactory
*.asmx * System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35
*.rem * System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
*.soap * System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
*.asax * System.Web.HttpForbiddenHandler
*.ascx * System.Web.HttpForbiddenHandler
*.master * System.Web.HttpForbiddenHandler
*.skin * System.Web.HttpForbiddenHandler
*.browser * System.Web.HttpForbiddenHandler
*.sitemap * System.Web.HttpForbiddenHandler
*.dll.config GET,HEAD System.Web.StaticFileHandler
*.exe.config GET,HEAD System.Web.StaticFileHandler
*.config * System.Web.HttpForbiddenHandler
*.cs * System.Web.HttpForbiddenHandler
*.csproj * System.Web.HttpForbiddenHandler
*.vb * System.Web.HttpForbiddenHandler
*.vbproj * System.Web.HttpForbiddenHandler
*.webinfo * System.Web.HttpForbiddenHandler
*.licx * System.Web.HttpForbiddenHandler
*.resx * System.Web.HttpForbiddenHandler
*.resources * System.Web.HttpForbiddenHandler
*.mdb * System.Web.HttpForbiddenHandler
*.vjsproj * System.Web.HttpForbiddenHandler
*.java * System.Web.HttpForbiddenHandler
*.jsl * System.Web.HttpForbiddenHandler
*.ldb * System.Web.HttpForbiddenHandler
*.ad * System.Web.HttpForbiddenHandler
*.dd * System.Web.HttpForbiddenHandler
*.ldd * System.Web.HttpForbiddenHandler
*.sd * System.Web.HttpForbiddenHandler
*.cd * System.Web.HttpForbiddenHandler
*.adprototype * System.Web.HttpForbiddenHandler
*.lddprototype * System.Web.HttpForbiddenHandler
*.sdm * System.Web.HttpForbiddenHandler
*.sdmDocument * System.Web.HttpForbiddenHandler
*.mdf * System.Web.HttpForbiddenHandler
*.ldf * System.Web.HttpForbiddenHandler
*.exclude * System.Web.HttpForbiddenHandler
*.refresh * System.Web.HttpForbiddenHandler
* GET,HEAD,POST System.Web.DefaultHttpHandler
* * System.Web.HttpMethodNotAllowedHandler

Ce qu'il y a d'intéressant avec cette technique c'est que cela ne récupère pas seulement les informations du web.config mais également les informations du machine.config et les valeurs par défaut.

Pour bien comprendre cette astuce, il faut voir un fichier de config comme un ensemble de section. Toutes les sections sont désérialisées et stockées en mémoire lors de l'initialisation de l'application. ConfigurationManager.GetSection nous permet d'accèder à cette configuration stockée en mémoire. Bien sur cette astuce fonctionne avec tous les fichiers de config .net pas seulement le web.config.
Pour voir toutes les sections disponibles nativement, rendez vous dans le machine.config (%windir%\Microsoft.NET\Framework\v2.0.50727\CONFIG), vous pouvez également créer vos propres sections.

Capture A partir de là je me suis posé une question. Avec ASP.net il est possible d'avoir des fichiers de configuration au niveau d'un dossier, par exemple pour avoir un page d'erreur différente en fonction du dossier de la requête.

Je me suis donc créé un mini site web (voir capture à gauche), chaque web.config possède une page d'erreur personnalisée différente.

Lorsque je vais dans une des pages, j'ai la bonne valeur qui m'est retourné, c'est à dire si je vais sur /folder1/Default.aspx j'ai la valeur configuré dans /folder1/Web.config.
ConfigurationManager.GetSection nous retourne les valeurs en fonction de la requête active.

 

Voici le code pour afficher la page d'erreur personnalisée par défaut.

CustomErrorsSection section = (CustomErrorsSection)ConfigurationManager.GetSection(@"system.web/customErrors"); lblDefaultRedirect.Text = section.DefaultRedirect;

Si vous devez obtenir une valeur de votre web.config, inutile d'essayer de le parser ou je ne sais quelle autre bidouille ;-)

[C#] le mot clé methodof - l'instruction MSIL ldtoken

Alors que j'étais en train de regarder le fonctionnement du compilo C#3, Reflector me surpris avec un mot clé que je ne connaissais pas : methodof.

Capture

J'ai de suite testé ce nouveau mot clé dans Visual Studio et celui-ci n'est pas reconnu. Quel est la raison pour laquelle Reflector à décider d'utiliser un mot clé inexistant ?

Je me suis alors intéressé au code MSIL généré :

ldtoken instance string ConsoleApplication9.Product::get_ProductName() call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetMethodFromHandle( valuetype [mscorlib]System.RuntimeMethodHandle)

Si on regarde la spécification de la CLI, on voit la définition de l'instruction ldtoken page 444 

The ldtoken instruction pushes a RuntimeHandle for the specified metadata token. The token shall be one of:

  • A methoddef, methodref or methodspec: pushes a RuntimeMethodHandle
  • A typedef, typeref, or typespec : pushes a RuntimeTypeHandle
  • A fielddef or fieldref : pushes a RuntimeFieldHandle

The value pushed on the stack can be used in calls to reflection methods in the system class library

L'instruction ldtoken permet donc de récupérer une instance de RuntimeHandle, ce qui nous permet d'obtenir une instance de MemberInfo, c'est à dire un Type, un MethodBase ou un FieldInfo

Capture2

A noter qu'il n'est pas possible via cette instruction de récuperer un EventInfo.

En C#, il n'est pas possible de récupérer un MethodBase à partir du nom de la méthode, il faut obligatoirement passer par de la reflection. Ce code n'est donc pas directement reproductible en C#. Puisque l'instruction ldtoken est utilisé lorsqu'on utilise le mot clé typeof , on comprend alors pourquoi Reflector a choisi d'inventer un mot clé.

Niveau performance que donne cette instruction ?

Comparons un programme permettant de récupérer la méthode GetHashCode du type String écrit en C# et le même programme écrit en MSIL en utilisant l'instruction ldtoken.

C# :

Type t = typeof(String); for (int i = 0; i < 10; i++) { Stopwatch watcher = Stopwatch.StartNew(); MethodBase mi = t.GetMethod("GetHashCode"); watcher.Stop(); Console.WriteLine(watcher.ElapsedTicks); }

MSIL :

// initialization du chrono call class [System]System.Diagnostics.Stopwatch [System]System.Diagnostics.Stopwatch::StartNew() stloc.1 // récuperation de la méthode // ce code serait l'équivalent d'une hypothétique instruction methodof ldtoken method instance int32 [mscorlib]System.String::GetHashCode() call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetMethodFromHandle( valuetype [mscorlib]System.RuntimeMethodHandle) /// on dégage notre MethodBase de la pile pop // arret du chrono ldloc.1 callvirt instance void [System]System.Diagnostics.Stopwatch::Stop() // récuperation du temps ldloc.1 callvirt instance int64 [System]System.Diagnostics.Stopwatch::get_ElapsedTicks() // affichage du temps call void [mscorlib]System.Console::WriteLine(int64)

Untitled 

J'ai également mesuré l'instruction ldtoken qui dure environ 10 ticks et est constant.

La différence de temps sur le premier appel est surement du à la récupération du RuntimeMethodHandle par la méthode GetMethod à partir d'un String, ensuite les 2 solutions utilisent la méthode MethodBase.GetMethodFromHandle qui est relativement longue lors du premier chargement.

Si l'on fait un programme qui utilise d'abord ldtoken puis GetMethod on obtient ces résultats :

Untitled2

On remarque que le premier chargement de la méthode GetMethod est beaucoup plus rapide, on a un gain de 8500 ticks ce qui correspond environ à l'appel de MethodBase.GetMethodFromHandle. Les 1500 ticks restant sont due à la récupération du RuntimeMethodHandle à partir du String.

Les premiers appels de ce programme nous montrent que la CLR met en cache le MethodInfo à partir du RuntimeMethodHandle.

Puisque nous n'avons pas exactement le même temps entre ldtoken et GetMethod, on peut penser que la CLR (ou le framework .net) met en cache le MethodInfo à partir d'un String, c'est pour cela que GetMethod est un petit plus rapide que ldtoken après le premier chargement. Attention on parle de µs ...

Pourquoi l'instruction methodof (et fieldof) n'existe pas en C# ? Quel intérêt ?

Je ne vois pas de raison particulière à son absence, celle-ci serait relativement simple à mettre en place.

L'intérêt d'avoir une telle méthode est le même que typeof. typeof permet de nous assurer à la compilation que le type existe, actuellement si on veut récupérer une MethodInfo on passe par un String, si la méthode n'existe pas on le saura seulement lors de l'éxécution et non lors de la compilation comme le ferait methodof.

Je n'ai pas vu d'information la dessus pour C#4, wait & see ...


Puisque cet instruction n'existe pas en C#, vous pouvez vous demander comment j'ai réussi à l'obtenir dans Reflector à partir d'un programme écrit en C#. L'instruction ldtoken est en fait utilisé par le compilo C#3 lors de la construction de l'arbre d'expression à partir d'une requête Linq. Afin d'obtenir ce mot clé, il faut désactiver les optimisations C# de Reflector.


Voir aussi :