You are not Logged in
Would you like to Login or Register

Today is: Saturday, 19 July, 2008
Check this months hot topics

SQL ViewState

Whilst working on a recent project, I was having problems with the size of the view state being downloaded to the client.

Whilst surfing Google, I found the following article which has now solved my problems, http://weblogs.asp.net/adweigert/archive/2004/03/09/86628.aspx

The code is in three parts, an addition to the web.config file, a new class and a new database, although you could integrate it into your current applications database with minimal code changes.

Many thanks to Adan Weigert.

web.config

<appSettings>
       <add key="ViewStateConnectionString" value="Server=(local);Database=ASPState;Trusted_Connection=yes;"/>
       <add key="ViewStateTimeout" value="20"/>
</appSettings>

SqlViewState.cs

using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using System.IO;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;

public class SqlViewStatePage : Page
{
    private const int DefaultViewStateTimeout = 20;
    private string _viewStateConnectionString;
    private TimeSpan _viewStateTimeout;

    public SqlViewStatePage()
        : base()
    {
        if (this.IsDesignMode)
            return;

        this._viewStateConnectionString = ConfigurationSettings.AppSettings["ViewStateConnectionString"];

        try
        {
            this._viewStateTimeout = TimeSpan.FromMinutes(Convert.ToDouble(ConfigurationSettings.AppSettings["ViewStateTimeout"]));
        }
        catch
        {
            this._viewStateTimeout = TimeSpan.FromMinutes(SqlViewStatePage.DefaultViewStateTimeout);
        }
    }

    protected bool IsDesignMode
    {
        get { return (this.Context == null); }
    }

    protected bool IsSqlViewStateEnabled
    {
        get { return (this._viewStateConnectionString != null && this._viewStateConnectionString.Length > 0); }
    }

    public TimeSpan ViewStateTimeout
    {
        get { return this._viewStateTimeout; }
        set { this._viewStateTimeout = value; }
    }

    private string GetMacKeyModifier()
    {
        int value = this.TemplateSourceDirectory.GetHashCode() + this.GetType().Name.GetHashCode();

        if (this.ViewStateUserKey != null)
            return string.Concat(value.ToString(NumberFormatInfo.InvariantInfo), this.ViewStateUserKey);

        return value.ToString(NumberFormatInfo.InvariantInfo);
    }

    private LosFormatter GetLosFormatter()
    {
        if (this.EnableViewStateMac)
            return new LosFormatter(truethis.GetMacKeyModifier());

        return new LosFormatter();
    }

    private Guid GetViewStateGuid()
    {
        string viewStateKey;

        viewStateKey = this.Request.Form["__VIEWSTATEGUID"];

        if (viewStateKey == null || viewStateKey.Length < 1)
        {
            viewStateKey = this.Request.QueryString["__VIEWSTATEGUID"];

            if (viewStateKey == null || viewStateKey.Length < 1)
                return Guid.NewGuid();
        }

        try
        {
            return new Guid(viewStateKey);
        }
        catch (FormatException)
        {
            return Guid.NewGuid();
        }
    }

    protected override object LoadPageStateFromPersistenceMedium()
    {
        Guid viewStateGuid;
        byte[] rawData;

        if (this.IsDesignMode)
            return null;

        if (!this.IsSqlViewStateEnabled)
            return base.LoadPageStateFromPersistenceMedium();

        viewStateGuid = this.GetViewStateGuid();
        rawData = null;

        using (SqlConnection connection = new SqlConnection(this._viewStateConnectionString))
        using (SqlCommand command = new SqlCommand("GetViewState", connection))
        {
            command.CommandType = CommandType.StoredProcedure;
            command.Parameters.Add("@returnValue", SqlDbType.Int).Direction = ParameterDirection.ReturnValue;
            command.Parameters.Add("@viewStateId", SqlDbType.UniqueIdentifier).Value = viewStateGuid;

            connection.Open();

            using (SqlDataReader reader = command.ExecuteReader())
            {
                if (reader.Read())
                    rawData = (byte[])Array.CreateInstance(typeof(byte), reader.GetInt32(0));

                if (reader.NextResult() && reader.Read())
                    reader.GetBytes(0, 0, rawData, 0, rawData.Length);
            }
        }

        using (MemoryStream stream = new MemoryStream(rawData))
            return this.GetLosFormatter().Deserialize(stream);
    }

    protected override void SavePageStateToPersistenceMedium(object viewState)
    {
        Guid viewStateGuid;
        HtmlInputHidden control;

        if (this.IsDesignMode)
            return;

        if (!this.IsSqlViewStateEnabled)
        {
            base.SavePageStateToPersistenceMedium(viewState);
            return;
        }

        viewStateGuid = this.GetViewStateGuid();

        using (MemoryStream stream = new MemoryStream())
        {
            this.GetLosFormatter().Serialize(stream, viewState);

            using (SqlConnection connection = new SqlConnection(this._viewStateConnectionString))
            using (SqlCommand command = new SqlCommand("SetViewState", connection))
            {
                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 = this._viewStateTimeout.TotalMinutes;

                connection.Open();
                command.ExecuteNonQuery();
            }
        }

        control = this.FindControl("__VIEWSTATEGUID"as HtmlInputHidden;

        if (control == null)
            this.RegisterHiddenField("__VIEWSTATEGUID", viewStateGuid.ToString());
        else
            control.Value = viewStateGuid.ToString();
    }
}

Database

USE ASPState

GO


DECLARE @jobID BINARY(16)  


SELECT @jobID = job_id     

  FROM msdb.dbo.sysjobs    

 WHERE name = N'ASPState_Job_ExpireViewState'       


IF (@JobID IS NOT NULLBEGIN  

       IF EXISTS(SELECT * FROM msdb.dbo.sysjobservers WHERE job_id = @JobID AND server_id <> 0) BEGIN 

              RAISERROR (N'Unable to import job ''ASPNET_Job_ExpireViewState'' since there is already a multi-server job with this name.', 16, 1) 

       END ELSE 

              EXECUTE msdb.dbo.sp_delete_job @job_name = N'ASPState_Job_ExpireViewState' 

END

GO


IF OBJECTPROPERTY(OBJECT_ID(N'dbo.ExpireViewState'), N'IsProcedure') = 1

       DROP PROCEDURE dbo.ExpireViewState

IF OBJECTPROPERTY(OBJECT_ID(N'dbo.SetViewState'), N'IsProcedure') = 1

       DROP PROCEDURE dbo.SetViewState

IF OBJECTPROPERTY(OBJECT_ID(N'dbo.GetViewState'), N'IsProcedure') = 1

       DROP PROCEDURE dbo.GetViewState

IF OBJECTPROPERTY(OBJECT_ID(N'dbo.ViewState'), N'IsUserTable') = 1

       DROP TABLE dbo.ViewState

GO


CREATE TABLE dbo.ViewState (

       ViewStateId UNIQUEIDENTIFIER NOT NULL,

       Value IMAGE NOT NULL,

       LastAccessed DATETIME NOT NULL,

       Timeout INT NOT NULL

              CONSTRAINT CK_ViewState_Timeout CHECK(Timeout > 0),

       

       CONSTRAINT PK_ViewState PRIMARY KEY CLUSTERED (ViewStateId),

)

GO


CREATE PROCEDURE dbo.GetViewState (@viewStateId UNIQUEIDENTIFIER) AS

       SET NOCOUNT ON


       DECLARE @textPtr VARBINARY(16)

       DECLARE @length INT

       

       UPDATE dbo.ViewState

          SET LastAccessed = GETUTCDATE(),

              @textPtr = TEXTPTR(Value),

              @length = DATALENGTH(Value)

        WHERE ViewStateId = @viewStateId

       

       IF @length IS NOT NULL BEGIN

              SELECT @length AS Length

              

              READTEXT ViewState.Value @textPtr 0 @length

       END

       

       RETURN 0

GO


CREATE PROCEDURE dbo.SetViewState (@viewStateId UNIQUEIDENTIFIER, @value IMAGE, @timeout INT = 20) AS

       SET NOCOUNT ON

       

       IF @viewStateId IS NULL BEGIN

              RETURN -1

       END ELSE IF @timeout < 1 BEGIN

              RETURN -2

       END ELSE IF @value IS NULL BEGIN

              RETURN -3

       END


       IF EXISTS(SELECT * FROM ViewState WHERE ViewStateId = @viewStateID) BEGIN  

              UPDATE dbo.ViewState

                 SET LastAccessed = GETUTCDATE()

                        ,Value = @value

               WHERE ViewStateID = @viewStateId

       END ELSE BEGIN

              INSERT INTO dbo.ViewState (ViewStateId, Value, LastAccessed, Timeout) VALUES (@viewStateId, @value, GETUTCDATE(), @timeout)

       END

        

       RETURN 0

GO


CREATE PROCEDURE dbo.ExpireViewState AS

       SET NOCOUNT ON


       DELETE

         FROM dbo.ViewState

        WHERE GETUTCDATE() > DATEADD(minute, Timeout, LastAccessed)

GO


GRANT EXECUTE ON dbo.GetViewState TO [ASPNET]

GRANT EXECUTE ON dbo.SetViewState TO [ASPNET]

GO


BEGIN TRANSACTION            

       DECLARE @jobID BINARY(16)

       DECLARE @returnCode INT

       

       SET @returnCode = 0

       

       IF NOT EXISTS(SELECT * FROM msdb.dbo.syscategories WHERE name = N'[Uncategorized (Local)]')

              EXEC msdb.dbo.sp_add_category @name=N'[Uncategorized (Local)]'

              

       EXECUTE @returnCode = msdb.dbo.sp_add_job

                     @job_id = @jobID OUTPUT,

                     @job_name = N'ASPState_Job_ExpireViewState',

                     @owner_login_name = NULL,

                     @description = N'Deletes expired view state information.',

                     @category_name = N'[Uncategorized (Local)]',

                     @enabled = 1,

                     @notify_level_email = 0,

                     @notify_level_page = 0,

                     @notify_level_netsend = 0,

                     @notify_level_eventlog = 0,

                     @delete_level = 0

               

       IF @@ERROR <> 0 OR @returnCode <> 0

              GOTO QuitWithRollback

       

       EXECUTE @returnCode = msdb.dbo.sp_add_jobstep 

            @job_id = @jobID,

            @step_id = 1, 

            @step_name = N'ASPState_JobStep_ExpireViewState', 

            @command = N'EXECUTE ExpireViewState', 

            @database_name = N'ASPState', 

            @server = N'', 

            @database_user_name = N'', 

            @subsystem = N'TSQL', 

            @cmdexec_success_code = 0, 

            @flags = 0, 

            @retry_attempts = 0, 

            @retry_interval = 1, 

            @output_file_name = N'', 

            @on_success_step_id = 0, 

            @on_success_action = 1, 

            @on_fail_step_id = 0, 

            @on_fail_action = 2


    IF @@ERROR <> 0 OR @ReturnCode <> 0

              GOTO QuitWithRollback 


    EXECUTE @returnCode = msdb.dbo.sp_update_job

                     @job_id = @jobID,

                     @start_step_id = 1 

    

    IF @@ERROR <> 0 OR @ReturnCode <> 0

              GOTO QuitWithRollback 

    

    EXECUTE @returnCode = msdb.dbo.sp_add_jobschedule 

            @job_id = @jobID, 

            @name = N'ASPState_JobSchedule_ExpireViewState', 

            @enabled = 1, 

            @freq_type = 4,

            @active_start_date = 20001016, 

            @active_start_time = 0, 

            @freq_interval = 1, 

            @freq_subday_type = 4, 

            @freq_subday_interval = 1, 

            @freq_relative_interval = 0, 

            @freq_recurrence_factor = 0, 

            @active_end_date = 99991231, 

            @active_end_time = 235959


    IF @@ERROR <> 0 OR @ReturnCode <> 0

              GOTO QuitWithRollback 

    

    EXECUTE @returnCode = msdb.dbo.sp_add_jobserver

                     @job_id = @jobID,

                     @server_name = N'(local)' 

       

    IF @@ERROR <> 0 OR @ReturnCode <> 0

              GOTO QuitWithRollback 

    

    COMMIT TRANSACTION          

    GOTO   EndSave

              

QuitWithRollback:

    IF @@TRANCOUNT > 0

              ROLLBACK TRANSACTION 


EndSave:

GO


kick it on DotNetKicks.com del.icio.us digg Mister Wong YahooMyWeb Reddit Furl Spurl blogmarks
Barrington Haynes Skype
Author : Barrington Haynes
Published : Monday, 31 July, 2006

Barry is the coolest member of the Innovation Team - not only does he code he's also a rock star (lead vocals and guitarist in the bands BridgeFire and Best of Foo) - what a combination. Barry is extremely interested in the integration of software components and embraces a lot of the current hype around web 2.0 and related concepts as standard.

Comments

Paul Marshall said:

This is an excellent concept and it has really sorted out the bloated ViewState in an application that we are developing...

If you want to maximise the SEO potential of your site and you have a huge ViewState, this could help as I'm pretty sure Google doesn't want to trawl through pages with large page sizes

April 23, 2008 - 10:47 AM

Add Comment

Enter your comment below and it will be submitted for moderation.

Your Name

Add Tag

Please enter tags for this article, seperated by semi-colon ;

View Tag's by : # articles | # views