Pedir una cosa y recibir otra - Parte II: Transferir la ejecución

Post original en JASoft.org: http://www.jasoft.org/Blog/post/Pedir-una-cosa-y-recibir-otra-Parte-II-Transferir-la-ejecucion.aspx

desvioEn el post anterior de esta serie vimos cómo la forma más sencilla de transferir a un usuario desde una página a otra es mediante el uso de una redirección en el lado cliente, usando Redirect o RedirectPermanent. Sin embargo esto implica dos secuencias de petición-respuesta al servidor y además se visualiza la página final en la barra de direcciones del usuario.

Muchas veces (la mayoría) es probable que sea lo que queremos, pero hay otras situaciones en las que no será así y lo que necesitaremos es que la ejecución se cambie en el servidor, de manera inadvertida para el usuario, por ejemplo:

  1. Ejecutar código común a varias páginas que hace uso de controles y genera elementos para la interfaz de usuario. En la mayor parte de los casos podríamos usar una biblioteca común o un control de usuario, pero si involucra páginas complejas o, simplemente, si ya tenemos la funcionalidad hecha en un ASPX y no queremos repetir el trabajo, nos vendrá bien poder transferir la ejecución a esta página.
  2. Reutilizar resultados complejos hechos en una página para ser procesados en otra. Imaginemos que tenemos una página que efectúa algún tipo de operación costosa como un cálculo complejo, acceso a un servicio remoto que tarde bastante en devolver el resultado, obtener un conjunto de datos grande desde una base de datos o desde disco... En función del resultado de esta operación queremos mostrar una interfaz de usuario u otra (por ejemplo, varios posibles tipos de informe en función del resultado), pero queremos aprovechar esos resultados sin complicarnos la vida almacenándolos en algún lugar intermedio. En este caso una transferencia de ejecución desde una página a otra podría (eligiendo la apropiada para cada resultado) venirnos muy bien.
  3. Tenemos una página hecha con otra tecnología (como PHP o ASP clásico) y queremos ejecutarla para obtener sus resultados pero no enviar directamente al usuario a la misma.
  4. Existen una serie de páginas protegidas a las que no queremos que el usuario tenga acceso directamente escribiendo su dirección, pero sin embargo sí que queremos usarlas internamente para procesar ciertos datos.

ASP.NET ofrece diversas maneras de cambiar la ejecución de una página a otra dinámicamente, y cada una de ellas tiene sus ventajas, inconvenientes y limitaciones. Con algunas podremos resolver algunos de los casos anteriores y con otras no. Vamos a verlo.

Transferencia directa de la ejecución

ASP.NET hereda des la versión clásica ASP 3.0 un método de servidor llamado Transfer. Este método permite transferir la ejecución actual desde el punto en el que se esté llamando a otra páginas ASPX cualquiera dentro de la misma aplicación.

Así, por ejemplo, si escribo:

   1: Server.Transfer("P2.aspx");

desde un evento de la página, la ejecución de ésta se detiene de inmediato y se comienza a ejecutar la página P2.aspx. Ésta pasará por todos los eventos de su ciclo de vida como si de una página normal se tratara (profundizaré más sobre esto luego), y se devolverá su contenido mezclado con el contenido que hubiera hasta ese momento en la página original. El navegador del usuario seguirá mostrando la URL de la página original y para él será transparente el hecho de que en el servidor lo que se ha ejecutado es otra página diferente.

Si te bajas el ejemplo descargable de código, y abres la página Transfer.aspx verás que tiene el siguiente código en su evento Load:

   1: protected void Page_Load(object sender, EventArgs e)
   2: {
   3:     //In this example I'm using Server.Transfer to execute the P2.aspx file.
   4:     //As long as Transfer doesn't create a real new request and the ASP.NET pipeline is not regenerated, 
   5:     //then I'm able to access the contents although the page is protected.
   6:     Response.Write("This text is generated in Transfer.aspx <br>");
   7:     Server.Transfer("P2.aspx");
   8:     Response.Write("This does not appear in the page as long as Transfer breaks the processing of the current page!");
   9: }

Al ejecutarla verás lo siguiente en el navegador:

Transfer

De observar esta figura podemos extraer varias cosas importantes:

  1. La URL original no ha cambiado, y nada hace pensar al usuario que en el servidor se ha ejecutado otra página en realidad.
  2. En la página final resultante hay mezclado HTML de la primera y de la segunda página, a la que se ha transferido la ejecución.
  3. La última línea de código de la página inicial, justo después del Transfer, no se ha ejecutado.
  4. Estamos viendo el contenido de una página protegida: a pesar de que la página P2.aspx está protegida con el web.config se ha podido ejecutar sin problemas.

Seguridad

Esto último es especialmente importante. El motivo de que se pueda ver el contenido de una página protegida por HTTP como es P2.aspx, es debido a que la ejecución es directa a través del manejador (handler) actual, apropiado para páginas ASPX, pero en ningún caso se trata de una petición nueva y por lo tanto no se recorre de nuevo el ciclo de vida de una petición normal (es decir, no se ejecuta el "pipeline" de una petición nueva). Debido a ello no se realiza de nuevo la autorización en el servidor ya que ese módulo HTTP ya se ha ejecutado para la página original, pero no se ejecuta para esta segunda. Por el mismo motivo cualquier otro tipo de manejador intermedio que hayamos colocado no será procesado tampoco y debemos tenerlo en cuenta. Así, aunque la página P2 en condiciones normales no está accesible a través de una petición HTTP sí que lo está en este caso.

Debo aclarar aquí una cosa que dije antes: aunque no se procesa de nuevo el ciclo de vida de la petición HTTP en el servidor, lo que sí se procesa es todo el ciclo de vida de la propia página, es decir, sus eventos PreInit, Init, Load, etc.... como en el caso de cualquier página normal. Se trata esta de una distinción importante muy a tener en cuenta.

Existe una sobrecarga de este método Transfer que permite especificar una clase derivada de IHttpHandler de modo que hagamos que sea este tipo de manejador en concreto el que procese la petición. Así podríamos crear un manejador que efectúe ciertas tareas, comprobaciones u operaciones y luego enviar al lado cliente lo que consideremos oportuno. Yo no lo he usado nunca ni sé de nadie que lo haya hecho, pero ahí está por si quieres experimentar ;-)

Colecciones y datos de la página original.

Por defecto la página además conserva las colecciones Form o QueryString de la petición original. Por ello, desde la página a la que hemos transferido la ejecución podemos obtener cualquier dato de campos enviado a la página original y trabajar con ellos. Existe una sobrecarga del método en la que se pasa como segundo parámetro un booleano para indicar si se deben borrar o no esas colecciones.

De todos modos lo interesante es poder acceder a otro tipo de información albergada en la página original. Imaginemos el ejemplo 2 de la lista del principio de este texto. Si hemos obtenido una información muy costosa desde alguna fuente y queremos que en la página a la que transferimos ésta se encuentre disponible y pueda ser utilizada para generar los resultados ¿cómo podemos hacer?

Bien, este es un truco poco conocido, pero como ambas páginas comparten el mismo manejador de petición, es posible obtener una referencia a la primera página a través del contexto de la petición actual.

En el código de ejemplo hay una página TransferAccessPrevious.aspx que ilustra cómo hacerlo. Ésta dispone de un miembro público llamado _sharedData que contiene la información que queremos compartir con la página transferida (P3.aspx):

   1: public partial class TransferAccessPrevious : System.Web.UI.Page
   2: {
   3:     //This data will be available in the transferred page
   4:     public string _sharedData = "DATA";
   5:  
   6:     protected void Page_Load(object sender, EventArgs e)
   7:     {
   8:         //In this example I'm using Server.Transfer to execute the P3.aspx file.
   9:         //This file access the _sharedData member using a nice trick :-)
  10:         Server.Transfer("P3.aspx");
  11:     }
  12: }

En esta página P3.aspx, para obtener acceso a _sahredData o cualqueir otro miembro público de la página previa, hacemos lo siguiente:

   1: protected void Page_Load(object sender, EventArgs e)
   2: {
   3:     TransferAccessPrevious pagTransfer = (TransferAccessPrevious)Context.Handler;
   4:     Response.Write(pagTransfer._sharedData);
   5: }

Como vemos lo único que hemos hecho es obtener una referencia a la página original, que es del tipo TransferAccessPrevious, haciendo una conversión explícita desde el manejador actual del contexto de la petición. A partir de ahí es una clase normal y podemos acceder a sus métodos y miembros, por lo que en la página final veremos el contenido de la variable _shareddata, o sea, "DATA":

Transfer2

Es un truco sencillo pero, como digo, poco conocido.

En breve publicaré la tercera parte en la que profundizaré sobre la ejecución de páginas y sus implicaciones.

¿Te ha gustado este post? - Aprende .NET con los cursos on-line tutelados de campusMVP:
·
Preparación del examen 70-515: Desarrollo Web con .NET 4.0 (Tutelado por mi)
· Desarrollo Web con ASP.NET 4.0 Web Forms (Tutelado por mi)
· ASP.NET 4.0 Web Forms desde cero (Tutelado por mi)
· Desarrollo Web con ASP.NET MVC 3
· Silverlight 4.0 - Aplicaciones Ricas para Internet (RIA)
· jQuery paso a paso para programadores ASP.NET
· Visual Studio 2010 desde cero

>> Pedir una cosa y recibir otra

Archivado en:
Comparte este post: