摘要: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。注意这五个数据容器的正确使用方法发生了改变,因此有经验的程序员在考虑这些熟悉的对象时也许需要学习一些知识。
| 保持方法 | 谁需要数据 | 保持多长时间 | 数据量大小 |
| APPLICATION | 所有用户 | 整个应用程序生命期 | 任意大小 |
| COOKIE | 一个用户 | 可以很短,如果用户不删除也可以很长 | 小的、简单数据 |
| FORM POST | 一个用户 | 到下一次请求(可以跨越多个请求重复使用) | 任意大小 |
| QUERYSTRING | 一个或一组用户 | 到下一次请求(可以跨越多个请求重复使用) | 小的、简单数据 |
| SESSIONS | 一个用户 | 用户活动时一直保持+一段时间(一般20分钟) | 可以是任何大小,但是因为用户有单独的SESSIONS 存储,所有它应该最小。 |
| CACHE | 所有用户或某些用户 | 根据需要 | 可大可小、可简单可复杂 |
| CONTEXT | 一个用户 | 一个请求 | 可以保持大对象,但是一般不这样使用 |
| VIEWSTATE | 一个用户 | 一个WEB窗体 | 最小 |
| CONFIG FILE | 所有用户 | 知道配置文件被更新 | 可以保持大量数据,通常组织小的字符串和XML结构 |
表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.ASPXID=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.ASPXX=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" />