[DXPerience] Mettre en place le routage sur un ASPxNewsControl en ASP.NET 4

By Aymeric on août 8th, 2010

La SEO (Search Engine Optimization) est de plus en plus importante afin de mettre en avant votre site web sur internet via les moteurs de recherche. Pour une explication plus approfondie sur le SEO en ASP.NET 4, consultez la suite d’articles écrite par Nicolas Esprit en suivant le lien suivant.

DevExpress avec ses composants ASP.NET propose un ensemble de contrôles facilitant le développement web. L’un de ces contrôles, l’ASPxNewsControl permet d’afficher facilement une série de news/articles avec gestion de la pagination, extrait, etc…

Pour l’ASPxNewsControl, le but est d’avoir des URL du type http://www.aymericlagier.com/News/1/Titre-de-ma-news au lieu de http://www.aymericlagier.com/News.aspx?id=1 afin d’améliorer la lisibilité sur les moteurs de recherche. Cette modification d’URL passe par la mise en place de routage en ASP.NET.

La 1re étape consiste à définir notre route dans le fichier global.asax de l’application web.

        protected void Application_Start(object sender, EventArgs e)
        {
            RegisterRoutes(RouteTable.Routes);
        }

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapPageRoute("RouteToNews", "News/{newsID}/{newsTitle}", "~/News.aspx");
        }

Ici typiquement, les URL du type http://www.aymericlagier.com/News/1/Titre-de-ma-news pointeront sur la page News.aspx du site web. Le 1 (ID de la news) et Titre-de-ma-news pourront être récupérés dans le code en suivant le même principe que les paramètres passés en GET dans l’URL. Vous pouvez ajouter autant de routes que vous le souhaitez dans la méthode RegisterRoutes.

La 2ème étape consiste à créer l’ASPxNewsControl :

  • Default.aspx :
<pre><dx:ASPxNewsControl ID="ASPxNewsControl1" runat="server" Width="400px"
			NavigateUrlFormatString="~/News/{0}" ClientIDMode="AutoID"
			OnItemDataBound="ASPxNewsControl1_ItemDataBound">
			<ItemSettings DateHorizontalPosition="OutsideLeft" MaxLength="10"
				TailText="Lire la suite">
			</ItemSettings>
			<PagerSettings SEOFriendly="CrawlerOnly"></PagerSettings>
		</dx:ASPxNewsControl></pre>

L’attibut NavigateUrlFormatString indique à l’ASPxNewsControl le format de l’URL à respecter. {0} sera remplacé par /ID/Titre lorsque la news sera liée (bind) via l’événement OnItemDataBound.

Petite astuce, l’attribut SEOFriendly dans les PagerSettings de l’ASPxNewsControl, permet de modifier le comportement de la pagination. En effet, par défaut cette option est désactivée. Le crawler (robot) ne voit pas qu’il y a plusieurs pages puisque la pagination se fait via des callbacks en AJAX. Seule la 1re page est indexée. Mettre l’option sur Enabled (activée), les liens vers les autres pages se transforment en liens hypertextes que le robot peut voir, cependant l’utilisateur perd le bénéfice de l’AJAX. Un compromis existe entre ces 2 options, l’option CrawlerOnly. Pour les utilisateurs la pagnation se fait toujours en AJAX, mais pour les crawlers l’AJAX est remplacé par des liens hypertextes.

3ème étape, remplissage du contrôle avec une liste de news.

  • Classe News
    class News
    {
        public int id { get; set; }
        public string title { get; set; }
        public string content { get; set; }
        public DateTime date { get; set; }
    }
  • Default.aspx.cs
            List<News> li = new List<News>();

            for (int i = 1; i <= 10; i++)
            {
                li.Add(new News() { id = i, title = String.Format("Titre {0}", i), content = String.Format("Mon contenu de la news {0}", i), date = DateTime.Now });
            }

            ASPxNewsControl1.DataSource = li;
            ASPxNewsControl1.TextField = "content";
            ASPxNewsControl1.DateField = "date";
            ASPxNewsControl1.HeaderTextField = "title";

            ASPxNewsControl1.DataBind();

            Session["list"] = li;

La liste est stockée en session pour être utilisée par la suite sur la page News.aspx.

Si le titre de la news comporte des espaces  » « , des guillemets ou autres caractères spéciaux, il faut les convertir avant de les insérer dans l’URL. Le titre « La news du siècle ! » devra être transformée en « La-news-du-siecle » par exemple (suppression des espaces, accents, !, ?, etc….). La classe Routing permet cette opération :

   public class Routing
    {
        public static string ToFriendlyUrl(string urlToEncode)
        {
            urlToEncode = (urlToEncode ?? "").Trim().ToLower();

            StringBuilder url = new StringBuilder();
            string final = "";

            foreach (char ch in urlToEncode)
            {
                switch (ch)
                {
                    case ' ':
                        url.Append('-');
                        break;
                    case '&':
                        url.Append("et");
                        break;
                    case '\'':
                        break;
                    case '!':
                        url.Append("");
                        break;
                    case '?':
                        url.Append("");
                        break;
                    case 'é':
                        url.Append("e");
                        break;
                    case 'è':
                        url.Append("e");
                        break;
                    case 'ê':
                        url.Append("e");
                        break;
                    case 'à':
                        url.Append("a");
                        break;
                    case 'â':
                        url.Append("a");
                        break;
                    case 'ù':
                        url.Append("u");
                        break;
                    case 'û':
                        url.Append("u");
                        break;
                    default:
                        if ((ch >= '0' && ch <= '9') ||
                            (ch >= 'a' && ch <= 'z'))
                        {
                            url.Append(ch);
                        }
                        else
                        {
                            url.Append('-');
                        }
                        break;
                }

                final = Regex.Replace(url.ToString(), "-$", "");
            }

            return final;
        }
    }

4ème étape, la construction de l’URL pour chaque news via l’event handler ASPxNewsControl1_ItemDataBound :

        protected void ASPxNewsControl1_ItemDataBound(object source, DevExpress.Web.ASPxNewsControl.NewsItemEventArgs e)
        {
            News n = (News)e.Item.DataItem;
            e.Item.NavigateUrl = String.Format("{0}/{1}", n.id, Routing.ToFriendlyUrl(n.title));
        }

L’ASPxNewsControl est prêt pour un bon référencement.

Le projet utilisé dans cet article est disponible ici.

[Entity Framewok 4] Accélérer le temps de chargement d’Entity Framework 4 avec EFCachingProvider en ASP.NET

By Aymeric on juillet 11th, 2010

Comme vu précédemment dans l’article : Reduire le temps de chargement d’une page ASP.NET avec le SQL Cache Dependency, il est possible de réduire le temps de chargement d’une page en mettant en cache les résultats récupérés dans la base de données.

L’article précédent traité du SQL Cache Dependency avec Linq To SQL. Avec Entity Framework 4, cette méthode n’est pas disponible, il faut donc se rabattre sur un provider personnalisé créé par Jaroslaw Kowalski : EFCachingProvider.

Comme expliqué sur la page MSDN du provider, Entity Framework 4 permet l’ajout de providers personnalisés afin de supporter d’autres types de base de données comme Oracle, MySQL, etc… Cette possibilité permet également de rajouter une couche de cache, log, sécurité, etc… c’est le cas ici pour la cache. Pour plus de détails consulter la page MSDN mentionnée ci-dessus.

Utilisation de EFCachingProvider

Prérequis : Avoir un projet ASP.NET contenant un fichier .edmx correctement configuré.

- Commencez par télécharger les fichiers DLL des providers : ici.

- Ajoutez les fichiers DLL à votre projet et ajoutez les références vers ces DLL dans votre projet.

- Ouvrez le fichier web.config et ajoutez les lignes suivantes afin d’enregistrer les providers :

  <system.data>
    <DbProviderFactories>
      <add name="EF Caching Data Provider"
           invariant="EFCachingProvider"
           description="Caching Provider Wrapper"
           type="EFCachingProvider.EFCachingProviderFactory, EFCachingProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=def642f226e0e59b" />
      <add name="EF Generic Provider Wrapper"
           invariant="EFProviderWrapper"
           description="Generic Provider Wrapper"
           type="EFProviderWrapperToolkit.EFProviderWrapperFactory, EFProviderWrapperToolkit, Version=1.0.0.0, Culture=neutral, PublicKeyToken=def642f226e0e59b" />
    </DbProviderFactories>
  </system.data>

- Ajoutez (si ce n’est pas déjà le cas) un fichier Global.asax. Dans la méthode Application_Start, ajoutez les lignes suivantes :

EFCachingProviderConfiguration.DefaultCache = new AspNetCache();
EFCachingProviderConfiguration.DefaultCachingPolicy = CachingPolicy.CacheAll;

- Créer une classe ExtendedEntities qui hérite de la classe générée par Entity Framework 4 avec le .edmx (ici EFCachingEntities) :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using EFCachingProvider;
using EFCachingProvider.Caching;
using EFProviderWrapperToolkit;

namespace EFCaching
{
    public class ExtendedEntities : EFCachingEntities
    {
        public ExtendedEntities()
            : this("name=EFCachingEntities")
        {
        }

        public ExtendedEntities(string connectionString)
            : base(EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(
                    connectionString,
                    "EFCachingProvider"
            ))
        {
        }

        private EFCachingConnection CachingConnection
        {
            get { return this.UnwrapConnection<EFCachingConnection>(); }
        }

        public ICache Cache
        {
            get { return CachingConnection.Cache; }
            set { CachingConnection.Cache = value; }
        }

        public CachingPolicy CachingPolicy
        {
            get { return CachingConnection.CachingPolicy; }
            set { CachingConnection.CachingPolicy = value; }
        }
    }
}

A la ligne 14, n’oubliez pas de remplacer EFCachingEntities par votre chaine de connexion.

Tout est désormais prêt pour utiliser le système de cache. Ci-dessous, le code avec et sans l’utilisation du provider :

Avec EFCachingProvider

        protected void Page_Load(object sender, EventArgs e)
        {
            using (ExtendedEntities ent = new ExtendedEntities())
            {
                List<Ville> villes = (from v in ent.Villes select v).ToList();
            }
        }

Sans EFCachingProvider

        protected void Page_Load(object sender, EventArgs e)
        {
            using (EFCachingEntities ent = new EFCachingEntities())
            {
                List<Ville> villes = (from v in ent.Villes select v).ToList();
            }
        }

Notez que seule la ligne 3 change avec l’utilisation de ExtendedEntities à la place de EFCachingEntities.

Performances

Concernant mes tests, j’ai utilisé la base de données contenant toutes les villes françaises soit 34487 lignes.

Sur 10 essais, sans la couche EFCachingProvider, le temps de chargement moyen est de 0,5978s. Avec EFCachingProvider ce temps dessend à 0,3301s soit un temps de chargement divisé par 1,8.

Il est possible de télécharger ici le projet utilisé pour les tests.

[ASP.NET] UpdatePanel en ASP.NET, attention au piège

By Aymeric on mai 6th, 2010

En ASP.NET, l’UpdatePanel permet d’intéragir avec le serveur sans avoir à recharcher la page (mode asynchrone).

Le code suivant affiche la date courante dans le label lors du click sur le bouton sans avoir à recharger la page :

- UpdatePanel.aspx

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <asp:Button ID="bt1" runat="server" Text="Button"
                    onclick="bt1_Click" />
                <asp:Label ID="lbl1" runat="server" Text="Label"></asp:Label>
            </ContentTemplate>
        </asp:UpdatePanel>
    </div>
    </form>
</body>
</html>

- UpdatePanel.aspx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class UpdatePanel : System.Web.UI.Page
{
    protected void bt1_Click(object sender, EventArgs e)
    {
        lbl1.Text = DateTime.Now.ToString();
    }
}

Ce qui donne le résultat suivant lors du click sur le bouton, le tout sans rechargement de page :

Le processus est très simple, on pourrait être tenter de mettre ce contrôle partout sur le site internet pour bénéficier de la fluidité de l’AJAX.

Si on regarde d’un peu plus près ce qu’il se passe entre le client et le serveur, on s’aperçoit que tout le view state de la page est renvoyé à chaque action dans l’UpdatePanel :

Si on multiplie ce type d’action plusieurs fois dans la page avec plusieurs contrôles (TextBox, Label, etc…), chaque échange avec le serveur peut devenir très lourd. Cette lourdeur n’empêche pas l’utilisation des UpdatePanels, il faut juste être conscient du fonctionnement de ce contrôle pour ne pas l’utiliser à outrance. Ici l’utilisation du framework ASP.NET AJAX est une très bonne alternative pour alléger les échanges.

Le projet utilisé dans cet article est disponible ici.

Info : Pour obtenir des informations concernant les échanges entre le client et le serveur, utilisez Firefox avec le module Firebug.

[ASP.NET] Réduire le temps de chargement d’une page ASP.NET avec le SQL Server Cache Dependency

By Aymeric on avril 11th, 2010

Un système de cache permet de mettre temporairement des données en mémoire sur le serveur permettant un affichage plus rapide des pages du site web.

En ASP.NET, il existe plusieurs solutions pour créer un système de cache :

  • Output Caching : Une copie de la page web finale (HTML) est stockée sur le serveur. Lors du prochain appel à la page, la copie stockée sera automatiquement rendue au client n’exécutant ainsi pas les requêtes SQL, traitements côté serveur, etc… La copie expirera automatiquement après un temps préalablement configurée ou si le serveur manque de mémoire.
  • Data Caching : Il est possible de stocker en mémoire sur le serveur des données comme un DataSet. Si le DataSet est mis en cache, on évite ainsi un appel à la base de données. Contrairement à l’Output Caching, c’est à vous de définir les objets qui seront mis en cache.

Dans cet article, c’est une partie du Data Caching qui nous intéresse : le SQL Server Cache Dependency. Le principe de cette méthode de caching est de limiter les appels à la base de données en mettant en cache le résultat d’une requête SQL et en l’utilisant tant que la table à qui elle fait appel n’est pas modifiée.

Concrètement si un GridView utilise un SqlDataSource pour récupérer toutes les colonnes de la table MaTable dans une base de données.

  • Sans SQL Cache Dependencies, à chaque exécution de la page, un appel à la base de données sera fait pour récupérer les informations dans la table MaTable.
  • Avec SQL Cache Dependency, lors de la première exécution de la page un appel à la base de données sera fait puis lors de chaque appel le résultat de la requête sera récupéré dans le cache. Si la table vient à être modifiée, un nouvel appel à la base de données sera fait, et ainsi de suite.

Comment mettre en place le SQL Server Cache Dependency

Attention, la mise en place décrite ci-dessous ne fonctionne que sur SQL Server 2005 et plus. Pour SQL Server 2000 le SQL Cache Dependency est possible, mais beaucoup plus lourd à mettre en place.

Premièrement, pour activer les notifications sur SQL Server (qui permettront d’avertir du changement dans une table), démarrer Visual Studio Command Prompt (Démarrer -> Tous les programmes -> Microsoft Visual Studio 2010 -> Visual Studio Tools -> Visual Studio Command Prompt).

Tapez ensuite : SqlCmd -S <Serveur_de_base_de_données>

command_prompt

Entrez ensuite les requêtes suivantes :

USE <Base_de_données>
ALTER DATABASE <Base_de_données> SET ENABLE BROKER
GO

Attention, pour que cette requête s’exécute avec succès, aucune autre session ne doit être ouverte pour cette base de données.

Il faut ensuite ajouter une entrée dans le web.config pour indiquer que la base de données utilisera le cache :

    <system.web>
        <caching>
          <sqlCacheDependency enabled="true">
            <databases>
              <add name="Ma_Base" connectionStringName="Site" pollTime="20000"/>
            </databases>
          </sqlCacheDependency>
        </caching>
    </system.web>
  • name : Nom de la base de données
  • connectionStringName : nom de la chaine de connexion définie dans le web.config
  • pollTime : temps entre chaque notification (ici 20s)

Il faut ensuite activer les notifications pour la base de données et pour la table concernée. Pour pouvoir accéder à la classe SqlCacheDependencyAdmin, importez System.Web.Caching dans votre page.

            string cnx = ConfigurationManager.ConnectionStrings["Site"].ToString();
            SqlCacheDependencyAdmin.EnableNotifications(cnx);
            SqlCacheDependencyAdmin.EnableTableForNotifications(cnx, "Ma_Table");

Dernière étape, activer le Cache dans les propriétés du SqlDataSource ou ObjectDataSource.

cache_datasource

EnableCaching doit être passé à true et SqlCacheDependency doit contenir MaBase:MaTable.

Il faut noter qu’une table AspNet_SqlCacheTablesForChangeNotification est créée pour stocker les changements dans la base de données. A chaque modification le champ changeId est incrémenté de 1.

Les performances

Pour démontrer les performances de ce système de cache, j’ai fait des tests sur une requête SQL qui récupère les 34487 villes de France. Le test se base sur 10 exécutions de page dans les 2 cas et avec une désactivation du cache du navigateur.

Sans cache, l’exécution moyenne des pages est de 0,4825s alors qu’avec le cache, on obtient une exécution moyenne de 0,0284s soit une exécution 17 fois inférieure.

resultat

Il est possible de télécharger ici le projet utilisé pour les tests.

[ASP.NET] Récupérer l’UserId de l’utilisateur connecté en C#

By Aymeric on mars 13th, 2010

Avec l’utilisation du système intégré à ASP.NET pour gérer les utilisateurs, il peut être utile de récupérer l’identifiant de l’utilisateur actuellement connecté par exemple pour récupérer des données dans une base qui se rapporte à cet utilisateur.

Voici le code qui permet de récupérer l’UserId :

string userID = Membership.GetUser().ProviderUserKey.ToString();