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

ASP.NET 中状态管理技术的初学者指南

2008年12月23日

CPOL

23分钟阅读

viewsIcon

415284

downloadIcon

3448

本文讨论了 ASP.NET 中使用的状态管理技术。我们将讨论:QueryString、Cookie、Session、Profile、静态变量和应用程序状态。

目录

介绍

最近 Code Project 启动了一个针对 Web 开发的初学者指南,这是一项将 Web 开发所需的所有信息组织到一篇文章中的倡议。我认为这对于初学者获得 Web 开发的指导将很有帮助。本文是我对 ASP.NET 部分的贡献,其中我描述了在 ASP.NET 开发中使用的状态管理技术和最佳实践。

HTTP 协议和状态管理技术的需求

超文本传输协议(HTTP)是一种在“万维网(WWW)”中实现的通信协议。它是一种请求/响应式协议。客户端(浏览器、爬虫等)将向服务器(Web 服务器)发送请求,服务器响应这些请求。HTTP 使用 TCP 协议进行通信。它连接到服务器上的特定端口(默认为 80),并通过该端口进行通信。一旦完全收到响应,客户端程序将与服务器断开连接。对于每个请求,客户端程序必须重新获取与服务器的连接并再次执行所有请求周期。

ASP.NET 文件只是放置在服务器上并根据请求提供服务的文本文件。当页面请求到来时,服务器会找到请求的文件并要求 ASP.NET 引擎处理请求。ASP.NET 引擎将处理服务器标签并为其生成 HTML,然后返回给客户端。HTTP 是一种无状态协议,服务器在处理完请求后会放弃连接。

由于 HTTP 是无状态的,因此在 Web 应用程序中管理状态具有挑战性。状态管理技术用于在整个应用程序中维护用户状态。在独立应用程序中管理状态很简单,因为它们没有请求/响应的性质。您希望在 Web 应用程序生命周期中维护的信息取决于您的上下文。它可以是从简单的数字到非常复杂的对象。

理解状态管理技术在创建高效 Web 应用程序中起着重要作用。ASP.NET 在状态管理技术方面非常丰富。以下是常用的状态管理技术。

  • 查询字符串(QueryString)
  • Cookie
  • 缓存
  • 视图状态(ViewState)
  • 会话状态(Session state)
  • 应用程序状态(Application state)
  • 静态变量
  • 配置文件

CacheViewState 已在前面的章节中解释,本文将不予介绍。

查询字符串(QueryString)

这是在请求之间维护信息最简单有效的方法。您想要维护的信息将随 URL 一起发送。带有查询字符串的典型 URL 如下所示:

 www.somewebsite.com/search.aspx?query=foo

URL 中 ? 符号后面的部分称为 QueryString。QueryString 有两部分:键和值。在上面的示例中,query 是键,foo 是它的值。您可以通过查询字符串发送多个值,用 & 符号分隔。以下代码演示了向 foo.aspx 页面发送多个值。

 Response.Redirect("foo.aspx?id=1&name=foo");

foo.aspx 页面将按以下表格获取值。

id 1
名称 foo

以下代码显示了在 foo.aspx 中读取 QueryString 值

 string id = Request.QueryString["id"];
 string name = Request.QueryString["name"];

Request.QueryString 有两个重载的索引器。一个接受键名,另一个接受基于零的索引。后者在您不知道查询字符串名称时很有用。如果您尝试获取不在 QueryString 集合中的值,您将获得一个 NULL 引用。

查询字符串编码

URL RFC [^] 声明 URL 只能包含 ASCII 字符。因此,所有其他字符在通过 URL 传递时都应该进行编码。.NET Framework 提供了 HttpServerUtility.UrlEncode 类来编码 URL。

以下代码展示了如何将 nameid 作为编码值传递到 foo.aspx 页面。

  string id = "1";
  string name = "foo#";
  string url = string.Format("foo.aspx?{0}&{1}", Server.UrlEncode(id), Server.UrlEncode(name));
  Response.Redirect(url);

  // decoding can be done using it's counter part HttpServerUtility.UrlDecode()

  string id = Server.UrlDecode(Request.QueryString["id"]);
  string name = Server.UrlDecode(Request.QueryString["name"]);

注意: HttpServerUtility 可通过 Page.Server 属性在所有 Web 表单的代码中访问。此外,您只需编码包含非 ASCII 字符的值,而不是所有值。

安全读取查询字符串

我见过很多人使用如下代码从 QueryString 集合中读取值

 
  // Bad practice - Don't use!
  Request.QueryString["id"].ToString();

这很滑稽。Request.QueryString[key] 返回一个 string,无需再次将其转换为字符串。上述代码的另一个问题是,如果集合中找不到指定的键,Request.QueryString[key] 将返回 NULL。在这种情况下,调用 Request.QueryString[key].ToString() 将导致 NullReferenceException。以下代码演示了如何安全地读取 QueryString 值。

  string queryStringValue = Request.QueryString["key"];
  if (string.IsNullOrEmpty(queryStringValue)) {
      // querystring not supplied. Do necessary action
  }
  else
      // Querystring supplied. continue execution

优点和缺点

查询字符串轻量级,不消耗任何服务器资源。它非常易于使用,是最有效的状态管理技术。然而,它有许多缺点。

  • 您只能以字符串形式传递信息。如果您需要通过 QueryString 传递任何对象,这篇优秀的 文章 [^] 中解释的方法会起作用。但这需要更多的努力。
  • URL 长度有限制。因此,您不能通过 URL 发送太多信息。
  • 传递的信息对所有人清晰可见,并且可以轻松修改。

生成可被修改的 URL

URL 应该可被修改,这意味着用户应该能够通过修改 QueryString 值轻松地从一个页面导航到另一个页面。但是您需要确保通过实现此功能,您的网站安全性不会受到损害。MSDN 上有一个可被修改的 URL 的好例子。考虑以下内容:

 // System.Object classes documentation for .NET 1.1
 http://msdn.microsoft.com/en-us/library/system.object(VS.71).aspx 

 // System.Object classes documentation for .NET 2.0. Just change the 71 to 80
 http://msdn.microsoft.com/en-us/library/system.object(VS.80).aspx

这有助于用户更轻松地在页面之间导航。

保护查询字符串

由于 QueryString 中的值容易被篡改,因此程序员有责任确保其具有有效值。您可能会想,如果 QueryString 不安全,那么为什么不使用一些安全的状​​态管理技术来保护它呢?在某些情况下,您无法避免使用 QueryString,并且您将被迫通过它发送敏感信息。考虑一个用户电子邮件验证系统。当用户注册时,系统将发送一封带有激活用户帐户链接的邮件。在这种情况下,我们必须随 URL 一起传递用户帐户的一些标识。由于用户帐户信息是敏感的,我们必须加密电子邮件验证链接中的 QueryString 值。

考虑一个 Edit.aspx?pid= 页面,它允许编辑您的个人详细信息。您需要确保提供的个人 ID 具有有效类型,在本例中为整数。以下代码显示了如何完成此操作:

  string queryStringValue = Request.QueryString["pid"];
  if (string.IsNullOrEmpty(queryStringValue)) {
      // querystring not supplied. Do necessary action
  }
  else {
      // Querystring supplied. continue execution
      int personId;
      if(!int.TryParse(queryStringValue,out personId))
           // invalid type specified. Alert user
  }

然后您可能需要检查当前登录用户是否有权编辑此记录。

 int personId;
 if (int.TryParse(queryStringValue, out personId)) {
        // check the edit permissions here
 }

System.Security.Cryptography 命名空间中的类有助于加密字符串。您可以将加密的字符串通过 URL 传递,使其在一定程度上防篡改和安全。

Cookies

Cookie 是一个存储在访问者硬盘上的小文件。它有助于存储少量和不重要的信息。根据 RFC [^],一个 Cookie 的最大大小为 4KB。Web 服务器创建 Cookie,在响应中附加一个额外的 HTTP 头,并将其发送到浏览器。然后浏览器会在访问者的计算机中创建此 Cookie,并在对同一域的所有后续请求中包含此 Cookie。服务器可以从请求中读取 Cookie 值并保留状态。

服务器在 HTTP 头中添加以下内容以创建 Cookie

  Set-Cookie: key=value

浏览器读取上述值并在用户端创建 cookie。它将 cookie 值添加到请求中,如下所示:

  Cookie: key=value

注意: Cookie 的存储位置完全由浏览器控制。有时它可能将 Cookie 保留在内存中而不是创建文件。

在 ASP.NET 中创建和使用 Cookie 是微不足道的。HttpCookie 类是一个键/值集合,允许存储字符串值。以下代码演示了如何创建 Cookie 并将其发送到客户端。Cookie 使用 Response 属性添加,并使用 Request 属性检索。

  Response.Cookies["id"].Value = "10";

由于未指定过期时间,通过上述方法添加的 Cookie 将在浏览器关闭时立即被清除。如果您希望长时间保留 Cookie,则必须使用设置了过期日期的 HttpCookie.Expires 属性。以下代码演示了如何实现这一点。

  // this cookie expires after one day from the date it is set
  // browser will take care about removing the cookies after the expiry time
  Response.Cookies["id"].Value = "10";
  Response.Cookies["id"].Expires = DateTime.Now.AddDays(1);

一旦设置了 Cookie,浏览器将为每个请求都包含它。您可以通过指定 Cookie 名称从 Request.Cookies 集合中读取 Cookie。考虑以下代码:

  // for safety, always check for NULL as cookie may not exist
  if (Request.Cookies["id"] != null) {
       string userId = Request.Cookies["id"].Value;
       Response.Write("User Id value" + userId);
  }

Cookie 由浏览器管理,并负责删除过期的 Cookie。如果需要在过期期限之前删除 Cookie,您必须创建一个同名且过期日期已过的 Cookie。这将使浏览器认为该 Cookie 已过期,并立即将其删除。操作方法如下:

  Response.Cookies["id"].Expires = DateTime.Now.AddDays(-1);

使用 Cookie 前必须了解的一些有用属性

属性名 描述
定义域 指定与此 Cookie 关联的域。默认为当前域。请参阅本文后面的安全限制。
过期 一个 DateTime 值,指定 Cookie 的过期时间
HttpOnly Cookie 可以使用 JavaScript 访问。设置此属性可防止 Cookie 从 JavaScript 访问
安全 如果 Cookie 通过 SSL 传输,则设置此项。
名称 Cookie 名称
Cookie 值(字符串)

多值 Cookie

RFC 规定浏览器不应从一个域存储超过 20 个 Cookie。当您需要在 Cookie 中保存更多项目时,多值 Cookie 非常方便。要创建多值 Cookie,您需要实例化 HttpCookie 实例并设置其值。请看以下代码:

  HttpCookie cookie = new HttpCookie("user");
  cookie["name"] = "Foo";
  cookie["age"] = "22";
  cookie.Expires = DateTime.Now.AddDays(1);
  Response.Cookies.Add(cookie);

以下是读取方式

  HttpCookie cookie = Request.Cookies["user"];
  // for safety, always check for NULL. If cookie doesn't exist, it will be NULL
  if (cookie != null) {
      string name = cookie["name"];
      string age = cookie["age"];
  }
  else
     // Cookie not exist

一个实际示例

您可能已经注意到大多数网站上的“下次记住我”选项。这是通过使用 Cookie 完成的。当您选择此选项时,将涉及以下步骤。

  • 当用户选中“下次记住我”选项时,创建一个带有标识用户值(例如:用户 ID)的 Cookie。
  • 当页面加载时,检查 Cookie 是否存在。如果存在,则读取 Cookie 值。
  • 验证值并创建会话。

安全限制

由于 Cookie 存储在访问者的计算机中,为了防止其损害系统,浏览器会确保对 Cookie 施加一些安全限制。以下几点解释了这些安全限制:

  • Cookie 特定于域,这意味着从“域 A”设置的 Cookie 无法访问“域 B”。浏览器将每个 Cookie 与域一起存储,并确保它不会发送到另一个域。
  • 另一个限制是大小。浏览器不允许从一个域存储超过 4KB 的 Cookie。
  • 浏览器提供禁用 Cookie 的选项

应用程序的正确设计可以避免通过 Cookie 发起的恶意攻击。考虑一个网站,它将当前用户 ID 存储在 Cookie 中,以便在后续访问中保留用户帐户信息。由于 Cookie 以纯文本形式保存在访问者的系统中,用户可以随时进入存储 Cookie 的文件夹并将用户 ID 更改为其他值。当再次请求网站时,它将使用新的用户 ID,这将使黑客访问新帐户。

虽然 Cookie 主要用于存储账户信息,但它们并非真正为此目的而设计。您应始终采取预防措施,通过在将值存储到 Cookie 中之前对其进行加密来避免上述黑客攻击。

优点和缺点

Cookie 是一种非常方便且易于使用的状态管理技术。当您想要存储需要长时间保留的少量信息时,它很有用。与会话相比,Cookie 的处理开销要小得多。然而,它具有以下缺点:

  • Cookie 有 4KB 的大小限制。无法存储大量信息。
  • Cookie 存储在客户端机器上,容易被篡改。因此,在使用它们时需要进行额外的安全检查。
  • 用户可以禁用 Cookie。

会话状态

Cookie 非常简单,不适用于复杂的存储需求。会话状态是解决此问题的一种变通方法,它提供了一种安全地保存更复杂对象的方法。ASP.NET 允许程序员将会话中保存任何类型的对象。存储在会话中的数据将保存在服务器内存中,并且受到保护,因为它永远不会传输到客户端。使用应用程序的每个客户端都将有单独的会话。会话状态是存储用户特定信息的理想选择。

以下代码显示了将会话中存储字符串值。

 Session["name"] = "Navaneeth";

会话接受 System.Object 类型。因此,在读取时需要进行类型转换。从会话中读取值的方式如下:

 string name = Session["name"] as string;
 // null checking is needed as session may not exist
 if(!string.IsNullOrEmpty(name))
      // use the name here

存储在会话中的值可以通过几种方法删除。下表显示了所使用的不同方法。

方法 描述
Session.Abandon() 取消会话并触发 end 事件。这在完成会话时使用。
Session.Clear() / Session.RemoveAll() 清除会话的所有内容。这不会结束会话
Session.Remove(string) 删除提供的会话名称。

会话如何工作?

ASP.NET 为每个会话维护一个唯一的 ID,称为“会话 ID”。此 ID 是使用自定义算法生成的,并且始终是唯一的。会话 ID 将作为 Cookie 发送到客户端,浏览器在每个请求时都会重新发送此 ID。ASP.NET 使用此会话 ID 来识别会话对象。以下代码显示了如何获取会话 ID

 string sessionId = Session.SessionID;

如果您没有在会话中存储任何内容,ASP.NET 将为每个请求生成不同的会话 ID。一旦会话包含内容,会话 ID 将不会更改。会话 ID 是唯一发送给客户端的会话信息。如前所述,ASP.NET 在名为 ASP.NET_SessionId 的 Cookie 中发送会话 ID。但是,如果访问者禁用了 Cookie,这将不起作用。在这种情况下,ASP.NET 通过 URL 传递会话 ID。这种行为可以通过在 web.config 文件中的 system.web 部分下添加以下部分来控制。

<sessionState 
      cookieless="UseUri" />
Cookieless session

注意: 如果启用此功能,ASP.NET AJAX 扩展将无法按预期工作。

会话超时

每个会话都将有一个超时值(默认为 20 分钟)。如果在指定的超时限制内页面没有收到任何请求,ASP.NET 将假定用户已离开应用程序,并立即终止会话并触发 End 事件。这有助于服务器清理未使用的会话,并为新的请求腾出空间。超时值可以通过 web.config 文件或通过代码进行更改。超时值以分钟为单位指定。

 <sessionState 
        timeout="60" />

 or

 Session.Timeout = 60;

会话频繁超时

我见过讨论论坛上有很多问题,声称“我的会话超时是 60 分钟,但它在此之前就超时了。” 嗯,ASP.NET 会在以下任何一种情况发生时清除会话:

  • ASP.NET 工作进程频繁回收。发生这种情况时,它将清除所有活动会话。
  • web.config 或应用程序程序集等文件被修改时,ASP.NET 将回收工作进程。

会话存储在哪里?

ASP.NET 允许三种类型的会话存储,如下所述:

模式 配置 存储位置 描述 优点/缺点
InProc <sessionState mode="InProc" /> ASP.NET 进程内存区域 这是默认的会话存储。会话数据将保存在服务器内存中。InProc 模式性能很高,因为它从同一进程的内存中读取,并且允许您保留所有 .NET 类型。如果未指定会话模式,ASP.NET 将使用 InProc 作为默认模式。 由于 InProc 模式将数据保存在同一进程内存区域中,因此对 ASP.NET 工作进程的任何影响都将影响会话数据。
StateServer <sessionState
mode="StateServer"
stateConnectionString= "tcpip=您的服务器名:42424" />
服务器内存作为独立进程 StateServer 模式为数据存储提供了基本级别的隔离。它作为一个独立的 Windows 服务运行,并将会话数据保存在 ASP.NET 进程内存区域之外。要访问会话,ASP.NET 进程必须与此外部进程进行通信。 InProc 模式相比,它的性能较低。但这有助于您在 ASP.NET 工作进程重新启动时避免丢失会话数据。
SQL Server <sessionState mode="SQLServer" sqlConnectionString="..." /> 在 SQL Server 数据库中 如果您仍然需要更具弹性的存储,SQLServer 模式是您的选择。它允许将会话数据保存在 SQLServer 中。当您的 Web 应用程序托管在 Web 农场中时,这会很有帮助。
  • 数据访问速度慢
  • 只允许存储可序列化类型

如果上述任何方法都不能满足您的存储要求,ASP.NET 允许指定自定义存储提供程序。本文 [^] 展示了如何做到这一点。

用法和最佳实践

会话使用不当会导致您的应用程序崩溃。用户最常见的错误是使用会话时出现 NULL 引用异常。考虑以下代码:

  // bad code ! don't use
  string name = Session["name"].ToString();

此代码存在问题,因为 session["name"] 可能不存在或可能为 NULL,并且将在此 NULL 引用上调用 ToString(),这将抛出常见的“对象引用未设置为对象的实例”错误。

会话的另一个问题是它不是强类型的。会话保存 System.Object 类型,这意味着任何 .NET 类型都可以保存在会话中。考虑以下代码:

 Session["age"] = "I can store a value that is not number!";

由于它不是强类型,Session["age"] 可以包含任何值,并且在使用时会遇到问题。此外,您在输入会话名称时可能会出现拼写错误。这也将导致意外行为。以下部分描述了针对这些问题的变通方法。

将会话封装在强类型类中

为了解决上述问题,我们可以在会话周围创建强类型包装器类,并通过此包装器路由所有对会话的调用。考虑一个简单的场景,您需要在会话中保存用户详细信息,例如姓名、年龄、电子邮件验证等。我们创建一个类来表示所有必需的字段。请参阅以下代码:

 public class PersonSession
 {
    // This key is used to identify object from session
    const string KEY = "personDetails";

    public PersonSession(int id,string name,int age,bool emailValidated)
    {
        this.Id = id;
        this.Name = name;
        this.Age = age;
        this.HasEmailValidated = emailValidated;
    }

    public static PersonSession GetPersonSession() {
        return HttpContext.Current.Session[KEY] as PersonSession;
    }

    public static void CreatePersonSession(PersonSession person) {
        HttpContext.Current.Session[KEY] = person;
    }

    public int Id { get; private set; }
    public string Name { get; private set; }
    public int Age { get; private set; }
    public bool HasEmailValidated { get; private set; }
 }

上面给出的类抽象了会话访问,并提供了一个清晰的接口来安全地访问会话内容。静态方法 CreatePersonSessionGetPersonSession 可用于从会话中创建和获取人员详细信息。以下代码显示了如何将人员详细信息存储到会话中。

 PersonSession person = new PersonSession(int.Parse(txtPersonId.Text),
            txtName.Text, int.Parse(txtAge.Text), chkEmailValidated.Checked);
 PersonSession.CreatePersonSession(person);

要检索人员详细信息,您需要执行以下操作

 PersonSession person = PersonSession.GetPersonSession();
 // if session not exist, this will return NULL
 if (person != null) {
    // person exist. Use person's properties to get values
 }

注意: 当从非 System.Web.UI.Page 派生的类访问时,需要 HttpContext.Current.Session

基页类方法

假设您需要阻止用户查看您的页面,除非会话中存在人员详细信息。您最终可能会为所有需要保护的页面执行以下代码。

 protected void Page_Load(object sender, EventArgs e)
 {
        PersonSession person = PersonSession.GetPersonSession();
        // if session not exist, this will return NULL
        if (person == null) {
            // session not exist. Redirect user to login page
        }
 }

在所有需要会话的页面中编写上述代码是多余的。如果会话包装类-PersonSession 中发生任何更改,则很难进行更改。推荐的方法是保留一个从 System.Web.UI.Page 派生的基页类,并且所有需要 PersonSession 的页面都应该继承自这个基页类。这允许您在基类中进行会话检查,并在需要时重定向用户。下图显示了这种方法。

我们创建了两个类 NormalPageSecuredPage,它们都派生自 System.Web.UI.PageSecuredPage 重写了 OnInit 方法,如下所示:

 protected override void OnInit(EventArgs e) {
        base.OnInit(e);
        // check the person details existance here
        PersonSession person = PersonSession.GetPersonSession();
        if (person == null) {
            Response.Redirect("NotLogged.aspx");
        }
        else
            this.Person = person;
 }

如果在会话中未找到人员详细信息,它会重定向到 NotLogged.aspx 页面,如果会话存在,它会设置 Person 属性。从该类派生的类可以使用此 Person 属性来访问人员详细信息。该类的用法非常简单。请参阅以下代码。

 public partial class AuthenticatedPage : SecuredPage
 {
    protected void Page_Load(object sender, EventArgs e)
    {
        lblMessage.Text = "Session exist";
        lblMessage.Text += string.Format("Person Id : {0}",Person.Id);
        lblMessage.Text += string.Format("Person Name : {0}", Person.Name);
        lblMessage.Text += string.Format("Person Age : {0}", Person.Age);
        lblMessage.Text += string.Format("Email validated? : {0}", Person.HasEmailValidated);
    
    }
 }

注意: 我们在此处未使用 NormalPage。如果您有任何适用于所有未验证页面的特定行为,此类别是添加该行为的最佳位置。

以上就是关于会话的所有内容。使用时,尽量不要滥用会话。过度使用会话可能会降低您的应用程序性能。

应用程序状态

ASP.NET 使用 System.Web.HttpApplicationState 类实现应用程序状态。它提供了存储可以全局访问的信息的方法。存储在应用程序状态中的信息将可供使用该网站的所有用户使用。应用程序状态的用法与会话相同。以下代码演示了在应用程序变量中存储值并从中读取值。

 Application["pageTitle"] = "Welcome to my website - ";

 // Reading the value from application variable
 string pageTitle;
 if (Application["pageTitle"] != null)
      pageTitle = Application["pageTitle"].ToString();

理解会话和应用程序事件

在讨论应用程序状态的实际用法之前,您应该对与应用程序和会话相关的事件有基本的了解。这些事件可以在 global.asax 文件中看到。有关详细信息,请参阅下表。

事件名称 描述
Application_Start 此事件在应用程序初始化时执行。当 ASP.NET 工作进程回收并重新启动时,此事件将执行。
Application_End 在应用程序结束时执行。
Session_Start 在新会话开始时执行。
Session_End 会话结束时执行。注意:此事件仅当您使用 InProc 作为会话模式时才会触发。

一个实际示例

应用程序变量最常见的用途是计算当前正在浏览的活跃访问者数量。我们可以利用 session_startsession_end 事件来完成此操作。以下代码展示了如何实现这一点。

 void Application_Start(object sender, EventArgs e) 
 {
     // Application started - Initializing to 0
     Application["activeVisitors"] = 0;

 }
 
 void Session_Start(object sender, EventArgs e) 
 {
     if (Application["activeVisitors"] != null) {
         Application.Lock();
         int visitorCount = (int)Application["activeVisitors"];
         Application["activeVisitors"] = visitorCount++;
         Application.UnLock();
     }
 }

您可能已经注意到 Application.Lock()Application.UnLock() 调用。ASP.NET 是多线程的,当多个访问者同时访问站点时,这对于数据同步是必需的。

应用程序状态不像会话那样提供任何超时方法。应用程序状态将一直可用,直到应用程序结束。因此,在使用应用程序状态时必须非常小心。在使用完毕后,您应该显式清理存储的值。

将会话保存在静态(VB 中为共享)变量中

静态变量的生命周期直到它所托管的应用程序域结束。ASP.NET 将每个网站托管在单独的应用程序域中,以提供与同一服务器上托管的其他网站的隔离。

假设您有一个页面,其中显示所有产品详细信息。产品详细信息从数据库中获取并填充到自定义集合中,然后返回到页面。为了避免每次访问者都获取产品详细信息,我们可以在第一次请求时加载它,并将其保存在静态变量中以供后续请求使用。考虑以下 ProductProductServices 类。

 class Product
 {
        public Product(int id,string name) {
            this.Id = id;
            this.Name = name;
        }
        public int Id { get; private set; }
        public string Name { get; private set; }
 }

 class ProductService
 {
        static List<Product> products = null;
        static readonly object locker = new object();

        public static List<Product> GetAllProducts() {
            // If product is NULL, loading all products and returning
            if (products != null) {
                lock (locker) {
                    if (products != null) {
                        // fill the products here
                    }
                }
            }
            return products;
        }
 }

以上代码不言自明。变量 products 将在应用程序域卸载之前一直存在。当第一次调用 ProductService.GetAllProducts() 时,它会填充集合并返回。对于后续请求,它将只返回已填充的集合。

保存在静态变量中的值可供网站中的所有访问者访问。因此,在编写上述方法时应格外小心。您需要锁定,因为多个访问者可能同时调用 GetAllProducts 方法。

上述示例是“ 单例模式 [^] ”的一种实现。

配置文件(Profiles)

当访问者离开网页时,会话数据会丢失。如果您需要长时间保存所有用户信息怎么办?ASP.NET Profile 就是答案。它提供了一种整洁的方式来长时间保存信息。创建 Profile 是微不足道的。您只需要在 web.config 文件中进行一些条目,如下所示:

 <profile>
    <properties>
       <add name="Id" type="Int32"/>
       <add name="Name"/>
       <add name="Age" type="Int32"/>

    </properties>
 </profile>

ASP.NET 生成一个强类型类来访问配置文件数据。属性的数据类型根据 type 值选择。如果未指定类型,则默认类型为 string。以下代码显示了设置值并从配置文件中读取。

 Profile.Name = txtName.Text;
 Profile.Id = int.Parse(txtPersonId.Text);
 Profile.Age = int.Parse(txtAge.Text);

 // to read profile value, use

 int age = Profile.Age;
 string name = Profile.Name;
 int id = Profile.Id;

ASP.NET 将配置文件数据保存在 SQLServer 数据库中。如果项目中没有可用的数据库,它将在第一次使用时在 app_data 目录中创建一个数据库文件。配置文件是使用提供程序模式实现的。SQLProfileProvider 是默认的配置文件提供程序。配置文件默认使用 Windows 身份验证。配置文件对象可以与 ASP.NET 支持的任何身份验证模式一起使用。

配置文件在许多情况下都非常方便。然而,它有以下缺点:

  • 它只允许保存可序列化类型
  • 从配置文件读取数据需要数据库访问,这可能会降低您的应用程序性能。如果您的网站大量使用配置文件,则必须缓存结果以避免不必要的数据库调用。
这是对配置文件的总体概述。配置文件还提供许多其他功能,例如组、匿名访问等。详细解释整个配置文件超出了本文的范围。请查阅 这篇 [^] MSDN 文章以获取更多详细信息。

破解 ViewState

ViewState 已在上一 [^] 中解释。谈论 ViewState 的文章总是说它不够安全,不适合保存安全信息。让我们看看 ViewState 值是如何被破解的。

保留在 Viewstate 中的数据使用 LosFormater 进行序列化,这是一个鲜为人知的用于序列化的类。LosFormatter 有助于序列化简单类型,并生成对象图的 ASCII 字符串表示形式。以下代码显示了如何使用 LosFormatter

  [Serializable]
  class Customer
  {
       public Customer(int age,string name) {
           this.Age = age;
           this.Name = name;
       }
       public int Age { get; set; }
       public string Name { get; set; }
  }
  
  string Serialize() {
        
     // Creating customer object
     Customer customer = new Customer(25,"Navaneeth");

     // formatting
     LosFormatter formatter = new LosFormatter();
     using (StringWriter output = new StringWriter()) {
        formatter.Serialize(output, customer);
        return output.ToString();
     }
  }

上述代码序列化对象图并生成可在 HTTP 上传输的 ASCII 字符串。

以下代码显示了如何解密视图状态值。

  object HackViewstate(string viewStateValue) {
        LosFormatter formatter = new LosFormatter();
        return formatter.Deserialize(viewStateValue);
  }

下图显示了反序列化对象的内部结构

这清楚地解释了为什么不应该将安全数据保存在视图状态中。

结论

本文探讨了 ASP.NET 中使用的状态管理技术。您已经了解了 HTTP 协议以及状态管理的需求。我们讨论了 HTTP 协议的无状态架构以及网站的工作方式。

在第二部分,我们讨论了 QueryString 以及它如何帮助在不同页面之间维护信息。我们还讨论了可被修改的 URL 以及使用它的一些最佳实践。

第三部分讨论了 Cookie 的用法。我们已经看到了 Cookie 的优点和缺点。我们还讨论了多值 Cookie,它有助于克服网站可以设置的 Cookie 数量。此外,还讨论了安全限制和一个实际示例。

下一节讨论了提供更复杂存储的“会话状态”。我们已经了解了会话的工作原理、会话模式和使用它的最佳实践。我们还讨论了会话超时和无 Cookie 会话。

下一节讨论“应用程序状态”。讨论了与会话状态和应用程序状态相关的事件。我们看到了一个应用程序状态非常方便的实际示例。然后我们讨论了将状态存储在 static(VB 中为共享)变量中。我们了解了静态变量的生命周期以及如何使用锁定来同步多线程之间的访问。当您需要长时间保存信息时,配置文件非常方便。我们在下一节讨论了配置文件,并看到了配置文件的简单用法。

最后,我们讨论了 ViewState 破解技术,并证明了不应将安全信息保存在 ViewState 中。我们讨论了序列化类 LosFormatter 及其工作原理。

这就是本文的全部内容。我希望您会觉得它有帮助。如果您能评价这篇文章并在下面留下您的反馈,我将不胜感激。

感谢阅读,编程愉快!

历史

  • 12月23日 - 初次发布
© . All rights reserved.