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

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

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#

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();

[.NET] Envoyer un email grâce à un template en ASP.NET/C#

Dans un article précédent, j’expliquais comment envoyer un email en C#, un autre aspect intéressant de l’envoi de mail est de pouvoir utiliser un fichier texte comme template. Cette fonctionnalité est utilisée notamment par le contrôle CreateUserWizard avec le MailDefinition :

 

<MailDefinition BodyFileName="~/Skels/template.txt"
Subject="Sujet du mail">
</MailDefinition>

 

Le code ci-dessus permet donc d’utiliser le contenu du fichier template.txt comme corps de notre email.

Pour reproduire ce comportenant en C#, il faut utiliser la classe MailDefinition comme exposé dans l’exemple suivant.

Namespaces :

 

using System;
using System.Net.Mail;
using System.Collections.Generic;
using System.Web.UI.WebControls;

 

Code C# :

 

MailDefinition def = new MailDefinition();
def.BodyFileName = "/template.txt";
Dictionary<string, string> data = new Dictionary<string, string>();
data.Add("var1", "Contenu de ma variable 1");
data.Add("var2", "Contenu de ma variable 2");

MailMessage email = def.CreateMailMessage("aymeric.lagier@gmail.com", data, this);
email.From = new MailAddress("no-reply@aymericlagier.com");
email.Subject = "Sujet de mon mail";

SmtpClient client = new SmtpClient();
client.Send(email);

 

Contenu de template.txt :

 
ma var 1 : var1

ma var 2 : var2

 

Résultat :

mailrecu

[ASP.NET] ObjectDataSource et Guid, propriété non trouvée en ASP.NET

Lorsque l’on veut utiliser un GridView pour ajouter/modifier/supprimer des données dans une table SQL qui a pour clé primaire un Guid (par exemple aspnet_Users), une erreur survient lorsque l’on veut modifier ou supprimer un élément :

 

Could not find a property named ‘xxx’ on the type specified by the DataObjectTypeName property in ObjectDataSource ‘yyy’

 

Tout semble pourtant bien configurer, mais l’erreur persiste. J’ai été confronté à ce problème avec la table aspnet_Users, qui contient les utilisateurs créés avec le système interne d’ASP.NET.

 

Voici ma solution au problème, je ne garantis pas qu’elle soit optimale. Elle se base sur la table aspnet_Users.

 

Après plusieurs recherches sur Google, j’ai trouvé la solution suivante : Supprimer le contenu de l’attribut DataObjectTypeName qui contient normalement System.Guid.

 

Chez moi cette solution ne fonctionne pas.

 

La solution que je vous propose est de convertir ce Guid en nvarchar lors de la récupération de nos données. Ce nvarchar sera ensuite passer en argument des méthodes Update et Delete. Concrétement la requête de récupération des données sera de ce type :

 

SELECT u.UserId, CAST(u.UserId AS nvarchar(MAX)) AS UserIdStr, u.UserName
FROM aspnet_Users u
 

GridView

 

Du côté ASPX, il faut mettre UserIdStr comme valeur de la propriété DataKeyNames et rendre les colonnes UserIdStr et UserId non visible et en readonly. Le code du GridView ressemble à ceci :

 

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataSourceID="ObjectDataSource1" DataKeyNames="UserIdStr">
  <Columns>
    <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
    <asp:BoundField DataField="UserId" HeaderText="UserId"
    SortExpression="UserId" Visible="false" ReadOnly="true" />
    <asp:BoundField DataField="UserName" HeaderText="UserName"
    SortExpression="UserName" />
    <asp:BoundField DataField="Email" HeaderText="Email" SortExpression="Email" />
    <asp:CheckBoxField DataField="IsApproved" HeaderText="IsApproved"
    SortExpression="IsApproved" />
    <asp:BoundField DataField="UserIdStr" HeaderText="UserIdStr"
    SortExpression="UserIdStr" Visible="false" ReadOnly="true" />
  </Columns>
</asp:GridView>

 

Et l’ObjectDataSource ressemble à ceci :

 

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
DeleteMethod="Delete" SelectMethod="FetchAll"
TypeName="Project.Business.BLLUsers" UpdateMethod="Update">
  <DeleteParameters>
    <asp:Parameter Name="UserIdStr" Type="String" />
  </DeleteParameters>
  <UpdateParameters>
    <asp:Parameter Name="UserIDStr" Type="String" />
    <asp:Parameter Name="Username" Type="String" />
    <asp:Parameter Name="Email" Type="String" />
    <asp:Parameter Name="IsApproved" Type="Boolean" />
  </UpdateParameters>
</asp:ObjectDataSource>

 

Dans mon cas, les méthodes Update et Delete appellent des procédures stockées qui prennent pour identifiant un paramètre de type nvarchar(MAX).

Si vous trouvez une autre méthode pour arriver au même résultat, faites moi signe :).