As requested in this post, following is an example of a way to store viewstate in a database on the server instead of the hidden form field on the page. The form only maintains a guid that is used
to retrieve saved viewstate from the DB. This can reduce bandwidth considerably if viewstate is something that you can't or don't want to disable completely.
The following code snippets are included:
<div mce_keep="true">SamplePageStatePersister.cs - custom mechanism for loading and saving viewstate for the page.</div>
<div mce_keep="true">SamplePageAdapter.cs - returns a new instance of SamplePageStatePersister (this is enlisted by the App.Browser file).</div>
<div mce_keep="true">App.Browser - must be added to the web project and tells the web application to use our custom page adapter.</div>
<div mce_keep="true">PageViewStateServices.cs - handles the interaction with the database via stored procedures to optimise performance.</div>
<div mce_keep="true">PageViewState.sql - creates the table to hold the view state and the stored procedures to interact with the database. You will need to apply the appropriate permissions.</div>
<div mce_keep="true">CleanupPageViewState.sql - deletes old view state to stop the database growing uncontrollably. Configure as a scheduled job and can be modified to extend or reduce the time window for view state being maintained in
the database. Defaults to 4 hours.</div>
NOTE: This current example will not work if you use Server.Transfer and carry across the form data in the transfer. In this case, the view state ID will be carried across too and then subsequent back navigation will result in an error.
SamplePageStatePersister.cs
using System; using System.Globalization; using System.IO; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls;
/// <summary>
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// This class is the page state persister for the application.
///
/// Created by Jason Hill on 26/6/2007.
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// </summary> public class SamplePageStatePersister : System.Web.UI.PageStatePersister
{
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// <summary>
/// Get the unique ID for the view state.
/// </summary>
/// <remarks>
/// <author>jhill</author>
/// <creation>Wednesday, 30 May 2007</creation>
/// </remarks>
/// ----------------------------------------------------------------------------------------------------------------------------------------------------------------- private Guid GetViewStateID()
{ string viewStateKey;
// Get the ID from the request
viewStateKey = _page.Request["__VIEWSTATEID"];
// Assign a new ID if we don't have one in the request if (string.IsNullOrEmpty(viewStateKey))
{ return Guid.NewGuid();
}
// Use the ID from the request if it is valid, else assign a new ID try
{ return new Guid(viewStateKey);
} catch (FormatException)
{ return Guid.NewGuid();
}
}
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// <summary>
/// Load the view state from persistent medium.
/// </summary>
/// <remarks>
/// <author>jhill</author>
/// <creation>Wednesday, 30 May 2007</creation>
/// </remarks>
/// ----------------------------------------------------------------------------------------------------------------------------------------------------------------- public override void Load()
{
// Load view state from DB string pageViewState = PageViewStateServices.GetByID(GetViewStateID());
// Deserialize into a Pair of ViewState and ControlState objects
IStateFormatter formatter = StateFormatter;
Pair statePair = (Pair)formatter.Deserialize(pageViewState);
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// <summary>
/// Save the view state to persistent medium.
/// </summary>
/// <remarks>
/// <author>jhill</author>
/// <creation>Wednesday, 30 May 2007</creation>
/// </remarks>
/// <param name="viewState">View state to save.</param>
/// ----------------------------------------------------------------------------------------------------------------------------------------------------------------- public override void Save()
{
// Create a pair for ViewState and ControlState
Pair statePair = new Pair(ViewState, ControlState);
IStateFormatter formatter = StateFormatter;
// Save the view state
Guid id = GetViewStateID();
PageViewStateServices.Save(id, formatter.Serialize(statePair));
// Store the ID of the view state in a hidden form field
HtmlInputHidden control = _page.FindControl("__VIEWSTATEID")
as HtmlInputHidden; if (control == null)
{
ScriptManager.RegisterHiddenField(_page, "__VIEWSTATEID", id.ToString());
} else
{
control.Value = id.ToString();
}
}
}
SamplePageAdapter.cs
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
/// <summary>
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// This class is the page adapter for the application.
///
/// Created by Jason Hill on 26/6/2007.
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// </summary>public class SamplePageAdapter : System.Web.UI.Adapters.PageAdapter
{
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// <summary>
/// Gets the state persister for the page.
/// </summary>
/// <remarks>
/// <author>jhill</author>
/// <creation>Wednesday, 30 May 2007</creation>
/// </remarks>
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------public override PageStatePersister GetStatePersister()
{
return new SamplePageStatePersister(Page);
}
}
using System;
using System.Data;
using System.Data.SqlClient;
using System.Collections;
using System.Text;
/// <summary>
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// This class provides services for handling page view state.
///
/// Created by Jason Hill on 26/6/2007.
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// </summary>public static class PageViewStateServices
{
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// <summary>
/// Get a page view state by ID.
/// </summary>
/// <remarks>
/// <author>jhill</author>
/// <creation>Wednesday, 30 May 2007</creation>
/// </remarks>
/// <param name="id">ID.</param>
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------public static string GetByID(Guid id)
{
using (SqlConnection connection = new SqlConnection(Common.PageViewStateConnectionString))
{
connection.Open();
try
{
using (SqlCommand command = connection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "GetByID";
command.Parameters.Add(new SqlParameter("@id", id));
return (string)command.ExecuteScalar();
}
}
finally
{
connection.Close();
}
}
}
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// <summary>
/// Save the view state.
/// </summary>
/// <remarks>
/// <author>jhill</author>
/// <creation>Wednesday, 30 May 2007</creation>
/// </remarks>
/// <param name="id">Unique ID.</param>
/// <param name="value">View state value.</param>
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------public static void Save(Guid id, string value)
{
using (SqlConnection connection = new SqlConnection(Common.PageViewStateConnectionString))
{
connection.Open();
try
{
using (SqlCommand command = connection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "SaveViewState";
command.Parameters.Add(new SqlParameter("@id", id));
command.Parameters.Add(new SqlParameter("@value", value));
command.ExecuteNonQuery();
}
}
finally
{
connection.Close();
}
}
}
}
PageViewState.sql
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[PageViewState](
[ID] [uniqueidentifier] NOT NULL,
[Value] [text] NOT NULL,
[LastUpdatedOn] [datetime] NOT NULL,
CONSTRAINT [PK_PageViewState] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[GetByID]
@id uniqueidentifier
AS
BEGIN
SET NOCOUNT ON;
select Value
from PageViewState
where ID = @id
END
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[SaveViewState]
@id uniqueidentifier,
@value text
AS
BEGIN
SET NOCOUNT ON;
if (exists(select ID from PageViewState where ID = @id))
update PageViewState
set Value = @value, LastUpdatedOn = getdate()
where ID = @id
else
insert into PageViewState
(ID, Value, LastUpdatedOn)
values (@id, @value, getdate())
END
CleanupPageViewState.sql
delete from PageViewState
where LastUpdatedOn < dateadd(minute, -240, current_timestamp)
Did you not know you could just change the provider? There is a PageStatePersister class you inherit. The
SessionPageStatePersister is the most common one out of the box, but you can create your own. You just set the provider in your web.config file.
Jason, this is wonderful. Thank you very much. I am seeing about 80-90% reductions in bytes sent on my pages and about 40% on bytes received.
A couple of observations.
GetMacKeyModifier is not used anywhere.
In one of my applications, I keep getting the same viewStateKey from
_page.Request["__VIEWSTATEID"] no matter what page I was on. Found out why. This particular app uses Server.Transfer("to_page.aspx") so it retains the same _VIEWSTATEID. I am phasing out Server.Transfer though.
Thanks for the feedback. I have removed the GetMacKeyModifier() and GetLosFormatter() methods as they are redundant. They were used in a previous implementation but have been superceded.
We don't use Server.Transfer at all so I can't provide much help with that. Did it still work OK or did you encounter problems when using Server.Transfer?
Thanks for the feedback. I have removed the GetMacKeyModifier() and GetLosFormatter() methods as they are redundant. They were used in a previous implementation but have been superceded.
We don't use Server.Transfer at all so I can't provide much help with that. Did it still work OK or did you encounter problems when using Server.Transfer?
It works fine. Because it retains the same id, PageStatePersister keeps updating the same row, so the table for page views does not grow as fast. I think it's better that way.
Jason, I take back what I said about Server.Transfer retaining the same id being better.
The problem is, when the user presses the back button, you now have the same viewstate you backed from being applied to your current page, and results in an error.
Yeah...that is what I was expecting to see when you mentioned server.transfer. I haven't used it so don't have much insight. I think you can pass a parameter to indicate whether you want form data carried across. Specifying "false" might fix the problem
but would cause problems with your app if you are expecting the form data to be carried over.
It might be possible to tweak the code so that you generate a new viewstate ID for the page that you have trsansferred to instead of using the one saved in the form data.
Validation of viewstate MAC failed. If this application is hosted by a Web Farm or cluster, ensure that <machineKey> configuration specifies the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster.
I am phasing out Response.Redirect. Server.Transfer does not provide any value to me, and I don't really care much about hiding parameters.
Also when you go from pageA.aspx to pageB.aspx with Server.Transfer, the url still says PageA.aspx even though you are on pageB.aspx, which is irritating.
I was told that Server.Transfer was the proper .NET way.
Hello, and thanks for the useful example. However I have been trying to make it work for a VB.NET project (vs 2008) we are working on. I had some problems with the constructor of the page persister but it finally compiled. However when I try to run it,
I immediately get an error (no page is loaded first) stating that it cannot load the SamplePageAdapter type and points to the App.Browser file on the line were it is called.
Can you help??? Have you seen anyone do this in VB so that I can see that example??? I reallly don't know were to begin, since the compilation process goes through without a hitch, so I don't see why it can't load the type.
Jason Hill
Contributor
2019 Points
497 Posts
Store ViewState in the Database instead of Hidden Form Field
Jul 22, 2008 03:52 AM|LINK
As requested in this post, following is an example of a way to store viewstate in a database on the server instead of the hidden form field on the page. The form only maintains a guid that is used to retrieve saved viewstate from the DB. This can reduce bandwidth considerably if viewstate is something that you can't or don't want to disable completely.
The following code snippets are included:
NOTE: This current example will not work if you use Server.Transfer and carry across the form data in the transfer. In this case, the view state ID will be carried across too and then subsequent back navigation will result in an error.
SamplePageStatePersister.cs
using System;
using System.Globalization;
using System.IO;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
/// <summary>
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// This class is the page state persister for the application.
///
/// Created by Jason Hill on 26/6/2007.
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// </summary>
public class SamplePageStatePersister : System.Web.UI.PageStatePersister
{
private Page _page;
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// <summary>
/// Constructor.
/// </summary>
/// <remarks>
/// <author>jhill</author>
/// <creation>Wednesday, 30 May 2007</creation>
/// </remarks>
/// <param name="page">Page.</param>
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
public SamplePageStatePersister(Page page)
: base(page)
{
_page = page;
}
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// <summary>
/// Get the unique ID for the view state.
/// </summary>
/// <remarks>
/// <author>jhill</author>
/// <creation>Wednesday, 30 May 2007</creation>
/// </remarks>
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
private Guid GetViewStateID()
{
string viewStateKey;
// Get the ID from the request
viewStateKey = _page.Request["__VIEWSTATEID"];
// Assign a new ID if we don't have one in the request
if (string.IsNullOrEmpty(viewStateKey))
{
return Guid.NewGuid();
}
// Use the ID from the request if it is valid, else assign a new ID
try
{
return new Guid(viewStateKey);
}
catch (FormatException)
{
return Guid.NewGuid();
}
}
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// <summary>
/// Load the view state from persistent medium.
/// </summary>
/// <remarks>
/// <author>jhill</author>
/// <creation>Wednesday, 30 May 2007</creation>
/// </remarks>
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
public override void Load()
{
// Load view state from DB
string pageViewState = PageViewStateServices.GetByID(GetViewStateID());
if (pageViewState == null)
{
ViewState = null;
ControlState = null;
}
else
{
// Deserialize into a Pair of ViewState and ControlState objects
IStateFormatter formatter = StateFormatter;
Pair statePair = (Pair)formatter.Deserialize(pageViewState);
// Update ViewState and ControlState
ViewState = statePair.First;
ControlState = statePair.Second;
}
}
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
/// <summary>
/// Save the view state to persistent medium.
/// </summary>
/// <remarks>
/// <author>jhill</author>
/// <creation>Wednesday, 30 May 2007</creation>
/// </remarks>
/// <param name="viewState">View state to save.</param>
/// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
public override void Save()
{
// Create a pair for ViewState and ControlState
Pair statePair = new Pair(ViewState, ControlState);
IStateFormatter formatter = StateFormatter;
// Save the view state
Guid id = GetViewStateID();
PageViewStateServices.Save(id, formatter.Serialize(statePair));
// Store the ID of the view state in a hidden form field
HtmlInputHidden control = _page.FindControl("__VIEWSTATEID") as HtmlInputHidden;
if (control == null)
{
ScriptManager.RegisterHiddenField(_page, "__VIEWSTATEID", id.ToString());
}
else
{
control.Value = id.ToString();
}
}
}
docluv
Star
12685 Points
2005 Posts
ASPInsiders
MVP
Re: Store ViewState in the Database instead of Hidden Form Field
Jul 22, 2008 06:12 AM|LINK
Did you not know you could just change the provider? There is a PageStatePersister class you inherit. The SessionPageStatePersister is the most common one out of the box, but you can create your own. You just set the provider in your web.config file.
Jason Hill
Contributor
2019 Points
497 Posts
Re: Store ViewState in the Database instead of Hidden Form Field
Jul 22, 2008 06:19 AM|LINK
Um...the code I provided is a custom implementation of PageStatePersister.
profnachos
Member
86 Points
135 Posts
Re: Store ViewState in the Database instead of Hidden Form Field
Jul 22, 2008 07:24 AM|LINK
Jason, this is wonderful. Thank you very much. I am seeing about 80-90% reductions in bytes sent on my pages and about 40% on bytes received.
A couple of observations.
GetMacKeyModifier is not used anywhere.
In one of my applications, I keep getting the same viewStateKey from _page.Request["__VIEWSTATEID"] no matter what page I was on. Found out why. This particular app uses Server.Transfer("to_page.aspx") so it retains the same _VIEWSTATEID. I am phasing out Server.Transfer though.
Jason Hill
Contributor
2019 Points
497 Posts
Re: Store ViewState in the Database instead of Hidden Form Field
Jul 22, 2008 11:51 PM|LINK
Thanks for the feedback. I have removed the GetMacKeyModifier() and GetLosFormatter() methods as they are redundant. They were used in a previous implementation but have been superceded.
We don't use Server.Transfer at all so I can't provide much help with that. Did it still work OK or did you encounter problems when using Server.Transfer?
profnachos
Member
86 Points
135 Posts
Re: Store ViewState in the Database instead of Hidden Form Field
Jul 22, 2008 11:58 PM|LINK
It works fine. Because it retains the same id, PageStatePersister keeps updating the same row, so the table for page views does not grow as fast. I think it's better that way.
profnachos
Member
86 Points
135 Posts
Re: Store ViewState in the Database instead of Hidden Form Field
Jul 25, 2008 01:17 AM|LINK
Jason, I take back what I said about Server.Transfer retaining the same id being better.
The problem is, when the user presses the back button, you now have the same viewstate you backed from being applied to your current page, and results in an error.
Jason Hill
Contributor
2019 Points
497 Posts
Re: Store ViewState in the Database instead of Hidden Form Field
Jul 25, 2008 01:30 AM|LINK
Yeah...that is what I was expecting to see when you mentioned server.transfer. I haven't used it so don't have much insight. I think you can pass a parameter to indicate whether you want form data carried across. Specifying "false" might fix the problem but would cause problems with your app if you are expecting the form data to be carried over.
It might be possible to tweak the code so that you generate a new viewstate ID for the page that you have trsansferred to instead of using the one saved in the form data.
profnachos
Member
86 Points
135 Posts
Re: Store ViewState in the Database instead of Hidden Form Field
Jul 25, 2008 01:34 AM|LINK
The error I get is this
Validation of viewstate MAC failed. If this application is hosted by a Web Farm or cluster, ensure that <machineKey> configuration specifies the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster.
I am phasing out Response.Redirect. Server.Transfer does not provide any value to me, and I don't really care much about hiding parameters.
Also when you go from pageA.aspx to pageB.aspx with Server.Transfer, the url still says PageA.aspx even though you are on pageB.aspx, which is irritating.
I was told that Server.Transfer was the proper .NET way.
cguevara1970
Member
4 Points
2 Posts
Re: Store ViewState in the Database instead of Hidden Form Field
Dec 23, 2009 09:17 PM|LINK
Hello, and thanks for the useful example. However I have been trying to make it work for a VB.NET project (vs 2008) we are working on. I had some problems with the constructor of the page persister but it finally compiled. However when I try to run it, I immediately get an error (no page is loaded first) stating that it cannot load the SamplePageAdapter type and points to the App.Browser file on the line were it is called.
Can you help??? Have you seen anyone do this in VB so that I can see that example??? I reallly don't know were to begin, since the compilation process goes through without a hitch, so I don't see why it can't load the type.
Thanks.