susan warren
microsoft corporation
編寫 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()。
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
using system;
using system.web;
{
// 示範從業務類中生成一個 asp.net
// 跟蹤語句。
{
public void somemethod() {
// 擷取請求上下文
httpcontext _context = httpcontext.current;
// 使用上下文編寫跟蹤語句
_context.trace.write("在 traceemit.somemethod 中");
}
}
}
如何才能從業務類中通路會話狀态值?
回答:很簡單!使用 httpcontext.current 擷取 context 對象,然後通路 context.session。
' 示範從業務類中通路 asp.net 内部
' 會話。
public class usesession
' 通路内部會話
dim _value as object = _context.session("thevalue")
end namespace
// 示範從業務類中通路 asp.net 内部
// 會話
// 通路内部會話
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)
sub application_endrequest(sender as object, e as eventargs)
' 生成頁腳
context.response.write("<hr>" + controlchars.lf + _
"2002 microsoft corporation 版權所有" + _
controlchars.lf + "</body>" + controlchars.lf + "</html>")
</script>
[c# global.asax]
<%@ application language="c#" %>
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>");
如何在使用者經過身份驗證後顯示歡迎資訊?
回答:測試 user 上下文對象以檢視使用者是否經過身份驗證。如果是,還要從 user 對象擷取使用者名。當然,這是本文開頭的示例。
<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
<asp:hyperlink id="welcome" runat="server" maintainstate="false">
</asp:hyperlink>
<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";
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 中。由于在每個請求開始處都執行了開始請求,這使得與該頁籤有關的值在應用程式的所有頁群組件中都可用。請求完成後,對象将被自動丢棄 - 非常整齊!
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"))
context.items.add("portalsettings", _
new portalsettings(tabindex, tabid))
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" %>
public int tabindex;
public bool showtabs = true;
protected string logofflink = "";
// 從目前上下文擷取 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();
<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 height="40">
<asp:label id="sitename" runat="server" />
<td align="center" rowspan="2">
<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">
</span>
</selecteditemtemplate>
</asp:datalist>
</table>
您可以使用 visual basic 和 c# 在 http://www.ibuyspy.com(英文)聯機浏覽并運作 ibuyspy portal 的完整源檔案,或者下載下傳後再運作。
小結
context 是 asp.net 中的又一個“精益求精”的功能。它擴充了 asp 的已經很不錯的上下文支援,以便将兩個挂鈎添加到 asp.net 的新運作時功能中。同時添加了 context.items,作為短期值的新狀态機制。但對于開發人員,此功能的最大好處是使代碼更緊湊,且易于維護,而且此上下文我們都能看懂。