Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια
σε

 

Αρχική σελίδα Ιστολόγια Συζητήσεις Εκθέσεις Φωτογραφιών Αρχειοθήκες

The Path To Better ViewState Storage

Îåêßíçóå áðü ôï ìÝëïò George J. Capnias. Τελευταία δημοσίευση από το μέλος KelMan στις 26-05-2005, 11:46. Υπάρχουν 6 απαντήσεις.
Ταξινόμηση Δημοσιεύσεων: Προηγούμενο Επόμενο
  •  13-05-2005, 22:56 1973

    The Path To Better ViewState Storage

    Κάποιος με ρώτησε αν γίνεται να εξαλείψουμε το ViewState από μια σελίδα ASP.NET αλλά να μην χάσουμε την λειτουργικότητα που πρασφέρει.

    Εδώ πολλές λύσεις σε αυτό το πρόβλημα, διάφορα controls που ελαχιστοποιούν το viewstate, άλλα που το συμπιέζουν, αλλά αυτή που περίγραφεται παρακάτω είναι η πιο σωστή/ολοκληρωμένη λύση, αποθήκευση του viewstate μέσα στον SQLServer, όπως μπορεί να γίνει και με το sessionstate του ASP.NET. Ιδού:


    The Path To Better ViewState Storage

    A while ago, several colleges of mine were having a terrible time with the loading times of some pages of their web application. Come to find out, they were suffering from ViewState bloat. ViewState was something I always tried to stay away from in the past because of this factor. Sure, it was very helpful but I found ways around it.

    However, I have come to a change of heart to realize ViewState is a wonderful thing, except that it can still cause pages to suffer from the ViewState bloat symptom. You could be a frugal programmer and control the ViewState more by only allowing certain pieces into but there are times when you want to store large amounts of data in the ViewState. For example, you may have search results that were very costly to find and you need to persist the results for paging.

    So, I started looking at different devices to store the ViewState in. I thought of putting it into our session state which is stored in a SQL database, but the timeout settings for session and page view state were very different in some cases and this method would not allow for the flexibility that is required. Or perhaps someone avoids session state like the plague or it isn't stored in a central repository in case of a web farm approach.

    The decision was easy, I would create a small table that stores ViewState in it much like the session state. I actually put the table and procedures in the same database as our session state just for consistency. It was also easy to script the job that cleans up timed out ViewState since we already had one for the session state. Here is the install script you will need to run to setup the table, stored procedures, and job to clean up the table every so often.

    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 NULL) BEGIN  

           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, DateLastAccessed, 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

    To keep it simple I simply look for one value, the connection string to use in an appSetting key called “ViewStateConnectionString“. Optionally, you can define a “ViewStateTimeout“ with a value being the number of minutes to wait before clearing the ViewState data. You can also set this value per page if you have certain pages that will live longer on the client before a post back. If the connection string is not provided, it will not affect the ViewState processing as it will resort to the old method. The following is an example of the appSettings section.

    <appSettings>

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

           <add key="ViewStateTimeout" value="20"/>

    </appSettings>

    Here is the class that you will need to inherit from if you want to take advantage of this ViewState change, provided for you in the wonderful language of C#. I apologize for the lack of comments, hopefully it is self explanatory.

    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(true, this.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.Ticks / TimeSpan.TicksPerMinute;

     

                               connection.Open();

                               command.ExecuteNonQuery();

                         }

                  }

     

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

     

                  if (control == null)

                         this.RegisterHiddenField("__VIEWSTATEGUID", viewStateGuid.ToString());

                  else

                         control.Value = viewStateGuid.ToString();

           }

    }

    posted on Tuesday, March 09, 2004 12:14 PM

    Original Link

      
    George J.

    George J. Capnias: Χειροπρακτικός Υπολογιστών, Ύψιστος Γκουράρχης της Κουμπουτερολογίας
    w: capnias.org, t: @gcapnias, l: gr.linkedin.com/in/gcapnias
    dotNETZone.gr News
  •  14-05-2005, 01:44 1975 σε απάντηση της 1973

    Re: The Path To Better ViewState Storage

    Γιώργο,

    Υποθέτοντας οτι το παραπάνω είχε αφορμή τη χτεσινή μας κουβέντα στο σεμινάριο,
    να σε ευχαριστήσω για την άμεση απάντηση.

    Alex
  •  14-05-2005, 10:02 1977 σε απάντηση της 1975

    Re: The Path To Better ViewState Storage

    Δεν κάνει τίποτα, να βοηθήσουμε είμαστε εδώ... Smile


    George J.


    George J. Capnias: Χειροπρακτικός Υπολογιστών, Ύψιστος Γκουράρχης της Κουμπουτερολογίας
    w: capnias.org, t: @gcapnias, l: gr.linkedin.com/in/gcapnias
    dotNETZone.gr News
  •  20-05-2005, 23:50 2122 σε απάντηση της 1973

    Re: The Path To Better ViewState Storage

    Γιώργη καλησπέρα,

    Νομίζω ότι η λύση στο πρόβλημα είναι το ASP.NET 2.0  το οποίο προσφέρει το Control state όπου δεν επιρεάζεται από το ViewState


    Antonios Chatzipavlis

  •  21-05-2005, 10:30 2124 σε απάντηση της 2122

    Re: The Path To Better ViewState Storage

    Καλώς όρισες Αντώνη!

    Ναι, όπως επισημαίνεις, το ASP.NET 2.0 θα δώσει πολλές απαντήσεις στα ζητήματα που προβλημάτιζαν τους ASP.NET developers τόσο καιρό. Από την μια το Personalization & Membership σε συνδιασμό με τα Web Parts, από την άλλη η αλλαγές που έχουν γίνει στo State Management για να διώχνουν τον εφιάλτη του ViewState!

    To ViewState πλέον είναι ανεξάρτητο του ControlState. Μπορεί κάποιος να το κλείσει χωρίς να "τρελαθούν" τα web controls της σελίδας. Έχουν προστεθεί καινούργια States και έχουν γίνει βελτιστοποιήσεις σε παλιά.

    Έχει προστεθεί το ControlState για κάθε control στην σελίδα, που αποθηκεύεται όπως και το παλιό ViewState στον client, αλλά πλέον είναι πολύ μικρότερο. Καινούργιο επίσης είναι και το PageState που κρατάει πληροφορίες σχετικά με την σελίδα και μπορεί ή να στέλνεται όπως το ControlState πίσω στον client ή να αποθηκεύεται στο SessionState - καιρός ήταν το κάνουν αυτό!. Τέλος το SessionState με την έννοια που το ξέραμε έχει χαθεί, τώρα πλέον μπορούμε να φτιάξουμε δικούς μας μηχανισμούς στην θέση του αν δεν καλύπτουν οι παλιοί τρεις που υπήρχαν.

    George J.


    George J. Capnias: Χειροπρακτικός Υπολογιστών, Ύψιστος Γκουράρχης της Κουμπουτερολογίας
    w: capnias.org, t: @gcapnias, l: gr.linkedin.com/in/gcapnias
    dotNETZone.gr News
  •  21-05-2005, 21:56 2136 σε απάντηση της 2124

    Re: The Path To Better ViewState Storage

    Ακριβώς!.
    Βέβαια από την άλλη μερία ανεβαίνει λιγάκι η προασπάθεια που πρέπει ο προγραμματιστής να κάνει επειδή πλέον αυτός είναι υπεύθυνος για το serialization & deserialization των δεδομένων στο control state αλλά προσωπικά το προτιμώ από το viewstate

    Αυτά....

    Antonios Chatzipavlis

  •  26-05-2005, 11:46 2224 σε απάντηση της 2136

    Re: The Path To Better ViewState Storage

    Πάντως, ένα γενικό σχόλιο πάνω στο τι οδήγησε τον τύπο να υλοποιήσει αυτήν τη λύση, είναι ότι το ViewState υπάρχει με τη λογική της αποθήκης για object types όπως strings, numerics, booleans και collections δηλαδή είναι καλύτερο να αποθηκεύονται για τιμές που κρατούν τα controls και όχι τα ίδια τα controls.

    Καθώς η πληροφορία πηγαινοέρχεται με κάθε request/response και επιπρόσθετα γίνεται serialized/deserialized, encrypt/decrypt και base64 encoded μου φαντάζει μεγάλο το επιπρόσθετο overhead του "διαβάζω/γράφω στην DB". Εξάλλου, δεν δεν υπάρχει λόγος να μπαίνουν όλα μέσα στο ViewState. Aν θέλει κάποιος να αποθηκεύσει ένα dataset ή ένα datatable υπάρχει και το Session state (το οποίο και πάλι αν υπερβεί τα 200ΚΒ αρχίζει να μειώνεται δραματικά το response time του Server) ή το Cache object.

    Εδώ η Microsoft λέει ότι το τυπικό configuration της χρήσης του ViewState είναι να ενεργοποιηθεί σε επίδεδο Page και να γίνει disable control-by-control για controls που δεν έχει νόημα όπως labels που δεν αλλάζουν και comboboxes που γεμίζουν προγραμματιστικά.

    Άρα λοιπόν, η απόφαση της ενεργοποίησης του ViewState θα πρέπει να βασίζεται στην λογική "convinience for the user and reduced server load" (όπως πάλι λέει η Μαμά).


    Vir prudens non contra ventum mingit
Προβολή Τροφοδοσίας RSS με μορφή XML
Με χρήση του Community Server (Commercial Edition), από την Telligent Systems