ASP.NET保持用戶狀態(tài)的9種選擇(上)
發(fā)表時(shí)間:2024-05-17 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]摘要:ASP.NET為保持用戶請(qǐng)求之間的數(shù)據(jù)提供了多種不同的途徑。你可以使用Application對(duì)象、cookie、hidden fields、Sessions或Cache對(duì)象,以及它們的大量的方法。決定什么時(shí)候使用它們有時(shí)很困難。本文將介紹了上述的技術(shù),給出了什么時(shí)候使用它們的一些指導(dǎo)。盡管這...
摘要:ASP.NET為保持用戶請(qǐng)求之間的數(shù)據(jù)提供了多種不同的途徑。你可以使用Application對(duì)象、cookie、hidden fields、Sessions或Cache對(duì)象,以及它們的大量的方法。決定什么時(shí)候使用它們有時(shí)很困難。本文將介紹了上述的技術(shù),給出了什么時(shí)候使用它們的一些指導(dǎo)。盡管這些技術(shù)中有些在傳統(tǒng)ASP中已經(jīng)存在,但是有了.NET框架組件后該在什么時(shí)候使用它們發(fā)生了變化。為了在ASP.NET中保持?jǐn)?shù)據(jù),你需要調(diào)整從先前的ASP中處理狀態(tài)中學(xué)習(xí)到的知識(shí)。
隨著Web時(shí)代的到來,在無狀態(tài)的HTTP世界中管理狀態(tài)成為Web開發(fā)者的一個(gè)大問題。最近出現(xiàn)了幾種存儲(chǔ)和檢索數(shù)據(jù)的不同技術(shù)。本文我將解釋ASP.NET開發(fā)者能怎樣通過頁(yè)面請(qǐng)求維護(hù)或傳遞狀態(tài)。
在ASP.NET中,有幾種保持用戶請(qǐng)求間數(shù)據(jù)的途徑--實(shí)際上太多了,使沒有經(jīng)驗(yàn)的開發(fā)者對(duì)在哪個(gè)特定的環(huán)境下使用哪個(gè)對(duì)象很困惑。為了回答這個(gè)問題,需要考慮下面三個(gè)條件:
.誰(shuí)需要數(shù)據(jù)?
.數(shù)據(jù)需要保持多長(zhǎng)時(shí)間?
.數(shù)據(jù)集有多大?
通過回答這些問題,你能決定哪個(gè)對(duì)象為保持ASP.NET應(yīng)用程序請(qǐng)求間數(shù)據(jù)提供了最佳的解決方案。圖1列出了不同的狀態(tài)管理對(duì)象并描述了什么時(shí)候使用它們。ASP.NET中添加了四個(gè)新的對(duì)象:Cache、Context、ViewState和Web.Config文件。ASP.NET也支持傳統(tǒng)的ASP對(duì)象,包括Application、 Cookie、有隱藏字段的 Form Post 、 QueryString和Sessions。注意這五個(gè)數(shù)據(jù)容器的正確使用方法發(fā)生了改變,因此有經(jīng)驗(yàn)的程序員在考慮這些熟悉的對(duì)象時(shí)也許需要學(xué)習(xí)一些知識(shí)。
保持方法 | 誰(shuí)需要數(shù)據(jù) | 保持多長(zhǎng)時(shí)間 | 數(shù)據(jù)量大小 |
Application | 所有用戶 | 整個(gè)應(yīng)用程序生命期 | 任意大小 |
Cookie | 一個(gè)用戶 | 可以很短,如果用戶不刪除也可以很長(zhǎng) | 小的、簡(jiǎn)單數(shù)據(jù) |
Form Post | 一個(gè)用戶 | 到下一次請(qǐng)求(可以跨越多個(gè)請(qǐng)求重復(fù)使用) | 任意大小 |
QueryString | 一個(gè)或一組用戶 | 到下一次請(qǐng)求(可以跨越多個(gè)請(qǐng)求重復(fù)使用) | 小的、簡(jiǎn)單數(shù)據(jù) |
Sessions | 一個(gè)用戶 | 用戶活動(dòng)時(shí)一直保持+一段時(shí)間(一般20分鐘) | 可以是任何大小,但是因?yàn)橛脩粲袉为?dú)的Sessions 存儲(chǔ),所有它應(yīng)該最小。 |
Cache | 所有用戶或某些用戶 | 根據(jù)需要 | 可大可小、可簡(jiǎn)單可復(fù)雜 |
Context | 一個(gè)用戶 | 一個(gè)請(qǐng)求 | 可以保持大對(duì)象,但是一般不這樣使用 |
ViewState | 一個(gè)用戶 | 一個(gè)Web窗體 | 最小 |
Config file | 所有用戶 | 知道配置文件被更新 | 可以保持大量數(shù)據(jù),通常組織小的字符串和XML結(jié)構(gòu) |
表1. ASP.NET中的數(shù)據(jù)容器對(duì)象
Application
讓我們通過回答上面的狀態(tài)問題判定條件來說明該對(duì)象。誰(shuí)需要數(shù)據(jù)?所有的用戶需要訪問它。需要保持?jǐn)?shù)據(jù)多長(zhǎng)時(shí)間?永久保持,或在應(yīng)用程序生存期中保持。數(shù)據(jù)多大?可以是任何大小--在任何給定的時(shí)刻只有數(shù)據(jù)的一個(gè)副本存在。
在傳統(tǒng)ASP中,Application對(duì)象提供了一個(gè)保存頻繁使用但很少改變的數(shù)據(jù)片的位置,例如菜單內(nèi)容和參考數(shù)據(jù)。盡管在ASP.NET 中Application依然作為數(shù)據(jù)容器存在,但是有其它一些更適合以前保存在傳統(tǒng)ASP應(yīng)用程序的Application集合中的數(shù)據(jù)的對(duì)象。
在傳統(tǒng)的ASP中,如果被保存的數(shù)據(jù)在應(yīng)用程序的生存期中根本不會(huì)改變(或很少改變,例如只讀數(shù)據(jù)和大多數(shù)情況下是讀操作的數(shù)據(jù)),Application對(duì)象是理想的選擇。連接字符串就是保存在Application變量中的一個(gè)最普通的數(shù)據(jù)片,但是在ASP.NET中類似的配置數(shù)據(jù)最好保存在Web.config文件中。如果使用Application對(duì)象一個(gè)需要考慮的問題是任何寫操作要么在Application_OnStart事件(global.asax)中,要么在Application.Lock部分中完成。盡管使用Application.Lock來確保寫操作正確地執(zhí)行是必要的,但是它串行化了對(duì)Application對(duì)象的請(qǐng)求,而這對(duì)于應(yīng)用程序來說是個(gè)嚴(yán)重的性能瓶頸。圖2演示了怎樣使用Application對(duì)象,它包括一個(gè)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對(duì)象
它的輸出如下圖所示:
圖1. Application對(duì)象的內(nèi)容
注意圖3中Application對(duì)象的內(nèi)容是追蹤輸出的顯示。追蹤是個(gè)偉大的調(diào)試工具,但是在某個(gè)點(diǎn),被打開的有追蹤的頁(yè)面可能出現(xiàn)在產(chǎn)品環(huán)境中。如果出現(xiàn)這種情況,你肯定不希望顯示敏感的信息。這就是為什么Application對(duì)象從來不是推薦的存放敏感信息(例如連接字符串)的位置的主要原因之一。
Cookies
當(dāng)特定的用戶需要特定的數(shù)據(jù)片,并且需要把數(shù)據(jù)在某個(gè)可變的時(shí)段中保持的時(shí)候,cookie就非常方便。它的生命周期可能與瀏覽器窗體的一樣短,也可以長(zhǎng)達(dá)數(shù)月、數(shù)年。cookie可以小到只有幾個(gè)字節(jié)的數(shù)據(jù),因?yàn)樗鼈冊(cè)诿總(gè)瀏覽器請(qǐng)求中傳遞,它們的內(nèi)容需要盡可能的小。
Cookie提供了一條靈活的、強(qiáng)大的維護(hù)用戶請(qǐng)求間數(shù)據(jù)的途徑,這就是為什么Internet上大多數(shù)動(dòng)態(tài)站點(diǎn)使用它們的原因。因?yàn)閏ookie可以存儲(chǔ)的數(shù)據(jù)量很受限制,最好只在cookie中保存鍵字段,其它的數(shù)據(jù)保存在數(shù)據(jù)庫(kù)或其它的服務(wù)器端數(shù)據(jù)容器中。但是由于不是所有的瀏覽器都支持cookie,并且它可以被用戶禁止或刪除,因此它們也不能用于保存關(guān)鍵數(shù)據(jù)。你應(yīng)該很好地處理用戶的cookie被刪除的情況。最后,cookie作為簡(jiǎn)單的明文文本保存在用戶的計(jì)算機(jī)中,因此在它里面不能保存敏感的、未加密的數(shù)據(jù)。
圖2.單值和多值cookie
有種特殊的cookie可以保存單個(gè)值或名稱/值對(duì)的集合。圖4顯示了單個(gè)和多個(gè)值cookie的示例,通過ASP.NET的內(nèi)建追蹤特性輸出。這些值可以在ASP.NET頁(yè)面中使用Request.Cookies和Response.Cookies集合來維護(hù),這在代碼段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
// 把的值設(shè)置為空并把終止時(shí)間設(shè)置為過去某個(gè)時(shí)刻
Response.Cookies[txtName.Text].Value = null;
Response.Cookies[txtName.Text].Expires =
System.DateTime.Now.AddMonths(-1); //上個(gè)月
代碼段2.Accessing 在ASP.NET中訪問Cookies
Form Post / 隱藏的窗體字段
特定的用戶需要窗體的數(shù)據(jù),并且它需要在單個(gè)請(qǐng)求到應(yīng)用程序終止的任何階段都保持。這些數(shù)據(jù)事實(shí)上可以是任意大小的,它隨著每個(gè)form post在網(wǎng)絡(luò)上向前和向后發(fā)送。
在傳統(tǒng)的ASP中,這是在應(yīng)用程序中暴露狀態(tài)的通常的途徑,特別是在多頁(yè)面窗體應(yīng)用程序中。但是在ASP.NET中這種技術(shù)不太適合了,因?yàn)橹灰闶褂胮ostback模型(也就是頁(yè)面發(fā)回給自己),Web控件和ViewState自動(dòng)處理了這些操作。ViewState是ASP.NET對(duì)這種技術(shù)的實(shí)現(xiàn),我將在本文的后部分討論它。訪問通過POST發(fā)送的窗體值是使用HttpRequest對(duì)象的窗體集合完成的。在圖6中,一個(gè)ASP.NET頁(yè)面設(shè)置了某個(gè)用戶的ID,在這以后它保持在一個(gè)隱藏的窗體字段中。后面的向任何頁(yè)面的請(qǐng)求保留這個(gè)值,直到頁(yè)面使用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) // 新的請(qǐng)求或者來自form2.aspx的請(qǐng)求
{
// 檢查窗體集合
if(Request.Form["username"] == null)
pnlSetValue.Visible = true;
else
{
//需要設(shè)置用戶名值
pnlSetValue.Visible = false;
username = Request.Form["username"].ToString();
lblUsername.Text = username;
//數(shù)據(jù)綁定到隱藏的窗體字段值
this.DataBind();
}
}
}
private void btnSubmit_Click(object sender, System.EventArgs e)
{
if(IsValid)
{
//隱藏窗體來設(shè)置值
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中一個(gè)頁(yè)面上只能存在一個(gè)服務(wù)器端窗體,并且該窗體必須提交返回到自身(仍然可以使用客戶端窗體,沒有限制)。隱藏窗體字段再也沒有用于在.NET框架組件上建立的應(yīng)用程序間傳遞數(shù)據(jù)的主要原因之一是.NET框架組件控件都可以使用ViewState自動(dòng)維護(hù)自己的狀態(tài)。ViewState簡(jiǎn)單地把使用隱藏窗體字段設(shè)置和檢索值所包含的工作封裝進(jìn)一個(gè)使用簡(jiǎn)單的集合對(duì)象中。
QueryString
QueryString對(duì)象中保存的數(shù)據(jù)由單獨(dú)的用戶使用。它的生命周期可能只有一個(gè)請(qǐng)求那么短,也可能有用戶使用應(yīng)用程序的時(shí)間那么長(zhǎng)(如果構(gòu)造正確的話)。這類數(shù)據(jù)一般小于1KB。QueryString中的數(shù)據(jù)在URL中傳遞,對(duì)于用戶來說是可見的,因此你能猜到,使用這種技術(shù)時(shí),敏感的數(shù)據(jù)或可用于控制應(yīng)用程序的數(shù)據(jù)需要加密。
也就是說,QueryString是在ASP.NET Web窗體間發(fā)送信息的一條很好的途徑。例如,如果有一個(gè)含有產(chǎn)品列表的數(shù)據(jù)表格(DataGrid),并且在表格上有一個(gè)鏈接導(dǎo)向產(chǎn)品的細(xì)節(jié)頁(yè)面,使用QueryString就是理想的,可以把產(chǎn)品的ID包含在鏈接到產(chǎn)品細(xì)節(jié)頁(yè)面的QueryString中(例如productdetails.aspx?id=4)。使用QueryStrings的另一個(gè)好處是頁(yè)面的狀態(tài)包含在URL中。這意味著用戶可以把某個(gè)通過QueryStrings建立的窗體放入他的收藏夾中。當(dāng)它們作為收藏返回到頁(yè)面時(shí),將與作收藏的時(shí)候一樣。很明顯這只在頁(yè)面不依賴QueryString外的所有狀態(tài)和不作任何改變的時(shí)候有作用。
敏感數(shù)據(jù),以及任何不希望用戶操作的變量應(yīng)該避免出現(xiàn)在此處(除非加密使用戶不能閱讀)。并且URL中不合法的字符必須使用Server.UrlEncode編碼,如圖7所示。當(dāng)處理單個(gè)ASP.NET頁(yè)面時(shí),對(duì)維護(hù)狀態(tài)來說ViewState是比QueryString好的選擇。對(duì)于長(zhǎng)期的數(shù)據(jù)存儲(chǔ),Cookie、Sessions或Cache都比QueryStrings更加適于作為數(shù)據(jù)容器。
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傳遞數(shù)據(jù)
Sessions
Sessions數(shù)據(jù)對(duì)于特定的用戶是特定的。它的生存期是用戶持續(xù)請(qǐng)求的時(shí)間加上后來一段時(shí)間(一般是20分鐘)。Sessions可以保持或大或小的數(shù)據(jù)量,但是如果應(yīng)用程序用于成百上千的用戶,那么總共的存儲(chǔ)應(yīng)該保持最小。
不幸的是在傳統(tǒng)的ASP中Sessions對(duì)象的名聲很不好,因?yàn)樗褢?yīng)用程序約束到特定的計(jì)算機(jī)上,阻礙了用戶分組和Web范圍的可伸縮性。在ASP.NET中幾乎沒有這些問題,因?yàn)楦淖僑essions保存的位置很簡(jiǎn)單。在默認(rèn)情況下(性能最好的情況),Sessions數(shù)據(jù)仍然保存在本地Web服務(wù)器的內(nèi)存中,但是ASP.NET支持使用外部狀態(tài)服務(wù)器或數(shù)據(jù)庫(kù)管理Sessions數(shù)據(jù)。
使用Sessions對(duì)象很簡(jiǎn)單,并且它的語(yǔ)法與傳統(tǒng)ASP相同。但是Sessions對(duì)象是保存用戶數(shù)據(jù)的方法中效率很低的一種,因?yàn)榧词褂脩敉V故褂脩?yīng)用程序后它仍然保持在內(nèi)存中一段時(shí)間。這對(duì)于非常繁忙的站點(diǎn)的可伸縮性有嚴(yán)重的影響。其它的選擇允許對(duì)釋放內(nèi)存的更多的控制,例如Cache對(duì)象也許更適合大量的大數(shù)據(jù)值。并且在默認(rèn)情況下ASP.NET Sessionss依賴于cookie,因此如果用戶禁止或不支持cookie,Sessionss就不能工作,但是可以配置Sessionss支持cookie無關(guān)。對(duì)于小的數(shù)據(jù)量,Sessionss對(duì)象是保存只需要在用戶當(dāng)前對(duì)話中保持的特定數(shù)據(jù)的極好位置。下面的例子演示了怎樣設(shè)置和從Sessionss對(duì)象中檢索值:
private void btnSubmit_Click(object sender, System.EventArgs e)
{
if(IsValid)
{
// 設(shè)置Sessions值
Sessions[txtName.Text] = txtValue.Text;
//讀取和顯示剛才的設(shè)置
lblResult.Text = "The value of <b>" + txtName.Text + "</b> in the Sessions object is <b>" + Sessions[txtName.Text].ToString() + "</b>";
}
}
該Web窗體與Application對(duì)象中使用的幾乎相同,當(dāng)允許頁(yè)面追蹤時(shí)Sessions集合的內(nèi)容也是可見的。
你需要記住的是即使沒有使用,Sessionss也會(huì)有應(yīng)用程序開銷。把Sessionss狀態(tài)設(shè)置為只讀的也可以優(yōu)化只需要讀而不需要寫數(shù)據(jù)的頁(yè)面。可以使用下面兩種途徑之一來配置Sessionss:
<%@ Page EnableSessionsstate="false" %>
<%@ Page EnableSessionsstate="readonly" %>
ASP.NET Sessionss可以在Web.config或Machine.config中的Sessionsstate元素中配置。下面是在 Web.config中的設(shè)置的例子:
<Sessionsstate timeout="10" cookieless="false" mode="Inproc" />