Una manera mejor de persistir el ViewState

Siguiendo a mi post anterior, vamos a ver una técnica alternativa para conseguir la persistencia del ViewState en sesión.


La anterior técnica, nativa de ASP.NET 2.0 adolecía de varios problemillas, los más importantes son:



1.- No eliminaba del todo el ViewState, sólo una parte del mismo. En formularios Web con muchos controles el tamaño del ViewState sigue siendo considerable, si bien se reduce bastante.


2.- Sólo funciona en ASP.NET 2.0, pero no en versiones anteriores.


Voy a explicar a continuación una técnica alternativa que elimina estos problemas y tiene algunas ventajas a mayores.


Para conseguir manejar a nuestro antojo el ViewState podemos sobrescribir dos métodos importantes de la página: SavePageStateToPersistenceMedium y LoadPageStateFromPersistenceMedium. Como se deduce de su propio nombre estos métodos se llaman automáticamente por la infraestructura de páginas ASP.NET a la hora de almacenar y de recuperar el ViewState de una página respectivamente.


Dado que en nuestro caso lo que vamos a hacer es persistir el ViewState a memoria y de manera individual para cada usuario emplearemos una variable de sesión. Podemos usar una única variable para todas las páginas de modo que se sobrescriba cuando cambiemos de página, o bien podemos usar una variable diferente para cada página y así mantener incluso su estado entre varias visitas (más sobre esto luego). En el ejemplo que he preparado he usado esta segunda opción, por lo que defino el prefijo que llevarán todas estas variables:



private const string PREF_CLAVE = «SP_VWST_»;


Le he llamado así para recordar con estas iniciales la palabra «Session Persisted ViewState».


Bien, ahora sobreescribimos el método protegido SavePageStateToPersistenceMedium, y lo dejaremos definido de la siguiente manera:



protected override void SavePageStateToPersistenceMedium(object state)
{
  //base.SavePageStateToPersistenceMedium(state);
  //Se almacena en Sesión (la base lo hace en un campo oculto)
  Session[PREF_CLAVE + Request.RawUrl] = state;
}


Es decir, evitamos que se llame a la implementación por defecto de la base (la primera línea comentada) ya que si no se comportaría como siempre, y almacenamos el objeto de estado en una variable de sesión que se llamará con nuestro prefijo seguido por la URL de la página (para distinguirla del estado persistido de otras páginas). Así de sencillo.


Ahora sólo queda recuperar este estado cuando sea necesario. Para ello seguimos un procedimiento igual de simple, que consiste en sobrescribir el método LoadPageStateFromPersistenceMedium de la página, del siguiente modo:



protected override object LoadPageStateFromPersistenceMedium()
{
object state = Session[PREF_CLAVE + Request.RawUrl];
//Si no lo hay en memoria lo coge del campo oculto, aunque sólo debería darse en la primera llamada y aún así sería nulo…
return (state != null) ? state : base.LoadPageStateFromPersistenceMedium();
}


En este caso recuperamos el estado desde la variable de sesión anterior. Puede que esté vacío porque es la primeravez que pasamos por la página, en cuyo caso llamamos a la implementación por defecto de la clase base (Page) para que otorgue los valores iniciales correctos. También muy sencillo ¿verdad?


Conclusiones


Con esta técnica podremos persistir el estado de nuestras páginas en memoria desde cualquier versión de ASP.NET, lo cual brinda varias ventajas en ciertas circunstancias:




  1. No se debe trasegar la información de estado entre los diferetes postbacks, lo que ahorra mucho ancho de banda y aumenta la velocidad en formularios con muchos controles. Por ejemplo, esta técnica aplicada a un formulario de una de las últimas aplicaciones que hemos hecho en Krasis aumentó la velocidad del mismo de manera espectacular ya que contenía varias decenas de controles (era una entrada de datos muy compleja con muchos postbacks y efectos AJAX).


  2. Con esta técnica sí se elimina por completo el ViewState, al contrario que en el caso del objeto PageStatePersister de ASP.NET 2.0, que sí deja parte del mismo en el formulario.


  3. Al hacerlo de este modo podemos conservar el estado de una página aunque nos vayamos a otras y más adelante volvamos a la misma. Mientras la sesión esté activa podemos regresar a la página y los controles estarán exactamente como los dejamos porque el estado de los mismos están en la variable de sesión. Imagínate por ejemplo una página de búsqueda compleja, con muchas variables y valores compuestos que cuesta mucho configurar. Puedes hacerlo una vez y tenerlos así fijados siempre que regreses a la página (mientras no se borre la variable de sesión correspondiente).

He colocado aquí un archivo .cs con una clase que implementa esta técnica. Si quieres que una página tuya la soporte sólo tienes que heredar de esta clase en lugar de Page y ya está.


¡Espero que te resulte interesante! 🙂

Sin categoría

9 thoughts on “Una manera mejor de persistir el ViewState

  1. Chapó.

    En una aplicación que acabó de desarrollar tengo este problema que comentas Jose, una entrada bastante conpleja con cientos de validaciones y AJAX. Probaré.

    Salu2 y gracias por tus aportes.

  2. Si os interesa este tema teneis que conocer algunos detalles adicionales. Os recomiendo la lectura de estos post (sobre todo el primero, ya que comenta un problema que puede surgir cuando se usa como ID para la variable de sesion que almacena el viewstate la pagina actual)

    http://www.hanselman.com/blog/PermaLink.aspx?guid=e73efd87-2cae-49af-9674-7076a054f2ca
    y
    http://www.hanselman.com/blog/PermaLink.aspx?guid=291897dc-555d-44a1-ab94-65a70e484b3e

  3. Gracias por la aortación Juanlu.

    Scott tiene razón en lo de que la página se puede estar usando en dos ventanas a la vez. No caí en la cuenta de eso. De todos modos es bastante fácil de solucionar y será un caso poco frecuente.

    Para muchos casos esta técnica puede ser muy útil. Obviamente no se debe usar normalmente (como ya pongo en el post) puesto que no está exenta de problemas (sobre todo el mayor consumo de memoria).

    Saludos

    JM.

  4. interesante solución revisare la página que envio Juanlu y tratare de ver como mejora el rendimiento de mi página, luego les comento como me fue , gracias por sus aportes….

  5. Buenas tardes,

    He implementado la opción de guardar el ViewState comprimido en una variable de Sesión. Igualmente, solucioné el problema de nombres de sesion unicas por pagina (con la ayuda de GUID). Pero ahora me surge otro problema, en paginas con varios GridView anidados, pierde el controlState (no guarda los DataKey). Aquí el codigo:

    Imports Microsoft.VisualBasic

    Public Class fVWST
    Inherits System.Web.UI.Page

    Private ReadOnly genGUID As Guid = Guid.NewGuid()
    Private ReadOnly newGUID As String = genGUID.ToString

    Protected Overrides Sub SavePageStateToPersistenceMedium(ByVal state As Object)
    Dim format As New LosFormatter()
    Dim writer As New System.IO.StringWriter()
    format.Serialize(writer, state)
    Dim viewStateStr As String = writer.ToString()
    Dim bytes As Byte() = Convert.FromBase64String(viewStateStr)
    bytes = Compress(bytes)
    Dim vStateStr As String = Convert.ToBase64String(bytes)
    If Not Request.Form(«__VWST») Is Nothing Then
    Session(Request.Form(«__VWST»)) = vStateStr
    Else
    Session(newGUID) = vStateStr
    End If
    End Sub

    Protected Overrides Function LoadPageStateFromPersistenceMedium() As Object

    If Not Request.Form(«__VWST») Is Nothing Then
    Dim vState As String = Session(Request.Form(«__VWST»))
    If Trim(vState) <> «» Then
    Dim bytes As Byte() = Convert.FromBase64String(vState)
    bytes = Decompress(bytes)
    Dim format As New LosFormatter()
    Return format.Deserialize(Convert.ToBase64String(bytes))
    End If
    Else
    Dim vState As String = Session(newGUID)
    If Trim(vState) <> «» Then
    Dim bytes As Byte() = Convert.FromBase64String(vState)
    bytes = Decompress(bytes)
    Dim format As New LosFormatter()
    Return format.Deserialize(Convert.ToBase64String(bytes))
    End If
    End If

    End Function

    Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
    If Not Page.IsPostBack Then
    Me.ClientScript.RegisterHiddenField(«__VWST», newGUID)
    Call PreventSessionTimeout()
    End If
    End Sub

    Private Function Compress(ByVal bytes As Byte()) As Byte()

    Dim memory As New System.IO.MemoryStream()
    Dim compStream As New System.IO.Compression.DeflateStream(memory, IO.Compression.CompressionMode.Compress)

    compStream.Write(bytes, 0, bytes.Length)
    compStream.Close()

    Return memory.ToArray()
    End Function

    Private Function Decompress(ByVal bytes As Byte()) As Byte()

    Dim compStream As New System.IO.Compression.DeflateStream(New System.IO.MemoryStream(bytes), IO.Compression.CompressionMode.Decompress)
    Dim memory As New System.IO.MemoryStream()

    Dim theByte As Integer = compStream.ReadByte()

    While (theByte <> -1)
    memory.WriteByte(theByte)
    theByte = compStream.ReadByte()
    End While

    compStream.Close()
    Return memory.ToArray()
    End Function

    Private Sub PreventSessionTimeout()
    Dim int_MilliSecondsTimeOut As Integer = (Me.Session.Timeout * 60000) – 30000
    Dim str_Script As String = «»

    ClientScript.RegisterClientScriptBlock(GetType(String), «Reconnect», str_Script)
    End Sub

    End Class

    ————–

    Alguien me puede explicar cual es el error?

    Gracias por adelantado.

  6. lo que quiero es que mi pagina persista el ViewState, guardanlo en el cache o Session , y navege por diferentes paginas y cuando regrese a ella no se haya cambiado los datos, que tenga los datos del ultimo cambio que hice

  7. Buenas. Interesante el tema.
    Me llama la atención lo que comenta JM : ”podemos conservar el estado de una página aunque nos vayamos”. En tal caso reutilizaríamos formularios para funciones especificas, como confirmar, solicitar datos del usuario, etc, y luego regresar a la pagina de origen de una manera ‘transparente’ tal como hacemos en Windows con las ventanas ShowDialog, claro aquí en web la idea sería redireccionar (o transferir, no se) hacia la pagina ‘reutilizable’, pedir los datos o ejecutar cierta funcionalidad, y luego retornar a la pagina de origen con los controles en el estado igual a que tenían justo antes de irnos hacia la pagina ‘reutilizable’.

    He probado de varias formas y no funciona. Sobre escribí los métodos SavePageStateToPersistenceMedium y LoadPageStateFromPersistenceMedium y nada.

    Saben como hacer esto? Garcías de antemano.

  8. Hola, agradezco el post, no logro terminar de entender bien la forma de utilizarlo, por ejemplo, utilicé tu clase, puse a mi página que herede de ella, o sea, sustituí el .PAGE y puse el nombre de la clase, todo bien… pero al momento de que funcione no noto nada diferente. El Viewstate debe mantenerse habilitado en los controles de la página? en mi caso tengo un DataGrid con varios controles, mayormente ComboBox, y tengo problemas porque el ViewState con el que trabaja es enorme… por ello busco la forma de agilizarlo. saludos

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *