miércoles, 14 de julio de 2010

Cuidado con el control UpdatePanel!

El control UpdatePanel hace posible la representación parcial de una página. Esto es que, con este control, es posible actualizar sólo cierto contenido de la página en el cliente. Para su utilización hay que tener en cuenta 2 cosas muy importantes y que si no se entienden correctamente podrían arruinar el funcionamiento esperado del control.

  1. Durante el proceso de la petición asíncrona que desencadena un control UpdatePanel, en el servidor se ejecuta todo el ciclo de vida de la página que alberga el control. Esto es que, aunque el resultado de la petición sólo sea la actualización de una parte de la página del cliente, en realidad la petición ha desencadenado:
    • El envío y posterior actualización del estado de vista.
    • Se ha cargado de nuevo la página en el servidor y ha ocurrido todo el ciclo de vida de la página (aunque insisto nuestro único propósito era actualizar sólo cierta parte de la página).
    • Se han ejecutado todos los manejadores declarados en el código de la página en el servidor que procedan (Page_Load, Page_PreRender, etc.)
  2. El control UpdatePanel tiene una propiedad que determina drásticamente su comportamiento y eficiencia, la propiedad es UpdateMode. La propiedad UpdateMode tiene 2 posibles valores: Always y Conditional. Por defecto el valor de la propiedad es Always y eso justamente y en mi opinión es un error puesto que, aunque en principio nos facilita la vida cuando no queremos indagar en exceso sobre el funcionamiento del control UpdatePanel, cuando hablamos en términos de rendimiento y control preciso de que partes de la página cliente son actualizadas, es un escollo que tendremos que tratar.

Para ilustrar de forma concreta la importancia de estas 2 propiedades, lo mejor será un ejemplo de su utilización. Supongamos una sencilla página con 2 controles de tipo UpdatePanel y en cada uno de ellos un control Literal y un botón.

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<asp:Literal runat="server" ID="Literal1" Text="Texto no asignado"></asp:Literal>
<br />
<asp:Button runat="server" ID="Button1" Text="Actualizar" />
</ContentTemplate>
</asp:UpdatePanel>

<asp:UpdatePanel ID="UpdatePanel2" runat="server">
<ContentTemplate>
<asp:Literal runat="server" ID="Literal2" Text="Texto no asignado"></asp:Literal>
<br />
<asp:Button runat="server" ID="Button2" Text="Actualizar" />
</ContentTemplate>
</asp:UpdatePanel>


Y con el siguiente código en el servidor:

    Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
        Literal1.Text = System.DateTime.Now.ToString
    End Sub


    Protected Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button2.Click
        Literal2.Text = System.DateTime.Now.ToString
    End Sub


Con la página cargada, nuestra primera acción es pulsar el botón Button1. El resultado en pantalla es:

clip_image002

En principio todo parece correcto, pero con la ayuda de firebug vamos a poder ver los entresijos de la petición desencadenada por la pulsación del botón y ver como no es oro todo lo que reluce. La petición ha enviado toda la información de la página sólo para actualizar un literal con la hora actual. Vemos como se incluyen todos los campos del formulario (entre los que está el campo __VIEWSTATE – que puede ser enorme!). Aunque en este caso concreto nos parezca que no se ha enviado demasiada información (o al menos que es soportable el tamaño del envío), en páginas complejas el tamaño de los datos enviados puede ser mucho mayor y en ocasiones estaremos hablando de un “killer-application” o lo que también viene llamándose “matar moscas a cañonazos”.

clip_image004

Además, también es hora de examinar la respuesta del servidor a nuestra petición:

clip_image006

Ver como en la respuesta hay 3 bloques claramente diferenciados:



  • El contenido a actualizar para el control UpdatePanel1.

  • El contenido a actualizar para el control UpdatePanel2.

  • El contenido a actualizar para los controles Hidden que gestiona automáticamente ASP.NET (__VIEWSTATE, __EVENTVALIDATION, etc.)

En realidad el bloque 1 y 3 son innegociables, así funciona el control UpdatePanel y no podemos hacer nada para evitarlo, pero la pregunta es ¿Por qué se actualiza también el contenido de UpdatePanel2? De hecho, se actualiza al mismo contenido que tiene, así que además de “engordar” el tamaño de la respuesta de la petición, actualiza el control a su mismo contenido (aquí cabe mencionar que aunque parezca que no importa actualizar una parte del DOM del cliente a un mismo valor, en realidad bajo cuerda suceden eventos de cliente de ASP.NET AJAX, se tienen que eliminar elementos del árbol DOM de cliente y volverlos a crear, así que para un Literal no parece un problema, pero para contenido más complejo estaremos robando ciclos de CPU al cliente en un tarea que se puede evitar). Está en nuestra mano poder eliminar el bloque 2 (al que nadie invitó a la fiesta). Simplemente añadiremos la propiedad UpdateMode = “Conditional” al control UpdatePanel2. Con esta propiedad le estamos indicado al control UpdatePanel que sólo debe actualizarse si él mismo (o alguno de sus desencadenadores válidos – triggers) ha iniciado la petición.

<asp:UpdatePanel ID="UpdatePanel2" runat="server" UpdateMode="Conditional">


Veamos ahora como al pulsar de nuevo el botón Button1, la respuesta sólo incluye la actualización del control UpdatePanel1 y la actualización de los controles Hidden de ASP.NET (pero ya no incluye, ni toca, ni manipula, ni hace nada con el control UpdatePanel2).

clip_image008

A la hora de trabajar con UpdateMode y el valor Conditional, también hay que tener en cuenta las siguientes consideraciones. Si por ejemplo, como es nuestro caso, tenemos un primer UpdatePanel que siempre se actualiza con cualquier petición asíncrona de la página y un segundo UpdatePanel que sólo se actualiza si es él mismo quien desencadena la petición, el siguiente código puede hacer que en el servidor tengamos un valor en el control Literal distinto del control renderizado en el cliente.

clip_image010

Si nos fijamos, en la segunda pulsación del botón 1, podemos ver que al actualizar tanto el Literal1 (dentro de UpdatePanel1) como el Literal2 (dentro de UpdatePanel2) se produce una situación en la que el valor de Literal2 en el cliente no está coordinado con el valor de Literal2 en el servidor. Es decir, en el servidor el valor de Literal2 es la fecha actual, mientras que en el cliente Literal2 aún mantiene su valor original (“Texto no asignado”) porque no se ha actualizado (debido al valor de la propiedad UpdateMode a Conditional). Siempre que sea oportuno podemos forzar la actualización de un control UpdatePanel desde el servidor, con independencia del valor de su propiedad UpdateMode. Esto se hace a través del método Update(). Así, por ejemplo, aunque el control UpdatePanel2 se actualiza de forma condicional, el siguiente código fuerza a que se actualice siempre y que funcione como si tuviera el valor Always en su propiedad UpdateMode.

    Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
        Literal1.Text = System.DateTime.Now.ToString
        Literal2.Text = System.DateTime.Now.ToString
        UpdatePanel2.Update()

    End Sub


Como resumen a lo explicado se muestra una lista de las ventajas y desventajas más relevante del control UpdatePanel (en mi opinión, claro está!).




  • Ventajas



    • Actualización parcial de la página (no podría vivir sin ella, de hecho creo que es una de las características más demandadas en la llamada web 2.0).


    • Aunque el tamaño de la petición puede ser importante, siempre será menor que una llamada asíncrona convencional (PostBack).


  • Desventajas



    • Por defecto la propiedad UpdateMode tiene el valor Always que significa “actualiza todos los controles UpdatePanels de la página aunque con ellos no vaya la película siempre que se produzca una petición asíncrona).


    • Si no se tiene cuidado con la contención de los datos que envía un UpdatePanel (por dios no metas toda tu página dentro de un UpdatePanel a cholón!), el tamaño del envío y de la respuesta, lejos de ser beneficioso para la aplicación, puede ser un cuello de botella para nuestra aplicación del que después será difícil salir.

Un saludo!

4 comentarios:

  1. He descubierto una forma de trabajar mucho mejor, sin necesidad de update Panels o algo por el estilo, todo utilizando ajax con jquery, mucho mas rapido, eficiente, en resumen excelente. Llamo a Webservices que se ejecutan del lado del servidor y lo demás nítido.

    ResponderEliminar
    Respuestas
    1. y como es que lo conseguiste?

      Eliminar
    2. Ejemplos completos aplicaciones reales con código fuente y buenas prácticas?

      Eliminar
  2. se puede hacer con jquery o con ajax peticiones post, get , soap etc

    ResponderEliminar