ASP.NET 客户端和服务器端状态管理






4.71/5 (14投票s)
本文解释了如何通过 View State、隐藏字段、Cookies、查询字符串、Session State 和 Application State 来管理 ASP.NET 客户端和服务器端状态。
引言
在使用 ASP.NET 时,数据可以通过多种方式持久化在框架中,开发者必须对此有充分的了解。总的来说,在使用 ASP.NET 框架时,信息可以在客户端和服务器端进行处理。例如,隐藏字段和 View State 在客户端管理信息,而用户会话(User Session)在服务器端处理信息。
本文旨在解释管理 ASP.NET 中数据的每种机制。此外,还将讨论每种机制的适当用法,并鼓励读者深入理解每种机制。
提供了一段代码,用以举例说明此处涵盖的所有内容。
客户端状态管理
视图状态
View State 是开发 ASP.NET 时存储数据最有用的一种方式,因为它在页面的生命周期内存储变量值。具体来说,假设用户在一个名为 PageA.aspx 的页面上。用户可能会进行各种活动,这些活动涉及全部或部分页面提交。例如,他们可能会单击按钮或选择下拉列表组件,这会导致浏览器提交页面。如果用户停留在同一页面上,即执行了回发(post-backs),那么 View State 中保存的值在页面提交之间不会被删除。因此,只要页面不发生更改,就可以在其整个存在期间持久化值。
关于 View State 的另一个非常有趣的方面是它们存储在客户端,这意味着服务器端不会持久化额外数据。然而,明显的缺点是使用 View State 的页面会变大,这意味着最终需要更多的带宽来传输数据。下面是一个页面源代码及其 View State 的示例。
红色标记的区域是作为 View State 持久化的值。请注意,它们存储在隐藏字段中,因此不会干扰网页。此外,在此示例中它们很小;但是,View State 变量越大,这些隐藏字段也会越大。
现在,您可能想知道如何将值存储在 View State 中。这就像在 C# IDictionary 中存储值一样简单。下面的代码显示了如何将各种类型的值存储在 View State 中。请注意,为了调用 View State 的 static
方法,您必须位于代码隐藏页中。
// saving a simple Int32
ViewState["3In32"] = 3;
// saving a String
ViewState["AString"] = "My String";
// saving a serializable Object
ViewStateHolder holder = new ViewStateHolder();
holder.Value = "yet another value";
ViewState["SerializableObject"] = holder;
请注意,C# 原始类型和任何可序列化的对象都可以保存。这里创建了一个自定义对象 ViewStateHolder
,并为其 Value
属性设置了一个 String
。这个自定义对象来自一个 Serializable
类,如下所示。
/// <summary>
/// // Serializable class which can be persisted in the View State
/// </summary>
[Serializable]
public class ViewStateHolder
{
public String Value { get; set; }
}
要从 View State 中获取值,只需按其键检索它们并获取其值,如下所示。
// get a simple Int32
Int32 int3Value = (Int32) ViewState["3In32"];
// get a String
String myString = (String) ViewState["AString"];
// get a serializable Object
ViewStateHolder holder = (ViewStateHolder) ViewState["SerializableObject"];
因此,正如您所看到的,处理 View State 非常简单,这使得此工具非常有用且易于使用。
关于安全性的说明:View States 不过是高级的隐藏字段,因此避免在此存储敏感数据,因为它们可能会被篡改。虽然 View States 经过哈希、编码并包含消息认证码(MAC),但强烈建议将机密数据保留在服务器端。如果您必须在 View State 中保留敏感信息,可以在您的页面中(使用 Page
对象的 ViewStateEncryptionMode
属性)或为整个应用程序在 Web.Config 文件中进行加密,如下所示。
<configuration>
<system.web>
<pages viewStateEncryptionMode="Always"/>
</system.web>
</configuration>
隐藏字段
隐藏字段是 Web 开发者非常熟悉的标签。作为一个简单的 HTML 组件,它只是用于存储数据,用户看不到。这些数据将被发送到服务器。请注意,与 View States 不同,普通的 HTML 元素不会在 Web 页面的整个生命周期内存在。为了使其能够做到这一点,必须在每个请求中手动持久化隐藏字段中的值。
但是,有一个 ASP.NET 控件 HiddenField
。与任何其他 ASP.NET 控件一样,只要应用程序不离开页面,它就能在其 Web 页面的多个请求的整个生命周期内保持其生命周期。下面显示了如何在 ASP.NET 页面中声明一个隐藏字段。
<asp:HiddenField ID="hdnHiddenField" runat="server" />
下面显示了如何在代码隐藏中为上面声明的隐藏字段设置值。
hdnHiddenField.Value = "A String to be stored";
与 View States 一样,隐藏字段存储在客户端,因此它们不得包含安全信息。此外,HiddenField
ASP.NET 控件会被转换为 HTML 页面中的简单隐藏字段标签,没有任何哈希或加密。
Cookie
Cookie 是存储在客户端(浏览器将其存储在文件系统中)的小量数据,每次向特定站点发出请求时都会将其传递给服务器。
创建和使用 Cookie 的过程如下:
- 浏览器第一次向服务器发出请求 - 此处不使用 Cookie,因为浏览器仍然不知道服务器。
- 服务器以请求响应浏览器,并向浏览器提供一个 Cookie。
- 浏览器然后将收到的 Cookie 存储在客户端计算机上,通常是文件系统中的一个小文件。
- 浏览器使用此 Cookie 向服务器发出请求。这允许服务器识别特定客户端。
如上述过程所述,使用 Cookie 是一种识别使用应用程序的客户端(即特定用户)的非常好的方式。这实际上是 Cookie 的主要用途,即在许多请求中识别应用程序的用户。这可以持续数天,取决于您的 Cookie 有效期。下面显示了如何在 ASP.NET 的 Response
对象中创建 Cookie,因为正如上面所示的步骤,Cookie 是由服务器端在 Response
中创建并设置其值的。
Response.Cookies["LastTimeVisited"].Value = new DateTime().ToLongDateString();
Response.Cookies["LastTimeVisited"].Expires = new DateTime().AddDays(1);
注意下面的代码,由 String
"LastTimeVisited
" 标识的 Cookie 被设置为保存服务器当前日期的值。此外,其过期日期是创建 Cookie 后的一天。创建 Cookies
后,就可以读取它们了,下面的代码显示了如何进行操作。
// if there is a cookie, show its value which is the last time the site was visited
if (Request.Cookies["LastTimeVisited"] != null)
{
// use HtmlEnconde just to make sure no malicious string is set here
lblLastVisitedDate.Text = Server.HtmlEncode(Request.Cookies["LastTimeVisited"].Value);
}
请注意上面的代码,如果 Cookie 已创建,则其值将设置在名为 lblLastVisitedDate
的标签中。检查 Cookies
是否确实存在始终是一个很好的做法,因为它们可能尚未创建或已过期。
关于 Cookies
有一些重要的规则,如下所示:
- 如果您不设置 Cookie 的过期日期,当浏览器关闭时,Cookie 将从浏览器中删除。
- 您不能“说”删除 Cookie;但是,如果您将过期日期设置为早于当前日期的日期,Cookie 将从浏览器中删除。
- Cookie 仅限于您的域。这意味着浏览器不能跨域使用或发送 Cookie,因为这将被视为安全漏洞。
您还可以使用 Cookie 做更多事情,例如限制 Cookie 在服务器端目录中对网页的使用范围,以及在 Cookie 本身中保存多个值。然而,由于这里的想法是展示其基本工作原理,因此这些内容将留待以后讨论。
与 View States 一样,ASP.NET 框架中的 Cookie 非常容易处理,并且应探索其用户特定用途。
查询字符串
查询字符串(Query Strings)是 Web 开发者非常熟悉的结构。它们是附加在浏览器 URL 中的元素。这些元素是键/值对,表示如下:
- 在 URL 末尾添加问号(?)。
- 在问号 (?) 之后,写入一个键,并在等号 (=) 符号后设置其值。
- 如果要添加另一对键/值,只需加上一个 Ampersand (&) 和另一个用等号 (=) 分隔的键/值对。
- 要添加其他键/值对,只需遵循以下步骤。
这里是一个带有查询字符串的 URL 示例:http://www.mysite.com?key=abc&key2=s323&key3=abcd。请注意,在此示例中,有三个键:“key
”、“key2
”和“key3
”,其相应的值为:“abc
”、“s323
”和“abcd
”。
查询字符串键/值会被发送到服务器并由服务器处理。它们明确显示在 URL 中,因此如果需要传递敏感数据,这不是最佳场所。
查询字符串对于书签化页面状态非常有用。例如,如果有一个搜索页面,其参数通过查询字符串传递,您可以书签化此页面并稍后使用它来重现相同的结果。
下面显示了如何在 ASP.NET 代码隐藏页面中读取查询字符串。
// read values from the URL: http://www.mysite.com?key=abc&key2=s323&key3=abcd
String queryStringValue = Server.HtmlEncode(Request.QueryString["key"]); // read value is abc
String queryStringValue2 = Server.HtmlEncode(Request.QueryString["key2"]); // read value is 5323
String queryStringValue3 = Server.HtmlEncode(Request.QueryString["key3"]); // read value is abcd
请注意,使用 ASP.NET 读取这些数据非常简单。如果 QueryString
IDictionary 中没有由输入的 String
表示的键,则返回 null
。此外,使用了 Server.HtmlEncode
以避免恶意文本被输入。
同样请注意,如果您总是以相同的方式处理具有相同查询字符串值的页面,则可以使用此页面作为包含应用程序特定状态的书签。例如,假设您希望保存应用程序的特定搜索结果。如果它以相同的方式处理相同的 URL 和查询字符串,每当它们从浏览器发送时,此搜索都可以被书签化。
一个重要的说明是,浏览器的 URL 有一个最大长度限制,大约为 2000 个字符,所以请注意不要生成过长的 string
,以免超过此限制。
最后一点观察是,查询字符串通过 GET
HTTP 方法发送到服务器。请注意,此处未讨论此点,因为经典的 ASP.NET 开发并不深入探讨这些细节。显然,随着编写 REST 应用程序和回归使用 HTTP 协议(按预期处理)的简单 Web 应用程序的新趋势,GET
、POST
、PUT
等 HTTP 方法如今变得非常重要。
服务器端状态管理
应用程序状态
Application State 在应用程序的整个生命周期中保存数据,从 IIS(Internet Information Services)启动到关闭。只要应用程序未关闭,此处保存的数据就可以在所有用户会话中通用。因此,它的主要目的之一是创建应用程序缓存。然而,由于此数据存储在整个应用程序生命周期内,因此必须明智地使用此功能,以免消耗过多的服务器资源。
为了存储数据,只需像其他大多数情况一样进行操作,如下面的代码所示(请注意,为了使代码正常工作,您必须位于代码隐藏页中)。
// store value in the Application State
Application["myAppStateString"] = "my text";
// retrieve value from the Application state
String myText = (String) Application["myAppStateString"];
再次注意,使用键来识别正在保存的值,存储和检索 Application State 中的值非常简单。同样,与 View States 一样,您可以存储任何可序列化的 Object
。
请注意,存储在 Application State 中的数据永远不会发送到浏览器,因此敏感信息可以安全地存储在此处。
为了举例说明此状态的用法,请想象以下场景:假设您想计算在应用程序整个生命周期中有多少用户登录过。要做到这一点,当应用程序启动时,应该将一个计数器初始化为零。每当用户登录时,计数器必须增加一。这里不考虑同一用户登录多次的情况。最后,当应用程序关闭时,这些数据可以持久化到数据库。
如前所述,首先要做的是在应用程序启动时初始化一个计数器。这在 Global.asax 类的 Application_Start
事件中完成,如下所示。
public class Global : System.Web.HttpApplication
{
/// <summary>
/// Event called when the application is started.
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event arguments</param>
protected void Application_Start(object sender, EventArgs e)
{
Application["nUsersCounter"] = 0;
}
// other events
}
现在,每当用户登录时,都必须递增相同的计数器。下面是用户成功登录时调用的事件,并且计数器已递增。
/// <summary>
/// Event called when the user tries to log in.
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event arguments</param>
protected void btnLogin(object sender, EventArgs e)
{
// complex code where the user is authenticated successfully
if (authenticationSuccessful) {
// get the counter from the application state, increment int and add it back to the application state
int counter = (int) Application["nUsersCounter"];
counter++;
Application["nUsersCounter"] = counter++;
}
}
最后,当应用程序结束时,将调用 Global.asax 的 Application_End
事件,并将数据存储到数据库中,如下所示。
public class Global : System.Web.HttpApplication
{
// other events
/// <summary>
/// Event called when the application is terminated.
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event Arguments</param>
protected void Application_End(object sender, EventArgs e)
{
int counter = (int)Application["nUsersCounter"];
// persist data in the database
}
}
会话状态
Session State 在用户生命周期的范围内保存值。这意味着当用户在应用程序中进行身份验证时,只要用户登录,它就会有一个 Session State 来存储值。当用户注销时,这些值就会丢失。因此,这对于存储用户特定信息非常有用。此外,由于这里没有与浏览器进行交互,因此非常适合存储敏感信息。
下面是一个如何向 Session State 添加值以及如何检索它们的示例。
// store value in the Session state
Session["mySessionStateString"] = "my text";
// retrieve value from the Session State
String myText = (String) Session["mySessionStateString"];
正如您所注意到的,这与此处显示的绝大多数情况一样。同样,与 Application 和 View State 一样,可序列化对象可以存储在 Session State 中。
这里提供了一个例子,以使对这种状态的理解更加具体。假设您希望根据用户的登录检索用户的全部购买记录。要做到这一点,每当用户登录应用程序时,其登录名就可以存储在 Session 中,并且当要检索其购买记录时,只需从 Session 中获取登录名并用它来查询数据库。
下面是如何在用户登录时将其存储到 Session 中。
/// <summary>
/// Event called when the user tries to log in.
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event arguments</param>
protected void btnLogin(object sender, EventArgs e)
{
// complex code where the user is authenticated successfully
if (authenticationSuccessful) {
// Store the user in the Session
Session["login"] = txtLogin.Text; // retrieve the login from the TextField component called txtLogin
}
}
现在,要在检索购买记录的事件被调用时查询数据库,只需从 Session 中获取登录名并用它来查询数据库。
/// <summary>
/// Event called one whishes to retrieve all purchases related to a user
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event arguments</param>
protected void btnGetPurchases(object sender, EventArgs e)
{
String login = (String) Session["login"];
// instantiate the business object and retrieve the list or purchases related to a login
PurchaseBusiness purchaseBusiness = new PurchaseBusiness();
IList<PUrchase> purchaseList = purchaseBusiness.GetPurchasesByLogin(login);
// bind list of purchases - purchaseList to adequate control.
}
代码示例
您可以下载示例代码,并查看此处解释的所有内容的示例。由于代码非常简单,因此无需详细说明其工作原理。只要一位初学者 ASP.NET 开发者阅读了本文,应该就能完全理解它。代码还带有注释,以便更容易理解。
讨论
本文介绍了在 ASP.NET 应用程序中持久化数据的客户端和服务器端方法。
首先需要注意的是,不应在客户端存储敏感信息。如果确实有必要,可以在 View State 中使用加密进行存储,否则,应将此类信息存储在 Application State 或 Session State 中。
此外,请注意,有多种方法可以处理客户端数据。了解并使用这些机制的优势在于,JavaScript 的使用被最小化,因为用户界面的处理可以通过 Ajax 控件完成,并且数据可以使用此处讨论的状态存储框架之一来存储。
此外,请注意,ASP.NET 开发利用了这些功能,因此开发人员无需担心如何在 HTTP 协议上下文中处理数据的问题。例如,如果开发人员打算在页面生命周期内保留数据,他/她可以使用 View State。如果需要更简单的方法,可以使用隐藏字段。另一方面,如果必须处理用户特定的信息,则可以使用 Cookie 或 Session State 机制。
此外,从性能的角度来看,明智地定义将在客户端和服务器端存储什么非常重要。请记住,在服务器端存储信息会增加服务器的额外负担,从而降低其性能。因此,建议仅存储对用户或应用程序范围有意义的信息。在客户端,必须小心不要在页面中保留过多的信息,因为它可能会变得过大,需要大量的互联网带宽和浏览器负载。因此,在可能的情况下,如果不会导致性能问题,请从数据库中检索信息而不是将其保留在客户端。请注意,这个主题非常抽象,没有提供明确的答案。这是因为这类问题必须逐例处理,只有经验才能帮助开发人员做出最佳决定。
总而言之,ASP.NET 中的状态管理是使其极具生产力的功能之一,因为它使 Web 应用程序的数据处理变得更加容易。
历史
- 第一版
- 在 Session State 代码示例中,错误地使用了 Application 而不是 Session。现已更正。此外,简短的介绍也已更改为更精确的名称。
- 添加了更多标签和更多项到最终讨论中。
- 根据论坛用户的建议,为 Application 和 Session State 添加了示例。此外,在查询字符串说明中也添加了一个小示例。