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

使用 ASP.NET 2.0 提供程序体系结构管理 ViewState

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.93/5 (5投票s)

2007 年 5 月 8 日

CPOL

7分钟阅读

viewsIcon

80239

downloadIcon

788

使用基于 ASP.NET 2.0 提供程序模式体系结构的自定义提供程序进行服务器端 ViewState 管理。

引言

本文展示了使用 ASP.NET 2.0 提供程序体系结构和提供程序模型设计模式实现的几个自定义 viewstate 提供程序。有关 ASP.NET 2.0 提供程序模型的更多信息,请阅读提供程序模型简介

背景

大约半年前,我正在处理一个项目,该项目需要从数据库中提取中等大小的数据集并在 DataGrid 中显示。每次回发都去数据库似乎是在浪费资源,因此,我决定将数据存储在 viewstate 中。就这样,页面加载时间开始变得越来越长。我开始研究优化 viewstate 的替代解决方案,并在网上找到了一些信息和示例。尽管如此,我还没有找到一个完整的解决方案,可以让我轻松地管理和扩展 viewstate 的功能,而无需对网站进行重大更改。

我查阅了许多有趣且有用的文章,这些文章提供了对 viewstate 管理实现的不同看法,但没有一篇是完整且易于集成的解决方案。

下面的代码受到以下文章的启发或部分摘录自

使用代码

我将专注于描述 viewstate 提供程序代码,而不深入介绍提供程序模式设计。有关模式设计和体系结构的详细解释,请参阅上面列出的参考文献。

编写提供程序

首先,我们需要一个抽象类来表示 viewstate 并公开实际提供程序将实现的函数。ViewStateProvider 继承自 ProviderBase,并将成为所有具体 viewstate 提供程序类的基类。它将有两个必须由派生类实现的函数:LoadPageStateSavePageState

''' <summary>
''' Defines the contract that ASP.NET implements to provide viewstate
''' services using custom viewstate providers.
''' </summary>
Public MustInherit Class ViewStateProvider
    Inherits System.Configuration.Provider.ProviderBase


    Public MustOverride Property ApplicationName() As String

''' <summary>
''' The hidden field variable name where
''' our viewstate reference key is stored on a page.
''' </summary>
Public MustOverride Property ViewStateKeyName() As String

    ''' -----------------------------------------------------------------------------
    ''' <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 MustOverride Function LoadPageState(ByVal pControl As Control) As Object

    ''' -----------------------------------------------------------------------------
    ''' <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">A System.Object
    '''       in which to store the view-state information</param>
    ''' -----------------------------------------------------------------------------
    Public MustOverride Sub SavePageState(ByVal pControl As Control, _
           ByVal viewState As Object)

End Class

接下来,我们需要一个实际的提供程序类。在示例中,我有三个具体的提供程序类

  • SessionViewStateProvider
  • ViewState 被维护为一个会话变量。

  • CompressionViewStateProvider
  • ViewState 使用 ICSharpCode.SharpZipLib 进行压缩并写入页面。

  • SqlViewStateProvider
  • ViewState 使用修改后的 ASPState 数据库进行维护。ViewState 被写入数据库,类似于 OutOfProc Session。

让我们看看其中一个 - SessionViewStateProvider

注意:此提供程序需要与代码一起附加的 SQL 脚本,并在将存储 viewstate 的数据库上执行。必须在 Web.config 中设置正确的 SQL 连接和模拟。所有 SQL 脚本代码均摘自 Adam Weigert 的SqlViewState - 更好的 ViewState 存储之路。示例中的其他提供程序不需要任何额外的设置,除了在 Web.config 中注册(在此进行了解释)。

''' <summary>
''' Manages storage of viewstate object for an ASP.NET
''' application in a SQL Server database.
''' </summary>
''' <remarks><para><pre>
''' RevisionHistory:
''' --------------------------------------------------------------------------------
''' Date        Name            Description
''' --------------------------------------------------------------------------------
''' 10/11/2006    Oleg Sobol        Initial Creation
''' inspiration taken from
''' http://weblogs.asp.net/adweigert/archive/2004/03/09/
'''          sqlviewstate-the-path-to-better-viewstate-storage.aspx
''' 5/11/2007    Oleg Sobol      Added ViewStateKeyName and DEFAULT_TIMEOUT
''' </pre></para></remarks>
Public Class SqlViewStateProvider
    Inherits System.Web.UI.ViewStateProvider

	Public Const DEFAULT_TIMEOUT As Integer = 25

	Private _applicationName As String
	Private _connectionString As String
	Private _connectionStringName As String
	Private _timeout As TimeSpan
	Private _enableViewStateMac As Boolean
	Private _lockLoad As New Object
	Private _lockSave As New Object
	Private _viewStateKeyName As String = "__VIEWSTATE_KEY"

    Public Overloads Overrides Property ApplicationName() As String
        Get
            Return _applicationName
        End Get
        Set(ByVal value As String)
            _applicationName = value
        End Set
    End Property

    Public Property ConnectionStringName() As String
        Get
            Return _connectionStringName
        End Get
        Set(ByVal value As String)
            _connectionStringName = value
        End Set
    End Property

    Public Property Timeout() As TimeSpan
        Get
            Return _timeout
        End Get
        Set(ByVal value As TimeSpan)
            _timeout = value
        End Set
    End Property

    Public Property EnableViewStateMac() As Boolean
        Get
            Return _enableViewStateMac
        End Get
        Set(ByVal value As Boolean)
            _enableViewStateMac = value
        End Set
    End Property

    Public Overrides Property ViewStateKeyName() As String
        Get
        Return _viewStateKeyName
        End Get
        Set(ByVal value As String)
        _viewStateKeyName = value
        End Set
    End Property

    Public Overloads Overrides Sub Initialize(ByVal name As String, _
                     ByVal config As NameValueCollection)
        ' Verify that config isn't null
        If config Is Nothing Then Throw New ArgumentNullException("config")
 
	   	' Assign the provider a default name if it doesn't have one
	        If String.IsNullOrEmpty(name) Then name = "SqlViewStateProvider"

	    ' Add a default "description" attribute
	    ' to config if the attribute doesn't exist or is empty
	    If String.IsNullOrEmpty(config("description")) Then
	        config.Remove("description")
	        config.Add("description", "SQL viewstate provider")
	    End If

	    ' Call the base class's Initialize method
	    MyBase.Initialize(name, config)

	    ' Initialize _applicationName
	    _applicationName = config("applicationName")
	    If String.IsNullOrEmpty(_applicationName) Then _applicationName = "/"
	    config.Remove("applicationName")

	    Dim connect As String = config("connectionStringName")
	    If String.IsNullOrEmpty(connect) Then
	        Throw New ViewStateProviderException(_
	              "Empty or missing connectionStringName")
	    End If
	    config.Remove("connectionStringName")
	    If WebConfigurationManager.ConnectionStrings(connect) Is Nothing Then
	        Throw New ViewStateProviderException("Missing connection string")
	    End If
	    _connectionString = _
	      WebConfigurationManager.ConnectionStrings(connect).ConnectionString
	    If String.IsNullOrEmpty(_connectionString) Then
	        Throw New ViewStateProviderException("Empty connection string")
	    End If

	    Dim timeout As String = config("timeout")
	    If String.IsNullOrEmpty(timeout) OrElse Not IsNumeric(timeout) Then
	        _timeout = TimeSpan.FromMinutes(_
	            ViewStateProvidersConfig.DefaultViewStateTimeout)
	    Else
	        _timeout = TimeSpan.FromMinutes(CInt(timeout))
	    End If
	    config.Remove("timeout")

	    Dim enableViewStateMac As String = config("enableViewStateMac")
	    Try
	        _enableViewStateMac = CBool(enableViewStateMac)
	    Catch ex As Exception
	        _enableViewStateMac = False
	    End Try
	    config.Remove("enableViewStateMac")

	    ' Throw an exception if unrecognized attributes remain
	    If config.Count > 0 Then
	        Dim attr As String = config.GetKey(0)
	        If Not String.IsNullOrEmpty(attr) Then _
	            Throw New ViewStateProviderException(_
	                  "Unrecognized attribute: " + attr)
	    End If
	End Sub

    Public Overrides Function LoadPageState(ByVal pControl As _
                     System.Web.UI.Control) As Object
        Dim connection As SqlConnection = Nothing
        Dim rawData As Byte() = Nothing
        Dim stream As MemoryStream = Nothing

        Try
            Dim viewStateGuid As Guid = GetViewStateGuid(pControl)
            connection = New SqlConnection(_connectionString)
            Dim command As SqlCommand = _
                        New SqlCommand("GetViewState", connection)

            Try
                command.CommandType = CommandType.StoredProcedure
                command.Parameters.Add("@returnValue", _
                        SqlDbType.Int).Direction = ParameterDirection.ReturnValue
                command.Parameters.Add("@viewStateId", _
                        SqlDbType.UniqueIdentifier).Value = viewStateGuid
                connection.Open()

                Dim reader As SqlDataReader = command.ExecuteReader
                Try
                    If reader.Read Then
                        rawData = CType(Array.CreateInstance(GetType(Byte), _
                                  reader.GetInt32(0)), Byte())
                    End If
                    If reader.NextResult AndAlso reader.Read Then
                        reader.GetBytes(0, 0, rawData, 0, rawData.Length)
                    End If
                Catch e As Exception
                    Throw New ViewStateProviderException(_
                          "Problem reading data returned from SqlServer", e)
                Finally
                    CType(reader, IDisposable).Dispose()
                End Try

            Catch e As Exception
                Throw New ViewStateProviderException("Problem executing SqlCommand", e)
            Finally
                If Not command Is Nothing Then command.Dispose()
            End Try

        Catch e As Exception
            Throw New ViewStateProviderException("Problem with SqlConnection", e)
        Finally
            If Not connection Is Nothing Then connection.Dispose()
        End Try

        Try
            stream = New MemoryStream(rawData)
            Return Me.GetLosFormatter(pControl, False).Deserialize(stream)
        Catch e As Exception
            Throw New ViewStateProviderException("Problem with data deserialization", e)
        Finally
            If Not stream Is Nothing Then CType(stream, IDisposable).Dispose()
        End Try

        Return Nothing
    End Function

    Public Overrides Sub SavePageState(ByVal pControl As _
           System.Web.UI.Control, ByVal viewState As Object)
        Dim p As Page = Nothing
        Dim viewStateGuid As Guid
        Dim stream As MemoryStream = Nothing

        Try
            p = CType(pControl, Page)
            viewStateGuid = GetViewStateGuid(p)
            stream = New MemoryStream
            GetLosFormatter(p, _enableViewStateMac).Serialize(stream, viewState)

            Dim connection As SqlConnection = New SqlConnection(_connectionString)
            Try
                Dim command As SqlCommand = New SqlCommand("SetViewState", connection)
                Try
                    command.CommandType = CommandType.StoredProcedure
                    command.Parameters.Add("@returnValue", _
                            SqlDbType.Int).Direction = ParameterDirection.ReturnValue
                    command.Parameters.Add("@viewStateId", _
                            SqlDbType.UniqueIdentifier).Value = viewStateGuid
                    command.Parameters.Add("@value", _
                            SqlDbType.Image).Value = stream.ToArray
                    command.Parameters.Add("@timeout", _
                            SqlDbType.Int).Value = _timeout.TotalMinutes
                    connection.Open()
                    command.ExecuteNonQuery()
                Catch e As Exception
                    System.Diagnostics.Trace.Write(e.Message)
                Finally
                    If Not command Is Nothing Then command.Dispose()
                End Try

            Catch e As Exception
                System.Diagnostics.Trace.Write(e.Message)
            Finally
                If Not connection Is Nothing Then connection.Dispose()
            End Try

        Catch e As Exception
            System.Diagnostics.Trace.Write(e.Message)
        Finally
            If Not stream Is Nothing Then CType(stream, IDisposable).Dispose()
        End Try

        Dim control As Html.HtmlInputHidden _
         = CType(p.FindControl(ViewStateProvidersConfig.ViewStateKeyFieldName), _
           Html.HtmlInputHidden)
        If control Is Nothing Then
            p.ClientScript.RegisterHiddenField(_
              ViewStateProvidersConfig.ViewStateKeyFieldName, _
              viewStateGuid.ToString)
        Else
            control.Value = viewStateGuid.ToString
        End If

    End Sub

#Region " Private Helper Functions "

    Private Function GetViewStateGuid(ByVal pControl As Control) As Guid
        Dim p As Page = CType(pControl, Page)
        Dim viewStateKey As String = _
            p.Request.Form(ViewStateProvidersConfig.ViewStateKeyFieldName)

        If viewStateKey Is Nothing OrElse viewStateKey.Length < 1 Then
            viewStateKey = _
              p.Request.QueryString(ViewStateProvidersConfig.ViewStateKeyFieldName)
            If viewStateKey Is Nothing OrElse viewStateKey.Length < 1 Then
                Return Guid.NewGuid
            End If
        End If
        Try
            Return New Guid(viewStateKey)
        Catch e As FormatException
            Return Guid.NewGuid
        End Try
    End Function

    Private Function GetMacKeyModifier(ByVal pControl As Control) As String
        Dim p As Page = CType(pControl, Page)
        Dim value As Integer = p.TemplateSourceDirectory.GetHashCode + _
                               Me.GetType.Name.GetHashCode

        If Not (p.ViewStateUserKey Is Nothing) Then
            Return String.Concat(value.ToString(_
                   NumberFormatInfo.InvariantInfo), p.ViewStateUserKey)
        End If
        Return value.ToString(NumberFormatInfo.InvariantInfo)
    End Function


    Private Function GetLosFormatter(ByVal pControl As Control, _
            ByVal enableViewStateMac As Boolean) _
            As LosFormatter
        If enableViewStateMac Then
            Return New LosFormatter(True, _
                   GetMacKeyModifier(CType(pControl, Page)))
        Return New LosFormatter
    End Function

#End Region
End Class

SessionViewStateProviderInitialize 方法期望找到一个名为 ConnectionStringName 的配置属性,该属性标识 <connectionStrings> 配置部分中的连接字符串。连接字符串由 LoadPageStateSavePageState 方法使用,以从数据库加载/保存 viewstate 字符串。

其他属性包括

  • ApplicationName,它从 applicationName 属性中获取。
  • ViewStateKeyName 属性将包含 viewstate 引用在页面上保留的隐藏字段的名称。此引用不能保留在默认的 __VIEWSTATE 字段中,因为它会被 ASP.NET 渲染引擎覆盖。
  • Timeout 属性,用于设置数据库保留当前 viewstate 记录的时间(以分钟为单位)。SqlServer 作业会定期运行并删除表中所有过期的 viewstate 记录。
  • SavePageState 使用 System.Web.UI.LosFormatter 序列化传递给它的 viewState 对象。创建一个 GUID 作为唯一标识符,并将记录保存在数据库中。新生成的 GUID 然后保存在页面的隐藏字段变量中。
  • LoadPageState 从隐藏变量中检索 GUID,并从数据库获取 viewstate。

Web 应用程序配置

viewstate 提供程序需要在 Web 应用程序的 Web.config 中添加以下内容

<configuration>
    <configSections>
        <sectionGroup name="system.web">
            <section name="viewstate" 
               type="System.Web.UI.ViewStateSection, CustomProviders"
               restartOnExternalChanges="true" 
               allowDefinition="MachineToApplication" />
        </sectionGroup>
    </configSections>
    <connectionStrings>
        <add name="ViewStateConnectionString" 
          connectionString="Server=(local);Database=ASPState;
                            Integrated Security=True;" />
    </connectionStrings>
    <system.web>
        <viewstate defaultProvider="CompressionViewStateProvider" 
                  enabled="true">
            <providers>
                <add name="SqlViewStateProvider" 
                   type="System.Web.Configuration.Providers.SqlViewStateProvider,
                         CustomViewStateProviders" 
                   connectionStringName="ViewStateConnectionString" 
                   timeout="35" />
                <add name="SessionViewStateProvider" 
                   type="System.Web.Configuration.Providers.SessionViewStateProvider, 
                         CustomViewStateProviders" 
                   numberOfPagesInMemory="10" />
                <add name="CompressionViewStateProvider" 
                  type="System.Web.Configuration.Providers.CompressionViewStateProvider, 
                         CustomViewStateProviders" />
            </providers>
        </viewstate>
    <system.web>
</configuration>

配置基础结构的解释

由于 viewstate 不是股票配置节,因此必须编写一个自定义配置节,该节继承自 System.Configuration.ConfigurationSection。以下是 System.Web.UI.ViewStateSection 类,它公开两个属性:ProvidersDefaultProvider<ConfigurationProperty> 属性将 ViewStateSection 属性映射到配置文件中的 <viewstate> 属性。因此,如果存在,DefaultProvider 属性将从 <viewstate> 元素的 defaultProvider 属性获取其值,依此类推。

''' <summary>
''' Maps to a <viewstate> section in a configuration file
''' </summary>
Public Class ViewStateSection
    Inherits ConfigurationSection


    <ConfigurationProperty("providers")> _
    Public ReadOnly Property Providers() As ProviderSettingsCollection
        Get
            Return CType(MyBase.Item("providers"), ProviderSettingsCollection)
        End Get
    End Property

    <ConfigurationProperty("defaultProvider"), DefaultSettingValue("")> _
    Public Property DefaultProvider() As String
        Get
            Return CStr(MyBase.Item("defaultProvider"))
        End Get
        Set(ByVal value As String)
            MyBase.Item("defaultProvider") = value
        End Set
    End Property

End Class

以下是我们的自定义节在 ASP.NET 中注册的方式,以便位于配置文件的 system.web 节内。注意type="System.Web.UI.ViewStateSection, CustomProviders" 包含类所在的命名空间、类和程序集名称。

<configSections>
    <sectionGroup name="system.web">
        <section name="viewstate" 
           type="System.Web.UI.ViewStateSection, CustomProviders"
           restartOnExternalChanges="true" 
           allowDefinition="MachineToApplication" />
    </sectionGroup>
</configSections>

加载和初始化 viewstate 提供程序

最后,我们需要一个类来加载和管理在 Web.config 中注册的所有 viewstate 提供程序。ViewStateManager 类将在实际的 ASPX 页面中用于加载/保存 viewstate。它将包含在 Web.config 中注册的所有提供程序的集合,其中一个被设置为默认提供程序。

''' <summary>
''' Manages all viewstate manipulations.
''' </summary>
Public Class ViewStateManager

    Private Shared _provider As ViewStateProvider = Nothing
    Private Shared _providers As ViewStateProviderCollection = Nothing
    Private Shared _lock As New Object
    Private Shared _enabled As Boolean
    Private Shared _enabledSet As Boolean

    ''' <summary>
    ''' Default provider set in web.config
    ''' </summary>
    Public Shared ReadOnly Property Provider() As ViewStateProvider
        Get
            Return _provider
        End Get
    End Property

    Public Shared ReadOnly Property Providers() As ViewStateProviderCollection
        Get
           Return _providers
        End Get
    End Property

    Public Shared ReadOnly Property Enabled() As Boolean
        Get
            If Not _enabledSet Then
                _enabled = GetViewStateSection().Enabled
                _enabledSet = True
            End If
            Return _enabled
       End Get
    End Property

    Shared Sub New()
        Call LoadProviders()
    End Sub

    Public Shared Function LoadPageState(ByVal pControl As Control) As Object
        ' Make sure a provider is loaded
        Call LoadProviders()
        ' Delegate to the provider
        Return _provider.LoadPageState(pControl)
    End Function

    Public Shared Sub SavePageState(ByVal pControl As Control, _
                                    ByVal viewState As Object)
        ' Make sure a provider is loaded
         Call LoadProviders()
        ' Delegate to the provider
         _provider.SavePageState(pControl, viewState)
    End Sub

    Private Shared Sub LoadProviders()
        ' Avoid claiming lock if providers are already loaded
        If _provider Is Nothing Then
            SyncLock _lock
                ' Do this again to make sure _provider is still null
                If _provider Is Nothing Then
                    ' Get a reference to the <viewstate> section
                    Dim section As ViewStateSection = GetViewStateSection()
                    ' Is custom viewstate management enabled
                    If section IsNot Nothing Then _enabled = section.Enabled
                    _enabledSet = True

                    If _enabled Then
                        ' Load registered providers and
                        ' point _provider to the default provider
                        _providers = New ViewStateProviderCollection
                        ProvidersHelper.InstantiateProviders(section.Providers, _
                                        _providers, GetType(ViewStateProvider))
                        _provider = _providers(section.DefaultProvider)
                        If _provider Is Nothing Then
                            Throw New ViewStateProviderException(_
                               "Unable to load default ViewStateProvider")
                        End If
                    End If
                End If
            End SyncLock
        End If
    End Sub

    Private Shared Function GetViewStateSection() As ViewStateSection
        Return CType(WebConfigurationManager.GetSection(_
               "system.web/viewstate"), ViewStateSection)
    End Function

End Class

LoadProviders 内部,ProvidersHelper.InstantiateProviders 子例程循环遍历 Web.Configsystem.web 下的 viewstate/providers 部分并加载注册的提供程序。它会查看以下内容来加载提供程序

<add name="SqlViewStateProvider" 
  type="System.Web.Configuration.Providers.SqlViewStateProvider,
        CustomViewStateProviders" 
  connectionStringName="ViewStateConnectionString" timeout="35" />

使用 ViewStateManager

现在我们需要重载 Page 类公开的函数,以使用我们的 viewstate管理器来保存/加载 viewstate。在示例中,这是通过派生一个继承自 System.Web.UI.PagePageTemplate 类来实现的,并使所有 ASPX 页面继承自 PageTemplate

PageTemplate 除了重载的 viewstate 函数外,还公开以下内容

  • ServerSideViewState - 设置为 false 以使用常规页面 viewstate。此属性可以在 Web.config 中或在页面的 Init 方法中设置。
  • SetServerSideViewStateProvider - 设置要在页面上使用的提供程序。这是我用来启用按页面使用不同提供程序的技巧。

注意:常见的做法是使用 Web.config 中的 defaultProvider 属性为整个应用程序设置提供程序。我个人认为,使用 SetServerSideViewStateProvider 可能会很快变得混乱,并且不是最佳实践。

以下是重载页面的正确代码。示例中的代码会略有不同,以在按页面基础上显示不同的实现。

Protected Overloads Overrides Function LoadPageStateFromPersistenceMedium() As Object
    If _serverSideViewState AndAlso ViewStateManager.Enabled Then
        Return ViewStateManager.LoadPageState(Me)
    Else
        Return MyBase.LoadPageStateFromPersistenceMedium
        ' regular client viewState
    End If
End Function

Protected Overloads Overrides Sub SavePageStateToPersistenceMedium(ByVal viewState As Object)
    If _serverSideViewState AndAlso ViewStateManager.Enabled Then
        ViewStateManager.SavePageState(Me, viewState)
    Else
        MyBase.SavePageStateToPersistenceMedium(viewState)
        ' regular client viewState
    End If
End Sub

Anthem.Net 集成

在我看来,Anthem.Net 是迄今为止最好的 AJAX 库。如果它不能与 CustomViewState 实现结合使用,那就太可惜了。要了解更多关于 Anthem.Net 的信息,请参阅:Anthem.NET 简介

注意:此解决方案将始终使用 web.config 中设置的默认 viewstate 提供程序。使用 SetServerSideViewStateProvider 属性按页面设置提供程序将被忽略。

由于 Anthem 会独立于 ASP.NET 管理 ViewState 到页面的输出,因此我们需要让 Anthem 在页面上写入带有 ViewStateKeyName 的隐藏字段(如果存在)。ViewState 的检索和加载将由 ASP.NET 处理,因为 Anthem 在回调期间会启动正常的页面生命周期。

要集成 ViewStateManager,我们需要在 Anthem 项目中进行以下修改

  • CustomProviders.dll 添加为 Anthem 项目的引用,以便访问 ViewStateManager。
  • Manager.cs 中添加以下函数,以在回调后从页面的标记中检索 viewstate 键。
  • private string GetServerSideViewState(string html)
    {
        if (ViewStateManager.Enabled) {
            return GetHiddenInputValue(html, "<input type=\"hidden\" name=\"" +
            ViewStateManager.Provider.ViewStateKeyName + "\" id=\"" +
            ViewStateManager.Provider.ViewStateKeyName + "\" value=\"");
        }
        else {
            return null;
        }
    }
  • Manager.cs 中创建一个重载的 WriteValueAndError 函数,该函数会将值(包括更新的 serverSideViewState 变量)添加到要写回页面的响应字符串中。
  • /// <summary>
    /// Adds ServerSideViewState support
    /// </summary>
    private static void WriteValueAndError(
    	StringBuilder sb,
    	object val,
    	string error,
    	string viewState,
    	string viewStateEncrypted,
    	string serverSideViewState,
    	string serverSideViewStateKey,
    	string eventValidation,
    	Hashtable controls,
    	string[] scripts)
    {
        sb.Append("{\"value\":");
        WriteValue(sb, val);
        sb.Append(",\"error\":");
        WriteValue(sb, error);
        if (viewState != null)
        {
            sb.Append(",\"viewState\":");
            WriteValue(sb, viewState);
        }
        if (viewStateEncrypted != null)
        {
            sb.Append(",\"viewStateEncrypted\":");
            WriteValue(sb, viewStateEncrypted);
        }
        if (serverSideViewState != null)
        {
            sb.Append(",\"serverSideViewState\":");
            WriteValue(sb, serverSideViewState);
        }
        if (serverSideViewStateKey != null)
        {
            sb.Append(",\"serverSideViewStateKey\":");
            WriteValue(sb, serverSideViewStateKey);
        }
        if (eventValidation != null)
        {
            sb.Append(",\"eventValidation\":");
            WriteValue(sb, eventValidation);
        }
        if (controls != null && controls.Count > 0)
        {
            sb.Append(",\"controls\":{");
            foreach (DictionaryEntry control in controls)
            {
                sb.Append("\"" + control.Key + "\":");
                WriteValue(sb, control.Value);
                sb.Append(",");
            }
            --sb.Length;
            sb.Append("}");
        }
        if (scripts != null && scripts.Length > 0)
        {
            sb.Append(",\"pagescript\":[");
            foreach (string script in scripts)
            {
                WriteValue(sb, script);
                sb.Append(",");
            }
            --sb.Length;
            sb.Append("]");
        }
        if (GetManager()._clientSideEvalScripts.Count > 0)
        {
            sb.Append(",\"script\":[");
            foreach (string script in GetManager()._clientSideEvalScripts)
            {
                WriteValue(sb, script);
                sb.Append(",");
            }
            --sb.Length;
            sb.Append("]");
        }
        sb.Append("}");
    }
  • 接下来,我们需要修改 Manager.cs 中的 WriteResult(Stream stream, MemoryStream htmlBuffer) 函数以集成 CustomViewState。此函数将结果字符串发送回页面。修改处以**粗体**显示
  • internal void WriteResult(Stream stream, MemoryStream htmlBuffer)
    {
        string viewState = null;
        string serverSideViewState = null;
        string viewStateEncrypted = null;
        string eventValidation = null;
        Hashtable controls = null;
        string[] scripts = null;
        if (_updatePage)
        {
            string html = 
              HttpContext.Current.Response.ContentEncoding.GetString(
              htmlBuffer.GetBuffer());
            viewState = GetViewState(html);
            serverSideViewState = GetServerSideViewState(html);
    #if V2
            viewStateEncrypted = GetViewStateEncrypted(html);
            eventValidation = GetEventValidation(html);
    #endif
            controls = GetControls(html);
            foreach (object o in _targets.Values)
            {
                Control c = o as Control;
                if (c != null && !c.Visible)
                {
                    if (c.ID != null && controls.ContainsKey(c.ID))
                        controls[c.ID] = "";
                }
            }
    
            scripts = GetScripts(html);
        }
        StringBuilder sb = new StringBuilder();
        try
        {
            // If the serverSideViewState is null,
            // that means CustomViewState is turned off on the page,
            // and thus will be ignored inside WriteValueAndError.
            // But we will double check for it here anyways.
            if (ViewStateManager.Enabled && serverSideViewState != null) {
                WriteValueAndError(sb, _value, _error, viewState, viewStateEncrypted,
                    serverSideViewState, ViewStateManager.Provider.ViewStateKeyName, 
                    eventValidation, controls, scripts);
            }
            else {
                WriteValueAndError(sb, _value, _error, viewState, viewStateEncrypted, 
                                   eventValidation, controls, scripts);
            }
        }
        catch (Exception ex)
        {
            // If an exception was thrown while formatting the
            // result value, we need to discard whatever was
            // written and start over with nothing but the error
            // message.
            sb.Length = 0;
            WriteValueAndError(sb, null, ex.Message, null, null, null, null, null);
        }
    
        // If an IOFrame was used to make this callback,
        // then wrap the response in a <textarea> element
        // so the iframe will not mess with the text of the JSON object.
        string response = sb.ToString();
        if (string.Compare(HttpContext.Current.Request["Anthem_IOFrame"], "true", true) == 0)
        {
            response = "<textarea id=\"response\">" + 
                       response + "</textarea>";
        }
    
        byte[] buffer = HttpContext.Current.Response.ContentEncoding.GetBytes(response);
        stream.Write(buffer, 0, buffer.Length);
    }
  • 最后,我们需要修改 Anthem.js 以更新页面上的 serverSideViewState 隐藏输入值。修改处以**粗体**显示
  • function Anthem_UpdatePage(result) {
        var form = Anthem_GetForm();
        if (result.viewState) {
            Anthem_SetHiddenInputValue(form, "__VIEWSTATE", result.viewState);
        }
        
        if (result.serverSideViewState && result.serverSideViewStateKey) {
            Anthem_SetHiddenInputValue(form, result.serverSideViewStateKey, 
                                       result.serverSideViewState);
        }
        if (result.viewStateEncrypted) {
            Anthem_SetHiddenInputValue(form, "__VIEWSTATEENCRYPTED", 
                                       result.viewStateEncrypted);
        }
        if (result.eventValidation) {
            Anthem_SetHiddenInputValue(form, "__EVENTVALIDATION", 
                                       result.eventValidation);
        }
        if (result.controls) {
            for (var controlID in result.controls) {
                var containerID = "Anthem_" + 
                    controlID.split("$").join("_") + "__";
                var control = document.getElementById(containerID);
                if (control) {
                    control.innerHTML = result.controls[controlID];
                    if (result.controls[controlID] == "") {
                        control.style.display = "none";
                    } else {
                        control.style.display = "";
                    }
                }
            }
        }
        if (result.pagescript) {
            Anthem_LoadPageScript(result, 0);
        }
    }

就这样。服务器端 viewstate 使 Anthem 响应明显更快。您自己可以亲自试试!

关注点

此代码已编写,但遗憾的是,从未在实际应用程序中进行过测试。非常希望看到 SqlViewStateProvider 和其他提供程序在大规模、高使用量的应用程序中使用。有什么想法或其他的(更好的)提供程序实现吗?非常欢迎所有反馈。

历史

  • 2007 年 6 月 5 日
    • 删除了 ViewStateProvidersConfig 类。
    • 向 Viewstate 配置节添加了 Enabled 属性。
    • 添加了 Anthem 集成,以及一个使用 Anthem 数据网格的示例页面。
© . All rights reserved.