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

应用程序状态的演练

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (81投票s)

2010 年 6 月 11 日

CPOL

11分钟阅读

viewsIcon

266759

本文讨论了应用程序级别的事件、应用程序状态以及一个简单的示例。

目录

引言

网络上有大量关于不同状态管理方法的文章。我找不到关于应用程序对象和事件的详细信息。因此,我阅读了不同的书籍和文章,并想与大家分享我的知识。希望大家喜欢并提出反馈和建议。

众所周知,Web 是无状态的。每次回发到服务器时,Web 页面都会重新创建。在传统的 Web 编程中,页面和控件中的所有信息都会在每次回发时被清除。为了克服这个问题,ASP.NET Framework 提供了各种方法来在不同阶段保留状态,例如控件状态、视图状态、Cookie、Session 等。这些可以定义在客户端和服务器端状态管理。请看下图。

Various option to maintain the states

图:可用于维护状态的选项

其中大多数都有很多介绍。在本文中,我将主要探讨应用程序状态应用程序事件应用程序对象

应用程序生命周期

首先,我将解释 ASP.NET 应用程序生命周期。我们需要真正理解应用程序生命周期,以便能够高效地编写代码并利用可用资源。此外,随着我们将深入探讨应用程序级别的事件、对象等,讨论这一点也非常重要。

ASP.NET 使用惰性初始化技术来创建应用程序域,即应用程序域仅在 Web 服务器收到第一个请求时才创建。我们可以将应用程序生命周期划分为几个阶段。这些阶段可以是:

  • 阶段 1: 用户首次从 Web 服务器请求任何资源。
  • 阶段 2: 应用程序收到应用程序的第一个请求。
  • 阶段 3: 应用程序基本对象被创建。
  • 阶段 4: 一个HTTPapplication对象被分配给请求。
  • 阶段 5: 请求由HTTPApplication管道处理。

我将逐一解释这些要点。

阶段 1: 当用户在浏览器中键入 URL 并访问时,应用程序的生命周期便开始。浏览器将此请求发送到 Web 服务器。当 Web 服务器收到来自浏览器的请求时,它会检查请求文件的文件扩展名,并检查需要哪个ISAPI 扩展来处理此请求,然后将请求传递给相应的 ISAPI 扩展。

URL Request Flow

图:URL 处理

注意 1:如果任何扩展名未映射到任何 ISAPI 扩展,则 ASP.NET 将不会收到该请求,并且该请求由服务器本身处理,ASP.NET 身份验证等将不适用。

注意 2:我们也可以创建自己的自定义处理程序来处理任何特定的文件扩展名。

阶段 2:当 ASP.NET 收到第一个请求时,应用程序管理器会为其创建一个应用程序域。应用程序域非常重要,因为它们在 Web 服务器上的各种应用程序之间提供隔离,并且每个应用程序域都会单独加载和卸载。在应用程序域中,会创建一个HostingEnvironment类的实例,该实例提供对所有应用程序资源的访问信息。

ALC1.JPG

图:ASP.NET 处理第一个请求

阶段 3: 在创建应用程序域和宿主环境后,ASP.NET 会初始化基本对象,如HTTPContextHTTPRequestHTTPResponseHTTPContext包含指向特定应用程序请求的对象,例如HTTPRequestHTTPResponseHTTPRequest包含有关当前请求的所有信息,例如 cookie、浏览器信息等,而HTTPResponse包含发送到客户端的响应。

阶段 4: 在这里,所有基本对象都已初始化,并且应用程序通过创建HTTPApplication类启动。如果应用程序中有Global.asax(它派生自HTTPApplication类),那么它将被实例化。

Multiple request processing

图:ASP.NET 处理的多个请求

注意:当应用程序首次被访问时,会为后续请求创建一个HTTPApplication实例。它也可以用于其他请求。

阶段 5:HTTPApplication类会执行很多事件。在这里,我列出了一些重要的事件。这些事件可以用于任何特定需求。

Application Events

图:应用程序事件

什么是 Global.asax

要处理应用程序事件或方法,我们可以在应用程序的根目录下创建一个名为Global.asax的文件。在任何单个时间点,HTTPApplication实例只处理一个请求,因此我们无需担心任何非static成员的锁定和解锁,但对于static成员,我们需要这样做。我将在本文后面的部分详细讨论这一点。以下是global.asax文件中常用的事件。

AppEvents.JPG

图:Global.asax 中的方法

应用程序何时重启

如果我们去更改源代码,ASP.NET 会要求重新编译成程序集,并且应用程序也会重启。尽管如此,还有一些事情会强制应用程序重启。如果我们更改了以下文件夹(添加、修改或删除),应用程序将重启:

  • 应用程序的bin文件夹中的任何更改
  • 本地化资源(即App_GlobalResourcesApp_LocalResources文件夹)中的更改
  • Global.asax文件中的更改

  • App_code文件夹中任何源代码的修改

  • web.config文件中的任何更改
  • Web 服务引用(即App_WebReferences文件夹)中的任何更改。

应用程序状态:简介

应用程序状态是服务器上存储某些数据的一种可用方法,并且比访问数据库更快。存储在应用程序状态中的数据可供访问该应用程序的所有用户(Session)使用。因此,应用程序状态对于存储在应用程序中使用且对所有用户都相同的少量数据非常有用。我们也可以称它们为跨用户可用的全局变量。既然我提到了少量数据,我们就不应该在ApplicationState中存储大量数据,因为它存储在服务器上,如果数据量很大,可能会导致性能开销。我们稍后会讨论这一点。从技术上讲,数据通过HTTPApplcationState类在用户之间共享,并且数据可以以键值对的形式存储在此处。也可以通过HTTPContext类的Application属性进行访问。

应用程序状态如何工作

正如我上面已经讨论过的,当任何用户第一次发出请求以访问应用程序的任何资源时,就会创建一个HttpApplicationState实例。可以通过HTTPContext对象的Application属性来访问它。所有HTTPModulesHandlers都可以访问此属性。值的生命周期跨越 ASP.NET 应用程序的整个生命周期,直到应用程序被卸载。通常,我们在Global.asax文件中的Application_OnStart事件中设置这些Application变量,并通过 ASP.NET 页面访问和修改它们。

如何在应用程序状态中保存值

需要记住的一点是,应用程序状态将数据存储为Object类型,因此在读取时,我们需要将值转换为适当的类型。

因此,我们通常在应用程序状态中存储应用程序范围的数据,这些数据是用户之间共享的。所以我们可以在Global.asax文件中的Application_OnStart方法中保存数据,如下所示:

void Application_Start(object sender, EventArgs e)
{ 
Application["Message"] = "Welcome to my Website"; 
}

我们也可以在Application变量中保存某个类的对象。假设我们有一个类,如下所示:

/// <summary>
/// Summary description for Employee
/// </summary>
public class Employee
{
    private string _name;
    public string Name {
        get
        {
            return _name;
        }
        set
        {
            _name = value;
        } 
    }
    private decimal _annualSalary;
    public decimal AnnualSalary
    {
        get
        {
            return _annualSalary;
        }
        set
        {
            _annualSalary = value;
        }
    }
	public Employee()
	{
		//
		// TODO: Add constructor logic here
		//
	}
}

将此类文件放在App_Code文件夹中。现在该类将在整个应用程序中可用,也在Global.asax中可用。现在,在Application_OnStart中保存它,如下所示:

   void Application_Start(object sender, EventArgs e) 
    {
        Employee objEmployee = new Employee();
        objEmployee.Name = "Brij";
        objEmployee.AnnualSalary = 1000000;
        Application["EmployeeObject"] = objEmployee;
    }

注意:这里我想提一点,我们不需要序列化对象来存储在应用程序状态中,正如我们在视图状态等情况下需要记住的那样。所以这里不需要serialization。:) 我们也可以在应用程序的任何方法中修改这些值。这里我在一个页面的按钮的onclick方法中修改Application["Message"],如下所示:

protected void Button1_Click(object sender, EventArgs e)
    {
        Application["Message"] = "Welcome Dude!!";
    }

现在,当我们尝试访问它们时,我们将获得修改后的值。让我们在另一个按钮的 Click 事件中添加一个新变量:

 protected void Button3_Click(object sender, EventArgs e)
 {
     Application["NewValue"] = "Hello";
 }

此值在应用程序生命周期内也可用。我还有一件事想讨论,那就是应用程序状态不是线程安全的,因此多个线程可以同时访问它。也就是说,如果我们以应用程序状态存储了某个值(例如某个计数器),并且每当访问某个页面时就增加它。那么在某个时刻,来自不同会话的页面的两个实例可以读取相同的值并更新它。这样我们就无法得到预期的结果。所以这里我们需要某种同步机制,以便一次只有一个线程可以更新其值。在这里,我们可以调用System.Web.HttpApplicationState.Lock方法,设置应用程序状态值,然后调用System.Web.HttpApplicationState.UnLock方法来解锁应用程序状态,使其可供其他线程写入或更新,如下所示:

  if (Application["Counter"] != null)
        {
            Application.Lock();
            Application["Counter"] = ((int)Application["Counter"]) + 1;
            Application.UnLock();
        }

这样,我们可以避免多个线程同时写入相同的值。

如何从应用程序状态读取值

所以从应用程序状态读取相当简单。我们应该进行安全检查,看看我们正在访问的值是否为null。如果应用程序状态中不存在该数据,它将返回null,如果我们尝试将其转换为任何其他类型,它将引发异常。正如我已经讨论过的,应用程序状态以对象形式存储数据,因此在读取后我们需要进行类型转换。所以我们可以这样读取值:

if(Application["Message"] !=null)
{
      string message = Application["Message"] as string;
}

也可以这样读取对象:

if (Application["EmployeeObject"] != null)
{
    Employee myObj = Application["EmployeeObject"] as Employee;
    string Name = myObj.Name;
}

应用程序状态的经典示例

应用程序变量的一个经典示例是显示网站的在线用户数。这可以按以下步骤完成:

  • Global.asax文件的ApplicationStart方法中添加一个在线计数器变量,如下所示:
    Application["OnlineCounter"] = 0; 

    这样,当应用程序首次启动时,就会添加一个变量,并将其初始化为0,因为此时不会有登录用户。

  • 现在,我们知道每当新用户打开网站时,都会创建一个新的会话,并调用Global.asaxSession_Start方法。所以我们可以在此方法中增加计数器:
        void Session_Start(object sender, EventArgs e) 
        {
            // Code that runs when a new session is started
           if (Application["OnlineCounter"] != null)
            {
                Application.Lock();
                Application["OnlineCounter"] = 
    			((int)Application["OnlineCounter"]) + 1;
                Application.UnLock();
            }
        }

    我们应该使用锁定,否则可能会得到错误的结果,因为这可能同时被更新,而更新后的数据不正确。例如:假设我们当前Application["OnlineCounter"]5,同时,两个会话读取值5并将其递增到6并更新。应用程序状态为6。因此,尽管有两个用户登录,计数器只增加了一次。为了避免这种情况,我们应该使用锁定。

  • 因此,当会话结束时,我们也应该将其减一。正如我上面已经讨论过的,每当会话结束时,都会触发Session_End事件。因此,可以这样做:
        void Session_End(object sender, EventArgs e) 
        {
            // Code that runs when a new session is started
            if (Application["OnlineCounter"] != null)
            {
                Application.Lock();
                Application["OnlineCounter"] = 
    		((int)Application["OnlineCounter"]) - 1;
                Application.UnLock();
            }
        }
  • 并且该值可以在应用程序的任何时间点的整个应用程序中访问,如下所示:
      if (Application["OnlineCounter"] != null)
            {
                int OnlineUsers = ((int)Application["OnlineCounter"]);
            }    

    并且该值可以在应用程序中的任何地方使用。

应用程序状态:使用前的注意事项

  • 应用程序状态存储在 Web 服务器的内存中,因此大量数据可能会导致严重的性能开销。还要记住,这些变量将存储在内存中,直到应用程序结束,无论我们是否需要它们。所以要谨慎使用。
  • 如果应用程序崩溃或重新启动,那么存储在应用程序状态中的所有数据都将丢失。
  • 此外,在 Web Farm 场景中,应用程序数据不会在多个服务器之间共享。
  • 在 Webgarden 的情况下,应用程序状态也不起作用。在这些场景中存储在应用程序状态中的变量仅对应用程序正在运行的特定进程是全局的。每个应用程序进程都可以有不同的值。
  • 应用程序状态不是线程安全的,因此我们必须考虑同步,如上所述。

应用程序状态与缓存

尽管两者都提供相同的功能,并且可以用于在应用程序级别存储数据,但两者之间有很多区别。

序号 应用程序状态 缓存
1 应用程序变量是 ASP/ASP.NET 提供的在应用程序级别存储数据的技术之一,并且所有用户都可以访问。 缓存是由 ASP.NET 提供的一种在应用程序级别存储数据的方法,具有许多选项。
2 应用程序变量在应用程序的整个生命周期内都可用,直到被显式删除或覆盖。 缓存是易失的。它提供了根据设置自动过期数据的机会,或者当内存不足时回收内存。
3 应用程序变量仅应用于有限的数据,即不应该存储数据集并不断向其中添加数据,这可能会导致 Web 服务器拥塞。 缓存提供了一种灵活的方式将数据存储在 Web 服务器上,并在内存不足时将其删除。

反馈与建议

反馈对我来说一直是一个关键领域。我请求大家分享您的反馈并给我一些建议,这将鼓励我写更多内容。

© . All rights reserved.