Share via


Une question de contexte

 

Susan Warren
Microsoft Corporation

14 janvier 2002

L’un des problèmes les plus courants liés à l’écriture d’applications web consiste à informer votre code du contexte dans lequel il est exécuté. Examinons un exemple simple, la personnalisation d’une page, qui illustre ce problème :

     Connectez-vous.

et

     Bienvenue Susan!

Cela semble assez simple, mais même ce petit bout de l’interface utilisateur web nécessite quelques bits d’informations qui varient chaque fois que la page est demandée. Je dois savoir :

  1. L’utilisateur est-il connecté ?
  2. Quel est le nom d’affichage de l’utilisateur ?

Plus généralement, quel est le contexte unique chaquefois que la page est demandée ? Et comment écrire mon code pour qu’il prenne en compte ces informations ?

En fait, en raison de la nature sans état de HTTP, il existe de nombreux éléments de contexte différents qu’une application web peut avoir besoin de suivre. Lorsqu’un utilisateur interagit avec une application web, le navigateur envoie une série de requêtes HTTP indépendantes au serveur Web. L’application elle-même doit effectuer le travail de tricotage de ces demandes dans une expérience agréable pour l’utilisateur et connaître le contexte de la demande est essentiel.

ASP a introduit plusieurs objets intrinsèques comme Request et Application pour aider à suivre le contexte d’une requête HTTP. ASP.NET passe à l’étape suivante et regroupe ces objets, ainsi que plusieurs autres objets liés au contexte dans un objet intrinsèque extrêmement pratique appelé Contexte.

Context est un objet de type System.Web.HttpContext. Il est exposé en tant que propriété de la classe page ASP.NET. Il est également disponible à partir des contrôles utilisateur et de vos objets métier (plus d’informations à ce sujet ultérieurement). Voici une liste partielle des objets inscrits par HttpContext :

Object Description
Application Collection de paires clé/valeur de valeurs accessible par chaque utilisateur de l’application. L’application est de type System.Web.HttpApplicationState.
ApplicationInstance Application en cours d’exécution réelle, qui expose certains événements de traitement des requêtes. Ces événements sont gérés dans Global.asax, ou un HttpHandler ou HttpModule.
Cache L’objet Cache ASP.NET, qui fournit un accès par programmation au cache. La colonne mise en cache ASP.NET de Rob Howard fournit une bonne introduction à la mise en cache.
Error La première erreur (le cas échéant) s’est produite lors du traitement de la page. Pour plus d’informations , consultez Exception de Rob à la règle, partie 1 .
Éléments Collection de paires clé-valeur que vous pouvez utiliser pour transmettre des informations entre tous les composants qui participent au traitement d’une requête unique. Items est de type System.Collections.IDictionary.
Requête Informations sur la requête HTTP, notamment les informations de navigateur, les cookies et les valeurs transmises dans un formulaire ou sur la chaîne de requête. La requête est de type System.Web.HttpRequest.
Réponse Paramètres et contenu pour la création de la réponse HTTP. La requête est de type System.Web.HttpResponse.
Serveur Server est une classe utilitaire avec plusieurs méthodes d’assistance utiles, notamment Server.Execute(), Server.MapPath() et Server.HtmlEncode(). Server est un objet de type System.Web.HttpServerUtility.
Session Collection de paires clé/valeur de valeurs accessibles par un seul utilisateur de l’application. L’application est de type System.Web.HttpSessionState.
Trace L’objet Trace ASP.NET, qui fournit l’accès à la fonctionnalité de suivi. Pour plus d’informations, consultez l’article Suivi de Rob.
Utilisateur Contexte de sécurité de l’utilisateur actuel, s’il est authentifié. Context.User.Identity est le nom de l’utilisateur. User est un objet de type System.Security.Principal.IPrincipal.

Si vous êtes développeur ASP, certains des objets ci-dessus vous seront familiers. Il existe quelques améliorations, mais pour la plupart, elles fonctionnent exactement de la même façon dans ASP.NET que dans ASP.

Informations de base sur le contexte

Certains des objets dans Context sont également promus en tant qu’objets de niveau supérieur sur Page. Par exemple, Page.Context.Response et Page.Response référencent le même objet afin que le code suivant soit équivalent :

[Visual Basic® Web Form]

   Response.Write ("Hello ")
   Context.Response.Write ("There")

[Formulaire web C# ]

   Response.Write ("Hello ");
   Context.Response.Write ("There");

Vous pouvez également utiliser l’objet Context de vos objets métier. HttpContext.Current est une propriété statique qui retourne facilement le contexte de la requête actuelle. Cela est utile de toutes sortes de manières, mais voici un exemple simple de récupération d’un élément à partir du cache dans votre classe entreprise :

[Visual Basic]

      ' get the request context
      Dim _context As HttpContext = HttpContext.Current

   ' get dataset from the cache
   Dim _data As DataSet = _context.Cache("MyDataSet")

[C#]

      // get the request context
      HttpContext _context = HttpContext.Current;

   // get dataset from cache
   DataSet _data = _context.Cache("MyDataSet");

Contexte en action

L’objet Context fournit la réponse à plusieurs questions courantes ASP.NET « Comment faire ... ? ». Peut-être la meilleure façon de communiquer à quel point cette gemme peut être précieuse est de la montrer en action. Voici quelques-unes des meilleures astuces de contexte que je connais.

Comment émettre une instruction trace ASP.NET à partir de ma classe Business ?

Réponse: Facile! Utilisez HttpContext.Current pour obtenir l’objet Context , puis appelez Context.Trace.Write().

[Visual Basic]

Imports System
Imports System.Web

Namespace Context

   ' Demonstrates emitting an ASP.NET trace statement from a
   ' business class.

   Public Class TraceEmit
      
      Public Sub SomeMethod()
         
         ' get the request context
         Dim _context As HttpContext = HttpContext.Current
         
         ' use context to write the trace statement
         _context.Trace.Write("in TraceEmit.SomeMethod")

      End Sub

   End Class

End Namespace   

[C#]

using System;
using System.Web;

namespace Context
{
   // Demonstrates emitting an ASP.NET trace statement from a
   // business class.

   public class TraceEmit
   {

        public void SomeMethod() {
        
            // get the request context
            HttpContext _context = HttpContext.Current;

            // use context to write the trace statement
            _context.Trace.Write("in TraceEmit.SomeMethod");
        }
    }
}

Comment accéder à une valeur d’état de session à partir de ma classe Business ?

Réponse: Facile! Utilisez HttpContext.Current pour obtenir l’objet Context , puis accédez à Context.Session.

[Visual Basic]

Imports System
Imports System.Web

Namespace Context

   ' Demonstrates accessing the ASP.NET Session intrinsic 
   ' from a business class.

   Public Class UseSession
   
      Public Sub SomeMethod()
         
         ' get the request context
         Dim _context As HttpContext = HttpContext.Current
         
         ' access the Session intrinsic
         Dim _value As Object = _context.Session("TheValue")

      End Sub

   End Class

End Namespace

[C#]

using System;
using System.Web;

namespace Context
{
   // Demonstrates accessing the ASP.NET Session intrinsic 
   // from a business class.

   public class UseSession
   {

        public void SomeMethod() {
        
            // get the request context
            HttpContext _context = HttpContext.Current;

            // access the Session intrinsic
            object _value = _context.Session["TheValue"];
        }
    }
}

Réponse: Gérez les événements BeginRequest et EndRequest de l’application, et utilisez Context.Response.Write pour émettre le code HTML pour l’en-tête et le pied de page.

Techniquement, vous pouvez gérer les événements d’application tels que BeginRequest dans un HttpModule ou à l’aide de Global.asax. Les httpmodules sont un peu plus difficiles à écrire et ne sont généralement pas utilisées pour les fonctionnalités utilisées par une seule application, comme dans cet exemple. Par conséquent, nous allons utiliser le fichier Global.asax dans l’étendue de l’application à la place.

Comme pour une page ASP, plusieurs des intrinsèques de contexte ASP.NET sont promues pour être des propriétés de la classe HttpApplication, dont hérite la classe représentant Global.asax. Nous n’avons pas besoin d’utiliser HttpContext.Current pour obtenir une référence à l’objet Context ; il est déjà disponible dans Global.asax.

Dans cet exemple, je mets les <html> balises et <body> , plus une règle horizontale dans la section d’en-tête, et une autre règle horizontale plus les balises de fin pour celles-ci dans la section de pied de page. Le pied de page contient également un message de copyright. Le résultat ressemble à la figure ci-dessous :

Figure 1. Exemple d’en-tête et de pied de page standard rendus dans le navigateur

Il s’agit d’un exemple trivial, mais vous pouvez facilement l’étendre pour inclure votre en-tête et votre navigation standard, ou simplement générer les <instructions -- #include ---> pour ceux-ci. Une mise en garde : si vous souhaitez que l’en-tête ou le pied de page inclue du contenu interactif, vous devez envisager d’utiliser ASP.NET contrôles utilisateur à la place.

[Source SomePage.aspx — exemple de contenu]

<FONT face="Arial" color="#cc66cc" size="5">
Normal Page Content
</FONT>

[Visual Basic Global.asax]

<%@ Application Language="VB" %>

<script runat="server">

      Sub Application_BeginRequest(sender As Object, e As EventArgs)

         ' emit page header
         Context.Response.Write("<html>" + ControlChars.Lf + _
"<body bgcolor=#efefef>" + ControlChars.Lf + "<hr>" + _ ControlChars.Lf)

      End Sub 
      
      
      Sub Application_EndRequest(sender As Object, e As EventArgs)

         ' emit page footer
         Context.Response.Write("<hr>" + ControlChars.Lf + _
      "Copyright 2002 Microsoft Corporation" + _
      ControlChars.Lf + "</body>" + ControlChars.Lf + "</html>")

      End Sub 

</script>

[C# Global.asax]

<%@ Application Language="C#" %>

<script runat="server">

        void Application_BeginRequest(Object sender, EventArgs e) {

            // emit page header
            Context.Response.Write("<html>\n<body bgcolor=#efefef>\n<hr>\n");
        }

        void Application_EndRequest(Object sender, EventArgs e) {

            // emit page footer
            Context.Response.Write("<hr>\nCopyright 2002 Microsoft Corporation\n");
            Context.Response.Write("</body>\n</html>");
        }

</script>

Comment afficher un message de bienvenue lorsque l’utilisateur est authentifié ?

La réponse : Testez l’objet de contexte User pour voir si l’utilisateur est authentifié. Si c’est le cas, récupérez également le nom de l’utilisateur à partir de l’objet User . Il s’agit, bien sûr, de l’exemple du début de l’article.

[Visual Basic]

<script language="VB" runat="server">

    Sub Page_Load(sender As Object, e As EventArgs) {

        If User.Identity.IsAuthenticated Then
            welcome.Text = "Welcome " + User.Identity.Name
        Else
            ' not signed in yet, add a link to signin page
            welcome.Text = "please sign in!"
            welcome.NavigateUrl = "signin.aspx"
        End If

    End Sub

</script>

<asp:HyperLink id="welcome" runat="server" maintainstate="false">
</asp:HyperLink>

[C#]

<script language="C#" runat="server">

    void Page_Load(object sender, EventArgs e) {

        if (User.Identity.IsAuthenticated) {
            welcome.Text = "Welcome " + User.Identity.Name;
        }
        else {
            // not signed in yet, add a link to signin page
            welcome.Text = "please sign in!";
            welcome.NavigateUrl = "signin.aspx";
        }
    }

</script>

<asp:HyperLink id="welcome" runat="server" maintainstate="false">
</asp:HyperLink>

Et maintenant pour quelque chose de vraiment merveilleux: Context.Items

J’espère que les exemples ci-dessus montrent à quel point il est beaucoup plus facile d’écrire votre application web avec un peu d’informations contextuelles à portée de main. Ne serait-il pas génial de pouvoir accéder à un contexte propre à votre application de la même façon ?

C’est l’objectif de la collection Context.Items . Il contient les valeurs spécifiques à la requête de votre application d’une manière qui est disponible pour chaque partie de votre code qui participe au traitement d’une demande. Par exemple, les mêmes informations peuvent être utilisées dans Global.asax, dans votre page ASPX, dans les contrôles utilisateur de la page et par la logique métier que la page appelle.

Prenons l’exemple d’application IBuySpy Portal . Il utilise une page de main unique, DesktopDefault.aspx, pour afficher le contenu du portail. Le contenu affiché dépend de l’onglet sélectionné, ainsi que des rôles de l’utilisateur, s’il est authentifié.

Figure 2 : Page d’accueil IbuySpy

La chaîne de requête inclut les paramètres TabIndex et TabId pour l’onglet demandé. Ces informations sont utilisées tout au long du traitement de la demande pour filtrer les données affichées à l’utilisateur. http://www.ibuyspy.com/portal/DesktopDefault.aspx?tabindex=1&tabid=2

Pour utiliser une valeur de chaîne de requête, vous devez d’abord vous assurer qu’il s’agit d’une valeur valide et, si ce n’est pas le cas, effectuer une petite gestion des erreurs. Ce n’est pas beaucoup de code, mais voulez-vous vraiment le dupliquer dans chaque page et chaque composant qui utilise la valeur ? Bien sûr que non ! Dans l’exemple de portail, il est encore plus impliqué, car il existe d’autres informations qui peuvent être préchargées une fois que nous connaissons l’Id tab.

Le portail utilise les valeurs de chaîne de requête comme paramètres pour construire un nouvel objet « PortalSettings » et l’ajouter à Context.Items dans l’événement BeginRequest dans Global.asax. Étant donné que la demande de début est exécutée au début de chaque requête, les valeurs liées aux onglets sont ainsi disponibles pour toutes les pages et tous les composants de l’application. Une fois la demande terminée, l’objet est automatiquement ignoré , très bien rangé !

[Visual Basic Global.asax]

      Sub Application_BeginRequest(sender As [Object], e As EventArgs)
         
         Dim tabIndex As Integer = 0
         Dim tabId As Integer = 0
         
         ' Get TabIndex from querystring
         If Not (Request.Params("tabindex") Is Nothing) Then
            tabIndex = Int32.Parse(Request.Params("tabindex"))
         End If
         
         ' Get TabID from querystring
         If Not (Request.Params("tabid") Is Nothing) Then
            tabId = Int32.Parse(Request.Params("tabid"))
         End If
         
         Context.Items.Add("PortalSettings", _
New PortalSettings(tabIndex, tabId))

      End Sub

[C# Global.asax]

void Application_BeginRequest(Object sender, EventArgs e) {
        
    int tabIndex = 0;
    int tabId = 0;

    // Get TabIndex from querystring

    if (Request.Params["tabindex"] != null) {               
        tabIndex = Int32.Parse(Request.Params["tabindex"]);
    }
                
    // Get TabID from querystring

    if (Request.Params["tabid"] != null) {              
        tabId = Int32.Parse(Request.Params["tabid"]);
    }

    Context.Items.Add("PortalSettings", 
new PortalSettings(tabIndex, tabId));
}

Le contrôle utilisateur DesktopPortalBanner.ascx extrait l’objet de PortalSetting de Context pour accéder au nom et aux paramètres de sécurité du portail. En fait, ce module est un excellent exemple de contexte en action. Pour illustrer ce point, j’ai un peu simplifié le code et marqué tous les emplacements http ou contexte spécifique à l’application accessible en gras.

[C# DesktopPortalBanner.ascx]

<%@ Import Namespace="ASPNetPortal" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<script language="C#" runat="server">

    public int          tabIndex;
    public bool         ShowTabs = true;
    protected String    LogoffLink = "";

    void Page_Load(Object sender, EventArgs e) {

        // Obtain PortalSettings from Current Context
  PortalSettings portalSettings = 
(PortalSettings) Context.Items["PortalSettings"];

        // Dynamically Populate the Portal Site Name
        siteName.Text = portalSettings.PortalName;

        // If user logged in, customize welcome message
        if (Request.IsAuthenticated == true) {
        
            WelcomeMessage.Text = "Welcome " + 
Context.User.Identity.Name + "! <" + 
"span class=Accent" + ">|<" + "/span" + ">";

            // if authentication mode is Cookie, provide a logoff link
            if (Context.User.Identity.AuthenticationType == "Forms") {
                LogoffLink = "<" + "span class=\"Accent\">|</span>\n" + 
"<a href=" + Request.ApplicationPath + 
"/Admin/Logoff.aspx class=SiteLink> Logoff" + 
"</a>";
            }
        }

        // Dynamically render portal tab strip
        if (ShowTabs == true) {

            tabIndex = portalSettings.ActiveTab.TabIndex;

            // Build list of tabs to be shown to user                                   
            ArrayList authorizedTabs = new ArrayList();
            int addedTabs = 0;

            for (int i=0; i < portalSettings.DesktopTabs.Count; i++) {
            
                TabStripDetails tab = 
(TabStripDetails)portalSettings.DesktopTabs[i];

                if (PortalSecurity.IsInRoles(tab.AuthorizedRoles)) { 
                    authorizedTabs.Add(tab);
                }

                if (addedTabs == tabIndex) {
                    tabs.SelectedIndex = addedTabs;
                }

                addedTabs++;
            }          

            // Populate Tab List at Top of the Page with authorized 
// tabs
            tabs.DataSource = authorizedTabs;
            tabs.DataBind();
        }
    }

</script>
<table width="100%" cellspacing="0" class="HeadBg" border="0">
    <tr valign="top">
        <td colspan="3" align="right">
            <asp:label id="WelcomeMessage" runat="server" />
            <a href="<%= Request.ApplicationPath %>">Portal Home</a>
<span class="Accent"> |</span> 
<a href="<%= Request.ApplicationPath %>/Docs/Docs.htm">
                Portal Documentation</a>
            <%= LogoffLink %>
            &nbsp;&nbsp;
        </td>
    </tr>
    <tr>
        <td width="10" rowspan="2">
            &nbsp;
        </td>
        <td height="40">
            <asp:label id="siteName" runat="server" />
        </td>
        <td align="center" rowspan="2">
      &nbsp;
        </td>
    </tr>
    <tr>
        <td>
            <asp:datalist id="tabs" runat="server">
               <ItemTemplate>
                  &nbsp;
<a href='<%= Request.ApplicationPath %>
/DesktopDefault.aspx?tabindex=<%# Container.ItemIndex %>&tabid=
<%# ((TabStripDetails) Container.DataItem).TabId %>'>
<%# ((TabStripDetails) Container.DataItem).TabName %>
</a>&nbsp;
                </ItemTemplate>
                <SelectedItemTemplate>
                  &nbsp;
                  <span class="SelectedTab">
<%# ((TabStripDetails) Container.DataItem).TabName %>
</span>&nbsp;
                </SelectedItemTemplate>
            </asp:datalist>
        </td>
    </tr>
</table>

Vous pouvez parcourir et exécuter la source complète du portail IBuySpy en ligne dans Visual Basic et C# à l’adresse http://www.ibuyspy.com, ou la télécharger et l’exécuter vous-même.

Résumé

Le contexte est un autre de ces « bonnes choses de s’améliorer encore dans ASP.NET fonctionnalités ». Il étend la prise en charge du contexte déjà très utile d’ASP pour ajouter les deux hooks aux nouvelles fonctionnalités d’exécution de ASP.NET. De plus, il ajoute Context.Items en tant que nouveau mécanisme d’état pour les valeurs de très courte durée. Mais l’avantage ultime pour vous en tant que développeur est plus compact, plus facile à gérer du code, et c’est un contexte que nous pouvons tous prendre en compte.