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

将 View State 存储在 SQL Server 中

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2017年3月13日

CPOL

5分钟阅读

viewsIcon

14347

downloadIcon

193

将 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 Job - 1 - General

转到“步骤”页面并启动一个新作业步骤。将步骤名称设置为“Delete View State”。将数据库从“master”更改为您的数据库,最后在步骤命令中输入 exec sp_delete_view_state。在步骤的“高级”页面下,将操作更改为在成功时退出作业。

Delete View State Job - 2a - Steps

Delete View State Job - 2b - Steps

转到“计划”页面并单击“新建”。将计划名称设置为“Every 2 Hours”。将频率更改为“每天”,每天(一次)。将每日频率更改为“2 小时”。为了完成作业,无意中使用了双关语,我还建议您添加作业失败时的通知,但这只是可选的。

Delete View State Job - 3 - Schedules

视图状态管理

辅助类 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 有两个用于保存和加载视图状态的方法:LoadPageStateFromPersistenceMediumSavePageStateToPersistenceMedium,我们需要同时重写它们。由于这段代码无论在哪种网页中都将是相同的,因此我们将它写在一个所有网页都继承的基类中。如果您不需要这样做,则必须将此代码单独复制到每个网页中。

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.PageLoadPageStateFromPersistenceMedium

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
    }
}
© . All rights reserved.