|
|||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
IntroductionThis article shows an implementation of a couple of custom viewstate providers using ASP.NET 2.0 Provider architecture and the Provider Model design pattern. To get more information regarding the ASP.NET 2.0 Provider Model please read Introduction to the Provider Model BackgroundAbout half a year ago I was working on a project that required moderately large datasets fetched from the database and displayed in a datagrid. Going to the database on every postback seemed like a waste of resources, therefore, I decided to store the data in viewstate. And thats when the page load times started taking longer and longer. I started researching alternative solutions for optimizing viewstate and found a moderate amount of information and examples online. Nevertheless, I have not been able to find a complete solution that would let me easily manage and extend the functionality of viewstate without having to make significant changes to the website. I have looked at a number of interesting and useful articles that provided different takes on the implementation of the viewstate management, but none had a complete and easily integrable solution. The code presented below was inspired by or partially taken from the following articles:
Using the codeI will concentrate on describing just the viewstate provider code without going into details on the provider pattern design. For detailed explanation of pattern design and architecture please see references listed above. Writing the providersFirst, we need an abstract class representing the viewstate and exposing functions actual providers will implement. ''' <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
Next, we need an actual provider class. I have three concrete provider classes in the example:
Lets look at one of them - ''' <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 "init">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
Other properties include:
Web Application ConfigurationViewstate provider requires the following be added to the Web.config of the Web Application: <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>
Explanation of infrastructure for configurationSince viewstate is not a stock configuration section, a custom
configuration section must be written that derives from
System.Configuration.ConfigurationSection. The following is a ''' <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
Below is the way our custom section is registered with ASP.NET to be inside <configSections>
<sectionGroup name="system.web">
<section name="viewstate" type="System.Web.UI.ViewStateSection, CustomProviders"
restartOnExternalChanges="true" allowDefinition="MachineToApplication" />
</sectionGroup>
</configSections>
Loading and initializing viewstate providersAt last we need a class that will load and manage all viewstate providers registered in 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
</viewstate>
Inside <add name="SqlViewStateProvider" type="System.Web.Configuration.Providers.SqlViewStateProvider,
CustomViewStateProviders" connectionStringName="ViewStateConnectionString" timeout="35" />
Using ViewStateManagerNow we need to overload the functions exposed by the Page class to
use our viewstate manager to save/load the viewstate. In the example
this is done by deriving a
defaultProvider
property in Web.config to set the provider for the whole application.
Using SetServerSideViewStateProvider could get messy fast and is not a best practice in my opinion.
Below is the proper code for the overloaded page. The code in the example will differ slightly to show different implementations on per page basis. 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 integrationAnthem.Net is, in my opinion, the best AJAX library out to date. And it would be a shame if it couldn't be used in conjunction with CustomViewState implementation. To read more on Anthem.Net please see Introduction to Anthem.NET. NOTE: This solution will always use the default viewstate provider set in web.config. Setting provider on per page basis usingSetServerSideViewStateProvider property will be ignored.
Because Anthem is managing outputting ViewState to the page separately from Asp.Net, we need to make Anthem write our
hidden variable with the
That's it. ServerSide viewstate makes Anthem noticeably more responsive. Check it out for yourself! Points of InterestThis code has been written but unfortunately never been tested in a real life application. Very interested to see History
| ||||||||||||||||||||||||||||||||||||||||||||||||