lundi 14 septembre 2009

Méthode d’extension : Object To Dictionary<string, object>

Je parle dans le précédent post de la méthode utilisée pour initialisé un dictionnaire de valeur par certaines briques de la SP1 du 3.5. Elle est souvent utilisée où l’écriture d’un objet anonyme simplifie l’écriture ou pour initialiser un objet en une seule ligne dans la nouvelle syntaxe. Cette méthode consiste à scanner un objet par réflexion pour créer un dictionnaire <Nom de la propriété, Valeur de la propriété>. Cette méthode n’est pas performante pour alimenter un dictionnaire, mais elle peut être utile. Voici donc une méthode d’extension plus générique permettant d’obtenir un résultat similaire :

public static class MyExtensions
{
    public static Dictionary<string, object> ToDictionary(this object obj)
    {
        Dictionary<string, object> dic = new Dictionary<string, object>();
        Type t = obj.GetType();
        foreach (var item in t.GetProperties())
        {
            dic.Add(item.Name, item.GetValue(obj,null));
        }
        return dic;
    }
}

Cette méthode est simple, mais elle peut s’agrémenter de plusieurs surcharges permettant ainsi de filtrer les types ou d’effectuer des conversions par exemple.

vendredi 11 septembre 2009

“System.Web.Routing” dans les applications ASP.NET

Cette assembly apporté par le .NET 3.5 SP1 principalement pour ASP.NET MVC et Dynamic Data nous permet d’écrire nos Url’s dans le format de notre choix. L’utilisation de cette assembly va de pair avec “System.Web.Abstraction” utilisée par “System.Web.Routing” dans l’encapsulation du HttpContext dans un HttpContextWrapper et représenté dans un HttpContextBase dans un object RequestContext :

RequestContext

Son utilisation et implémentation est extrêmement simple et elle commence par l’ajout d’une référence à l’Assembly “System.Web.Routing” et “System.Web.Abstraction” et la spécification du HttpModule analysant les Url’s à la source dans la configuration (version IIS 6) :

        <httpModules>
            <
add name="RoutingModule"
           type
="System.Web.Routing.UrlRoutingModule,System.Web.Routing,Version=3.5.0.0,
           Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
     </
httpModules>

L’étape suivante consistera à créer une classe permettant de résoudre le HttpHandler de la réponse. Pour que cette classe puisse être utilisé par la table de routage, elle doit implémenter l’interface “System.Web.Routing.IRouteHandler” :

public class PageRouteHandler : IRouteHandler
{
    public string UrlFormat { get; set; }

    public PageRouteHandler(string urlFormat)
    {
        this.UrlFormat = urlFormat;
    }

    IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
    {
        string vp = string.Format(this.UrlFormat, requestContext.RouteData.Values["page"]);
        return (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath(vp, typeof(IHttpHandler));
    }
}

Cette classe va donc nous permettre de résoudre le chemin virtuel d’une page grâce à un format de base et à l’information “page” située dans les valeurs de “RouteData” résultat du parsing de l’url. Afin de suivre la méthodologie de ASP.NET MVC, on va maintenant utiliser les méthodes d’extensions pour ajouter simplement un format à la table de routage :

public static class PageRouteExtentions
{
    public static void MapPage(this RouteCollection routes, string routeName, string serverFormat, string clientUrl)
    {
        routes.Add(routeName, new Route(clientUrl, new PageRouteHandler(serverFormat)));
    }
}

Grâce à cette méthode d’extension, il devient très aisé d’enregistrer une route :

public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        RouteTable.Routes.MapPage("Base", "~/{0}.aspx", "{page}.php");

Comme on le voit, le paramètre “page” est entouré de 2 accolades dans l’url client, c’est une règle permettant à la Route de résoudre le format de notre url. Il y a pleins d’autres options, comme des contraintes de formats, des valeurs attachées à la routes dans rapport à l’url, des valeurs par défaut…

On notera que l’objet “System.Web.Routing.RouteDataDictionary” utilisé pour toutes les valeurs d’une “System.Web.Routing.RouteData” à la particularité d’avoir un constructeur permettant de scanner un objet par réflexion pour obtenir clef/valeur de chaque propriété. Cette méthode est très répandue dans l’ASP.NET MVC, elle permet de faire usage à volonté des objets anonymes.

mercredi 19 août 2009

BuildProvider : Custum compilation ASP.NET

L’ASP.NET est extensible de multiple façon : IHttpHandler & Factory’s, les contrôles ASP.NET. Mais l’extension la plus intéressante du moteur ASP.NET est la compilation personnalisé.
Comment cela marche t-il ? Grâce à BuilderProvider. La classe BuilderProvider va nous permettre de compiler du code sur le même principe que l’ASP.NET avec les pages ASPX, ASCX, Master, etc. On va donc créer un générateur pour une extension de fichier définie dans la configuration et le moteur ASP.NET se chargera de compiler le code généré et l’intégrer dans notre application au démarrage de celle-ci.

Étape 1 : Génération du code source et création du provider de génération :

public class SampleBuildProvider : BuildProvider
{
private CompilerType _compilerType = null;

public SampleBuildProvider()
{
_compilerType = base.GetDefaultCompilerTypeForLanguage("C#");
}

public override CompilerType CodeCompilerType
{
get { return _compilerType; }
}

public override void GenerateCode(AssemblyBuilder assemblyBuilder)
{
using (TextWriter wr = assemblyBuilder.CreateCodeFile(this))
{
wr.WriteLine("using System;");
wr.WriteLine("using System.Collections.Generic;");
wr.WriteLine("using Builder;");

wr.WriteLine("namespace MyNamespace");
wr.WriteLine("{");
Uri uri = new Uri(base.VirtualPath);
wr.WriteLine("public class My{0} : IHttpHandler", Path.GetFileName(base.VirtualPath).Split(new char[] { '.' }));
wr.WriteLine("{");

// Implémentation de IHttpHandler

wr.WriteLine("}}");
}
}

public override Type GetGeneratedType(CompilerResults results)
{
string type = string.Format("MyNamespace.My{0}", Path.GetFileName(base.VirtualPath).Split(new char[] { '.' }));
return results.CompiledAssembly.GetType(type);
}
}

Étape 2 : Spécifier dans la section “Compilation” du Web.Config quels sont les fichiers concernés :

<compilation debug="true">
<buildProviders>
<
add type="SampleBuildProvider" extension=".myExt"/>
</
buildProviders>
</
compilation>

Étape 3 : Utilisation des éléments générés :

Dans notre exemple, on implémente IHttpHandler, il nous suffit de créer alors un Factory de Handler pour cette nouvelle extension :

[PermissionSet(SecurityAction.LinkDemand, Unrestricted = true), PermissionSet(SecurityAction.InheritanceDemand, Unrestricted = true)]
public class MyHandlerFactory : IHttpHandlerFactory
{
public MyHandlerFactory()
{
}

public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string virtualPath, string path)
{
Type type = BuildManager.GetCompiledType(virtualPath);
return Activator.CreateInstance(type) as IHttpHandler;
}

public virtual void ReleaseHandler(IHttpHandler handler)
{
}
}

et de le spécifier dans la configuration :

<httpHandlers>
<
add verb="*" path="*.sb" type="MyHandlerFactory"/>
</
httpHandlers>

On peut trouver de multiples applications, les plus courrantes vont utiliser le Xml pour décrire du code.

jeudi 4 juin 2009

Fonctionnement du Dictionary grâce aux nombres premiers

Le Dictionnaire est souvent utilisé pour obtenir une collection à accès rapide identique presque identique via une clef.

Mais comment marche t-il ?
Grâce aux nombres premiers !
Pour ceux qui ne connaissent pas, les nombres premiers ne se divise que par 1 ou par eux même. Ceux-ci vont nous permettre de calculer les index de positionnement de nos valeurs dans la collection.

Rappelons qu’une List<T> se base à l’origine sur un tableau T[] et que la List<T> s’occupe de gérer la taille de ce tableau et l’agrandir au besoin.

Un dictionnaire fonctionne de la même manière qu’une List<T> en utilisant une structure pour stocker Clef/Valeur, appelée Entry<TKey,TValue> dans le framework :

Disons que notre structure contient pour le moment ceci :

public struct Entry<TKey, TValue>
{
public TKey key;
public TValue value;
}

La différence avec une List<T>, c’est que le dictionnaire possède un index permettant un filtrage des données lors de leurs récupérations.
Cet index est un tableau d’entier de la même taille que le tableau de valeur :

public class Dictionary
{
private int[] buckets;
private Entry<TKey,TValue> entries;
}

Les tailles des deux tableaux sont égales entre elle et à un nombre premier.
Lors de l’ajout d’un item au tableaux “entries”, voici ce qui est effectué :

  1. On récupère le HashCode de la clef pour obtenir un entier :
    int hashCode = key.GetHashCode();
  2. On récupère le reste de la division par le nombre premier utilisé pour la taille des tableaux :
    int index = hashCode % entries.Length;
    On obtient ainsi un nombre strictement inférieur à la taille de la collection. Ce nombre correspondra à un index dans le tableau d’index “buckets”. À cette position, on y stockera un index du tableau “entries” correspondant à l’index de la dernière clef dont la division du HashCode retourne la même valeur.

Le problème est que maintenant, on ne peut que récupérer le dernier enregistrement ajouté. Il nous suffit alors de stocké ce lui du précédent dans notre structure “Entry” :

public struct Entry<TKey, TValue>
{
public TKey key;
public TValue value;
public int previous; // Appelé next dans le framework
public int hasCode;
}

En simplifiant le code d’ajout d’item, on obtient ceci :

public void Add(TKey key, TValue value)
{
// Calcul d'un hashcode (positif)
int hashCode = key.GetHashCode() & int.MaxValue;
// Reste de la division
int index = hashCode % this.buckets.Length;
// Positionnement dans le tableau de valeur
int freeEntry = this.count;
this.count++;
this.entries[freeEntry].hashCode = hashCode;
// Récupération de l'index de l'ancienne clef vu en premier
this.entries[freeEntry].next = this.buckets[index];
this.entries[freeEntry].key = key;
this.entries[freeEntry].value = value;
// Définition de la nouvelle entrée vu en premier
this.buckets[index] = freeEntry;
}

Code très simplifié (la suppression, la présence de la clef et l’augmentation de taille ne son pas gérés).

On récupèrera donc la valeur ainsi :

public TValue Get(TKey key)
{
int hashCode = key.GetHashCode() & int.MaxValue;
int index = hashCode % this.buckets.Length;
for (int i = this.buckets[index]; i >= 0; i = this.entries[i].next)
{
if (this.entries[i].hashCode == hashCode)
{
return this.entries[i].value;
}
}
return default(TValue);
}

La principale source de gains de performance vient à l’initialisation du dictionnaire, ce qui est aussi le cas des List<T> : La taille de départ. Celle ci est d’autant plus importante que le dictionnaire doit, en plus de la copie mémoire des tableaux, recalculer les index avec la nouvelle taille.

mardi 14 avril 2009

JavaScript : parseInt ne retourne pas la valeur souhaitée

Sur des valeurs comme "08" ou "09", le parseInt de JavaScript écrit ainsi :

var val = parseInt("09");
alert(val);


Nous affiche 0.

Pourquoi ? Parce que JavaScript par du principe que toutes chaines de caractères commençant par 0 est en base 8, comme une chaine commençant par un "x" est en base 16.

Alors comment régler ce problème ? En lui spécifiant la base :

var val = parseInt("09", 10);
alert(val);