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, aber auch dieses kleine Teil der Web-UI erfordert einige Bits von Informationen, die jedes Mal variieren, wenn die Seite angefordert wird. Ich muss wissen:

  1. Ist der Benutzer angemeldet?
  2. Was ist der Anzeigename des Benutzers?

Im Allgemeinen wird der eindeutige Kontext jedesMal angefordert, wenn die Seite angefordert wird? Und wie kann ich meinen Code schreiben, damit diese Informationen berücksichtigt werden?

Tatsächlich gibt es aufgrund der zustandslosen Natur von HTTP viele verschiedene Kontextelemente, die eine Webanwendung möglicherweise nachverfolgen muss. Wenn ein Benutzer mit einer Webanwendung interagiert, sendet der Browser eine Reihe von unabhängigen HTTP-Anforderungen an den Webserver. Die Anwendung selbst muss die Arbeit der Strickung dieser Anforderungen in eine ansprechende Erfahrung für den Benutzer durchführen und wissen, dass der Kontext der Anforderung kritisch ist.

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 ein äußerst praktisches systeminternes Objekt namens Context.

Context ist ein Objekt vom Typ System.Web.HttpContext. Sie wird als Eigenschaft der ASP.NET Page-Klasse verfügbar gemacht. Es ist auch über Benutzersteuerelemente und Ihre Geschäftsobjekte verfügbar (mehr dazu später). Nachfolgend finden Sie eine partielle Liste der Objekte, die von HttpContext bereitgestellt werden:

Object BESCHREIBUNG
Anwendung Eine Schlüssel-Wert-Paarsammlung von Werten, auf die jeder Benutzer der Anwendung zugreifen kann. Die Anwendung ist vom Typ System.Web.HttpApplicationState.
ApplicationInstance Die tatsächliche ausgeführte Anwendung, die einige Anforderungsverarbeitungsereignisse verfügbar macht. Diese Ereignisse werden in Global.asax oder einem HttpHandler oder HttpModule behandelt.
Cache Das ASP.NET Cacheobjekt, das programmgesteuerten Zugriff auf den Cache bereitstellt. Rob Howards ASP.NET Cachespalte bietet eine gute Einführung in das Zwischenspeichern.
Fehler Der erste Fehler (falls vorhanden) beim Verarbeiten der Seite. Weitere Informationen finden Sie unter Robs Ausnahme zur Regel, Teil 1 .
Elemente Eine Schlüsselwertpaarsammlung, die Sie zum Übergeben von Informationen zwischen allen Komponenten verwenden können, die an der Verarbeitung einer einzelnen Anforderung teilnehmen. Elemente sind vom Typ System.Collections.IDictionary.
Anforderung Informationen zur HTTP-Anforderung, einschließlich Browserinformationen, Cookies und Werten, die in einem Formular oder in der Abfragezeichenfolge übergeben wurden. Die Anforderung ist vom Typ System.Web.HttpRequest.
Antwort Einstellungen und Inhalte zum Erstellen der HTTP-Antwort. Die Anforderung ist vom 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.
Sitzungskonsistenz Eine Schlüssel-Wert-Paarsammlung von Werten, auf die von einem einzelnen Benutzer der Anwendung zugegriffen werden kann. Die Anwendung ist vom Typ System.Web.HttpSessionState.
Ablaufverfolgung Das ASP.NET Trace-Objekt, das Zugriff auf Ablaufverfolgungsfunktionen bietet. Weitere Informationen finden Sie im Artikel zur Ablaufverfolgung von Rob.
Benutzer Der Sicherheitskontext des aktuellen Benutzers, falls authentifiziert. Context.User.Identity ist der Name des Benutzers. Der Benutzer ist ein Objekt vom Typ System.Security.Principal.IPrincipal.

Wenn Sie EIN ASP-Entwickler sind, sehen einige der oben genannten Objekte ziemlich vertraut aus. Es gibt einige Verbesserungen, aber für den meisten Teil funktionieren sie genau so in ASP.NET wie in ASP.

Grundlagen des Kontexts

Einige der Objekte im Kontext werden auch als Objekte auf oberster Ebene auf Seite höhergestuft. Beispielsweise verweisen Page.Context.Response und Page.Response auf dasselbe Objekt, sodass der folgende Code gleichwertig ist:

[Visual Basic ® Webformular]

   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 den Kontext für die aktuelle Anforderung bequem 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-Klasse:

[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 auf mehrere gängige ASP.NET "How Do I ...?" Fragen bereit. Vielleicht die beste Möglichkeit, genau zu kommunizieren, wie wertvoll dieses Gem sein kann, um es in Aktion zu zeigen. Hier sind einige der besten Kontext-Tricks , die ich weiß.

Wie emittiere ich eine ASP.NET Trace-Anweisung aus meiner Business Class?

Antwort: Einfach! Verwenden Sie HttpContext.Current, um das Context-Objekt abzurufen, und rufen Sie "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 auf einen Sitzungsstatuswert aus meiner Business-Klasse 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 auszugeben.

Technisch können Sie die Anwendungsereignisse wie BeginRequest in einem HttpModule oder mithilfe von Global.asax behandeln. HttpModules 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 Datei "Global.asax" im Anwendungsbereich.

Wie bei einer ASP-Seite werden mehrere ASP.NET kontextinterne Eigenschaften der HttpApplication-Klasse höhergestuft, 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 stelle ich die <html> Und-Tags <body> sowie eine horizontale Regel in den Kopfzeilenabschnitt und eine andere horizontale Regel sowie die Endtags für diese in den Fußzeilenabschnitt ein. Die Fußzeile enthält auch eine Copyrightnachricht. Das Ergebnis sieht wie folgt aus:

Abbildung 1. Beispiel für Standardheader und Fußzeilen wie im Browser gerendert

Dies ist ein triviales Beispiel, aber Sie können dies ganz einfach erweitern, um Ihre Standardkopfzeile und -navigation einzuschließen oder einfach die <!-- #include ---> Anweisungen für diese auszuzugeben. Ein Vorbehalt– wenn die Kopf- oder Fußzeile interaktive Inhalte enthalten soll, 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. Rufen Sie ggf. 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 einem kleinen Kontextinformationen zur Hand zu schreiben. Wäre es nicht großartig, auf einen Kontext zuzugreifen, der auf dieselbe Weise für Ihre Anwendung einzigartig ist?

Das 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 teilnimmt. Beispielsweise können die gleichen Informationen in Global.asax, auf Ihrer ASPX-Seite, in den Benutzersteuerelementen auf der Seite und durch die Geschäftslogik der Seitenaufrufe verwendet werden.

Betrachten Sie die IBuySpy Portal-Beispielanwendung . Es verwendet eine einzelne Hauptseite – DesktopDefault.aspx – zum Anzeigen von Portalinhalten. Welche Inhalte angezeigt werden, hängt davon ab, welche Registerkarte ausgewählt ist, sowie die Rollen des Benutzers, falls authentifiziert.

Abbildung 2. IbuySpy Homepage

Die Abfragezeichenfolge enthält die Parameter TabIndex und TabId für die angeforderte Registerkarte. Diese Informationen werden während der gesamten 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 Abfragezeichenfolgenwert zu verwenden, müssen Sie zuerst sicherstellen, dass es sich um einen gültigen Wert handelt, und führen Sie ggf. eine kleine Fehlerbehandlung aus. Es ist nicht viel Code, aber möchten Sie es wirklich in jeder Seite und Komponente duplizieren, die den Wert verwendet? Keinesfalls! Im Portalbeispiel ist es noch mehr beteiligt, da andere Informationen vorhanden sind, die vorgeladen werden können, sobald wir die TabId kennen.

Das Portal verwendet die Abfragezeichenfolgenwerte als Parameter, um ein neues "PortalSettings"-Objekt zu erstellen und es in "Context.Items " im BeginRequest-Ereignis in Global.asax hinzuzufügen. Da die Anfangsanforderung am Anfang jeder Anforderung ausgeführt wird, stellt dies die registerkartenbezogenen Werte für alle Seiten und Komponenten in der Anwendung zur Verfügung. 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 ein Modul ein großartiges All-Around-Beispiel für Kontext in Aktion. Um den Punkt zu veranschaulichen, habe ich den Code etwas vereinfacht und alle Stellen entweder HTTP oder anwendungsspezifischen Kontext fett formatiert.

[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# http://www.ibuyspy.comunter , oder laden Sie sie herunter, und führen Sie sie selbst aus.

Zusammenfassung

Kontext ist eine der "guten Dinge werden noch besser in ASP.NET"-Features. Es erweitert die bereits großartige Kontextunterstützung von ASP, um beide Hooks zu den neuen Laufzeitfeatures von ASP.NET hinzuzufügen. Darüber hinaus 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, und das ist ein Kontext, den wir alle zurückholen können.