将 View State 存储在 SQL Server 中
将 view state 移出网页并将其存储在 SQL Server 中
引言
在 SQL Server 中存储视图状态已经是很久以前的技术了,但我认为再次探讨这个主题也无妨。本文中的代码主要面向拥有大量网页和用户的大型网站。在这些条件下,减少客户端和服务器之间来回传输的视图状态对于任何网站来说都是必须的。本文中的大部分代码都是从他人那里借鉴而来,但也包含了一些我没有在其他地方找到的、经过我个人修改的东西。这是一套经过实践检验的代码,希望对您有所帮助。
我必须提及两篇文章。第一篇是 Robert Boedigheimer 于 2003 年发表的 Server Side Viewstate。如我所说,这是一项老技术。他的文章是这份代码的基础。第二篇是相关的文章,也引用了 Boedigheimer 的文章,是 Peter Bromberg 于 NullSkull 发表的 Analysis of Keeping ViewState out of the Page。
在深入研究代码之前,您应该知道这里有一个假设:您可以设置一个 SQL Server 作业。这个作业的目的是清理过期的视图状态,否则视图状态表将不受控制地膨胀。
视图状态表
我们从 View_State
表开始。View_State_Key
是唯一标识已存储视图状态的键列。此键将注入到网页中,而不是实际的视图状态。键的类型是字符串,但如果您喜欢,可以将其从字符串更改为其他类型(如 uniqueidentifier
),但请务必相应地在 .NET 代码中反映出来。View_State_Value
列存储实际的视图状态字符串。Insert_Time
列是时间戳,默认情况下由 getdate()
填充。
create table [dbo].[View_State] (
View_State_Key nvarchar(250) not null,
View_State_Value nvarchar(max) null,
Insert_Time datetime not null
constraint DF_View_State_Insert_Time default (getdate()),
constraint PK_View_State primary key clustered (
View_State_Key asc
) on [PRIMARY]
) on [PRIMARY] textimage_on [PRIMARY]
获取和设置视图状态
检索和存储视图状态的存储过程非常直接。
create procedure [dbo].[sp_get_view_state]
@View_State_Key nvarchar(250)
as
begin
set nocount on;
select top 1 View_State_Value
from View_State with (nolock)
where View_State_Key = @View_State_Key
end
create procedure [dbo].[sp_set_view_state]
@View_State_Key nvarchar(250),
@View_State_Value nvarchar(max)
as
begin
set xact_abort on;
begin try
begin transaction
insert into View_State(View_State_Key, View_State_Value)
values(@View_State_Key, @View_State_Value)
commit transaction
return 0
end try
begin catch
if @@TRANCOUNT > 0
rollback transaction
return -1
end catch
end
删除视图状态
删除存储过程会删除所有至少有 2 小时历史记录的视图状态。@hours
的值必须大于会话超时时间,因此这里的假设是网站的会话超时时间小于 2 小时。显然,您需要根据您网站的超时时间相应地进行替换。
create procedure [dbo].[sp_delete_view_state]
as
begin
set nocount on;
-- higher than the web site's session time out
declare @hours int = 2
delete from View_State
where datediff(hour, Insert_Time, getdate()) > @hours
end
此存储过程将由定期的 SQL Server 作业执行,而不是直接从 .NET 调用。该作业将每 2 小时运行一次,并清理 View_State
表中的过期视图状态。在 SSMS 中,打开对象资源管理器,在服务器名称下找到 SQL Server 代理,然后在其下方找到作业。右键单击作业并启动一个新作业。在“常规”页面上,将作业名称设置为“Delete View State”,将描述设置为“Delete view state every 2 hours”。根据您的喜好更改这些值。
转到“步骤”页面并启动一个新作业步骤。将步骤名称设置为“Delete View State”。将数据库从“master”更改为您的数据库,最后在步骤命令中输入 exec sp_delete_view_state
。在步骤的“高级”页面下,将操作更改为在成功时退出作业。
转到“计划”页面并单击“新建”。将计划名称设置为“Every 2 Hours”。将频率更改为“每天”,每天(一次)。将每日频率更改为“2 小时”。为了完成作业,无意中使用了双关语,我还建议您添加作业失败时的通知,但这只是可选的。
视图状态管理
辅助类 ViewStateManagement
负责读取和写入视图状态。它在网页和数据库之间进行协调。SetViewState
方法存储视图状态。首先,该方法使用专用的 .NET 类 LosFormatter
将视图状态对象序列化为字符串。然后,它构建一个唯一标识视图状态的键。该键由请求的 IP 地址、时间戳(DateTime.Now.Ticks
)和项目特定信息(uniqueKey
)构成,例如用户代码。成功后,该方法将视图状态键的值注入到网页中,作为隐藏字段 __VIEWSTATE_KEY
。以下是隐藏字段的示例:如果用户代码是 10,IP 是 127.0.0.1,当前时间戳是 636250201790017849,则隐藏字段将是 __VIEWSTATE_KEY=VIEWSTATE_10_127.0.0.1_636250201790017849
。
public static class ViewStateManagement
{
public static bool SetViewState(
Page page,
HttpContext context,
string connectionString,
object viewState,
string uniqueKey = null)
{
StringBuilder sb = new StringBuilder();
using (StringWriter swr = new StringWriter(sb))
new System.Web.UI.LosFormatter().Serialize(swr, viewState);
string viewStateKey = string.Format("VIEWSTATE_{0}_{1}_{2}",
uniqueKey,
GetIP(context), // retrieves the IP from the HTTP Request
DateTime.Now.Ticks
);
// database call
bool succeeded = SetViewState(connectionString, viewStateKey, sb.ToString());
if (succeeded)
page.ClientScript.RegisterHiddenField("__VIEWSTATE_KEY", viewStateKey);
return succeeded;
}
}
get 方法从 HTTP 请求中提取 __VIEWSTATE_KEY
。使用此键,它将查询数据库,从数据库中获取视图状态字符串,并使用 LosFormatter
将其从字符串反序列化为对象。
public static class ViewStateManagement
{
public static object GetViewState(HttpContext context, string connectionString)
{
if (context == null || context.Request == null)
return null;
string viewStateKey = context.Request.Form["__VIEWSTATE_KEY"];
if (string.IsNullOrEmpty(viewStateKey) == false &&
viewStateKey.StartsWith("VIEWSTATE_"))
{
// database call
string viewState = GetViewState(connectionString, viewStateKey);
if (string.IsNullOrEmpty(viewState) == false)
return new System.Web.UI.LosFormatter().Deserialize(viewState);
}
return null;
}
}
现在我们准备好挂钩视图状态了。System.Web.UI.Page
有两个用于保存和加载视图状态的方法:LoadPageStateFromPersistenceMedium
和 SavePageStateToPersistenceMedium
,我们需要同时重写它们。由于这段代码无论在哪种网页中都将是相同的,因此我们将它写在一个所有网页都继承的基类中。如果您不需要这样做,则必须将此代码单独复制到每个网页中。
SavePageStateToPersistenceMedium
方法根据特定于页面和当前会话的值构建 uniqueKey
。这是您需要编写自己的代码以反映您自己项目的地方。然后,它调用 ViewStateManagement.SetViewState
来保存视图状态并将键注入到页面中。如果整个过程失败,它将回退到 System.Web.UI.Page
的默认 SavePageStateToPersistenceMedium
实现。
public abstract class BasePage : System.Web.UI.Page
{
protected override void SavePageStateToPersistenceMedium(object viewState)
{
// Any unique project-related values. user code, guid, ...
string uniqueKey = null;
// Change this to however you retrieve the connection string
string connectionString =
HttpContext.Current.Session["ConnectionString"] as string;
// set view state
bool succeeded = ViewStateManagement.SetViewState(
this,
HttpContext.Current,
connectionString,
viewState,
uniqueKey
);
if (succeeded == false)
base.SavePageStateToPersistenceMedium(viewState); // fallback
}
}
非常类似地,LoadPageStateFromPersistenceMedium
通过调用 ViewStateManagement.GetViewState
来检索反序列化的视图状态。如果无法执行此操作,它将回退到 System.Web.UI.Page
的 LoadPageStateFromPersistenceMedium
。
public abstract class BasePage : System.Web.UI.Page
{
protected override object LoadPageStateFromPersistenceMedium()
{
// Change this to however you retrieve the connection string
string connectionString =
HttpContext.Current.Session["ConnectionString"] as string;
// get view state
object viewState = ViewStateManagement.GetViewState(
HttpContext.Current,
connectionString
);
if (viewState != null)
return viewState;
else
return base.LoadPageStateFromPersistenceMedium(); // fallback
}
}