lunes, 27 de diciembre de 2010

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!!

4 comentarios:

  1. ¿Cuál es la ventaja de usar este método de integración de scripts, contra el método natural de HTML?

    ResponderEliminar
  2. se conoce algun tipo de vulneravilidad con ScriptResource?

    ResponderEliminar
  3. Miguel Rodríguez, Microsoft siempre se ha esforzado para que los programadores sean dependientes de ellos en ves de utilizar estandares abiertos establecidos por ISO y W3C.

    Por lo contrario, al ser embebido causa un consumo extra en la carga del servidor WEB, sin mensionar la cantidad impresionante de carácteres basura que incrusta.

    Yo programo en ASPX y PHP MVC y lejos es mas eficiente y cómo trabajar en PHP + Eclipse en ves de .NET, eso de utilizar recursos dinámicos es una tontería, si sirve para controlar los objetos y hacer compatible la interacción del HTML con HTTP y .NET pero para mi es una manera muy curte de haberlo hecho.

    Por algo menos del 10% de todos los sitios WEBs en todo el mundo utilizan .NET

    ResponderEliminar
  4. Gracias por el artículo. ¿Porque siempre que busco algo de ASP me sale un hater gritando que PHP es mejor?

    ResponderEliminar