miércoles, 16 de mayo de 2012

Comprendiendo el Viewstate de Asp.net



El documento “Understanding Asp.net Viewstate” de Scott Mitchell (del año 2004) explica claramente y paso por paso cómo actúa el viewState.

Como él dice, el viewstate ha sido el mayor causante de los problemas de los desarrolladores de ASP.NET.

Como son más de 20 páginas de texto, y me sigue causando verdaderos problemas en mis desarrollos porque todavía no lo tengo asimilado, me animo a crear un resumen traducido.

Espero que os sirva!


El ciclo de vida



Cada vez que llega una petición a un servidor web, lo intercepta el IIS (v6) y se lo pasa al aspnet_isapi.dll (el motor de asp.net). Pasa por los diferentes HttpModules (filtros, BeginRequest, … ) y llama al Http Handler ProcessRequest(), que es el método desencadena el ciclo de vida de una página.

Al final del ciclo, se devuelve al IIS una página HTML, que se manda al cliente.

Centrémonos en las fases del ciclo de vida de una página, una vez se llama al ProcessRequest.


Etapa 0: Instanciación.


Las páginas están hechas de tags html y tags runat=”server”. 

En la instanciación se crea una jerarquía de controles de página de la página asp.net solicitada.

Si hay valores asignados declarativamente, también se asignan en el árbol de jerarquía.

<html>

<body>

  <h1>Welcome to my Homepage!</h1>

  <form runat="server">

    What is your name?

    <asp:TextBox runat="server" ID="txtName"></asp:TextBox>

    <br />What is your gender?

    <asp:DropDownList runat="server" ID="ddlGender">

      <asp:ListItem Select="True" Value="M">Male</asp:ListItem>

      <asp:ListItem Value="F">Female</asp:ListItem>

      <asp:ListItem Value="U">Undecided</asp:ListItem>

    </asp:DropDownList>

    <br />

    <asp:Button runat="server" Text="Submit!"></asp:Button>

  </form>

</body>

</html>


Este árbol de jerarquía se almacena en la carpeta WINDOWS\Microsoft.NET\Framework\version\Temporary ASP.NET Files como archivo .cs o .vb

Etapa 1: Inicialización.


La página y sus controles lanzan sus eventos INIT. 

Con respecto al viewstate, esto es importante ya que:

-          Los controles de servidor no empiezan a seguir los cambios en el viewstate hasta el final de la fase de inicialización.

-          Cuando añadimos controles dinámicos que necesiten usar el viewstate, esos controles de tendrán que añadir en el evento Init, en vez de en el Load.

Etapa 2: Carga del ViewState


La etapa de LoadViewState solo ocurre cuando se produce un postback.

Durante esta etapa, los datos del viewstate que han sido guardados en la visita de la página previa son cargados y, recursivamente, rellenados en la jerarquía de controles.

Es durante esta fase cuando el viewstate es validado. El viewstate podría venir inválido por muchas razones, como manipulaciones, inyecciones de controles dinámicos, etc.

Etapa 3: Carga de datos de Postback


La etapa de la carga de los datos del Postback solo ocurre cuando se produce un postback.

En esta etapa, se sobrescriben los valores en la jerarquía de controles  con los valores que vienen en el form, llamando a cada función su método LoadPostData()

Es decir, si tenemos un <input type="text" id="txtName" name="txtName" /> y el usuario ha introducido un “Hello, world” en el input,  y se realiza el postback, el dato “hello world” pasa al servidor en la cabecera del HTTP POST, en el que también viaja el viewstate.

Se carga en la jerarquía de controles el control Input con el valor “”

Se lanza el LoadViewState. Si hubiera algún valor previo en el viewstate, se le asignaría el valor en la jerarquía.

 Se lanza el LoadPostbackData y asigna al control el valor “Hello world”

Etapa 4: Load


Cuando se lanza el evento Load (el famoso Page_Load), el viewState ha sido cargado junto con los datos del formulario.

Etapa 5: Lanzamiento de eventos Postback


Cuando se está realizando un postback, en esta fase es donde los controles procesan sus eventos de postbacks (selectedindexchanged, button_click, …)

Etapa 6: Guardado del ViewState.


En esta etapa, la clase Page construye el viewstate de la página, que representa el estado que debe de persistir entre postbacks.

El Page llama recursivamente al método SaveViewState de cada control en toda la jerarquía. Luego, el resultado se serializa en un string codificado en Base64.

Etapa 7. Representación. (Render)


Etapa de representación, se llama recursivamente al método RenderControl de cada control, y se genera el HTML que se enviará al cliente.



Estas 7 etapas son las más importantes para entender el viewstate.  A medida que continúes leyendo el artículo, ten en mente que cada vez que se solicita una página asp.net o se produce un postback, se procesan estas etapas.

El papel del ViewState


El objetivo del viewstate es persistir el estado a través de postbacks.

Si no hay cambios en un control, no se crea viewstate.  Lo que se guarda en el viewstate son los cambios que ha habido en un control desde su inicialización.

Los cambios hechos en programación también se deben de guardar en el viewstate.

Supongamos el siguiente caso:

<asp:Label runat = "server" ID = "lblMessage"

  Font-Name="Verdana" Text="Hello, World!"></asp:Label> Font-name = "Verdana" Text = "Hola, Mundo!"/>

 <br /> <br />

<asp:Button runat="server" <asp:Button runat = "server"

  Text="Change Message" ID="btnSubmit"></asp:Button> Text = "Cambia el mensaje de" ID = "btnSubmit"/>

<br /> <br />

<asp:Button runat="server" Text="Empty Postback"></asp:Button> <asp:Button runat="server" Text="Empty Postback"/>

private void btnSubmit_Click (object sender, EventArgs e)

{ {

  lblMessage.Text = "Goodbye, Everyone!"; lblMessage.Text = "Adiós a todos!";

} }






ViewState y la adición dinámica de controles.




Muchas veces se crean dinámicamente controles. (por programación).

Recordemos que la jerarquía de controles es creada y las propiedades declarativas se crean en la fase de instanciación.

Más tarde, en la etapa de carga del viewstate, el estado también fue alterado.

Pensando un poco sobre esto:

-          Como el viewstate sólo persiste los cambios entre postbacks, los controles creados dinámicamente deben ser añadidos a la página asp.net, tanto en la página inicial como en los consecuentes postbacks.

-          Los controles dinámicos son añadidos a la jerarquía en el code-behi nd, y por lo tanto se añaden en algún momento después de las etapas de instancias

-          El viewstate de los controles se guardan automáticamente en la etapa del guardado de ViewState.


Por lo tanto, los controles creados dinámicamente por programación deben de ser añadidos a la página en cada visita de la página. El mejor momento para añadir estos controles son en la inicialización de la página (evento Init), que ocurre, antes de la carga del viewstate.  Es decir, queremos tener una jerarquía de controles completa antes de que llegue la carga del viewstate.

Nota: Puede ser que la carga de controles dinámicos en el evento Load funcione, ya que la propiedad Controls.Add(dynamicControl) carga el viewstate del control y de sus controles hijos.



La propiedad ViewState


Cada control se encarga de almacenar su estado. La propiedad ViewState (que no es lo mismo que el concepto ViewState) está definida en la clase property is defined in the System.Web.UI.Control, lo que significa que todos los controles tienen esta propiedad disponible.

Usando reflector y fijándose en una propiedad, se aprecia que se cambia directamente en la propiedad viewstate, y que se lee de la propiedad viewstate en primer lugar.

public string NavigateUrl

{

  get

  {

    string text = (string) ViewState["NavigateUrl"];

    if (text != null)

       return text;

    else

       return string.Empty;

  }

  set

  {

    ViewState["NavigateUrl"] = value;

  }

}

Medir el tiempo del seguimiento del viewstate.


Recordemos que dijimos que el viewstate sólo almacena estados que necesitan ser persistidos a través de postbacks.

Es decir, que si tenemos un control hyperlink, y declarativamente asignamos su NavigateUrl, esta información no necesita guardarse en el viewstate. Pero si en algún sitio ponemos  HyperLink1.NavigateUrl = http://www.ScottOnWriting.NET, esta información se almacenará en el viewstate.

Si esto mismo se hace en la inicialización, antes de la llamada a la función TrackViewState(), no se guarda en el viewstate. ¿Por qué esta diferencia?

Los cambios en el ViewState se empiezan a auditar desde la llamada al TrackViewState() hasta la llamada al SaveViewState().

Así pues, el método TrackViewState() permite empezar a gestiona el viewstate una vez instanciada e inicializada la página.

 


Guardando información en la propiedad ViewState de la página


Como Page hereda de Control, también tiene su propiedad ViewState. De hecho, puedes usar esta propiedad para guardar información a través de postbacks.

El coste del viewstate


-          Durante las visitas a todas las páginas, la clase page reúne todos los viewstates de todos los controles en la jerarquía de controles, y lo serializa en codificación Base64. (el control Hidden __VIEWSTATE).
Paralelamente, para cada postback, se tiene que deserializar y actualizar los estados de los controles en la jerarquía de congtroles.

-          El campo hidden __VIEWSTATE  añade tamaño extra a la página web que el cliente debe de descargarse. También, para cada postback, el control __ViewState tiene que enviarse al servidor en la cabecera HTTP POST, incrementando el tiempo.

Por todo esto, debemos de ser selectivo, indicando los controles que no queremos que guarden su viewstate.

Como otra alternativa, también podríamos almacenar en el servidor el viewstate.

Deshabilitando el viewstate.


En la etapa de guardado de ViewState, se llama a cada control de la jerarquía de controles, invocando al método SaveViewState()  de cada control. Si a un control le establecemos la propiedad EnableViewState a false, se evitará que se llame al SaveViewState() de este control y el de sus  hijos.

Por lo tanto, si queremos desactivar el viewstate de toda la página, estableceremos Page.EnableViewState a false, o incluso con la directiva <%@Page EnableViewState="False" %>

No todos los controles almacenan la misma cantidad de información en el viewstate. Un label no tiene gran impacto en el tamaño del viewstate. Sin embargo, un datagrid almacena toda la información en el viewstate.

La pregunta es:

¿Cuándo puedo poner el EnableViewState a false?

Cuando no necesites recordar el estado del control entre postbacks. 

El DataGrid guarda su contenido en el viewstate, así que en cada postback no hay que volver a sacar los datos y enlazarlos.

Especificar dónde persistir el viewstate.


Después de que la página haya recopilado toda la información de estado de la jerarquía de controles, la persiste en el control hidden "__VIESWTATE". Éste es serializado al campo llamando al método de Page SavePageStateToPersistenceMedium() durante la etapa de guardado del viewstate.

Podríamos crearnos una clase que derive de Page y sobrescribir los métodos LoadPageStateFromPersistenceMedium() y SavePageStateToPersistenceMedium().

El viewstate es serializado y deserializado usando la clase System.Web.UI.LosFormatter y está pensado para serializar eficientemente cierto tipo de objetos a codificación Base64 (strings, integers, boooleans, arrays, arraylists, hashtables, pairs, triplets)

Si sobrescribimos SavePageStateToPersistenceMedium(), necesitaremos usar LosFormatter para serializar el viewstate y guardarlo en un fichero del servidor web.  Podemos usar las siguientes aproximaciones:

-              Montar un buen sistema de nombres de ficheros. Ya que el viewstate de una página variará según la interacción del usuario con la página, el viewstate guardado debe ser único por cada página y usuario.

- Borrar los ficheros de viewstate cuando no sean necesitados.

Podríamos llamar a los ficheros basándonos en el SessionID del usuario unido al nombre de la URL. Esto no funcionaría con usuarios con las cookies deshabilitadas.

Otra solución pasaría por usar un identificador único (GUID) como nombre de fichero, guardando este guid en un campo oculto de la página web.

Un ejemplo de cómo persistirlo sería:

public class PersistViewStateToFileSystem : Page

{

   protected override void

     SavePageStateToPersistenceMedium(object viewState)

   {

      // serialize the view state into a base-64 encoded string

      LosFormatter los = new LosFormatter();

      StringWriter writer = new StringWriter();

      los.Serialize(writer, viewState);

      // save the string to disk

      StreamWriter sw = File.CreateText(ViewStateFilePath);

      sw.Write(writer.ToString());

      sw.Close();

   }

   protected override object LoadPageStateFromPersistenceMedium()

   {

      // determine the file to access

      if (!File.Exists(ViewStateFilePath))

         return null;

      else

      {

         // open the file

         StreamReader sr = File.OpenText(ViewStateFilePath);

         string viewStateString = sr.ReadToEnd();

         sr.Close();

         // deserialize the string

         LosFormatter los = new LosFormatter();

         return los.Deserialize(viewStateString);

      }

   }

   public string ViewStateFilePath

   {

      get

      {

         string folderName =

           Path.Combine(Request.PhysicalApplicationPath,

           "PersistedViewState");

         string fileName = Session.SessionID + "-" +

           Path.GetFileNameWithoutExtension(Request.Path).Replace("/",

           "-") + ".vs";

         return Path.Combine(folderName, fileName);

      }

   }

}



Analizando el ViewState



Recorrer el estado de la vista mediante programación



Viewstate e implicaciones de seguridad.


El viewstate es fácilmente decodificable, pudiendo ver su contenido.

Por tanto, podría ser manipulado o podríamos llegar a ver passwords o connectionstrings.

Por ello, el LosFormatter añade una MAC (machine autentication check) o hash en las codificaciones.

En la Page se puede configurar usando la propiedad  EnableViewStateMac. En el machine.config podemos especificar el tipo de codificación de esa hash

También podríamos cifrar el viewstate usando TripleDES. Para indicarlo, en el machine.config , <machinekey>,  atibuto Validation, poner 3DES.

Tambien el <machinekey> contiene los atributos ValidationKey y DescryptionKey. ValidationKey informa de la clave usada para generar las hash de la MAC, y DescryptionKey indica la clave usada para el cifrado tripleDES.

Por defecto, las claves se autogeneran solas(AutoGenerate,IsolateApp,)  pero en granjas de servidores conviene cambiarlas y que todos los servidores compartan las claves.

La propiedad ViewStateUserKey


Esta propiedad, si es usada, se le debe asignar un string en el evento Page_Init. El objetivo de la propiedad es asignar una clave específica del usuario (username). El viewstateuserkey es usado como clave sal para codificar la mac.

La propiedad ViewStateUserKey protege contra los casos en los que un usuario malintencionado visita la página, captura el viewstate, y luego otro usuario entra con el viewstate capturado.




Saludos!!! 

1 comentario:

Anónimo dijo...

Muy bien explicado el ciclo, ahora lo entiendo mejor.