65.9K
CodeProject 正在变化。 阅读更多。
Home

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

2014 年 6 月 17 日

CPOL

14分钟阅读

viewsIcon

52894

downloadIcon

396

本文解释了如何通过 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 的过程如下:

  1. 浏览器第一次向服务器发出请求 - 此处不使用 Cookie,因为浏览器仍然不知道服务器。
  2. 服务器以请求响应浏览器,并向浏览器提供一个 Cookie。
  3. 浏览器然后将收到的 Cookie 存储在客户端计算机上,通常是文件系统中的一个小文件。
  4. 浏览器使用此 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 应用程序的新趋势,GETPOSTPUT 等 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.asaxApplication_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 应用程序的数据处理变得更加容易。

历史

  1. 第一版
  2. 在 Session State 代码示例中,错误地使用了 Application 而不是 Session。现已更正。此外,简短的介绍也已更改为更精确的名称。
  3. 添加了更多标签和更多项到最终讨论中。
  4. 根据论坛用户的建议,为 Application 和 Session State 添加了示例。此外,在查询字符串说明中也添加了一个小示例。
© . All rights reserved.