ViewState Provider - 使用 Provider Model 设计模式的实现






4.83/5 (40投票s)
2004 年 8 月 16 日
9分钟阅读

195612

2865
使用业界知名的模式——提供者模型设计模式来解决 ViewState 问题。
引言
本文重点介绍提供者模型设计模式,并描述如何使用它来解决某些问题。为了帮助读者更好地理解或欣赏这种设计模式,我选择了视图状态管理作为示例来演示这种设计模式的实用性和实际性。因此,在本文中,我们将首先了解视图状态固有的问题,然后我将解释为什么提供者模型设计模式很重要以及我们如何利用它来解决上述问题。
背景
视图状态——许多 ASP.NET 开发人员爱恨交加的关系。开发人员喜欢它,因为它不需要任何服务器状态资源,并且实现起来很简单。页面和控件状态在同一页面实例的连续回发中自动保留,这使得 Web Forms 模型的魔力成为可能。此外,视图状态中的值经过哈希、压缩和编码,以用于 Unicode 实现,因此比隐藏字段具有更高的安全性。另一方面,围绕其使用存在一些热门问题,主要与安全性和性能相关。对于安全性,因为视图状态存储在页面上的隐藏字段中。尽管视图状态以哈希格式存储数据,但如果直接查看页面输出源,隐藏字段中存储的信息仍然可见,从而造成潜在的安全问题。对于性能,因为视图状态存储在页面本身中,存储大量值可能会导致浏览器显示或回发页面时速度变慢。
对于 ASP.NET 新手,有一些很好的资源可以了解更多关于 ASP.NET 视图状态的信息。
- 深入了解 ASP.NET ViewState,作者 Susan Warren。
- 理解 ASP.NET View State,作者 Scott Mitchell。
幸运的是,`Page` 类可以支持视图状态的替代存储方案。该类包含运行时用于反序列化或序列化视图状态的几个受保护的虚方法。`LoadPageStateFromPersistenceMedium` 用于在页面生命周期开始时恢复视图状态。相比之下,名为 `SavePageStateToPersistenceMedium` 的方法用于在页面呈现之前持久化视图状态。
protected virtual void SavePageStateToPersistenceMedium (object viewState);
protected virtual object LoadPageStateFromPersistenceMedium();
通过重写这两个方法,您可以随意操纵视图状态。(请注意,您不能只重写一个方法;您必须同时重写两个。)典型的解决方案是:
- 压缩视图状态(性能)
- 加密视图状态(安全)
- 将视图状态保存到服务器端文件或数据库表中,并在处理该页面时读取/写入视图状态。(安全和性能)
上述每种建议的解决方案都有其优缺点。众所周知,在安全性和性能之间总是需要权衡。问题在于我们如何设计应用程序,使其能够实现这些解决方案,但又能针对特定的问题领域选择最佳方案。例如,您可能希望将视图状态存储在 Microsoft SQL Server 数据库中用于您的开发任务,而在生产环境中(例如客户要求的 Oracle 数据库)使用不同的数据库。因此,选择正确的解决方案取决于您如何有效地解决问题来匹配您的需求。如果您能够在一个最适合您需求的存储库之上实现这些解决方案,同时保持应用程序的架构、设计和代码独立于此选择,那岂不是很好?
引入提供者模型设计模式
提供者模型设计模式源自许多广为接受的模式,并于 2002 年夏天正式命名。“提供者”被定义为一个可插拔组件,用于扩展或完全替换现有系统功能。换句话说,提供者模型允许您拔下 API 的默认实现(例如,视图状态管理、会话状态管理、个性化等等)并插入您自己的实现。通过为您支持该模型的特定系统功能编写您自己的提供者,您可以更改底层实现、数据格式和存储介质,而不会破坏应用程序设计(保持顶层接口不变)。更简单地说:它允许开发人员发布文档完善、易于理解的 API(对象模型),同时让开发人员完全控制这些 API 被调用时内部发生的一切。
具有提供者的功能实现必须在 `web.config` (Web 应用程序) 或 `app.config` (Windows 应用程序) 文件中定义一个配置节。其目的是注册可用的“提供者”并选择一个作为默认提供者。例如
<!-- View State Provider Configuration -->
<viewstate defaultProvider= "SqlViewStateProvider">
<add name="SqlViewStateProvider"
type="System.Web.Configuration.Providers.SqlViewStateProvider,
ViewStateProviders"
connectionString="SERVER=(local);DATABASE=(Database Name );USERID=sa;PWD=" />
<add name="CompressionViewStateProvider"
type="System.Web.Configuration.Providers.CompressionViewStateProvider,
ViewStateProviders"
connectionString="" />
</providers>
</viewstate>
根据上面显示的配置,以下是一些要遵循的重要指南
defaultProvider
每个功能配置应指定默认提供程序,它指示系统使用列出的哪个提供程序。例如
<viewstate defaultProvider="SqlViewStateProvider">
提供者友好名称
在配置中定义提供程序时,需要定义 name 属性。此外,提供程序名称应遵循一定的模式,以便轻松区分提供程序的所有者。建议的模式是:[提供程序创建者][数据存储][功能]Provider。
<addname="SqlViewStateProvider"
type="System.Web.Configuration.Providers.SqlViewStateProvider,
ViewStateProviders"
connectionString="SERVER=(local);DATABASE=(Database Name);USER ID=sa;PWD=" />
下表列出了一些常用名称和大小写约定,适用于各种数据存储(其中名称为 [名称][功能]Provider)。例如,`SqlViewStateProvider` 应该用于命名使用 SQL Server 作为数据存储的提供程序。
名称 | 适用于 |
Sql | 任何使用 SQL Server 作为数据存储的提供程序。 |
访问 | 任何使用 Access/Jet 数据库作为数据存储的提供程序。 |
XML | 任何使用 XML 文件作为数据存储的提供程序。 |
AD | 任何使用 Active Directory 作为数据存储的提供程序。 |
文件 | 任何使用文件作为数据存储的提供程序。 |
内存 | 任何使用内存数据存储的提供程序。 |
提供者类型
在配置中定义提供程序时,必须定义 type 属性。type 值必须是遵循以下格式的完全限定名称
type="[namespace.class], [assembly name], Version=[version],
Culture=[culture], PublicKeyToken=[public token]"
强烈建议使用强类型名称,但是,使用较短的程序集类型名称也是合法的。例如
type="System.Web.Configuration.Providers.SqlViewStateProvider, ViewStateProviders"
要了解更多关于提供者模型设计模式的信息,请阅读 Rob Howard 撰写的 提供者模型设计模式和规范。
ViewState Provider 对象模型
该模型定义了一组类来支持视图状态提供者框架。下图描绘了 ViewState Provider 对象模型及其与 Page 的交互。
- `ViewStateManager` - 它公开了两个静态方法,`LoadPageState` 和 `SaveViewState`,应用程序(在我们的上下文中是 Page)分别使用它们来加载或保存其视图状态。它不包含业务逻辑;相反,它只是将这些调用转发给已配置的提供程序,例如 `SqlViewStateProvider`。`SqlViewStateProvider` 提供程序类的责任是包含这些方法的实现,调用任何业务逻辑层 (BLL) 或数据访问层 (DAL) 来完成工作。
/// <summary> /// Loads any saved view-state of the current page from virtually any /// storage medium other than a hidden field /// </summary> /// <param name="pControl">System.Web.UI.Page</param> /// <returns>The saved view state</returns> public static System.Object LoadPageState(System.Web.UI.Control pControl) /// <summary> /// Saves any view-state information of the page to virtually any /// storage medium other than a hidden field /// </summary> /// <param name="pControl">System.Web.UI.Page</param> /// <param name= "viewState">An System.Object in which /// to store the view-stateinformation</param> public static void SavePageState (System.Web.UI.Control pControl, System.Object viewState)
- `ProviderBase` - 用于将实现者标记为提供者,并强制实现所有提供者共有的必需方法和属性。
- `ViewStateProviderBase` - 它向视图状态管理服务公开一个标准接口(众所周知的 API),并且所有自定义 ViewState 提供程序都必须继承自此基类。这些众所周知的 API 是:
public abstract System.Object LoadPageState(System.Web.UI.Control pControl); public abstract void SavePageState(System.Web.UI.Control pControl, System.Object viewState);
- `ViewStateConfigurationHandler` - 它解释并处理 `Web.config` 文件中 `<viewstate>` 部分内 XML 标签中定义的设置,并根据配置设置返回适当的配置对象。
- `ViewStateConfiguration` - 它包含 `Web.config` 文件中定义的所有 ViewState 提供程序的设置信息。
SqlViewStateProvider - 实现
`SqlViewStateProvider` 提供程序继承自 `ViewStateProviderBase` 类,它使用 SQL Server 作为数据存储,在页面生命周期中存储和检索页面的视图状态。用于存储视图状态的表架构如下图所示,非常简单
字段名 | 数据类型 | 描述 |
vsKey |
NVARCHAR(100) |
唯一标识特定页面视图状态的键 |
vsValue |
NTEXT |
页面的视图状态。 |
时间戳 |
日期时间 |
此视图状态的上次访问时间戳 |
在页面呈现其输出之前,框架将调用 `SavePageState` 方法以保存页面的视图状态。在内部,它检查页面 `Controls` 集合中是否存在名为 "__vsKey" 的隐藏字段(即 `HtmlInputHidden` 控件),如果不存在则创建该隐藏字段。该方法的代码只是创建 `LosFormatter` 对象的一个实例,并调用其 `Serialize()` 方法,将传入的视图状态信息序列化到 `StringWriter` 写入器中。之后,将生成一个全局唯一标识符 (GUID) 并将其用作键,以将特定页面的视图状态保存到数据库表中。最后,这个 GUID 存储在该页面的隐藏字段中。
当页面回发时,框架将调用 `LoadPageState` 方法以检索特定页面的已保存视图状态。这通过使用存储在上次访问时“`__vsKey`”隐藏字段中的 GUID 在数据库表中查找视图状态,并通过 `LosFormatter` 的 `Derialize()` 方法返回反序列化对象来完成。
这里有一个问题,每次用户访问不同的页面时,数据库表中都会创建一个新的记录来保存该页面的视图状态。随着时间的推移,这将导致数百万条记录,这可能会严重影响查找过程的性能。需要某种自动化任务来定期清理早于特定日期的视图状态记录。我将此留给读者作为练习。
下面的图表显示了在使用 `SqlViewStateProvider` 提供程序将视图状态存储在数据库中而不是存储在页面名为 "__VIEWSTATE" 的隐藏字段中之前和之后的差异。
视图状态存储在隐藏字段中(之前)
视图状态存储在数据库中(之后)
使用代码
本文附带了两个视图状态提供程序,可从本文顶部的链接获取。其中一个提供程序将视图状态存储到 SQL Server 数据库表中,本文通篇对此进行了描述;另一个提供程序具有压缩视图状态的功能。如前所述,如果这些提供程序的默认功能不满足您的需求,您可以创建自己的提供程序并将其插入到框架中。
有了 ViewState Provider 框架,开发自定义视图状态提供程序就像下面列出的几个步骤一样简单:
- 创建一个新类并使其派生自 `ViewStateProviderBase` 类。(请记住遵循提供程序类的命名模式)。
- 实现 `LoadPageState` 和 `SavePageState` API。
- 将新类添加到 `Web.config` 文件中的 `<providers>` 部分。
- 将 `web.config` 文件中 `<viewstate>` 部分的 `defaultProvider` 属性值设置为新创建的提供程序名称。
结论
提供者设计模式使开发人员能够拥有灵活的设计和丰富、企业级的可扩展模型。更重要的是,您可以通过简单地更改一些配置,非常容易地为一个提供者用于您的开发任务,而为生产中的应用程序使用另一个提供者。最后但并非最不重要的一点是,这是 ASP.NET 2.0 中一个重要的新设计模式,它广泛应用于许多常见的 Web 应用程序功能,如站点成员资格、角色管理、个性化等。因此,从今天开始在您的应用程序中使用它,您明天就能在理解 ASP.NET 2.0 中的这种设计模式方面领先一步。