实现会话状态存储提供程序

更新:2007 年 11 月

描述自定义会话状态存储提供程序实现,并演示示例提供程序实现。

使用 ASP.NET 会话状态可以将用户会话数据存储在不同的源中 。默认情况下,会话状态值和信息都存储在 ASP.NET 进程的内存中。一个方法是将会话数据存储在状态服务器中,状态服务器将会话数据保存在单独的进程中,如果 ASP.NET 应用程序关闭再重新启动,则它会保留会话数据。另一个方法是将会话数据存储在 SQL Server 数据库中,这种情况下会话数据可由多个 Web 服务器共享。

可以使用 ASP.NET 附带的会话状态存储,也可以实现自己的会话状态存储提供程序。由于下列原因,您可以创建自定义会话状态存储提供程序:

  • 需要将会话状态信息存储在 SQL Server 以外的数据源中,如 FoxPro 数据库或 Oracle 数据库。

  • 需要使用不同于 .NET Framework 附带的提供程序所使用的数据库架构来管理会话状态信息。例如,使用预定义架构存储在现有 SQL Server 数据库中的购物车数据。

可以通过创建一个继承 SessionStateStoreProviderBase 类的类,来实现自定义会话状态存储提供程序。有关更多信息,请参见本主题后面的“必需的类”一节。

会话状态模块

会话状态由 SessionStateModule 类进行管理,在请求过程中的不同时间,该类调用会话状态存储提供程序在数据存储区中读写会话数据。请求开始时,SessionStateModule 实例通过调用 GetItemExclusive 方法或 GetItem 方法(如果 EnableSessionState 页属性已设置为 ReadOnly)从数据源检索数据。请求结束时,如果修改了会话状态值,则 SessionStateModule 实例调用 SessionStateStoreProviderBase.SetAndReleaseItemExclusive 方法将更新的值写入会话状态存储区。SessionStateModule 调用 SessionStateStoreProviderBase 实现的其他成员来初始化一个新会话,并在调用 HttpSessionState.Abandon 方法时从数据存储区中删除会话数据。SessionStateStoreProviderBase 类的每个成员稍后将在本主题的“必需的类”一节中进行更详细的讨论。

SessionStateModule 自己确定 SessionID 值,而不依赖于会话状态存储提供程序进行确定。如果需要,可以通过创建继承 ISessionIDManager 接口的类来实现自定义 SessionIDManager。有关更多信息,请参见 ISessionIDManager 中的“备注”一节。

SessionStateModule 将恢复为 ASP.NET 进程标识以访问任何安全资源,例如数据库服务器。可以指定 SessionStateModule 实例模拟 IIS 提供的标识,方法是将 <sessionState> 配置元素的 useHostingIdentity 属性设置为 false。例如,如果已经将 IIS 应用程序配置为使用 Windows 集成安全性,并且希望 ASP.NET 模拟 IIS 提供的标识进行会话管理,则在应用程序的 Web.config 文件的 <system.web> 配置节中指定 <identity impersonate="true" />,并将 <sessionState> 配置元素的 useHostingIdentity 属性设置为 false。如果 useHostingIdentity 属性为 true,则 ASP.NET 在连接数据源时将模拟进程标识或提供给 <identity> 配置元素的用户凭据(如果进程标识和用户凭据存在)。有关 ASP.NET 进程标识的更多信息,请参见配置 ASP.NET 进程标识ASP.NET 模拟

锁定会话存储数据

ASP.NET 应用程序是多线程的,因此可支持对多个并发请求的响应。多个并发请求可能会试图访问同一会话信息。假设有这样一种情况,框架集中的多个框架全部引用同一应用程序中的 ASP.NET 网页。框架集中每个框架的独立请求可以在 Web 服务器的不同线程上并发执行。如果每个框架的 ASP.NET 页都访问会话状态变量,则可能会有多个线程并发访问会话存储区。为避免会话存储区中的数据冲突和意外的会话状态行为,SessionStateModuleSessionStateStoreProviderBase 类提供了一种功能,能在执行 ASP.NET 页期间以独占方式锁定特定会话的会话存储项。请注意,如果 EnableSessionState 属性标记为 ReadOnly,则不会对会话存储项设置锁定。但是,同一应用程序中的其他 ASP.NET 页也许可以写入会话存储区,因此对存储区中只读会话数据的请求可能仍然必须等待锁定数据被释放。

在对 GetItemExclusive 方法的调用中,请求开始时即对会话存储数据设置锁定。请求完成后,在调用 SetAndReleaseItemExclusive 方法期间释放锁定。

如果 SessionStateModule 实例在调用 GetItemExclusiveGetItem 方法过程中遇到锁定的会话数据,则该实例每隔半秒重新请求一次该会话数据,直到锁定被释放或 ExecutionTimeout 属性中指定的时间已经过去。如果请求超时,SessionStateModule 将调用 ReleaseItemExclusive 方法来释放会话存储数据,然后立即请求该会话存储数据。

为当前响应调用 SetAndReleaseItemExclusive 方法之前,锁定的会话存储数据可能已经在单独的线程上由对 ReleaseItemExclusive 方法的调用释放。这可能导致 SessionStateModule 实例设置和释放已经由其他会话释放和修改的会话状态存储数据。为避免这种情况,SessionStateModule 为每个请求都提供一个锁定标识符,以便修改锁定的会话存储数据。仅当数据存储区中的锁定标识符与 SessionStateModule 提供的锁定标识符匹配时,会话存储数据才能修改。

删除过期的会话存储数据

为会话调用 Abandon 方法时,将使用 RemoveItem 方法从数据存储区中删除该会话的数据。否则,这些数据将保留在会话数据存储区中,供以后对该会话的请求使用。

删除过期会话数据的机制取决于数据源的功能。如果可以将数据源配置为根据会话 Timeout 属性删除过期会话数据,则可以使用 SetItemExpireCallback 方法引用 Session_OnEnd 事件的委托并在删除过期会话数据时引发该委托。

ApplicationName

为维护会话范围,会话状态提供程序为每个应用程序单独存储会话信息。这使多个 ASP.NET 应用程序能够使用同一数据源而不会在出现重复的会话标识符时产生冲突。

由于会话状态存储提供程序为每个应用程序单独存储会话信息,因此必须确保数据架构、查询和更新包括应用程序名称。例如,下面的命令可以用于从数据库检索会话数据。

SELECT * FROM Sessions 
  WHERE SessionID = 'ABC123' AND ApplicationName = 'MyApplication'

或者,也可以将会话标识符和应用程序名称的组合存储为会话状态数据存储区中的项的唯一标识符。

必需的类

若要实现会话状态存储提供程序,请创建一个继承 SessionStateStoreProviderBase 抽象类的类。SessionStateStoreProviderBase 类又继承 ProviderBase 抽象类,因此还必须实现 ProviderBase 类必需的成员。下表列出了必须从 ProviderBaseSessionStateStoreProviderBase 抽象类实现的属性和方法,并提供了每个属性和方法的说明。若要查看每个成员的实现,请参见会话状态存储提供程序示例

必需的 ProviderBase 成员

成员

说明

Initialize 方法

采用提供程序的名称和配置设置的 NameValueCollection 实例作为输入。此方法用于设置提供程序实例的属性值,包括特定于实现的值和在配置文件(Machine.config 或 Web.config)中指定的选项。

必需的 SessionStateStoreProvider 成员

成员

说明

InitializeRequest 方法

采用当前请求的 HttpContext 实例作为输入,并执行会话状态存储提供程序必需的所有初始化操作。

EndRequest 方法

采用当前请求的 HttpContext 实例作为输入,并执行会话状态存储提供程序必需的所有清理操作。

Dispose 方法

释放会话状态存储提供程序不再使用的所有资源。

GetItemExclusive 方法

采用当前请求的 HttpContext 实例和当前请求的 SessionID 值作为输入。从会话数据存储区中检索会话的值和信息,并在请求持续期间锁定数据存储区中的会话项数据。GetItemExclusive 方法设置几个输出参数值,这些参数值将数据存储区中当前会话状态项的状态通知给执行调用的 SessionStateModule

如果数据存储区中未找到任何会话项数据,则 GetItemExclusive 方法将 locked 输出参数设置为 false,并返回 null。这将导致 SessionStateModule 调用 CreateNewStoreData 方法来为请求创建一个新的 SessionStateStoreData 对象。

如果在数据存储区中找到会话项数据但该数据已锁定,则 GetItemExclusive 方法将 locked 输出参数设置为 true,将 lockAge 输出参数设置为当前日期和时间与该项锁定日期和时间的差,将 lockId 输出参数设置为从数据存储区中检索的锁定标识符,并返回 null。这将导致 SessionStateModule 隔半秒后再次调用 GetItemExclusive 方法,以尝试检索会话项信息和获取对数据的锁定。如果 lockAge 输出参数的设置值超过 ExecutionTimeout 值,SessionStateModule 将调用 ReleaseItemExclusive 方法以清除对会话项数据的锁定,然后再次调用 GetItemExclusive 方法。

如果 regenerateExpiredSessionId 属性设置为 true,则 actionFlags 参数用于其 Cookieless 属性为 true 的会话。actionFlags 值设置为 InitializeItem (1) 则指示会话数据存储区中的项是需要初始化的新会话。通过调用 CreateUninitializedItem 方法可以创建会话数据存储区中未初始化的项。如果会话数据存储区中的项已经初始化,则 actionFlags 参数设置为零。

如果提供程序支持无 Cookie 会话,请将 actionFlags 输出参数设置为当前项从会话数据存储区中返回的值。如果被请求的会话存储项的 actionFlags 参数值等于 InitializeItem 枚举值 (1),则 GetItemExclusive 方法在设置 actionFlagsout 参数之后应将数据存储区中的值设置为零。

GetItem 方法

除了不尝试锁定数据存储区中的会话项以外,此方法与 GetItemExclusive 方法执行的操作相同。GetItem 方法在 EnableSessionState 属性设置为 ReadOnly 时调用。

SetAndReleaseItemExclusive 方法

采用当前请求的 HttpContext 实例、当前请求的 SessionID 值、包含要存储的当前会话值的 SessionStateStoreData 对象、当前请求的锁定标识符以及指示要存储的数据是属于新会话还是现有会话的值作为输入。

如果 newItem 参数为 true,则 SetAndReleaseItemExclusive 方法使用提供的值将一个新项插入到数据存储区中。否则,数据存储区中的现有项使用提供的值进行更新,并释放对数据的任何锁定。请注意,只有与提供的 SessionID 值和锁定标识符值匹配的当前应用程序的会话数据才会更新。

调用 SetAndReleaseItemExclusive 方法后,SessionStateModule 调用 ResetItemTimeout 方法来更新会话项数据的过期日期和时间。

ReleaseItemExclusive 方法

采用当前请求的 HttpContext 实例、当前请求的 SessionID 值以及当前请求的锁定标识符作为输入,并释放对会话数据存储区中的项的锁定。在调用 GetItemGetItemExclusive 方法,并且数据存储区指定被请求项已锁定,但锁定时间已超过 ExecutionTimeout 值时会调用此方法。此方法清除锁定,释放该被请求项以供其他请求使用。

RemoveItem 方法

采用当前请求的 HttpContext 实例、当前请求的 SessionID 值以及当前请求的锁定标识符作为输入,并删除数据存储区中与提供的 SessionID 值、当前应用程序和提供的锁定标识符相匹配的数据存储项的会话信息。此方法在 Abandon 方法被调用时调用。

CreateUninitializedItem 方法

采用当前请求的 HttpContext 实例、当前请求的 SessionID 值以及当前请求的锁定标识符作为输入,并向会话数据存储区添加一个 actionFlags 值为 InitializeItem 的未初始化项。

如果 regenerateExpiredSessionId 属性设置为 true,则 CreateUninitializedItem 方法用于无 Cookie 会话,这将导致遇到过期会话 ID 时,SessionStateModule 会生成一个新的 SessionID 值。

生成新的 SessionID 值的过程需要浏览器重定向到包含新生成的会话 ID 的 URL。在包含过期的会话 ID 的初始请求期间,会调用 CreateUninitializedItem 方法。SessionStateModule 获取一个新的 SessionID 值来替换过期的会话 ID 之后,它会调用 CreateUninitializedItem 方法以将一个未初始化项添加到会话状态数据存储区中。然后,浏览器重定向到包含新生成的 SessionID 值的 URL。如果会话数据存储区中存在未初始化项,则可以确保包含新生成的 SessionID 值的重定向请求被视为新的会话,而不会被误认为是对过期会话的请求。

会话数据存储区中未初始化的项与新生成的 SessionID 值关联,并且仅包含默认值,其中包括到期日期和时间以及与 GetItemGetItemExclusive 方法的 actionFlags 参数相对应的值。会话状态存储区中的未初始化项应包含一个与 InitializeItem 枚举值 (1) 相等的 actionFlags 值。此值由 GetItemGetItemExclusive 方法传递给 SessionStateModule,并针对 SessionStateModule 指定当前会话是新会话。然后,SessionStateModule 将初始化该新会话,并引发 Session_OnStart 事件。

CreateNewStoreData 方法

采用当前请求的 HttpContext 实例和当前会话的 Timeout 值作为输入,并返回带有空 ISessionStateItemCollection 对象的新的 SessionStateStoreData 对象、一个 HttpStaticObjectsCollection 集合和指定的 Timeout 值。使用 GetSessionStaticObjects 方法可以检索 ASP.NET 应用程序的 HttpStaticObjectsCollection 实例。

SetItemExpireCallback 方法

采用引用 Global.asax 文件中定义的 Session_OnEnd 事件的委托作为输入。如果会话状态存储提供程序支持 Session_OnEnd 事件,则设置对 SessionStateItemExpireCallback 参数的局部引用,并且此方法返回 true;否则,此方法返回 false。

示例提供程序

若要查看管理 Access 数据库中会话信息的自定义会话状态存储提供程序的示例实现,请参见会话状态存储提供程序示例

请参见

概念

会话状态存储提供程序示例

ASP.NET 会话状态概述

ASP.NET 状态管理概述