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>\r\n");
  50:         this.Write("<h2>Object moved to <a href=\"" + url + "\">here</a>.</h2>\r\n");
  51:         this.Write("</body></html>\r\n");
  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

💪🏻 ¿Este post te ha ayudado?, ¿has aprendido algo nuevo?
Pues NO te pido que me invites a un café... Te pido algo más fácil y mucho mejor

Escrito por un humano, no por una IA