關於上下文
Susan Warren
Microsoft Corporation
2002年1月14日
編寫 Web 應用程序時最常見的問題之一,是要讓代碼知道它的執行上下文。讓我們通過一個簡單的例子(即個性化頁面)來說明這個問題:
請登錄。
與
歡迎 Susan!
雖然看起來很簡單,但即使是這一小段 Web UI,仍然需要好幾段信息,而且每次請求該頁時這些信息都會發生變化。我們需要知道以下內容:
- 用戶登錄了嗎?
- 用戶的顯示名是什麼?
更通常的問題是,每次請求該頁時,唯一的上下文是什麼?以及如何編寫代碼以便能考慮到此信息?
事實上,由於 HTTP 的無狀態特性,Web 應用程序可能需要跟蹤許多不同的上下文片段。當用戶與 Web 應用程序交互時,瀏覽器將一系列獨立的 HTTP 請求發送到 Web 服務器。應用程序自身必須將這些請求組織成令用戶感到愉悅的體驗;同時,知道請求的上下文也十分關鍵。
ASP 引入了幾個內部對象,如 Request 和 Application,以便幫助跟蹤 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.Response 和 Page.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"]; } } }
如何才能在應用程序的每頁中添加標準頁眉和頁腳?
回答:處理應用程序的 BeginRequest 和 EndRequest 事件,並使用 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 %>
</td>
</tr>
<tr>
<td width="10" rowspan="2">
</td>
<td height="40">
<asp:label id="siteName" runat="server" />
</td>
<td align="center" rowspan="2">
</td>
</tr>
<tr>
<td>
<asp:datalist id="tabs" runat="server">
<ItemTemplate>
<a href='<%= Request.ApplicationPath %>
/DesktopDefault.aspx?tabindex=<%# Container.ItemIndex %>&tabid=
<%# ((TabStripDetails) Container.DataItem).TabId %>'>
<%# ((TabStripDetails) Container.DataItem).TabName %>
</a>
</ItemTemplate>
<SelectedItemTemplate>
<span class="SelectedTab">
<%# ((TabStripDetails) Container.DataItem).TabName %>
</span>
</SelectedItemTemplate>
</asp:datalist>
</td>
</tr>
</table>
您可以使用 Visual Basic 和 C# 在 http://www.ibuyspy.com(英文)聯機瀏覽並運行 IBuySpy Portal 的完整源文件,或者下載後再運行。
小結
Context 是 ASP.NET 中的又一個“精益求精”的功能。它擴展了 ASP 的已經很不錯的上下文支持,以便將兩個掛鉤添加到 ASP.NET 的新運行時功能中。同時添加了 Context.Items,作爲短期值的新狀態機制。但對於開發人員,此功能的最大好處是使代碼更緊湊,且易於維護,而且此上下文我們都能看懂。