mercredi 5 mai 2010

ASP.NET MVC : Helpers et méthodes d'extentions, le mariage parfait

ASP.NET MVC fournit 3 Helpers essentiels au framework : UrlHelper, HtmlHelper et AjaxHelper que l'on retrouve respectivement sous le nom de Url, Html et Ajax comme propriété.
Ces helpers fournissent un contexte qui expose l'essentiel des méthodes et propriétés "core" les concernant.
Les helpers deviennent alors des accrocheurs de méthodes d'extensions qui vont étendre les fonctionnalités en utilisant le contexte de celui ci. Ainsi, au lieu de créer une méthode d'extension pour ViewPage, ViewPage<T>, ViewUserControl, ViewUserControl<T>, ViewMasterPage et ViewMasterPage<T>, une seule suffit sur HtmlHelper. On peut constater d'ailleur que la plupart des méthodes que l'on retrouve sur ces objets sont des méthodes d'extensions :
Les helpers permettent donc de fournir un contexte aux méthodes d'extensions sans polluer le reste des objets et permettant ainsi de classer ces méthodes, de créer des catalogues de méthodes statiques. Le helper devient un "this" et son écriture devrait justement omettre l'écriture du "this". J'ai ainsi 3 helpers me permettant de résoudre bien des problèmes d'implémentations dans mon application MVC (Ex : AccountHelper, FileHelper). On peut imaginer que ce genre de concept peut s'appliquer dans bien d'autres cas.

mardi 4 mai 2010

Optimisation de listes SQL Server - ASP.NET - HTML

Il est assez fréquent d'avoir des listes simples (listes de liens, DropDownList's, ListItem's, etc.) de clefs/valeurs nécessitant de multiples requêtes, mappings, objets, etc...

Voici ma solution permettant d'alléger le travail du développeur et des serveurs :
Énoncé :
On souhaite afficher une fiche "détail" issue d'une table d'aliments ([produits]). Chaque aliment possède une ou plusieurs catégories (Ex : Légume, Viande, Produit Frais, Cru, etc...) issues d'une table ([categories_in_produits]) en liaison avec une table de référence ([categories]).
On souhaite maintenant afficher ces catégories sous formes de liens dans la fiche du produit afin de pouvoir rediriger vers une liste des produits pour la catégorie en question.

1. Script T-SQL :
Voici le corps de la procédure stockée :

DECLARE @categories varchar(1000)

-- Concaténation des catégories. ex : 55=Légumes;45=Cru;
SET @categories = ''
SELECT @categories = @categories + c.id + '=' + c.display_name + ';'
FROM categories c
INNER JOIN categories_in_produits cp ON c.id = cp.category_id
WHERE cp.produit_id = @produit_id

-- Requête de résultat
SELECT *, @categories categories
FROM produits
WHERE id = @produit_id

On obtient donc toutes nos catégories en 1 seule requête.

2. Étendre jQuery :
Cette méthode va permettre de parser coté client notre chaine de catégories et de les binder sur un template :
(function($) {
$.fn.databind = function(d, r) {
var p = $.extend({ r: /([^=]*)=([^;]*);/gi, d: '' }, { d: d, r: r });
return this.each(function() {
var s = $(this);

var h = s.html();
var l = h.length - 3;
if(l > 4 && h.indexOf('', l) == l)
h = h.substring(4, l);
s.html(p.d.replace(p.r, h)).show();
});
};
})(jQuery);


3. Allégez votre Html :
Laisser le client parser la chaine de catégories (utilisation du model MVC) :

<script type="text/javascript">
$(document).ready(function() {
$('#template').databind('<%= Model.Categories %>');
});
</script>
<p id="template" style="display: none">[<a href="<%= Url.Action("CategoryList") %>/$1">$2<a>]
</p>

Grâce à cette méthode, on économise les transactions avec notre serveur de bases de données, on ne transforme rien coté IIS/ASP.NET, on optimise le HTML généré et on laisse le client parser la chaine. Coté développeur, une seule procédure stockée est nécessaire ainsi que tout ce qui en découle (objets, mapping, data access).

Un exemple testable directement (en incluant jQuery et l'extension de jQuery du point 2) :
Il est possible de spécifier une expression régulière personnalisée pour gérer vos propres concaténations :

<script type="text/javascript">
$(document).ready(function() {
$('#list-languages').databind('FR;EN;IT;ES;', /([^;]*);/gi);
$('#list-languages2').databind('fr=Francais;en=Anglais;it=Italien;es=Espagnol;');

});
</script>
<select id="list-languages"
style="display:none"><option value="$1" >$1</option></select>
<select id="list-languages2" style="display:
none"><option value="$1" >$2</option></select>


Le fonctionnement de la méthode 'databind' ajoutée à jQuery étant basé sur une expression régulière, il faudra utiliser la syntaxe correspondante pour le template.

Sur certains navigateurs, vous serrez obligé de mettre le template en commentaire () parce que les quotes autour des valeurs des attributs disparaissent. La mise en commentaire peut aussi permettre d'afficher le cadre container avant le binding évitant ainsi d'éventuels sauts lors du chargement de votre page car il n'est plus nécessaire de placer le style "display:none".

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.