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 = "Hola, Mundo!"/>
<br />
<asp:Button runat =
"server"
Text = "Cambia el mensaje de"
ID = "btnSubmit"/>
<br />
<asp:Button runat="server" Text="Empty Postback"/>
private void btnSubmit_Click (object sender,
EventArgs e)
{
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
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.
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.
1 comentario:
Muy bien explicado el ciclo, ahora lo entiendo mejor.
Publicar un comentario