RIA y páginas con estado

Como usuarios de internet tenemos patrones de comportamiento adquiridos… imagina que estas navegando por un sitio y has llegado a un punto que no te convence. Llegados a este punto tienes básicamente 3 opciones:

1 – Hacer click en el menú de navegación e ir al paso anterior

2 – Hacer click en el típico logo para volver hasta el principio y empezar de nuevo

3 – Hacer click en el botón de atrás del navegador

Las dos primeras opciones están más trabajadas desde un punto de vista de desarrollo, pero desde un punto de vista de usabilidad, depende de cómo hayas hecho esos enlaces y dónde los hayas puesto para que el usuario los perciba o no. De accesibilidad hablaremos en otro post. La opción 3 estamos acostumbrados a que nunca falle… nos lleva a la página anterior, que conlleva tradicionalmente el estado anterior y listo!

Claro… que si estamos trabajando con aplicaciones p.ej AJAX… no esta tan claro que cada página se corresponda con un estado en la navegación por el sitio web. Al trabajar con Javascript, la parte cliente puede variar tanto como para poder considerar que ha pasado pro diferentes estados.

Esto nos puede afectar tanto a nivel de navegación, como a nivel de guardar un determinado estado en favoritos del navegador.

Imaginemos que tenemos una galería de fotos en Javascript, al hacer click en una foto la mostramos mas grande. Podríamos considerar que tenemos 2 estados diferentes, la galería y la vista detalle; pero al haber estado trabajando con Javascript, las acciones se han realizado en la misma página, de modo que cuando estamos viendo la foto en detalle, no podemos dar atrás en el navegador para ver la vista galería ni guardar la foto en favoritos. No hay un estado que podamos almacenar.

¿ Entonces ?

Bien, entonces lo que nos toca es ser capaces de serializar el estado de una página y almacenarlo de alguna forma que podamos acceder a ella. Y teniendo en cuenta las diferencias que tenemos entre navegadores… fácil, no?  x)

Para ilustrar el post he creado una pequeña aplicación de ejemplo que os podéis descargar en este enlace

Creando el estado

El estado pueden ser infinidad de cosas… cómo se esta mostrando la página, que valores tienen los controles… lo primero que debemos tener claro es qué es un estado y qué lo forma.

En el ejemplo que tenemos, el estado va a implicar una u otra foto en primer plano, de modo que lo que hemos de preservar es la dirección de la foto a mostrar.

 Almacenando el estado

Aqui es donde la matan x)  Lo más fácil a día de hoy para programadores .NET es utilizar las asp.net 3.5 extensions preview, el Microsoft.Ajax.js ha sido actualizado con unos prototipos para el manejo del estado. Tiene como ventaja que podemos trabajar con el estado tanto desde el cliente como desde el servidor.

Si os habéis descargado los laboratorios que comentaba en un post anterior, tenéis los siguientes proyectos para practicar y echarle un primer vistazo

NETFramework35Enhancements_TrainingKitLabsAspNetAjaxHistory

Hoy… por ser miércoles… vamos a hacerlo con la parte cliente de la librería.

Lo primero y muy importante …

En el scriptmanager de la página poned la propiedad EnableHistory a true

<asp:ScriptManager ID=»ScriptManager1″ runat=»server» EnableHistory=»true» >

Esto va a añadir un iframe a la página cliente para poder hacer los trucos Javascript que tendríamos que hacer nosotros a mano, si echaís un vistazo al fuente de la página en el cliente…

<iframe id=»__historyFrame» src=»/WebSite1/ScriptResource.axd?d=mfsa1_YluC_H6UGgoZq8xp82Y2Fwlt3V_nmPLNbI8rE1″ style=»display:none;»>

😉

Una vez hemos habilitado el iframe, vamos a trabajar con los nuevos objetos en javascript para manejar el historial. Primero vamos a subscribirnos al evento de navegación, así tendremos control sobre lo que pasa cuando el usuario navega

function pageLoad()
{
    Sys.Application.add_navigate(onNavegar);
}

function onNavegar(sender ,e)
{
    if (userNavigated)
    {
        restaurarEstado(e.get_state());
    }
}

La variable userNavigated nos va a ayudar a discernir entre una navegación provocada por el usuario o una provocada por nosotros desde desarrollo. Tras implementar estas funciones, sabemos que cuando el usuario navegue se va a llamar a nuestra función restaurarEstado

Como hemos dicho antes, en esta aplicación sencilla el estado va a consistir en la dirección de la imagen a mostrar, de modo que para restaurar el estado sólo hay que recuperar la imagen guardada y mostrarla

function restaurarEstado ( estado )
{
    var RutaImagen = estado.RutaImagen;
    if ( RutaImagen == null || RutaImagen == «»)
    {
        RutaImagen = ImagenXDefecto;
    }
    $get(«fotoGrande»).src = RutaImagen;
}

La variable estado la cargamos y la almacenamos nosotros cada vez que queramos almacenar un estado nuevo.

Llegados a este punto ya tenemos una aplicación que es capaz de recuperar un estado grabado y mostrarlo en la página. Lo único que nos falta es saber cómo almacenar el estado. Si miramos el código HTML, vemos que cada vez que se hace click se llama a un método que muestra la foto

<li><img src=»imgs/VisitaBilbao01.JPG» alt=»Vacas» id=»i1″ onclick=»MostrarFoto(1)»/></li>

Dada la naturaleza de la aplicación de ejemplo, esta es la acción que va a implicar que queramos almacenar un nuevo estado, porque así el usuario podrá navegar atrás y adelante por las fotos con los botones del navegador. Vamos a ver la implementación

function MostrarFoto(id)
{
    var img = «imgs/» + fotos[–id];
    $get(«fotoGrande»).src = img;
    almacenarEstado(img);
}

function almacenarEstado( img )
{
    var pageState = { «RutaImagen» : img };
    Sys.Application.addHistoryPoint(pageState,img);
    userNavigated = true;
}

Es muy sencillo, creamos un objeto con el estado de la página (pageState) y se lo pasamos como parámetro a Sys.Application.addHistoryPoint. El segundo parámetro que recibe es el nombre con el que se va a almacenar ese estado en el historial. El echo de añadir un nuevo estado, internamente va a trabajar contra el iframe y va a provocar una navegación, para diferenciar esta navegación de una iniciada por el usuario, marcamos el userNavigated a true.

y listo 🙂

 

Otras opciones

Como comentaba más arriba, podéis utilizar infinidad de frameworks javascript o controles descargados de internet para que os gestionen el estado, de hecho si os animáis podéis programaros el vuestro propio…

<bricomania> mantenéis un hashtable indice/estado, cada vez que haya un nuevo estado, se marca la url con el índice nuevo y se añade el nuevo estado al hashtable. Cuando el indice de la url no se corresponda con el indice actual, recuperáis del hashtable con el índice de la url como base y establecéis el nuevo estado en la página…. </bricomania>

…bien.. no es tan fácil… hay que lidiar con las diferencias entre navegadores, ver cuándo cambia la url y en base a qué, tener en cuenta navegación hacia atrás y hacia adelante… realmente un framework os ahorrará bastante tiempo 🙂

Happy Hacking

Ds

Lectura interesante sobre el tema de almacenar y recuperar estado

http://yuiblog.com/blog/2007/02/21/browser-history-manager/
http://weblogs.asp.net/bleroy/archive/2007/09/07/how-to-build-a-cross-browser-history-management-system.aspx

Un poco de windbg aplicado…

Ayer Rodrigo planteó un problema curioso en su blog, dios que rabia me dio no tener las herramientas instaladas en el portatil que tenía en casa!!!  sólo tenía ie y una tarjeta 3G para navegar por la red, a si que ni si quiera pude bajármelas :_)


Pero esta mañana me he puesto con ello en cuanto he llegado a la oficina 😛


Ahí va la aproximación que yo he seguido para intentar atajar el problema.


(Nota: Por si alguien lo intenta resolver… tened en cuenta que las direcciones de memoria vana  variar siempre y tenéis que buscar las que os apliquen a vosotros)


Empezando con el problema


Como es Rodrigo quien dice que la aplicación pierde memoria, pues me lo creo y listo, así que asumo que una vez la ejecute va a empezar a crecer la memoria. Hay un montón de herramientas para ver cómo crece la memoria, que tipo de memoria es la que crece, etcétera… una de las alternativas es adjuntar un depurador al proceso y pararlo cada X tiempo para ver como esta la memoria del proceso.


Herramientas


Como depurador he utilizado el WinDBG, diponible en las debugging tools for windows


Depurando el proceso


Compilamos y ejecutamos el ejemplo que nos pone Rodrigo.


Arrancamos WinDBG y Vamos a File .. Attach To Process y escogemos el proceso que acabamos de arrancar (buscad el pid adecuado).


image 


En la ventana de command del depurador vemos información del proceso, esta parado esperando a que introduzcamos alguna orden.


image


Por ahora no vamos a hacer nada… vamos a volver a activar el proceso para que siga corriendo un rato y empiece a cargarse la memoria, de modo que en la ventana de comandos, introducimos el comando g y damos a intro


image


Al de un minuto (por ejemplo) pulsamos ctrl + break … asi pararemos el proceso e inspeccionaremos su estado actual. Dado que Rodrigo nos ha dicho que es un problema de memory leak… vamos a tiro hecho y buscamos información sobre objetos que se acumulan en el managed heap (normalmente no es tan fácil y hay que invertir tiempo en determinar a que tipo de problema de memoria te enfrentas)


Obteniendo la información de referencia


Lo primero que vamos a hacer es cargar la SOS.dll. Esta librería es una extensión del depurador que nos va a permitir movernos por estructuras de datos manejadas por el CLR, de modo que nos facilita la depuración de código .net. Para cargarla ejecutamos este comando


.loadby sos mscorwks


Una vez esta cargada la extensión podemos usar sus comandos. Vamos a ver cuales son los objetos que hay en el managed heap


!dumpheap -stat


De la salida del comando nos quedamos con las últimas líneas (mayor número de objetos en memoria) y guardamos los números para poder compararlos en la próxima parada


790fd8c4     5632       345980 System.String
003ec570       18       4064392      Free
7912d8f8     2514      4291592 System.Object[]
79104c38   771803    12348848 System.WeakReference


Una vez apuntado, volvemos a ejecutar el comando g y dejamos que el proceso corra durante otro rato, para ver si varían los números.


…. ( minutos musicales mientras el proceso corre ) …


Volvemos a pararlo de nuevo con ctrl + break y volvemos a mostrar los objetos en el managed heap como antes


790fd8c4     5632       345980 System.String
003ec570       19      8258724      Free
7912d8f8     3678      8516160 System.Object[]
79104c38  1308060     20928960 System.WeakReference

Vemos que los Strings se han conservado, pero las WeakReferences han crecido ( 12348848 vs 20928960 ). Es recomendable hacer este proceso unas cuantas veces para tener mas muestras y poder establecer una tendencia 🙂


Una vez vemos que las weakreferences crecen, hemos de saber porqué…. a si que entramos en modo diagnostico diferencial de house…


house> ¿por qué no se liberan?


chase> será lupus?


cameron> no creo.. puede ser que alguien mantenga una referencia a ellos y por eso no los recoja el GC


house> ok.. analitica completa, TAC, rajadlo y mira las referencias que apuntan a los objetos weakreference


Para mirar las referencias, primero necesitamos saber la dirección en memoria de los objetos. Tomemos la salida del comando !dumpheap -stat, si nos fijamos en la fila relativa a las wekreferences


79104c38  1308060     20928960 System.WeakReference


El primer valor es la MethodTable del tipo, ahora que la conocemos, podemos ejecutar un comando que vuelca todos los objetos de una determinada method table


!dumpheap -mt  79104c38 


(el resultado hara scroll en pantalla varias veces, podéis pararlo con Ctr+break) Ahí tenemos todos los objetos del tipo, vamos a ver quién les esta referenciando y manteniéndolos vivos. La salida del comando tiene el siguiente formato: Dir.Objeto Dir.MethodTable Tamaño


Tomamos unas cuantas direcciones de objeto y ejecutamos el siguiente comando sobre ellas


!GCRoot dir del Objeto


Nos muestra la relación de referencias que mantienen vivo al objeto. Vemos al volcar unas cuantas que hay System.Collections.Generic.List que las referencia


0:003> !GCRoot 02497570
Note: Roots found on stacks may be false positives. Run «!help gcroot» for
more info.
Scan Thread 0 OSTHread 179c
Scan Thread 2 OSTHread 1624
DOMAIN(003E4AC0):HANDLE(Pinned):a13f0:Root:02df3030(System.Object[])->
01df4300(System.Collections.Generic.List`1[[System.WeakReference, mscorlib]])->
035d52e0(System.Object[])->
02497570(System.WeakReference)


Para seguir asegurando…


Volcamos la genericList y vemos su tamaño


!do 01df4300


79102290  40009c8        c         System.Int32  1 instance  1308060 _size


dejamos correr un rato al proceso ( g ) y volvemos a comprobar el tamaño del objeto ( ejecutamos el mismo comando )


79102290  40009c8        c         System.Int32  1 instance  1524568 _size

BINGO!!!  va creciendo woooooohoooooo


Si estuviésemos en un proyecto grande habría que localizar el assembly y volcar el código para ver que pasa…. como estamos en un ejemplo pequeño es fácil… no hay ningun array en nuestro código.. a si que con ayuda de reflector miramos la clase TraceSwitch… que nos lleva a Switch… y al siguiente código (resumido)


   lock (switches)

   {

       switches.Add(new WeakReference(this));

   }

El array switches crece indefinidamente y no se liberan las weakReferences (se liberan los strings a los que apuntan las weakreferences pero no estas en sí)

y ahora qué?

Pues he estado mirando por la web y no puedo decir aun si es un bug o si hay alguna consideración que nos hayamos saltado a la hora de trabajar con el TraceSwitch, en cuanto lo sepa os lo cuento

<update 28/4/08>
Tenemos respuesta del grupo de producto, es un bug corregido para la próxima major relase de .net 🙂
</update 28/4/08>

Happy Hacking!

David Salgado

.NET 3.5 Enhancements training kit

 

Hola a todos,

Si bien no me gusta hacerme eco de noticias  que salen por otros blogs, esta es una ocasion especial. Creemos que esta puede resultar de interés. Se ha publicado el .net 3.5 Extensions Training Kit. Es un recurso de formación (demos, hands-on-labs, doc… update 21/04/08 Por ahora solo 6 hands on labs) que cubre las siguientes tecnologías:

ADO.NET Data Services

ADO.NET Entity Framework

ASP.NET AJAX History

ASP.NET Dynamic Data

ASP.NET MVC

ASP.NET Silverlight Controls

Os recuerdo que por ahora estas APIs no vienen por defecto con .net 3.5 y se instalan aparte. Espero que os sirva de ayuda, aquí os dejo el enlace al post original

HappyHacking!

David Salgado

MIX Essentials Madrid se retrasa a Octubre

Hola a todos!

Extraña noticia, no? Sobre todo teniendo en cuenta el último post donde comentaba que la agenda se estaba cociendo y que solo faltaba poco más de un mes x)

La decisión viene motivada por un motivo fundamental… y es que este verano van a pasar algunas cosas en el entorno Microsoft  <aclaración> no, no puedo hablar claro 😀 prefiero conservar mi trabajo 😛 </aclaración> De modo que cubriremos el contenido del MIX de Las Vegas y las cositas que pasen en verano en el MIX en Octubre.

Os iremos avisando a través del MSDN Flash y de este blog de las novedades en cuanto al MIX, ponentes, agenda, posible concurso }=P según se acerque más la fecha….

¿Por qué no antes? .. porque la mayoría estáis de vacaciones y no queremos darnos la charla para nosotros solos 🙂

¿Y mientras tanto que? .. No os preocupéis, si bien no vamos a tener ahora en mayo el evento presencial que habíamos previsto en un principio, ya le hemos estado dando al coco para ver algo de contenido de otras formas 😉

¿Grabaremos el evento? .. Si, nuestra intención es cada vez ir grabando más contenidos en diferentes formatos (evento, screencast, entrevistas, webcast… ) y ponerlos disponibles en la red.

¿? 42

Lo siento si alguno de vosotr@s estaba planificando ya el viaje, a mi tb me hacía ilusión tener un evento grande ahora… pero confío en hacer algo grande en Octubre, además así tenemos más tiempo para ver/hacer más cosas 🙂

happy hacking

David Salgado

Se esta cociendo: MIX Essentials 2008 – 20/5 – Kinepolis Madrid

 

Algunos conocéis el MIX 08 de Las Vegas, el año pasado hicimos un evento similar en españa, el ReMIX. Este año volvemos a la carga, por causas de guión, este año se llamará MIX Essentials 🙂

Estamos intentando construir una agenda de lo más completa posible para todos… pero os preguntaréis  ¿quienes son todos?  El evento está destinado a desarrolladores de escritorio, desarrolladores web, creativos, diseñadores web, agencias de publicidad… De modo que habrá contenidos de todo tipo, tanto de tecnología, como producto, como tendencias.

Algunos cotilleos…

– Para asegurarnos de que hay contenidos para todos, tenemos 3 salas de Kinepolis en paralelo, aproximadamente unas 15 sesiones en un día!!  Ya podéis veniros con la agenda pensada de casa 😉

– Habrá un concurso curioso online

Por ahora no os podemos adelantar nada más. Al más puro estilo de Scott Guttrie, iré publicando más información sobre el evento en los próximos días. 

ciao!