關於上下文

關於上下文


Susan Warren
Microsoft Corporation
2002年1月14日

編寫 Web 應用程序時最常見的問題之一,是要讓代碼知道它的執行上下文。讓我們通過一個簡單的例子(即個性化頁面)來說明這個問題:

     請登錄。

     歡迎 Susan!

雖然看起來很簡單,但即使是這一小段 Web UI,仍然需要好幾段信息,而且每次請求該頁時這些信息都會發生變化。我們需要知道以下內容:

  1. 用戶登錄了嗎?
  2. 用戶的顯示名是什麼?

更通常的問題是,每次請求該頁時,唯一的上下文是什麼?以及如何編寫代碼以便能考慮到此信息?

事實上,由於 HTTP 的無狀態特性,Web 應用程序可能需要跟蹤許多不同的上下文片段。當用戶與 Web 應用程序交互時,瀏覽器將一系列獨立的 HTTP 請求發送到 Web 服務器。應用程序自身必須將這些請求組織成令用戶感到愉悅的體驗;同時,知道請求的上下文也十分關鍵。

ASP 引入了幾個內部對象,如 RequestApplication,以便幫助跟蹤 HTTP 請求的上下文。ASP.NET 完成下一步驟,並將這些對象以及其他幾個與上下文有關的對象捆綁在一起,形成一個極爲方便的內部對象 Context

Context System.Web.HttpContext(英文)類型的對象。它作爲 ASP.NET Page 類的屬性公開。也可以通過用戶控件和業務對象(下文中詳細介紹)獲得該對象。以下是 HttpContext 形成的對象的部分列表:

對象 說明
Application 值的關鍵字/值對集合,可由應用程序的每個用戶訪問。Application 是 System.Web.HttpApplicationState 類型。
ApplicationInstance 實際運行的應用程序,它公開一些請求處理事件。這些事件在 Global.asax、HttpHandler 或 HttpModule 中處理。
Cache ASP.NET Cache 對象,它提供對緩存的編程訪問。Rob Howard 的 ASP.NET Caching 專欄(英文)對緩存作了詳盡介紹。
Error 處理頁時遇到的第一個錯誤(如果有)。有關詳細信息,請參閱 Rob 撰寫的 Exception to the Rule, Part 1(英文)。
Items 關鍵字/值對集合,可以用來在參與處理同一請求的所有組件之間傳遞信息。Items 是 System.Collections.IDictionary 類型。
Request 有關 HTTP 請求的信息,包括瀏覽器信息、Cookies 以及在窗體或查詢字符串中傳遞的值。Request 是 System.Web.HttpRequest 類型。
Response 用於創建 HTTP 響應的設置和內容。Response 是 System.Web.HttpResponse 類型。
Server 服務器是一個實用程序類,帶有一些有用的幫助器方法,包括 Server.Execute()Server.MapPath()Server.HtmlEncode()。Server 是 System.Web.HttpServerUtility 類型的對象。
Session 值的關鍵字/值對集合,可由應用程序的單個用戶訪問。Session 是 System.Web.HttpSessionState 類型。
Trace ASP.NET 的 Trace 對象,提供對跟蹤功能的訪問。有關詳細信息,請參閱 Rob 撰寫的文章 Tracing(英文)。
User 當前用戶(如果已經過身份驗證)的安全上下文。Context.User.Identity 是用戶的名稱。User 是 System.Security.Principle.IPrincipal 類型的對象。

如果您是一位 ASP 開發人員,那麼對上面講述的部分對象應不會感到陌生。雖然有一些改進,但大體而言,它們在 ASP.NET 中的作用與在 ASP 中是完全一樣的。

Context 基礎知識

Context 中的部分對象也已升級爲 Page 中的頂級對象。例如,Page.Context.ResponsePage.Response 指的是同一個對象,因此,以下代碼是等價的:

[Visual Basic® Web 窗體]

   Response.Write ("您好")
   Context.Response.Write ("你好")

[C# Web 窗體]

   Response.Write ("您好");
   Context.Response.Write ("你好");

還可以從業務對象使用 Context 對象。HttpContext.Current 是靜態屬性,可以很方便地返回當前請求的上下文。這在各種方法中都十分有用,下面僅列舉一個從業務類的緩存中檢索項目的簡單示例:

[Visual Basic]

      ' 獲取請求上下文
      Dim _context As HttpContext = HttpContext.Current

   ' 獲取緩存中的數據集
   Dim _data As DataSet = _context.Cache("MyDataSet")

[C#]

      // 獲取請求上下文
      HttpContext _context = HttpContext.Current;

   // 獲取緩存中的數據集
   DataSet _data = _context.Cache("MyDataSet");

操作中的 Context

Context 對象爲一些常見的 ASP.NET“如何…?”問題提供了答案。也許,說明此寶貴對象的價值的最好方法,就是在操作中將它展示出來。下面是一些我所知道的最巧妙的 Context 技巧。

我如何從自己的業務類中生成 ASP.NET 跟蹤語句?

回答:很簡單!使用 HttpContext.Current 獲取 Context 對象,然後調用 Context.Trace.Write()

[Visual Basic]

Imports System
Imports System.Web

Namespace Context

   ' 演示從業務類中生成一個 ASP.NET 
   ' 跟蹤語句。

   Public Class TraceEmit
      
      Public Sub SomeMethod()
         
         ' 獲取請求上下文
         Dim _context As HttpContext = HttpContext.Current
         
         ' 使用上下文編寫跟蹤語句
         _context.Trace.Write("在 TraceEmit.SomeMethod 中")

      End Sub

   End Class

End Namespace   

[C#]

using System;
using System.Web;

namespace Context
{
   // 演示從業務類中生成一個 ASP.NET 
   // 跟蹤語句。

   public class TraceEmit
   {

        public void SomeMethod() {
        
            // 獲取請求上下文
            HttpContext _context = HttpContext.Current;

            // 使用上下文編寫跟蹤語句
            _context.Trace.Write("在 TraceEmit.SomeMethod 中");
        }
    }
}

如何才能從業務類中訪問會話狀態值?

回答:很簡單!使用 HttpContext.Current 獲取 Context 對象,然後訪問 Context.Session

[Visual Basic]

Imports System
Imports System.Web

Namespace Context

   ' 演示從業務類中訪問 ASP.NET 內部
   ' 會話。

   Public Class UseSession
   
      Public Sub SomeMethod()
         
         ' 獲取請求上下文
         Dim _context As HttpContext = HttpContext.Current
         
         ' 訪問內部會話
         Dim _value As Object = _context.Session("TheValue")

      End Sub

   End Class

End Namespace

[C#]

using System;
using System.Web;

namespace Context
{
   // 演示從業務類中訪問 ASP.NET 內部 
   // 會話

   public class UseSession
   {

        public void SomeMethod() {
        
            // 獲取請求上下文
            HttpContext _context = HttpContext.Current;

            // 訪問內部會話
            object _value = _context.Session["TheValue"];
        }
    }
}

如何才能在應用程序的每頁中添加標準頁眉和頁腳?

回答:處理應用程序的 BeginRequestEndRequest 事件,並使用 Context.Response.Write 生成頁眉和頁腳的 HTML。

從技術上講,可以在 HttpModule 中或通過使用 Global.asax 處理 BeginRequest 這樣的應用程序。HttpModules 的編寫比較困難,而且正如本例所示,簡單應用程序使用的功能通常不使用它。因此,我們使用應用程序範圍的 Global.asax 文件。

與 ASP 頁一樣,一些固有的 ASP.NET 上下文已提升爲 HttpApplication 類的屬性,其中的類表示 Global.asax 繼承類。我們不需要使用 HttpContext.Current 獲取對 Context 對象的引用;它在 Global.asax. 中已可用。

本例中,我將 <html><body> 標記以及一條水平線放入頁眉部分,而將另一條水平線及相應的結束標記放入頁腳部分。頁腳還包含版權消息。運行結果應如下圖所示:

圖 1:瀏覽器中呈現的標準頁眉和頁腳示例

這是一個簡單的示例,但您可以很容易地將它擴展,使其包含標準的頁眉與導航,或者僅輸出相應的 <!-- #include ---> 語句。請注意,如果希望頁眉或頁腳包含交互內容,應考慮使用 ASP.NET 用戶控件。

[SomePage.aspx 源代碼 - 內容示例]

<FONT face="Arial" color="#cc66cc" size="5">
常規頁面內容
</FONT>

[Visual Basic Global.asax]

<%@ Application Language="VB" %>

<script runat="server">

      Sub Application_BeginRequest(sender As Object, e As EventArgs)

         ' 生成頁眉
         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)

         ' 生成頁腳
         Context.Response.Write("<hr>" + ControlChars.Lf + _
      "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) {

            // 生成頁眉
            Context.Response.Write("<html>/n<body bgcolor=#efefef>/n<hr>/n");
        }

        void Application_EndRequest(Object sender, EventArgs e) {

            // 生成頁腳
            Context.Response.Write("<hr>/2002 Microsoft Corporation 版權所有/n");
            Context.Response.Write("</body>/n</html>");
        }

</script>

如何在用戶經過身份驗證後顯示歡迎信息?

回答:測試 User 上下文對象以查看用戶是否經過身份驗證。如果是,還要從 User 對象獲取用戶名。當然,這是本文開頭的示例。

[Visual Basic]

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

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

        If User.Identity.IsAuthenticated Then
            welcome.Text = "歡迎" + User.Identity.Name
        Else
            ' 尚未登錄,添加一個指向登錄頁的鏈接
            welcome.Text = "請登錄!"
            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 = "歡迎" + User.Identity.Name;
        }
        else {
            // 尚未登錄,添加一個指向登錄頁的鏈接
            welcome.Text = "請登錄!";
            welcome.NavigateUrl = "signin.aspx";
        }
    }

</script>

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

Context.Items 簡介

希望以上示例可以說明,使用手頭僅有的上下文信息編寫 Web 應用程序是多麼容易。那麼,如果可以用同樣的方法訪問您應用程序獨有的一些上下文,不是很好嗎?

這就是 Context.Items 集合的用途。它使用在參與處理請求的各部分代碼中都可用的方法,保存應用程序的請求特有值。例如,同樣一條信息可以用在 Global.asax、ASPX 頁、頁內的用戶控件中,也可以由頁調用的業務邏輯使用。

請考慮 IBuySpy Portal(英文)應用程序示例。它使用一個簡單的主頁 DesktopDefault.aspx 來顯示門戶內容。顯示的內容取決於所選擇的選項卡,以及用戶(如果已經過身份驗證)角色。

圖 2:IbuySpy 主頁

查詢字符串包含正被請求的選項卡的 TabIndedx 和 TabId 參數。在處理請求的整個過程中,一直使用此信息篩選要顯示給用戶的數據。http://www.ibuyspyportal.com/DesktopDefault.aspx?tabindex=1&tabid=2(英文)

要使用查詢字符串值,需要首先確保它是一個有效值,如果不是,則要進行一些錯誤處理。它並不是一大串代碼,但是您真的要在每個使用該值的頁和組件中複製它嗎?當然不!在 Portal 示例中,甚至更多的地方都涉及到它,因爲一旦我們知道了 TabId,就可以預先加載其他信息。

Portal 使用查詢字符串值作爲參數,以構造一個新的 PortalSettings 對象,並將它添加到 Global.asax 的 BeginRequest 事件的 Context.Items 中。由於在每個請求開始處都執行了開始請求,這使得與該選項卡有關的值在應用程序的所有頁和組件中都可用。請求完成後,對象將被自動丟棄 - 非常整齊!

[Visual Basic Global.asax]

      Sub Application_BeginRequest(sender As [Object], e As EventArgs)
         
         Dim tabIndex As Integer = 0
         Dim tabId As Integer = 0
         
         ' 從查詢字符串獲取 TabIndex
         If Not (Request.Params("tabindex") Is Nothing) Then
            tabIndex = Int32.Parse(Request.Params("tabindex"))
         End If
         
         ' 從查詢字符串獲取 TabID
         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;

    // 從查詢字符串獲取 TabIndex

    if (Request.Params["tabindex"] != null) {               
        tabIndex = Int32.Parse(Request.Params["tabindex"]);
    }
                
    // 從查詢字符串獲取 TabID

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

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

DesktopPortalBanner.ascx 用戶控件從 Context 請求 PortalSetting 的對象,以訪問 Portal 的名稱和安全設置。事實上,此模塊是操作中的 Context 的一個典型綜合示例。爲闡明這一點,我已將代碼進行了一些簡化,並用粗體標記了 HTTP 或應用程序特定的 Context 被訪問過的所有地方。

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

        // 從當前上下文獲取 PortalSettings
  PortalSettings portalSettings = 
(PortalSettings) Context.Items["PortalSettings"];

        // 動態填充門戶站點名稱
        siteName.Text = portalSettings.PortalName;

        // 如果用戶已登錄,自定義歡迎信息
        if (Request.IsAuthenticated == true) {
        
            WelcomeMessage.Text = "歡迎" + 
Context.User.Identity.Name + "!<" + 
"span class=Accent" + ">|<" + "/span" + ">";

            // 如果身份驗證模式爲 Cookie,則提供一個註銷鏈接
            if (Context.User.Identity.AuthenticationType == "Forms") {
                LogoffLink = "<" + "span class=/"Accent/">|</span>/n" + 
"<a href=" + Request.ApplicationPath + 
"/Admin/Logoff.aspx class=SiteLink> 註銷" + 
"</a>";
            }
        }

        // 動態顯示門戶選項卡條
        if (ShowTabs == true) {

            tabIndex = portalSettings.ActiveTab.TabIndex;

            // 生成要向用戶顯示的選項卡列表                                   
            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++;
            }          

            // 用已授權的選項卡填充頁頂部的選項卡
// 列表
            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 主頁</a>
<span class="Accent"> |</span> 
<a href="<%= Request.ApplicationPath %>/Docs/Docs.htm">
                Portal 文檔</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>

您可以使用 Visual Basic 和 C# 在 http://www.ibuyspy.com(英文)聯機瀏覽並運行 IBuySpy Portal 的完整源文件,或者下載後再運行。

小結

Context 是 ASP.NET 中的又一個“精益求精”的功能。它擴展了 ASP 的已經很不錯的上下文支持,以便將兩個掛鉤添加到 ASP.NET 的新運行時功能中。同時添加了 Context.Items,作爲短期值的新狀態機制。但對於開發人員,此功能的最大好處是使代碼更緊湊,且易於維護,而且此上下文我們都能看懂。

發佈了25 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章