摘要:ASP.NET為保持用戶請求之間的數據提供了多種不同的途徑。你可以使用Application對象、cookie、hidden fIElds、Sessions或Cache對象,以及它們的大量的方法。決定什麼時候使用它們有時很困難。本文將介紹了上述的技術,給出了什麼時候使用它們的一些指導。盡管這些技術中有些在傳統ASP中已經存在,但是有了.NET框架組件後該在什麼時候使用它們發生了變化。為了在ASP.Net中保持數據,你需要調整從先前的ASP中處理狀態中學習到的知識。
隨著Web時代的到來,在無狀態的HTTP世界中管理狀態成為web開發者的一個大問題。最近出現了幾種存儲和檢索數據的不同技術。本文我將解釋ASP.Net開發者能怎樣通過頁面請求維護或傳遞狀態。
在ASP.Net中,有幾種保持用戶請求間數據的途徑--實際上太多了,使沒有經驗的開發者對在哪個特定的環境下使用哪個對象很困惑。為了回答這個問題,需要考慮下面三個條件:
.誰需要數據?
.數據需要保持多長時間?
.數據集有多大?
通過回答這些問題,你能決定哪個對象為保持ASP.NET應用程序請求間數據提供了最佳的解決方案。圖1列出了不同的狀態管理對象並描述了什麼時候使用它們。ASP.NET中添加了四個新的對象:Cache、Context、VIEwState和Web.Config文件。ASP.Net也支持傳統的ASP對象,包括Application、 CookIE、有隱藏字段的 Form Post 、 QueryString和Sessions。注意這五個數據容器的正確使用方法發生了改變,因此有經驗的程序員在考慮這些熟悉的對象時也許需要學習一些知識。
表1. ASP.Net中的數據容器對象
Application
讓我們通過回答上面的狀態問題判定條件來說明該對象。誰需要數據?所有的用戶需要訪問它。需要保持數據多長時間?永久保持,或在應用程序生存期中保持。數據多大?可以是任何大小--在任何給定的時刻只有數據的一個副本存在。
在傳統ASP中,Application對象提供了一個保存頻繁使用但很少改變的數據片的位置,例如菜單內容和參考數據。盡管在ASP.Net 中Application依然作為數據容器存在,但是有其它一些更適合以前保存在傳統ASP應用程序的Application集合中的數據的對象。
在傳統的ASP中,如果被保存的數據在應用程序的生存期中根本不會改變(或很少改變,例如只讀數據和大多數情況下是讀操作的數據),Application對象是理想的選擇。連接字符串就是保存在Application變量中的一個最普通的數據片,但是在ASP.Net中類似的配置數據最好保存在Web.config文件中。如果使用Application對象一個需要考慮的問題是任何寫操作要麼在Application_OnStart事件(global.asax)中,要麼在Application.Lock部分中完成。盡管使用Application.Lock來確保寫操作正確地執行是必要的,但是它串行化了對Application對象的請求,而這對於應用程序來說是個嚴重的性能瓶頸。圖2演示了怎樣使用Application對象,它包括一個Web窗體和它的代碼文件。
Application.ASPx
<form id="Application" method="post" runat="server">
<ASP:validationsummary id="valSummary" Runat="server">
</ASP:validationsummary>
<table>
<tr>
<td colSpan="3">Set Application Variable:</td>
</tr>
<tr>
<td>Name</td>
<td><asp:textbox id="txtName" Runat="server"></ASP:textbox>
</td>
<td><ASP:requiredfIEldvalidator id="nameRequired"
runat="server" Display="Dynamic" ErrorMessage="Name is
required." ControlToValidate="txtName">*
</ASP:requiredfIEldvalidator></td>
</tr>
<tr>
<td>Value</td>
<td><ASP:textbox id="txtValue" Runat="server">
</ASP:textbox></td>
<td><ASP:requiredfIEldvalidator id="valueRequired"
Runat="server" Display="Dynamic" ErrorMessage="Value is
required." ControlToValidate="txtValue">*
</ASP:requiredfIEldvalidator></td>
</tr>
<tr>
<td colSpan="3"><ASP:button id="btnSubmit" Runat="server"
Text="Update Value"></ASP:button></td>
</tr>
</table>
<ASP:Label ID="lblResult" Runat="server" />
</form>
Application.ASPx.cs
private void btnSubmit_Click(object sender, System.EventArgs e)
{
if(IsValid)
{
Application.Lock();
Application[txtName.Text] = txtValue.Text;
Application.UnLock();
lblResult.Text = "The value of <b>" + txtName.Text +
"</b> in the Application object is <b>" +
Application[txtName.Text].ToString() + "</b>";
}
}
代碼段1.在ASP.Net中訪問Application對象
它的輸出如下圖所示:
圖1. Application對象的內容
注意圖3中Application對象的內容是追蹤輸出的顯示。追蹤是個偉大的調試工具,但是在某個點,被打開的有追蹤的頁面可能出現在產品環境中。如果出現這種情況,你肯定不希望顯示敏感的信息。這就是為什麼Application對象從來不是推薦的存放敏感信息(例如連接字符串)的位置的主要原因之一。
CookIEs
當特定的用戶需要特定的數據片,並且需要把數據在某個可變的時段中保持的時候,cookie就非常方便。它的生命周期可能與浏覽器窗體的一樣短,也可以長達數月、數年。cookIE可以小到只有幾個字節的數據,因為它們在每個浏覽器請求中傳遞,它們的內容需要盡可能的小。
Cookie提供了一條靈活的、強大的維護用戶請求間數據的途徑,這就是為什麼Internet上大多數動態站點使用它們的原因。因為cookIE可以存儲的數據量很受限制,最好只在cookIE中保存鍵字段,其它的數據保存在數據庫或其它的服務器端數據容器中。但是由於不是所有的浏覽器都支持cookie,並且它可以被用戶禁止或刪除,因此它們也不能用於保存關鍵數據。你應該很好地處理用戶的cookie被刪除的情況。最後,cookIE作為簡單的明文文本保存在用戶的計算機中,因此在它裡面不能保存敏感的、未加密的數據。
圖2.單值和多值cookIE
有種特殊的cookie可以保存單個值或名稱/值對的集合。圖4顯示了單個和多個值cookIE的示例,通過ASP.NET的內建追蹤特性輸出。這些值可以在ASP.Net頁面中使用Request.Cookies和Response.CookIEs集合來維護,這在代碼段2中演示。
CookIEs.ASPx.cs
//使用HttpCookie類是指cookIE的值和/或子值
HttpCookie cookIE;
if(Request.CookIEs[txtName.Text] == null)
cookie = new HttpCookIE(txtName.Text, txtValue.Text);
else
cookie = Request.CookIEs[txtName.Text];
if(txtSubValueName.Text.Length > 0)
cookIE.Values.Add(txtSubValueName.Text, txtSubValueValue.Text);
cookIE.Expires = System.DateTime.Now.AddDays(1); // tomorrow
Response.AppendCookie(cookIE);
//檢索cookIE的值
if(!Request.CookIEs[txtName.Text].HasKeys)
lblResult.Text = "The value of the <b>" + txtName.Text + "</b>
cookie is <b>" + Request.CookIEs[txtName.Text].Value.ToString() +
"</b>";
else
{
lblResult.Text = "The value of the <b>" + txtName.Text + "</b>
cookie is <b>" + Request.CookIEs[txtName.Text].Value.ToString() +
"</b>, with subvalues:<br>";
foreach(string key in Request.CookIEs[txtName.Text].Values.Keys)
{
lblResult.Text += "[" + key + " = " +
Request.CookIEs[txtName.Text].Values[key].ToString() + "]<br>";
}
}
刪除CookIE
// 把的值設置為空並把終止時間設置為過去某個時刻
Response.CookIEs[txtName.Text].Value = null;
Response.CookIEs[txtName.Text].Expires =
System.DateTime.Now.AddMonths(-1); //上個月
代碼段2.Accessing 在ASP.Net中訪問CookIEs
Form Post / 隱藏的窗體字段
特定的用戶需要窗體的數據,並且它需要在單個請求到應用程序終止的任何階段都保持。這些數據事實上可以是任意大小的,它隨著每個form post在網絡上向前和向後發送。
在傳統的ASP中,這是在應用程序中暴露狀態的通常的途徑,特別是在多頁面窗體應用程序中。但是在ASP.NET中這種技術不太適合了,因為只要你使用postback模型(也就是頁面發回給自己),Web控件和ViewState自動處理了這些操作。VIEwState是ASP.NET對這種技術的實現,我將在本文的後部分討論它。訪問通過POST發送的窗體值是使用HttpRequest對象的窗體集合完成的。在圖6中,一個ASP.Net頁面設置了某個用戶的ID,在這以後它保持在一個隱藏的窗體字段中。後面的向任何頁面的請求保留這個值,直到頁面使用Submit按鈕鏈接到其它的用戶。
Form1.ASPx
<h1>Form 1</h1>
<form id="Application" method="post" runat="server">
<p>Your username:
<ASP:Label ID="lblUsername" Runat="server" />
</p>
<ASP:Panel Runat="server" ID="pnlSetValue">
<ASP:validationsummary id="valSummary" Runat="server">
</ASP:validationsummary>
<TABLE>
<TR>
<TD colSpan="3">Set Hidden Form Username Variable:</TD></TR>
<TR>
<TD>Username</TD>
<TD>
<asp:textbox id="txtName" Runat="server"></ASP:textbox></TD>
<TD>
<ASP:requiredfIEldvalidator id="nameRequired" runat="server"
ControlToValidate="txtName" ErrorMessage="Name is required."
Display="Dynamic">*</ASP:requiredfIEldvalidator></TD></TR>
<TR>
<TD colSpan="3">
<ASP:button id="btnSubmit" Runat="server" Text="Set Value">
</ASP:button></TD></TR></TABLE>
</ASP:Panel>
<ASP:Label ID="lblResult" Runat="server" />
</form>
<form action="form2.ASPx" method="post" name="form2" id="form2">
<input type="hidden" name="username" value="<%# username %>" >
<input type="submit" value="Go to Form2.ASPx"
</form>
Form1.ASPx.cs
private void Page_Load(object sender, System.EventArgs e)
{
if(!IsPostBack) // 新的請求或者來自form2.ASPx的請求
{
// 檢查窗體集合
if(Request.Form["username"] == null)
pnlSetValue.Visible = true;
else
{
//需要設置用戶名值
pnlSetValue.Visible = false;
username = Request.Form["username"].ToString();
lblUsername.Text = username;
//數據綁定到隱藏的窗體字段值
this.DataBind();
}
}
}
private void btnSubmit_Click(object sender, System.EventArgs e)
{
if(IsValid)
{
//隱藏窗體來設置值
pnlSetValue.Visible = false;
username = txtName.Text;
lblResult.Text = "Username set to " + txtName.Text + ".";
lblUsername.Text = username;
this.DataBind();
}
}
Form2.ASPx
<h1>Form 2</h1>
<form id="Application" method="post" runat="server">
<p>Your username: <ASP:Label ID="lblUsername" Runat="server" /></p>
</form>
<form action="form1.ASPx" method="post" id="form2" name="form2">
<input type="hidden" name="username" value="<%# username %>" >
<input type="submit" value="Go to Form1.ASPx"
</form>
Form2.ASPx.cs
private void Page_Load(object sender, System.EventArgs e)
{
if(Request.Form["username"] != null)
{
username = Request.Form["username"].ToString();
lblUsername.Text = username;
this.DataBind();
}
}
代碼段3.在ASP.Net中使用隱藏窗體字段
在ASP.NET中一個頁面上只能存在一個服務器端窗體,並且該窗體必須提交返回到自身(仍然可以使用客戶端窗體,沒有限制)。隱藏窗體字段再也沒有用於在.NET框架組件上建立的應用程序間傳遞數據的主要原因之一是.Net框架組件控件都可以使用ViewState自動維護自己的狀態。VIEwState簡單地把使用隱藏窗體字段設置和檢索值所包含的工作封裝進一個使用簡單的集合對象中。
QueryString
QueryString對象中保存的數據由單獨的用戶使用。它的生命周期可能只有一個請求那麼短,也可能有用戶使用應用程序的時間那麼長(如果構造正確的話)。這類數據一般小於1KB。QueryString中的數據在URL中傳遞,對於用戶來說是可見的,因此你能猜到,使用這種技術時,敏感的數據或可用於控制應用程序的數據需要加密。
也就是說,QueryString是在ASP.Net Web窗體間發送信息的一條很好的途徑。例如,如果有一個含有產品列表的數據表格(DataGrid),並且在表格上有一個鏈接導向產品的細節頁面,使用QueryString就是理想的,可以把產品的ID包含在鏈接到產品細節頁面的QueryString中(例如productdetails.ASPx?id=4)。使用QueryStrings的另一個好處是頁面的狀態包含在URL中。這意味著用戶可以把某個通過QueryStrings建立的窗體放入他的收藏夾中。當它們作為收藏返回到頁面時,將與作收藏的時候一樣。很明顯這只在頁面不依賴QueryString外的所有狀態和不作任何改變的時候有作用。
敏感數據,以及任何不希望用戶操作的變量應該避免出現在此處(除非加密使用戶不能閱讀)。並且URL中不合法的字符必須使用Server.UrlEncode編碼,如圖7所示。當處理單個ASP.Net頁面時,對維護狀態來說VIEwState是比QueryString好的選擇。對於長期的數據存儲,CookIE、Sessions或Cache都比QueryStrings更加適於作為數據容器。
Querystring.ASPx
<form id="Querystring" method="post" runat="server">
<ASP:validationsummary id="valSummary" Runat="server">
</ASP:validationsummary>
<table>
<tr>
<td colSpan="3">Set Querystring Variable:</td>
</tr>
<tr>
<td>Name</td>
<td><asp:textbox id="txtName" Runat="server"></ASP:textbox>
</td>
<td><ASP:requiredfIEldvalidator id="nameRequired"
runat="server" Display="Dynamic" ErrorMessage="Name is
required." ControlToValidate="txtName">*
</ASP:requiredfIEldvalidator></td>
</tr>
<tr>
<td>Value</td>
<td><ASP:textbox id="txtValue" Runat="server">
</ASP:textbox></td>
<td><ASP:requiredfIEldvalidator id="valueRequired"
Runat="server" Display="Dynamic" ErrorMessage="Value is
required." ControlToValidate="txtValue">*
</ASP:requiredfIEldvalidator></td>
</tr>
<tr>
<td colSpan="3"><ASP:button id="btnSubmit" Runat="server"
Text="Update Value"></ASP:button></td>
</tr>
</table>
<ASP:Label ID="lblResult" Runat="server" />
<a href="querystring.ASPx?x=1">Set querystring x equal to 1</a>
</form>
Querystring.ASPx.cs
private void Page_Load(object sender, System.EventArgs e)
{
// 檢索cookIE的值
if(Request.QueryString.HasKeys())
{
lblResult.Text = "The values of the <b>" + txtName.Text +
"</b> querystring parameter are:<br>";
foreach(string key in Request.QueryString.Keys)
{
lblResult.Text += "[" + key + " = " +
Request.QueryString[key].ToString() + "]<br>";
}
}
}
private void btnSubmit_Click(object sender, System.EventArgs e)
{
if(IsValid)
{
string url = "querystring.ASPx?";
foreach(string key in Request.QueryString.Keys)
{
url += key + "=" + Request.QueryString[key].ToString() + "&";
}
Response.Redirect(url + txtName.Text + "=" +
Server.UrlEncode(txtValue.Text));
}
}
代碼段4.在ASP.Net中使用QueryStrings傳遞數據
Sessions
Sessions數據對於特定的用戶是特定的。它的生存期是用戶持續請求的時間加上後來一段時間(一般是20分鐘)。Sessions可以保持或大或小的數據量,但是如果應用程序用於成百上千的用戶,那麼總共的存儲應該保持最小。
不幸的是在傳統的ASP中Sessions對象的名聲很不好,因為它把應用程序約束到特定的計算機上,阻礙了用戶分組和Web范圍的可伸縮性。在ASP.NET中幾乎沒有這些問題,因為改變Sessions保存的位置很簡單。在默認情況下(性能最好的情況),Sessions數據仍然保存在本地Web服務器的內存中,但是ASP.Net支持使用外部狀態服務器或數據庫管理Sessions數據。
使用Sessions對象很簡單,並且它的語法與傳統ASP相同。但是Sessions對象是保存用戶數據的方法中效率很低的一種,因為即使用戶停止使用應用程序後它仍然保持在內存中一段時間。這對於非常繁忙的站點的可伸縮性有嚴重的影響。其它的選擇允許對釋放內存的更多的控制,例如Cache對象也許更適合大量的大數據值。並且在默認情況下ASP.Net Sessionss依賴於cookie,因此如果用戶禁止或不支持cookie,Sessionss就不能工作,但是可以配置Sessionss支持cookIE無關。對於小的數據量,Sessionss對象是保存只需要在用戶當前對話中保持的特定數據的極好位置。下面的例子演示了怎樣設置和從Sessionss對象中檢索值:
private void btnSubmit_Click(object sender, System.EventArgs e)
{
if(IsValid)
{
// 設置Sessions值
Sessions[txtName.Text] = txtValue.Text;
//讀取和顯示剛才的設置
lblResult.Text = "The value of <b>" + txtName.Text + "</b> in the Sessions object is <b>" + Sessions[txtName.Text].ToString() + "</b>";
}
}
該Web窗體與Application對象中使用的幾乎相同,當允許頁面追蹤時Sessions集合的內容也是可見的。
你需要記住的是即使沒有使用,Sessionss也會有應用程序開銷。把Sessionss狀態設置為只讀的也可以優化只需要讀而不需要寫數據的頁面。可以使用下面兩種途徑之一來配置Sessionss:
<%@ Page EnableSessionsstate="false" %>
<%@ Page EnableSessionsstate="readonly" %>
ASP.Net Sessionss可以在Web.config或Machine.config中的Sessionsstate元素中配置。下面是在 Web.config中的設置的例子:
<Sessionsstate timeout="10" cookIEless="false" mode="Inproc" />