martes, 28 de diciembre de 2010

Cabeceras de cache

Hoy vamos a hablar de la cache.

No de la cache de ASP.NET, ni de la cache de JSP ni PHP, sino de la cache a secas porque primero hay que sembrar para después recoger.

Cuando un navegador solicita un recurso al servidor existen 3 distintas formas de llevar a cabo la petición.

De menos a más costosa:

  • Usar la versión local cacheada del recurso.
  • Enviar una petición HTTP condicional al servidor solicitando el recurso sólo si es distinta de la versión local cacheada.
  • Enviar una petición HTTP al servidor solicitando el recurso.
    • Esto puede suceder por distintas razones, pero a grandes rasgos se solicitará el recurso al servidor bien porque no se dispone de una versión local cacheada, bien porque aunque se disponga de ella, ésta ya ya ha expirado o bien porque hemos sido instruidos por el servidor a través de una petición HTTP condicional previa, para renovar el recurso que se tenía en la cache local.

Durante la solicitud del recurso al servidor por parte del cliente, algunas de las cabeceras HTTP más habituales que se puede enviar son:

  • Pragma: no-cache
    • Esta cabecera indica que cuando el cliente solicita al servidor una nueva copia del recurso, no espera recibir ninguna versión cacheada del recurso ya sea desde el propio servidor o desde alguno de los proxys que pudiera aparecer en su camino.
    • Esta cabecera está en desuso y no debería utilizarse ni confiar en ella. Si aun se soporta (y no todos los clientes y servidores lo hacen) es por mantener cierta retro compatibilidad.
  • If-Modified-since: <fecha/hora>
    • El servidor solo debería devolver el recurso solicitado si ha sido modificado después de la fecha y hora suministrada.
      • El formato de la fecha/hora es el de RFC822.
        Por ejemplo, Fri, 9 Aug 2002 21:12:00 GMT
  • If-None-Match: <Etag>
    • El servidor solo debería devolver el recurso solicitado si el ETag actual del recurso en el servidor es distinto del ETag suministrado por el cliente.

Un ETag (entity tag) es un identificador único asignado a una versión concreta de un recurso del servidor (normalmente su valor se obtiene a partir de una función de hash aplicada al recurso). Siendo así, cualquier cambio en el recurso supone un cambio en su ETag y entonces comparando ETags podemos validar si el recurso ha cambiado.


Si la petición del recurso devuelve un código de estado HTTP/304 Not Modified, el servidor le está comunicando al cliente que el recurso solicitado no ha cambiado y que puede utilizar su versión del recurso local cacheado (esto podría suceder si hemos realizado una petición condicional con If-Modified-Since o If-None-Match). En caso contrario el servidor devolverá una nueva copia del recurso y el navegador cliente deberá desechar su antigua copia de cache por la nueva copia que recibe. Además y en función de las cabeceras incluidas en la respuesta junto al recurso solicitado, el cliente podrá o no almacenar el nuevo recurso en su cache local o desecharlo automáticamente.

Una petición condicional es normalmente una excelente solución para optimizar nuestra aplicación. Sin embargo, no debemos olvidar que aunque la solicitud y respuesta sólo han incluido cabeceras (esto es que la respuesta no incluye ningún cuerpo), no deja de producirse un roundtrip al servidor (viaje de ida y vuelta). En cualquier caso, es más optimo que obtener siempre una nueva copia del recurso solicitado.

 
Un cliente sabe si tiene o no que almacenar una copia del recurso solicitado en cache local y durante cuánto tiempo, en función de las cabeceras incluidas en la respuesta por parte del servidor.

Las cabeceras de respuesta más habituales para el establecimiento de directivas de cache por parte del servidor, son las siguientes:

  • Expires: <fecha/hora>|<-1|0>
    • Si se especifica un valor de fecha y hora se indica el momento hasta el que se considera válido el recurso en la cache local.
    • Cualquier valor distinto de fecha y hora (normalmente 0 o -1) indica que el recurso expira automáticamente y no debe guardarse en ninguna cache.
    • Esta cabecera está obsoleta y debería no utilizarse.
  • Cache-Control:
    • public
      • La respuesta podría ser cacheada en cualquier cache, incluyendo caches compartidas de usuario (por ejemplo un proxy).
    • private
      • La respuesta podría ser cacheada en cualquier cache que sólo pertenezca a un usuario (por ejemplo un navegador).
    • no-cache
      • La respuesta no puede ser cacheada en ningún cache.
    • no-store
      • Igual que no-cache no puede ser cacheada en ningún cache y además no puede ser escrita en disco (normalmente por temas de seguridad).
    • max-age=segundos
      • Sólo durante los siguientes “segundos” se podría reutilizar la copia local cacheada.
    • must-revalidate
      • El recurso solicitado podría ser cacheado, pero siempre se debería contactar con el servidor para verificar que el recurso aún es válido.

Realmente Cache-Control es la cabecera principal si hablamos de caché. Su definición completa la puedes encontrar en http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

Cuando elegir Public y cuando Private depende del recurso servido. Si el recurso es algo que no específico a ningún usuario (por ejemplo la imagen del logo de la empresa) podría ser Public (es decir, lo pida quien lo pida es el mismo logo). Sin embargo, si lo que estamos cacheando es un recurso que contiene información relativa a un usuario en concreto (por ejemplo la página de inicio de nuestra aplicación que lleva su nombre para acceder a su perfil) pues entonces tendrá que ser cacheado como Private ¡no queremos que un usuario X reciba la página con la información del usuario Y!.

Cuando hablamos de proxys nos referimos a servidores que puede encontrase en el camino una petición, desde el cliente al servidor o viceversa, y que pueden inspeccionar y, opcionalmente transformar, la petición o la respuesta.

 

Un proxy puede ser de 2 tipos : forward proxy (cerca del cliente) o reverse proxy (cerca del servidor).

 

Un proxy de tipo forward es que ponen las empresas antes de que el tráfico de su intranet salga a internet. Las tareas más habituales de este tipo de proxy son:

  • Cachear recursos. Por ejemplo, si toda mi empresa necesita acceso a facebook, voy a cachear (si Cache-Control: Public) ciertos recursos de facebook para acelerar la navegación de mis empleados.
  • Control de acceso. No voy a dejar navegar a mis empleados a twitter, que se pasan el día allí…
  • Auditoría. Voy a registrar la navegación de mis empleados para luego tirarles de las orejas.
  • Eliminar información sensible. Por ejemplo la cabecera Referer que podría apuntar a recursos sensibles en la intranet.

Un proxy de tipo reverse los ponen las aplicaciones para liberar de carga al servidor de la aplicación. Por ejemplo, algunos tareas que podrían cumplir este tipo de proxys son:

  • Cachear recursos. Como resulta que mi aplicación tiene mucho éxito, para mejorar la salud y rendimiento del servidor principal voy a cachear .js, .css y cosas así en un servidor proxy y así no llegan ni al servidor principal, que tiene cosas más importantes que hacer…
  • Realizar tareas que aligeren la carga del servidor. Por ejemplo, si hay que comprimir en gzip, que lo haga el proxy, si hay que encriptar/desencriptar porque tenemos HTTPS, que lo haga el proxy, que de nuevo, el servidor principal tiene otras más importantes que hacer…
  • Balancear la carga de peticiones entre distintos frontales en función se su carga actual de trabajo.

Como ves, distintas características del protocolo HTTP puede ser arquitecturadas en capas gracias a la simpleza de los peticiones y respuestas (mensajes en texto plano fáciles de parsear y manipular). De este modo, los distintos servicios que puede ofrecer un proxy (sea del tipo que sea) son muy variopintos.


En cualquier caso, para que un recurso puede ser cacheado se debe encontrar una definición completa de cómo y hasta cuándo cachearlo.

Es decir, si recibimos una cabecera Cache-control sabremos “el cómo” pero si no va acompañado de una valor de expiración relativo al tiempo, por ejemplo Cache-control: max-age, Expires o similar, no sabremos “hasta cuándo” y tampoco podremos hacer una petición condicional si no recibimos una cabecera que lo permita (ETag o Last-Modified). Si esto sucede, estaríamos incurriendo en un problema de incoherencia con nuestras cabeceras de cache y el cómo cachear los recursos será entonces decisión del navegador de turno y hasta cuándo será también decisión del modulo heurístico del propio navegador (esta solución es jugártelo todo a blanco o negro y no nos gusta).

De hecho quiero hacer especial mención al módulo heurístico del navegador en lo relativo a la cache local. En este sentido, a nadie se le escapa que ahora mismo sufrimos una encarnizada batalla entre navegadores (IE, FF, Opera, Chrome, Safari, etc.) y como no podía ser de otra forma, el módulo heurístico por el que el navegador de turno decide cuando ha expirado un recurso (recuerda que esto sucede siempre que no se haya establecido una expiración concreta para ese recurso) es todo un misterio y distinto para cada uno de los navegadores del mercado. Incluso para directivas que suponen peticiones condicionales, un navegador no se comporta igual que otro (por ejemplo, no siempre que se envía desde el servidor un ETag o LastModified, siguientes peticiones del mismo recurso provocan peticiones condicionales). De este modo, mi recomendación (mía y sólo mía) es que para aquellos recursos sensibles se establezcan políticas explícitas de expiración concretas y para el resto no inviertas ni un solo minuto de tu tiempo asumiendo que se hará una petición condicional en determinado momento o que el módulo heurístico del navegador X será tan listo como para renovar el recurso cuando tú creas que debería haber sido.

¿Conoces la diferencia entre pulsar F5 y Ctrl+F5 en tu navegador?.

 

F5 viene a ser “por favor, actualiza todo el documento y todas sus dependencias, pero si hay recursos que no han cambiado, tampoco los descargues…”

 

Ctrl+F5 viene a ser “con favor y sin favor, actualiza todo el documento y no te preocupes de que algo no haya cambiando, el tráfico de red no es mi problema…”

 

En cualquier caso, esto es responsabilidad del navegador y distintos navegadores podrían tratar de distinto forma lo aquí expuesto.


Hasta ahora siempre hemos hablado de cabeceras en solicitud y respuesta para controlar la expiración de un recurso pero, y aunque no es lo más habitual, también es posible establecer el comportamiento de la cache a través de directivas META de HTML. Para más información ver la meta Cache-Control en Useful HTML Meta Tags.

Otra cabecera que está relacionada con la cache y que podría aparecer es Vary. Yo no la he trabajo mucho (francamente) pero parece que permite agregar más condiciones a la decisión de si el recurso debe o no ser servidor de nuevo por el servidor. Más información en http://stackoverflow.com/a/1975677 y http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44

Otras consideraciones al respecto de la cache son las siguientes:

  • Peticiones con el verbo POST nunca son cacheadas.
  • HTTPS se rige por las mismas cabeceras que HTTP. Más info en Top 7 Myths about HTTPS.

Cache en ASP.NET

Todo lo anteriormente expuesto es relativo a conceptos generales sobre la cache, pero en nuestro caso nos vamos a enfocar en como trabaja ASP.NET en lo relativo a la cache.

A grandes rasgos tenemos las siguientes posibilidades:

  • Response.Cache.
  • IIS
  • Manejadores para distintos tipos de archivo.

Response.Cache sólo es efectivo para la página .aspx y no para los recursos incluidos en la misma. Un artículo muy bueno que habla sobre ello puedes encontrarlo en http://dotnetperls.com/cache-aspnet

IIS permite establecer directivas (a través de cabeceras), bien sea a un sitio web entero, a una carpeta o incluso a un fichero concreto. Lo cierto es que si se tiene acceso al servidor y a IIS, es un buen sitio para especificar de forma precisa y rápida nuestras políticas de cache y expiración.

Otra opción para establecer cabeceras a recursos concretos (no a la página .aspx sino a los ficheros .jpg por ejemplo) es crear un nuevo manejador (clase que hereda de IHttpHandler) e instruir a IIS para que los ficheros .jpg en vez de servirlos él mismo lo haga ASP.NET. Un ejemplo se puede encontrar en http://www.codeproject.com/KB/aspnet/CachingImagesInASPNET.aspx

También comentar que existe la directiva OutputCache, que aunque en cierta medida puede intervenir en esta ida y vuelta de cabeceras de cache, su propósito está más orientado a cachear páginas (el resultado de la página o control de usuario) en el servidor para ahorrar coste de procesamiento. http://jimenezroda.wordpress.com/2009/08/19/using-outputcache-directive-utilizando-la-directiva-outputcache/

Por último, te dejo un enlace buenísimo que habla sobre la cache en su amplio espectro Caching Tutorial.

Un saludo!

lunes, 27 de diciembre de 2010

system.WebServer

Llevo tiempo viendo que al crear un nuevo fichero web.config se incluye una sección system.webServer pero, con franqueza, hasta hoy no sabía cuál era su cometido.

Esta sección permite configurar el servidor IIS si el servidor es la versión 7.0 o superior (esto es Windows Vista, Windows 7 y Windows Server 2008 R2).

Esto no es tan trivial como parece. Hay un ejemplo muy claro que me ha servido para entenderlo definitivamente. ¿Cuántas veces después de publicar una aplicación has accedido al complemento MMC de IIS (inetmgr) para definir el documento predeterminado si se accede a tu aplicación sin especificarlo? Pues bien, ahora y a través de system.WebServer es posible configurar este documento predeterminado directamente, es decir, estamos no sólo configurando nuestra aplicación ASP.NET sino también el servidor donde se ejecutará. Por supuesto, el documento predeterminado es sólo un ejemplo y el grado de personalización es casi tan completo como todo lo que puedas hacer desde el complemento MMC.

Por otro lado, el pipeline (de ahora en adelante “canalización de solicitudes”) también ha cambiado de IIS 6.0 a IIS 7.x, por lo que ahora en vez de httpModules hablamos de modules y en vez de httpHandlers hablamos de handlers. Bueno, esto no es un gran cambio (digo yo que estarás pensando) pero la realidad es que ahora los módulos llegan más lejos de lo que llegaban antes los httpModules e igualmente pasa con los manejadores. Ahora se disponen de más eventos durante ese proceso de “pipeline” (esto es que el ciclo de vida de una aplicación ASP.NET ahora es más rico con IIS 7.0) y además se puede acceder a más sitios, ya no es necesario por ejemplo asignar aspnet_isapi.dll a los ficheros .jpg para poder procesarlos con ASP.NET porque ahora directamente ya están accesibles, pero… siempre y cuando el grupo de aplicaciones en el que se ejecuta nuestra aplicación ASP.NET esté configurado en el modo integrado.

Este modo integrado también es nuevo en IIS 7.x y realmente tiene 2 posibles valores: clásico e integrado. Clásico es funcionar como IIS 6.0 e integrado es funcionar como IIS 7.x. Por defecto, el grupo de aplicaciones DefaultAppPool está configurado en modo integrado y además también tenemos disponible después de la instalación un grupo de aplicaciones Classic .NET AppPool que se ejecuta en modo clásico.

La idea realmente a memorizar es que con IIS 7.x los límites entre aplicación ASP.NET y configuración del servidor se han limado bastante y ahora como programadores tenemos a nuestra disposición más oportunidades y mayor control sobre el despliegue de nuestras aplicaciones.

Se puede encontrar más información en http://msdn.microsoft.com/es-es/library/bb515251.aspx y también en http://www.iis.net/ConfigReference/system.webServer

Un saludo!.

WebResource.axd y ScriptResource.axd

Hoy veremos que es un WebResource y un ScriptResource.

Lo cierto es que los ves aparecer como por arte de magia en tu página y dices ¿Pero quién metió eso?. Pues bien, te adelanto que no son más recursos embebidos (normalmente ficheros .js, imágenes .jpg, etc.) en un ensamblado y que se inyectan automáticamente en tu página cuando es necesario para el correcto funcionamiento de la parte cliente (claro está en función de los controles que incluyas en tu página).

Crear un nuevo de control ASP.NET personalizado

Lo primero que necesitamos para comprender el uso y creación de un WebResource y un ScriptResource es un control ASP.NET de servidor personalizado con el que podamos poner en práctica los ejemplos requeridos. Por ello, el propósito de nuestro nuevo control será una caja de texto que al recibir el foco cambia el color de fondo y mostrará una imagen para indicar tal suceso (vale, sé que es absurdo, pero para el caso sirve…)

Crear un nuevo control ASP.NET de servidor es sencillo y simplemente requiere estos pasos:

1.      Crear un nuevo proyecto de aplicación web.

a.      En nuestro caso le llamaremos EjemploRecursos.

2.      En la misma solución, crear un nuevo proyecto de librería de clases.

a.      Al que llamaremos MisControlesWeb.

3.      Agregar una referencia de proyecto desde “EjemploRecursos” a “MisControlesWeb”.

4.      Agregar una referencia a System.Web en el proyecto “MisControlesWeb”.

5.      Crear una nueva clase “MiCajaDeTexto” en el proyecto “MisControlesWeb” que herede de System.Web.UI.WebControls.TextBox.

6.      Generar la solución.

7.      Arrastrar un nuevo control del tipo “MiCajaDeTexto” en cualquier página del proyecto “EjemploRecursos”.

Ahora lo que vamos a hacer es incluir en la página .aspx del proyecto EjemploRecursos el código Javascript necesario para llevar a cabo nuestra funcionalidad. Más tarde veremos cómo no será necesario inclur este código en la página .aspx sino que será servido automáticamente por el control de usuario (porque claro está que si cada vez que agrego a una página un control del tipo “MiCajaDeTexto” tengo además que incluir código Javascript propio para implementar su funcionalidad, no parece un buen control autocontenido y además me han mentido y de serie no incorpora la funcionalidad prometida).

El código Javascript necesario para implementa la funcionalidad de cambiar el color de fondo de la caja de texto al recibir el foco puede ser cualquiera, y de hecho imagino que habrá 100 formas distintas de llevarlo a cabo, pero lo que importa realmente es que nuestro control de usuario necesita de “algo” de código Javascript para funcionar correctamente.

        /* Función que no quiero tener que incluir en la página cada vez que utilice el control MiCajaDeTexto */

        function gestionarFoco(textBox) {

            textBox.unbind("focus").focus(function (e) {

                $(this).css({ "background-image": "url('tiene_foco.png')", "background-repeat": "no-repeat", "background-color": "yellow" });

            });

            textBox.unbind("blur").blur(function (e) {

                $(this).css({ "background-image": "", "background-color": "" });

            });

        }

 

        /* Enlace a la función anterior que de nuevo no quiero tener que incluir y me gustaría olvidarme de que existe */

        $().ready(function () {

            gestionarFoco($("#<%=MiCajaDeTexto1.ClientID %>"));

        });

 

¿Por qué queremos recursos embebidos?

 

Ahora lo realmente interesante sería que cuando un usuario arrastrara un control de tipo “MiCajaDeTexto” a su página .aspx, el código Javascript anterior fuera “Inyectado” automáticamente y además fuera transparente para el desarrollador, que de hecho no tendría que saber ni tener ningún conocimiento del mismo. Para ello utilizaremos, tanto WebResource como ScriptResource.

La idea detrás de WebResource y ScriptResource es la de poder servir recursos embebidos en un ensamblado a un explorador web a través de un url. Por ejemplo y en nuestro caso, poder servir automáticamente al navegador cliente, tanto el código Javascript como la imagen necesaria para que nuestro control web de servidor funcione correctamente.

Embeber un recurso en una biblioteca de clases

Para ello llevaremos a cabo las siguientes operaciones:

1.      Agregar un fichero MiCajaDeTexto.js (no es obligatorio que se llame igual que el control, pero imagino que así es más sencillo para todos) al proyecto “MisControlesWeb”, que contiene sólo el código de la función gestionarFoco.

2.      Seleccionar el valor “Recurso embebido” en la propiedad “Acción de compilación” para el fichero “MiCajaDeTexto.js” desde la ventana de propiedades del fichero.

3.      Añadir la siguiente instrucción al fichero AssemblyInfo.vb del proyecto “MisControlesWeb”

a.  <Assembly: WebResource("MisControlesWeb.MiCajaDeTexto.js", "text/javascript")>

4.      Agregar tiene_foco.png al proyecto “MisControlesWeb” y seleccionar de nuevo “Recurso embebido” en la propiedad “Acción de compilación”.

5.      Incluir la siguiente instrucción en el fichero AssemblyInfo.vb.

a.  <Assembly: WebResource("MisControlesWeb.tiene_foco.png", "image/png")>

 

Para que la instrucción Assembly esté disponible hay que agregar una referencia en el proyecto a System.Web y para la instrucción ScriptResource hay que agregar una referencia a System.Web.Extensions.

Al respecto de la instrucción Assembly, cabe mencionar que con independencia de si el recurso va a ser consumido con WebResource o ScriptResource, la forma de embeber el recurso en el ensamblando es siempre la misma y es con la instrucción Assembly: WebResource en el fichero AssemblyInfo.vb del proyecto. Esta instrucción recibe 2 parámetros, el primero es la ruta al fichero/recurso (con la forma EspacioDeNombreRaiz.Fichero) y el segundo parámetro es el content-type del fichero. Opcionalmente, el tercer parámetro (llamado PerformSubstitution) puede recibir un valor booleano.

Servir los recursos embebidos automáticamente

Durante este post hemos hablado de que existen 2 posibles vías para servir recursos embebidos desde un ensamblado, WebResource y ScriptResource. Las diferencias entre ambos son las siguientes:

  • WebResource es más generalista y serviría para servir cualquier tipo de contenido (ficheros .js incluidos), aunque normalmente se utiliza con recursos binarios (imágenes, videos, etc.)
  • Por otro lado, ScriptResource (y como su propio nombre indica) está especializado en servir recursos de script y presenta las siguientes ventajas (siempre en el contexto de la utilización del control ScriptManager, es decir si utilizamos ScriptManager podremos utilizar ScriptResource con la instrucción ScriptReference, sino tendremos que servir los ficheros .js siempre como WebResources)
    • Compresión GZip automática.
    • Resuelve dinámicamente el modo de compilación activa (Release o Debug). Esto es sólo para ficheros .js como recursos web (importante, no para ficheros .js que apunten directamente a alguna carpeta de nuestro sitio web o aplicación web). Esto significa que podríamos tener un fichero javascript para el modo Debug y otro para el modo Release y ScriptResource serviría uno u otro en función del modo de compilación activo. Esto se consigue simplemente teniendo 2 versiones del mismo fichero (por ejemplo, Funciones.js y Funciones.debug.js) y establecido la propiedad ScriptMode, bien en el control ScriptManager, bien en la plantilla ScripReference de ScriptManager.
    • Soporta localización,  esto es distintas versiones de los recursos en función de la cultura actual. Por ejemplo, un fichero .js para español y otro para inglés.
    • Además podemos ver como “misteriosamente” se agrega automáticamente al final de nuestro fichero .js el código siguiente, en función de si está o no activa la propiedad NotifyScriptLoaded (que por defecto, está activa)
      if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();

Public Class MiCajaDeTexto

    Inherits System.Web.UI.WebControls.TextBox

 

    Private Sub MiCajaDeTexto_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        ' Tanto Me.Page.ClientScript.RegisterClientScriptResource como Me.Page.ClientScript.GetWebResourceUrl

        ' generan una Url que utiliza el manejador WebResource.axd.

 

        ' <script src="/WebResource.axd?d=UOQnDV7W6AhbSiBysPImiOX2P_ASdSzukYRPufAOLwcgYKdW3QZvQ_vM4P_2OPyldw3YL44uIX2XIrzA3OziaNCknPbmqjGU2Ngu8W9cpdkHSoAqa2QsrIe3D9rdo4LbOT8bzQ2&amp;t=634290437347694538" type="text/javascript"></script>

        Me.Page.ClientScript.RegisterClientScriptResource(Me.GetType, "MisControlesWeb.MiCajaDeTexto.js")

 

        ' /WebResource.axd?d=tpwcz4dkzkCruL-NvWYZGpSxhUNPWZerTtLfHSFGYvaKYOrJL5f_GShVQcmuF8yhXwhOjqzUI7jHGLBsv1sjUB6obAWpRvBKsjg3E1nnNuLqk4uOA4uNh2D5J9LxJCNLAdvYpg2&t=634290437347694538

        Dim imagePath As String = Me.Page.ClientScript.GetWebResourceUrl(Me.GetType, "MisControlesWeb.tiene_foco.png")

 

        ' <script type="text/javascript">

        ' //<![CDATA[

        ' $().ready(function () {gestionarFoco($('#MiCajaDeTexto1'), '/WebResource.axd?d=tpwcz4dkzkCruL-NvWYZGpSxhUNPWZerTtLfHSFGYvaKYOrJL5f_GShVQcmuF8yhXwhOjqzUI7jHGLBsv1sjUB6obAWpRvBKsjg3E1nnNuLqk4uOA4uNh2D5J9LxJCNLAdvYpg2&t=634290437347694538');});//]]>

        ' </script>

        Dim startUpScript As String = _

            "$().ready(function () {" & _

                "gestionarFoco($('#" & Me.ClientID & "'), '" & imagePath & "');" & _

            "});"

        Me.Page.ClientScript.RegisterStartupScript(Me.GetType, Me.ID, startUpScript, True)

    End Sub

End Class

 

Como podemos ver según el código anterior:

  • Me.Page.ClientScript.RegisterClientScriptResource escribe directamente en la página .aspx una etiqueta <script> con el atributo src apuntando a nuestro recurso embebido a través de WebResource.axd.
  • Me.Page.ClientScript.GetWebResourceUrl nos devuelve una Url que apunta de nuevo a un recurso embebido a través de WebResource.axd y será nuestra responsabilidad el utilizar este Url obtenida en algún lugar de nuestro control/página.

Obtener recursos desde una librería externa

Si queremos acceder a un recurso web desde un ensamblado diferente de donde está contenido, tenemos que tener en cuenta que el tipo suministrado en RegisterClientScriptResouce o GetWebResourceUrl es muy importante. Por ejemplo, si en nuestro proyecto “EjemploRecursos” necesitaramos un Url a un recurso de “MisControlesWeb”, deberíamos suministrar un tipo de “MisControlesWeb” (yo creo que cualquiera, luego de aquí se deduce que el menos la librería que tiene los recursos embebidos al menos debe de tener 1 tipo público). Por ejemplo, desde una página .aspx de “EjemploRecursos” tendríamos que escribir:

Me.Page.ClientScript.GetWebResourceUrl(GetType(MisControlesWeb.MiCajaDeTexto), "MisControlesWeb.tiene_foco.png")

Qué es d y qué es t

En todas las Url generadas para acceder a un recurso embebido hemos visto que tienen 2 parámetros, d y t.

El parámetro d representa los datos y t es una marca de fecha/hora.

El parámetro t es una fecha y hora (expresada en ticks) de la última fecha y hora de modificación de la librería “MisControlesWeb”. De este modo 634290437347694538 representa el 27 de diciembre de 2010 10:48:54. Aunque este parámetro no parezca muy relevante, es definitivo puesto que el navegador almacenará en memoria cache el recurso solicitado mientras no cambie la fecha de última modificación de la librería que lo sirve. De este modo, si volviéramos a compilar “MisControlesWeb”, el parámetro t cambiaría y el recurso almacenado en cache sería descartado por una nueva copia solicitada al ensamblado.

En cuando al parámetro d, es un parámetro que está cifrado.

Si utilizamos WebResource, el parámetro d será algo parecido a esto “pNombreProyecto|Recurso”, donde p es un literal fijo.

En cambio si utilizamos ScriptResource, el parámetro d contiene mucha más información.

Para utilizar ScriptResource en vez de WebResource, basta con utilizar el control ScriptManager en vez de la clase ClientScript para registrar nuestro script, además ScriptResource utiliza el manejador ScriptResource.axd en vez de WebResource.axd.

Para poder acceder al objeto ScriptManager desde nuestra clase MiCajaDeTexto, tendremos que agrega una referencia a System.Web.Extensions al proyecto “MisControlesWeb”.

Ahora ya podemos escribir la siguiente instrucción

ScriptManager.RegisterClientScriptResource(Me.Page, Me.GetType, "MisControlesWeb.MiCajaDeTexto.js")

 

Que genera de nuevo una etiqueta <script> pero llamando a ScriptResource en vez de a WebResource. Cabe mencionar a este respecto, que si en nuestra página .aspx no hemos incluido un control ScriptManager, la instrucción anterior no fallará pero hará caso omiso de nuestra intención y utilizará WebResource.axd en vez de ScriptResource.axd.

<script src="/ScriptResource.axd?d=-yssDyrHaXJC96bAmMegiDEPCjHaUohK3XhYbYYcaDVnW4EueedWhYClqwhJGrh4WUkKsSJ8fl37x8U71hqyTzRcGYrI7Gu_5y5siDPoeHry6T07-wogONV3a2Z5EFtqdv1FiA2&amp;t=6940fdeb" type="text/javascript"></script>

 

Volviendo al parámetro d, ahora vemos que si desencriptamos el parámetro es valor obtenido es ZMisControlesWeb|MisControlesWeb.MiCajaDeTexto.js|

  • El primer carácter (Z) indica que el resultado debe ser devuelto en un formato comprimido GZip (también podría ser (u) que significa que no se quiere comprimir).
  • El siguiente parámetro es el nombre completo del ensamblado donde puede encontrarse el recurso (MisControlesWeb, en este ejemplo).
  • El parámetro que se indica a continuación es el nombre del recurso que se tiene que recuperar (en esta instancia, MisControlesWeb.MiCajaDeTexto.js)
  • El parámetro final es la referencia cultural para la que se debe recuperar (en nuestro caso está vacío lo que significa una cultura neutral).

Cómo desencriptar d y t

Para desencriptar utilizaremos por reflexión el método estático DecryptString privado de la clase System.Web.UI.Page. Un ejemplo en C# completo se puede encontrar en http://www.guysmithferrier.com/post/2007/07/Script-Resource-Viewer.aspx

 

    Private Function GetDecrypteddParameter(ByVal url As String) As String

        Dim d As String = GetdParameter(url)

        Dim p As Object() = {d}

        Dim methodInfo As System.Reflection.MethodInfo = _

            GetType(System.Web.UI.Page).GetMethod( _

                "DecryptString", _

                System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.Static)

        Dim text As String = methodInfo.Invoke(Nothing, p)

        Return text

    End Function

 

    Private Function GetdParameter(ByVal text As String) As String

        Dim indexd As Integer = text.IndexOf("?d=")

        If indexd > -1 Then

            Dim indext As Integer = text.IndexOf("&amp;t=")

            If indext = -1 Then

                indext = text.IndexOf("&t=")

            End If

            If indext > -1 Then

                Return text.Substring(indexd + 3, indext - indexd - 3)

            Else

                Return text.Substring(indexd + 3)

            End If

        End If

        Return text

    End Function

 

PerformSubtitution

PerformSubstitution es un parámetro de la instrucción Assembly:WebResource que permite indicar si el recurso definido debe de llevar a cabo tareas de sustitución. Entendemos tareas de sustitución, el acceso a otros recursos desde el recurso actual. Imagina por ejemplo una hoja de estilos (.css) embebida como recurso y una clase definida que tiene acceso a una imagen que es también un recurso del mismo ensamblado.

De este modo, sería posible definir una hoja de estilos como la siguiente y que cuando se sirviera esta hoja de estilos como recurso, la misma hoja de estilos resolvería automáticamente la llamada al recurso de la imagen para inyectar la dirección adecuada y entregar finalmente al cliente una hoja de estilos resuelta y correcta.

.nombreClase

{

    background-image: url('<%=WebResource("Acceso a recurso")%>');

}

 

Bueno, por hoy nada más, sigo mi camino a un script rápido y eficiente!!