Eine Frage des Kontexts

 

Susan Warren
Microsoft Corporation

14. Januar 2002

Eines der häufigsten Probleme beim Schreiben von Webanwendungen besteht darin, Ihren Code über den Kontext zu informieren, in dem er ausgeführt wird. Sehen wir uns ein einfaches Beispiel an – die Personalisierung einer Seite –, das dieses Problem veranschaulicht:

     Bitte melden Sie sich an.

im Vergleich zu

     Willkommen Susan!

Scheint einfach genug zu sein, aber selbst diese kleine Web-Benutzeroberfläche erfordert ein paar Informationen, die bei jeder Anforderung der Seite variieren. Ich muss Folgendes wissen:

  1. Ist der Benutzer angemeldet?
  2. Wie lautet der Anzeigename des Benutzers?

Allgemeiner gesagt, was ist der eindeutige Kontext jedesMal, wenn die Seite angefordert wird? Und wie kann ich meinen Code so schreiben, dass er diese Informationen berücksichtigt?

Tatsächlich gibt es aufgrund der zustandslosen Natur von HTTP viele verschiedene Kontextelemente, die eine Webanwendung nachverfolgen muss. Wenn ein Benutzer mit einer Webanwendung interagiert, sendet der Browser eine Reihe unabhängiger HTTP-Anforderungen an den Webserver. Die Anwendung selbst muss die Arbeit tun, um diese Anforderungen in eine angenehme Erfahrung für den Benutzer zu stricken, und es ist wichtig, den Kontext der Anforderung zu kennen.

ASP hat mehrere systeminterne Objekte wie Request und Application eingeführt, um den Kontext für eine HTTP-Anforderung nachzuverfolgen. ASP.NET führt den nächsten Schritt aus und bündelt diese Objekte sowie mehrere zusätzliche kontextbezogene Objekte in einem äußerst praktischen systeminternen Objekt namens Context.

Context ist ein Objekt vom Typ System.Web.HttpContext. Sie wird als Eigenschaft der ASP.NET Page-Klasse verfügbar gemacht. Sie ist auch über Benutzersteuerelemente und Ihre Geschäftsobjekte verfügbar (dazu später mehr). Hier sehen Sie eine partielle Liste der Objekte, die von HttpContext in ein Rollup ausgeführt wurden:

Object BESCHREIBUNG
Anwendung Eine Schlüssel-Wert-Paarauflistung von Werten, auf die jeder Benutzer der Anwendung zugreifen kann. Die Anwendung hat den Typ System.Web.HttpApplicationState.
ApplicationInstance Die tatsächlich ausgeführte Anwendung, die einige Anforderungsverarbeitungsereignisse verfügbar macht. Diese Ereignisse werden in Global.asax oder einem HttpHandler oder HttpModule behandelt.
Cache Der ASP.NET Cache-Objekt, das programmgesteuerten Zugriff auf den Cache ermöglicht. Rob Howards spalte "ASP.NET Caching" bietet eine gute Einführung in das Zwischenspeichern.
Fehler Der erste Fehler (sofern vorhanden) beim Verarbeiten der Seite. Weitere Informationen finden Sie unter Robs Ausnahme von der Regel, Teil 1 .
Elemente Eine Schlüssel-Wert-Paarauflistung, die Sie verwenden können, um Informationen zwischen allen Komponenten zu übergeben, die an der Verarbeitung einer einzelnen Anforderung beteiligt sind. Items hat den Typ System.Collections.IDictionary.
Anforderung Informationen zur HTTP-Anforderung, einschließlich Browserinformationen, Cookies und Werten, die in einem Formular oder in der Abfragezeichenfolge übergeben werden. Die Anforderung hat den Typ System.Web.HttpRequest.
Antwort Einstellungen und Inhalte zum Erstellen der HTTP-Antwort. Die Anforderung hat den Typ System.Web.HttpResponse.
Server Server ist eine Hilfsklasse mit mehreren nützlichen Hilfsmethoden, einschließlich Server.Execute(),, Server.MapPath() und Server.HtmlEncode().. Server ist ein Objekt vom Typ System.Web.HttpServerUtility.
Sitzung Eine Schlüssel-Wert-Paarauflistung von Werten, auf die ein einzelner Benutzer der Anwendung zugreifen kann. Die Anwendung hat den Typ System.Web.HttpSessionState.
Ablaufverfolgung Der ASP.NET Trace-Objekt , das Zugriff auf Ablaufverfolgungsfunktionen ermöglicht. Weitere Informationen finden Sie im Artikel Zur Ablaufverfolgung von Rob.
Benutzer Der Sicherheitskontext des aktuellen Benutzers, wenn er authentifiziert ist. Context.User.Identity ist der Name des Benutzers. User ist ein Objekt vom Typ System.Security.Principal.IPrincipal.

Wenn Sie EIN ASP-Entwickler sind, werden einige der oben genannten Objekte ziemlich vertraut aussehen. Es gibt einige Verbesserungen, aber in den meisten Fällen funktionieren sie in ASP.NET genauso wie in ASP.

Kontextgrundlagen

Einige der Objekte in Context werden auch als Objekte der obersten Ebene auf Page heraufgestuft. Beispielsweise verweisen Page.Context.Response und Page.Response auf dasselbe Objekt, sodass der folgende Code gleichwertig ist:

[Visual Basic® Web Form]

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

[C#-Webformular]

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

Sie können auch das Context-Objekt aus Ihren Geschäftsobjekten verwenden. HttpContext.Current ist eine statische Eigenschaft, die bequem den Kontext für die aktuelle Anforderung zurückgibt. Dies ist auf verschiedene Arten nützlich, aber hier ist ein einfaches Beispiel für das Abrufen eines Elements aus dem Cache in Ihrer Business Class:

[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");

Kontext in Aktion

Das Context-Objekt stellt die Antwort für mehrere häufig ASP.NET "How Do I ...?" Fragen bereit. Vielleicht ist der beste Weg, um zu kommunizieren, wie wertvoll dieses Juwel sein kann, es in Aktion zu zeigen. Hier sind einige der besten Kontext-Tricks , die ich kenne.

Wie kann ich eine ASP.NET Ablaufverfolgungsanweisung aus meiner Business Class ausgeben?

Antwort: Einfach! Verwenden Sie HttpContext.Current , um das Context-Objekt abzurufen, und rufen Sie dann Context.Trace.Write() auf.

[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");
        }
    }
}

Wie kann ich über meine Business Class auf einen Sitzungszustandswert zugreifen?

Antwort: Einfach! Verwenden Sie HttpContext.Current , um das Context-Objekt abzurufen, und greifen Sie dann auf Context.Session zu.

[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"];
        }
    }
}

Antwort: Behandeln Sie die BeginRequest - und EndRequest-Ereignisse der Anwendung, und verwenden Sie Context.Response.Write , um den HTML-Code für die Kopf- und Fußzeile ausgibt.

Technisch gesehen können Sie die Anwendungsereignisse wie BeginRequest entweder in einem HttpModule-Modul oder mithilfe von Global.asax behandeln. HttpModule sind etwas schwieriger zu schreiben und werden in der Regel nicht für Funktionen verwendet, die von einer einzelnen Anwendung verwendet werden, wie in diesem Beispiel. Daher verwenden wir stattdessen die anwendungsbezogene Datei Global.asax.

Wie bei einer ASP-Seite werden einige der intrinsischen ASP.NET Kontexts zu Eigenschaften der HttpApplication-Klasse heraufgestuft, von der die Klasse, die Global.asax darstellt, erbt. Wir müssen httpContext.Current nicht verwenden, um einen Verweis auf das Context-Objekt abzurufen. Es ist bereits in Global.asax verfügbar.

In diesem Beispiel lege ich die <html> Tags und <body> sowie eine horizontale Regel in den Kopfzeilenabschnitt und eine weitere horizontale Regel plus die Endtags für diese in den Fußzeilenabschnitt ein. Die Fußzeile enthält auch eine Urheberrechtsmeldung. Das Ergebnis sieht wie in der folgenden Abbildung aus:

Abbildung 1. Beispiel für standard-Kopf- und Fußzeilen, wie sie im Browser gerendert werden

Dies ist ein triviales Beispiel, aber Sie können es einfach erweitern, um Ihren Standardheader und die Standardnavigation einzuschließen, oder einfach die <-- #include ---> -Anweisungen für diese ausgeben. Eine Einschränkung: Wenn Sie möchten, dass die Kopf- oder Fußzeile interaktive Inhalte enthält, sollten Sie stattdessen ASP.NET Benutzersteuerelemente verwenden.

[SomePage.aspx-Quelle – Beispielinhalt]

<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>

Wie kann ich eine Willkommensnachricht anzeigen, wenn der Benutzer authentifiziert ist?

Die Antwort: Testen Sie das Benutzerkontextobjekt , um festzustellen, ob der Benutzer authentifiziert ist. Wenn dies der Fall ist, rufen Sie auch den Namen des Benutzers aus dem User-Objekt ab. Dies ist natürlich das Beispiel vom Anfang des Artikels.

[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>

Und jetzt für etwas wirklich Wunderbares: Context.Items

Ich hoffe, die obigen Beispiele zeigen, wie viel einfacher es ist, Ihre Webanwendung mit ein wenig Kontextinformationen zur Hand zu schreiben. Wäre es nicht großartig, auf die gleiche Weise auf einen Kontext zugreifen zu können, der für Ihre Anwendung einzigartig ist?

Dies ist der Zweck der Context.Items-Auflistung . Es enthält die anforderungsspezifischen Werte Ihrer Anwendung auf eine Weise, die für jeden Teil Ihres Codes verfügbar ist, der an der Verarbeitung einer Anforderung beteiligt ist. Beispielsweise können die gleichen Informationen in Global.asax, auf Ihrer ASPX-Seite, in den Benutzersteuerelementen auf der Seite und von der Geschäftslogik verwendet werden, die die Seite aufruft.

Betrachten Sie die IBuySpy Portal-Beispielanwendung . Es verwendet eine einzelne Standard Seite – DesktopDefault.aspx – zum Anzeigen von Portalinhalten. Welcher Inhalt angezeigt wird, hängt davon ab, welche Registerkarte ausgewählt ist, sowie von den Rollen des Benutzers, falls er authentifiziert wird.

Abbildung 2. IbuySpy-Homepage

Die Abfragezeichenfolge enthält die Parameter TabIndex und TabId für die angeforderte Registerkarte. Diese Informationen werden während der verarbeitung der Anforderung verwendet, um zu filtern, welche Daten dem Benutzer angezeigt werden. http://www.ibuyspy.com/portal/DesktopDefault.aspx?tabindex=1&tabid=2

Um einen Querystring-Wert zu verwenden, müssen Sie zunächst sicherstellen, dass es sich um einen gültigen Wert handelt, und, falls nicht, eine kleine Fehlerbehandlung durchführen. Es handelt sich nicht um viel Code, aber möchten Sie ihn wirklich auf jeder Seite und Komponente duplizieren, die den Wert verwendet? Keinesfalls! Im Portalbeispiel ist dies noch wichtiger, da es andere Informationen gibt, die vorab geladen werden können, sobald wir die TabId kennen.

Das Portal verwendet die querystring-Werte als Parameter, um ein neues "PortalSettings"-Objekt zu erstellen und es context.Items im BeginRequest-Ereignis in Global.asax hinzuzufügen. Da die begin-Anforderung am Anfang jeder Anforderung ausgeführt wird, werden die tabulatorbezogenen Werte für alle Seiten und Komponenten in der Anwendung verfügbar. Wenn die Anforderung abgeschlossen ist, wird das Objekt automatisch verworfen – sehr ordentlich!

[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));
}

Das Benutzersteuerelement DesktopPortalBanner.ascx ruft das PortalSetting-Objekt aus Context ab, um auf den Namen und die Sicherheitseinstellungen des Portals zuzugreifen. In der Tat ist dieses eine Modul ein großartiges Rundum-Beispiel für Kontext in Aktion. Zur Veranschaulichung habe ich den Code etwas vereinfacht und alle Stellen markiert, an denen entweder HTTP oder anwendungsspezifischer Kontext fett angezeigt wird.

[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>

Sie können die vollständige Quelle für das IBuySpy-Portal online in Visual Basic und C# unter http://www.ibuyspy.comdurchsuchen und ausführen oder sie herunterladen und selbst ausführen.

Zusammenfassung

Kontext ist eine weitere dieser "guten Dinge werden noch besser in ASP.NET"-Features. Es erweitert die bereits hervorragende Kontextunterstützung von ASP, um beide Hooks in die neuen Laufzeitfeatures von ASP.NET hinzuzufügen. Außerdem wird Context.Items als neuer Zustandsmechanismus für sehr kurzlebige Werte hinzugefügt. Aber der ultimative Vorteil für Sie als Entwickler ist kompakter, einfacher zu verwalten von Code, und das ist ein Kontext, den wir alle hinter uns haben können.