Pedir una cosa y recibir otra – Parte IV: Llamadas de servidor que recrean la petición completa

Post original en JASoft.org: http://www.jasoft.org/Blog/post/Pedir-una-cosa-y-recibir-otra-Parte-IV-Llamadas-de-servidor-que-recrean-la-peticion-completa.aspx

Desvio2Hasta ahora con Transfer y Execute hemos visto que podemos ejecutar otras páginas del mismo tipo sin necesidad de redirección desde el lado cliente. Sin embargo un efecto secundario que tiene su uso es que, como no se regenera la petición sino que se ejecutan dentro del contexto de la petición actual, no entran en juego los controles de seguridad y otros eventos del pipeline HTTP para la segunda página. Es por ello por lo que en nuestro ejemplo se ha podido mostrar el contenido de la página «P2.aspx» a pesar de que estaba protegida.

Existe una variante de estos métodos llamada TransferRequest que permite conseguir una petición completa desde el propio servidor, sin pasar por el cliente como ocurría con Redirect.

Este método se utiliza del mismo modo que Transfer pero en este caso la segunda página pasa por todo los estadios de una petición HTTP normal, como si hubiera sido hecha desde el cliente desde el principio.

Así, si pretendemos llamar a P2.aspx con este método (página TransferRequest.aspx del ejemplo descargable), al contrario que con transfer obtendremos un código HTTP de acceso denegado, como si hubiese sido solicitada desde el cliente en primera instancia, ya que se ha ejecutado la autenticación y autorización de la URL nuevamente:

TransferRequest[2]_thumb

Fíjate como en la URL aparece todavía la página original (el usuario no se entera) pero al haber intentado transferir a P2.aspx falla exactamente igual.

Otra cosa más interesante todavía de este método es que como la petición se regenera por completo en realidad no estamos limitados a llamar a recursos del mismo tipo que el anterior, como pasaba con Transfer o Execute. Ahora podemos llamar a páginas ASPX, manejadores ASHX cualquier otro recurso como si lo estuviésemos pidiendo desde el navegador. En concreto podemos llamar a páginas o recursos creados con otras tecnologías. Es decir, que si tenemos PHP instalado podemos llamar a páginas .php o URLs con esta tecnología y obtener sus resultados. El usuario nunca lo sabrá.

En el ejemplo descargable hay una sencillísima página ASP 3.0 clásico (p2.asp) con esta línea:

   1: <%= "Hi from classic ASP!" %>

Si la llamamos desde el navegador veremos que funciona perfectamente bajo IIS Express.

Si ahora la llamamos desde una página ASPX usando el método TransferRequest veremos que se ejecuta y devuelve su resultado:

TransferRequest2[2]_thumb

Como vemos se ejecuta en el servidor la petición a un recurso de ASP clásico, pero en la lado cliente se sigue viendo la página ASPX original. ¿Mola o no?

Este método está disponible desde ASP.NET 2.0, no así en versiones anteriores, y para que funcione debemos usar Internet Information server 7.0 o superior en el modo integrado.

Existe una sobrecarga de ExecuteRequest que nos permite enviar nuestras propias cabeceras a la petición, por lo que sería posible simular desde el servidor una petición hecha con un determinado navegador (sólo con cambiar el User-Agent), o incorporar un determinado «referer», cabecera de seguridad, cookie, etc… Tiene muchas posibilidades.

Reescribir la ruta internamente

Un último método que nos falta por ver y que está relacionado con los anteriores es el que permite reescribir las rutas de las peticiones en el servidor, de forma que aunque se solicita un recurso en realidad por detrás se está llamando a otro.

Así, por ejemplo, el usuario puede solicitar desde el navegador una ruta amigable como esta:

www.myserver.com/products/details/Cheese

y que en realidad por detrás se esté llamando a:

www.myserver.com/internal/reports/viewProduct.aspx?Name=Cheese

que es una ruta interna mucho más fea y menos amigable con los buscadores (y los usuarios).

Existen muchas formas de conseguir esto: desde usar el módulo de reescritura de URLs compatible con mod_rewrite de Apache que tiene IIS 7.x, hasta usar el sistema de enrutado de ASP.NET MVC que ya he explicado aquí. De hecho esta última opción es la mejor en caso de que quieras crear rutas amigables. Pero ¿qué pasa cuando has cambiado de ubicación las páginas de tu sitio y quieres que los que las tengan guardadas puedan seguir accediendo a las mismas y que no se rompan los enlaces?

Pues en ese caso el uso de RewritePath es el más adecuado.

Basta con interceptar el método Application_BeginRequest de Global.asax para buscar rutas que queramos re-escribir en el servidor. En el ejemplo descargable si escribes la ruta «/alias» te envía a la página «P1.aspx»:

   1: protected void Application_BeginRequest(object sender, EventArgs e)

   2: {

   3:     string cPath = HttpContext.Current.Request.Path.ToLower();

   4:     if (cPath.Contains("/alias"))

   5:         Context.RewritePath(cPath.Replace("/alias", "P1.aspx"));

   6: }

Nota: el código es muy «cutre» y sólo lo he puesto a efectos demostrativos. En una aplicación real habría que hacer una búsqueda y sustitución mucho mejores de las cadenas, usando una expresión regular.

El resultado es que escribiendo la carpeta «/alias» en el navegador obtenemos el contenido de P1.aspx, pero para el usuario es transparente:

RewritePath[2]_thumb

En este caso la intercepción se hace arriba en el pipeline de procesamiento de la petición por lo que se ejecutan los módulos correspondientes, incluyendo los encargados de la autenticación y autorización.

Existe una sobrecarga de este método que permite indicar que las rutas virtuales que hubiese en la pagina (para descargar recursos), no se cambien de base. Lo que hace por defecto es cambiarlas de modo que así, si la verdadera página que se utiliza está ubicada en otro nivel de la jerarquía de carpetas del sitio, diferente a la original, se sigan encontrando los recursos (como imágenes, CSS o scripts).

Con este último método he dado un repaso (creo que) muy completo a todas las formas que tiene ASP.NET de servir una página diferente a la que se solicita, sea este hecho explicitado o no. Cada una tiene sus ventajas y sus inconvenientes y es más apropiada que las otras para cada tipo de situación.

¡Espero que te haya resultado útil esta serie!

¡Espero que te resulte útil!

¿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

Pedir una cosa y recibir otra – Parte III: Ejecutar otras páginas

Post original en JASoft.org: http://www.jasoft.org/Blog/post/Pedir-una-cosa-y-recibir-otra-Parte-III-Ejecutar-otras-paginas.aspx

U-TurnEn la entrega anterior de esta serie vimos a fondo cómo funcionaba el método Transfer. Seguimos ahora profundizando en otros métodos relacionados.

El método Transfer por debajo lo único que hace es llamar al método Execute de la misma clase HttpUtility:

Transfer3[2]

Este método es prácticamente idéntico a Transfer, como vemos en este código, por lo que a todos los efectos podemos sustituir las llamadas a Transfer por llamadas a Execute y no perder funcionalidad.

Además, si nos fijamos en el código de Transfer de una figura anterior, llama continuación a Response.End. Esto como ya vimos en la primera parte con Redirect, tiene algunas implicaciones respecto al rendimiento debido a la generación de excepciones ThreadAbortException, por lo que en sitios muy grandes donde cada milésima cuenta puede ser interesante sustituir las llamadas a Transfer por llamadas a Execute con una posterior a CompleteRequest (repasa el primer post para verlo).

Si hacemos lo mismo que antes pero llamando a Execute en lugar de Transfer (ejemplo Execute.aspx de la descarga), esto es lo que obtenemos:

Execute[2]

Lo interesante de Execute, sin embargo, es que dispone de varias sobrecargas, algunas de las cuales ofrecen como segundo parámetro una clase TextWriter. Lo interesante de estas sobrecargas es que en lugar de devolver al cliente los resultados de la ejecución de la página, obtenemos todo el HTML dentro de esta clase especializada, para hacer con él lo que queramos y manipularlo antes de enviarlo al cliente.

Por ejemplo, en la descarga he incluido una página llamada ExecuteWriter.aspx que hace lo siguiente:

   1: protected void Page_Load(object sender, EventArgs e)

   2: {

   3:     //In this example I'm using Server.Execute to redirect the request to the P2.aspx file.

   4:     //In this case I'm using the overload that takes a TextWriter and process the resulting data later.

   5:     StringWriter res = new StringWriter();

   6:     Server.Execute("P2.aspx", res);

   7:     Response.ContentType = "text/plain";

   8:     Response.ContentEncoding = System.Text.Encoding.UTF8;

   9:     Response.Write(res.ToString());

  10: }

Primero se crea un StringWriter (un tipo especial de TextWriter) que se le pasa al método Execute como segundo parámetro para ejecutar la página P2.aspx.

El resultado de la ejecución, en lugar de enviarse al cliente se recoge dentro del StringWriter, en la variable res. Ahora podemos hacer lo que queramos con él. En este ejemplo lo que hago es enviarlo al cliente en formato texto, con lo que en la práctica lo que consigo es ver el código fuente resultante de la ejecución de ambas páginas:

Execute2[2]

En este caso se trata de un ejemplo un poco «chusquero» ya que al dejar que se ejecuten las dos páginas vemos que el código fuente resultante contiene dos cabeceras, dos cuerpos, dos formularios, etc… pero puede tener otro tipo de aplicaciones interesantes.

En el próximo post veremos cómo crear peticiones internas en el servidor pero que realicen el ciclo completo de vida de la petición.

¡Espero que te resulte útil!

¿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

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

Pedir una cosa y recibir otra – Parte I: Redirect y RedirectPermanent

Post original en JASoft.org: http://www.jasoft.org/Blog/post/Pedir-una-cosa-y-recibir-otra-Parte-I-Redirect-y-RedirectPermanent.aspx

Desvio3En ASP.NET existen muchos métodos para conseguir que cuando un usuario solicita una página en el servidor, acabe obteniendo el resultado de ejecutar otra página o recurso diferente.

¿Para qué querríamos hacer algo así? Por múltiples motivos, y en realidad es algo que se usa constantemente:

  • Redirigir a los usuarios a una página nueva
  • Ejecutar una funcionalidad que reside en otra página o recurso
  • Ofrecer al usuario rutas amigables cuando por debajo usamos una estructura de páginas más compleja
  • Hacer que las URLs antiguas de una aplicación sigan funcionando aunque hayamos cambiado la estructura del sitio
  • ….

El framework de ASP.NET ofrece muchos métodos diferentes de cambiar el recurso utilizado para responder a una petición, y cada uno tiene sus aplicaciones específicas. Así que ¿Cuál utilizar?

Vamos a verlos todos para resolver las dudas y conocer bien sus diferencias y mejores aplicaciones.

El ejemplo

Para ilustrar en la práctica todos estos métodos he creado una aplicación de ejemplo con Visual Studio 2010. Para poder utilizarla debes tener instalado el Service Pack 1 de Visual Studio y también Internet Information Server Express, ya que algunos de los métodos necesitan estar ejecutándose en el modo integrado de IIS 7.x.

Puedes descargarla aquí (24,7 KB).

NOTA: El ejemplo está con los (pocos) textos que tiene en inglés ya que así no tengo que hacerlo dos veces si lo quiero usar en mi blog en inglés.

En el proyecto tengo dos páginas base a las que redirigiré peticiones usando los diferentes métodos: P1.aspx y P2.aspx.

La página P1 es una página normal y corriente con una cabecera que pone su nombre y dice que no está protegida.

La página P2 es también una página normal pero en el archivo de configuración web.config he bloqueado el acceso a la misma para todos los usuarios:

   1: <location path="P2.aspx">

   2:     <system.web>

   3:         <authorization>

   4:             <deny users="*"/>

   5:         </authorization>

   6:     </system.web>

   7: </location>

De este modo cualquier intento de acceso a la misma resultará en un error de Acceso Denegado. Enseguida veremos para qué he hecho esto.

El caso más simple: crear redirecciones

El método más sencillo y primitivo de realizar un cambio en el recurso procesado es por medio de una redirección.

Para ello ASP.NET ofrece el método Redirect de la clase HttpResponse. Este método es tan antiguo que ya estaba presente en las clásicas páginas ASP desde su primera versión.

Se usa así:

   1: Response.Redirect("P1.aspx");

Es decir, sólo hay que pasarle una URL absoluta o relativa, y lo que hace es envíar al navegador una cabecera de redirección HTTP indicando cuál es la nueva página que se debe solicitar. Esto genera en realidad dos peticiones de recursos desde el lado cliente (el navegador), aunque para el usuario pase inadvertido este hecho.

Así, si llamamos a Redirect.aspx en el ejemplo descargable, aparecerá finalmente en nuestra barra de direcciones la página «P1.aspx» a la que la hemos redirigido.

Redirect

Como vemos en la captura, en la parte superior, aunque hemos solicitado Redirect.aspx, el navegador termina en P1.aspx, que es a donde hemos redirigido. Si nos fijamos en la parte inferior de la figura (una captura hecha con Fiddler), la petición de la página Redirect.aspx devuelve un código HTTP 302 (el objeto se ha movido) invitando a realizar la petición a la nueva ubicación de éste, que es la página P1.aspx.

No tiene misterio alguno, y en realidad lo que ocurre es que se lanzan dos peticiones, por lo que es más lento y menos eficiente que el resto de métodos que vamos a ver.

Estas redirecciones se usan muy a menudo para enviar a páginas de resultados tras alguna operación o para decidir qué versión de una misma página mostrar en función de quién sea el usuario o de qué tipo de navegador esté usando, por ejemplo.

Redirecciones permanentes

Existe una variante de este método llamada RedirectPermanent, que en lugar de enviar un código HTTP 302 utiliza un código HTTP 301, que es muy similar pero indica que el objeto se ha movido de ubicación permanentemente.

Esto es útil cuando debemos tener en cuenta a las arañas de los buscadores en páginas públicas, ya que las redirecciones permanentes son muy importantes dse cara a la optimización para buscadores (SEO). Con ese tipo de redirecciones logramos que éstos indexen las páginas correctas cuando se han movido y se deshagan de las páginas anteriores. Así, en las búsquedas aparecerán con el tiempo los resultados correctos, es decir, las páginas en las ubicaciones correctas.

Este método está disponible solamente desde la versión 4.0 de ASP.NET.

Cancelar la petición actual y rendimiento

Por defecto, cuando llamamos a Redirect o a RedirectPermanent, tras enviar la cabecera de redirección al navegador, se lama a Response.End para terminar de inmediato la petición actual en el servidor.

Este método viene heredado de los tiempos de ASP clásico, pòr lo que para simular al máximo su comportamiento lo que intenta hacer es terminar directamente la ejecución del hilo actual llamando a Thread.CurrentThread.Abort. Esto provoca que se lance una excepción de tipo ThreadAbortException que aunque es transparente para nosotros, no es una situación ideal ya que se termina abruptamente la ejecución, y además puede influir (mínimamente) en el rendimiento del sitio. Si no es capaz de cortar la ejecución directamente lo que hace es enviar al cliente lo que todavía estuviese pendiente de enviar del contenido del recurso, y esto  lo hace de manera síncrona por lo que influye también (aunque mínimamente) en el rendimiento. Sólo en algunos casos logra llegar a terminar correctamente la petición, que es llamando al método CompleteRequest.

Podemos ver el código interno del método End para entenderlo mejor:

   1: public void End()

   2: {

   3:     if (this._context.IsInCancellablePeriod)

   4:     {

   5:         InternalSecurityPermissions.ControlThread.Assert();

   6:         Thread.CurrentThread.Abort(new HttpApplication.CancelModuleException(false));

   7:     }

   8:     else if (!this._flushing)

   9:     {

  10:         this.Flush();

  11:         this._ended = true;

  12:         if (this._context.ApplicationInstance != null)

  13:         {

  14:             this._context.ApplicationInstance.CompleteRequest();

  15:         }

  16:     }

  17: }

Aunque generalmente no tendremos que preocuparnos demasiado por este detalle técnico, en sitios grandes con muchas peticiones cada milisegundo que arañemos cuenta por lo que podría ser interesante cambiar este comportamiento. Además, siempre es más recomendable acabar las peticiones de la manera apropiada y no cancelando la ejecución de un hilo.

Así, para tener más control, ambos métodos ofrecen una sobrecarga con un parámetro booleano opcional que sirve para indicar si se debe cancelar la ejecución actual o no. Si le pasamos un valor «false» entonces la ejecución del hilo no se interrumpe abruptamente y podemos llamar a mano al método Completerequest que fuerza un término de la ejecución por los cauces apropiados (lo puedes ver en la página RedirectCompleteRequest.aspx):

   1: Response.Redirect("P1.aspx", false);

   2: Context.ApplicationInstance.CompleteRequest();

Este tipo de código es más recomendable, si bien el anterior tampoco presenta problemas en una aplicación normal.

Ambos métodos, Redirect y RedirectPermanent y sus dos sobrecargas en realidad hacen uso por debajo de un método interno llamado Redirect, que toma tres parámetros: la URL, si se debe cancelar la petición o no, y si se debe hacer permanente o no. El código de este método, por si tienes curiosidad, es el siguiente:

   1: internal void Redirect(string url, bool endResponse, bool permanent)

   2: {

   3:     if (url == null)

   4:     {

   5:         throw new ArgumentNullException("url");

   6:     }

   7:     if (url.IndexOf('n') >= 0)

   8:     {

   9:         throw new ArgumentException(SR.GetString("Cannot_redirect_to_newline"));

  10:     }

  11:     if (this._headersWritten)

  12:     {

  13:         throw new HttpException(SR.GetString("Cannot_redirect_after_headers_sent"));

  14:     }

  15:     Page page = this._context.Handler as Page;

  16:     if ((page != null) && page.IsCallback)

  17:     {

  18:         throw new ApplicationException(SR.GetString("Redirect_not_allowed_in_callback"));

  19:     }

  20:     url = this.ApplyRedirectQueryStringIfRequired(url);

  21:     url = this.ApplyAppPathModifier(url);

  22:     url = this.ConvertToFullyQualifiedRedirectUrlIfRequired(url);

  23:     url = this.UrlEncodeRedirect(url);

  24:     this.Clear();

  25:     if (((page != null) && page.IsPostBack) && (page.SmartNavigation && (this.Request["__smartNavPostBack"] == "true")))

  26:     {

  27:         this.Write("<BODY><ASP_SMARTNAV_RDIR url="");

  28:         this.Write(HttpUtility.HtmlEncode(url));

  29:         this.Write(""></ASP_SMARTNAV_RDIR>");

  30:         this.Write("</BODY>");

  31:     }

  32:     else

  33:     {

  34:         this.StatusCode = permanent ? 0x12d : 0x12e;

  35:         this.RedirectLocation = url;

  36:         if ((((url.IndexOf(":", StringComparison.Ordinal) == -1) || 

  37:            url.StartsWith("http:", StringComparison.OrdinalIgnoreCase)) ||

  38:            (url.StartsWith("https:", StringComparison.OrdinalIgnoreCase) || 

  39:            url.StartsWith("ftp:", StringComparison.OrdinalIgnoreCase))) || 

  40:            (url.StartsWith("file:", StringComparison.OrdinalIgnoreCase) || 

  41:            url.StartsWith("news:", StringComparison.OrdinalIgnoreCase)))

  42:         {

  43:             url = HttpUtility.HtmlAttributeEncode(url);

  44:         }

  45:         else

  46:         {

  47:             url = HttpUtility.HtmlAttributeEncode(HttpUtility.UrlEncode(url));

  48:         }

  49:         this.Write("<html><head><title>Object moved</title></head><body>rn");

  50:         this.Write("<h2>Object moved to <a href="" + url + "">here</a>.</h2>rn");

  51:         this.Write("</body></html>rn");

  52:     }

  53:     this._isRequestBeingRedirected = true;

  54:     EventHandler redirecting = Redirecting;

  55:     if (redirecting != null)

  56:     {

  57:         redirecting(this, EventArgs.Empty);

  58:     }

  59:     if (endResponse)

  60:     {

  61:         this.End();

  62:     }

  63: }

¡Espero que te resulte útil!

¿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