JASoft.org

El blog de José Manuel Alarcón Aguín. Programación web y mucho más...

MENÚ - JASoft: JM Alarcón

Recibir resultados de una página en otra: Transferencias y Cross-page posting

Hay ocasiones en las que tenemos una página muy poblada de controles que se deben mostrar en pasos sucesivos. Desde una simple recogida de datos que se haya complicado un poco, hasta varios pasos de un asistente, por ejemplo.

Existen muchas maneras de solucionar esta situación y hacer que las páginas estén un poco más despejadas. Por ejemplo, podemos usar un control Wizard nativo de ASP.NET Web Forms. Sin embargo una forma mucho más natural de conseguirlo es recibir los resultados de la primera página en una nueva que los procesa y continúa el flujo de la aplicación. De hecho, en los paradigmas de programación convencionales (o sea, en todo lo que no sea Web Forms, incluyendo ASP.NET MVC pero también PHP, etc...) realmente es como se ha hecho siempre: unas páginas pasan datos a otras para procesarlos.

Transferencia en el lado del servidor

En ASP.NET disponemos de la instrucción Server.Transfer que nos permite derivar la ejecución actual de una página hacia otra, como si se ejecutasen en serie pero sin pasar por el lado cliente como ocurre con Response.Redirect. Además, durante la ejecución de la segunda página tenemos acceso al contexto de la llamada inicial y también a los controles de la página original y sus valores. Esta instrucción está disponible desde siempre en todas las versiones de ASP.NET ya que en realidad venía heredada de ASP 3.0 clásico, donde ya se utilizaba.

El principal inconveniente de la transferencia en el lado servidor es que los usuarios no son conscientes jamás de que este proceso ha tenido lugar, es decir, no ven reflejado en la barra de direcciones del navegador que en realidad se ha procesado otra página.

Si nos fijamos en la página de ejemplo que podrás descargar al final de este post, la página original es "Default.aspx":

PagEjemplo

Al ejecutar una transferencia en el servidor pulsando el correspondiente botón, aunque la página que se ha ejecutado es la que en el ejemplo he llamado "Transfer.aspx" en realidad en el navegador esto no se ve reflejado:

EjemploTransfer

Esto, además de despistar a los desarrolladores más noveles, puede tener inconvenientes dependiendo de cómo esté hecha la página cuando se pulsa el botón de volver en el navegador, por ejemplo.

Otro inconveniente menos frecuente de este método -pero que yo he tenido ya que sufrir en alguna ocasión- es la generación de excepciones de tipo ThreadAbortException debido a que se interrumpe la ejecución de un hilo. Ello es debido a que, internamente, Transfer llama siempre a Response.End (como se observa en la captura siguiente del código fuente), interrumpiendo la ejecución del hilo actual, lo cual puede dar lugar a este tipo de problemas:

ServerTransfer_Codigo
Pulsa para aumentar

Es un comportamiento documentado en la Knowledge Base de Microsoft, y se recomienda sustituir la llamada a transfer por otra a Server.Execute.

El método Transfer tiene un parámetro adicional aparte de la página que se quiere ejecutar que sirve para indicar si queremos que la página a la que transferimos tenga acceso a las colecciones Form y QueryString del objeto Response de la página original:

   1: Server.Transfer("~/MiOtraPagina.aspx", true);

Con esto podremos acceder desde la página transferida a los datos enviados a la página original desde un formulario o a través de la URL.

Cross Page Posting

Con ASP.NET 2.0 se introdujo una nueva forma de hacer la transferencia entre páginas llamada Cross Page Posting. Ésta se parece a la transferencia o la ejecución de páginas pero en realidad lo que hace es enviar la página directamente desde el cliente con un PostBack normal, sólo que en lugar de dirigir éste a la página original lo hace a una nueva página. Es, básicamente, provocar el envío del formulario a una nueva página en lugar de hacerlo a la actual que es lo habitual.

Lo que conseguimos es que se ejecute la página de la manera normal pero enviando los datos a un nuevo Web Form.

Se parece mucho a la transferencia de la página pero tiene dos diferencias fundamentales:

  1. El cambio de flujo se realiza desde el lado cliente, no desde el servidor.
  2. El usuario ve en su navegador la URL de la nueva página: no se queda en la misma página.

Si vemos el resultado de ejecutar el ejemplo adjunto con el botón de hacer "Cross page posting", observamos que la página resultante es la esperada y no la original:

EjemploCrossPage

Implementar Cross page Posting es muy sencillo: basta con asignar en el control que queremos que lo provoque, la propiedad PostbackUrl a la página a la que queremos enviar los datos:

PostBackUrlProp

Al hacerlo con un botón por ejemplo, se modifica el código de lado cliente para el evento "click" del botón HTMl resultante, de modo que envía los datos a la nueva página:

CodigoHTMLCrossPost
Pulsa para aumentar

Una cuestión adicional: aunque los datos se envían finalmente a otra página diferente a la original, el evento de servidor del botón se ejecuta igualmente. Los cambios que se hagan desde dicho evento a los controles de la página original no se verán reflejados al acceder a éstos en la página final, pero sí se pueden ejecutar otro tipo de tareas, como escribir a una base de datos, por ejemplo. Basta con poner un punto de interrupción en Visual Studio para ver que es así.

¿Cómo accedemos a los datos de la página original?

Tenemos diversas formas de acceder a los datos de los controles de la página original, tanto al hacer un Transfer como un Cross Page Posting.

La manera más fácil es obtener una referencia a la página original usando la propiedad PreviousPage de la página actual. Una vez obtenida la referencia es fácil encontrar cualquier control usando el método FindControl:

   1: if (Page.PreviousPage != null)
   2: {
   3:     TextBox txtPrev = (TextBox)Page.PreviousPage.FindControl("TextBox1");
   4:     if (txtPrev != null)
   5:         lblMsg2.Text = txtPrev.Text;
   6: }
   7: else
   8: {
   9:     lblMsg2.Text = "No es Cross page ni transferencia";
  10: }

Como vemos se verifica si hay una página anterior o no, en cuyo caso se busca el control TextBox original y se muestra su contenido en la página actual (todo esto dentro del evento Load de la página).

Sin embargo acceder a los controles usando FindControl es un método muy frágil y propenso a errores. Basta con que el control se cambie de ubicación en la página para introducirlo dentro de un contenedor, por ejemplo, para que ya no se encuentre. Eso por no mencionar la posibilidad de que le cambiemos el ID sin darnos cuenta de esta dependencia y que de repente todo deje de funcionar.

Por ello un método mucho más recomendable es definir una o más propiedades de sólo lectura en la página original para tener acceso a los controles subyacentes. en nuestro ejemplo podemos definir una página NombreAsaludar en la página Default.aspx, de este modo:

   1: //Definimos una propiedad para facilitar la lectura de los controles
   2: public string NombreASaludar {
   3:     get
   4:     {
   5:         return TextBox1.Text;
   6:     }
   7: }

Ahora podemos acceder en la página secundaria a la que hemos transferido o hecho CrossPosting al valor del control usando un código un poco más seguro:

   1: if (Page.PreviousPage != null)
   2: {
   3:     Default pagPrev = (Default)Page.PreviousPage;
   4:     lblMsg2.Text = pagPrev.NombreASaludar;
   5: }
   6: else
   7: {
   8:     lblMsg2.Text = "No es Cross page";
   9: }

Como vemos ahora en lugar de usar FindControl convertimos la PreviousPage al tipo correcto de la página original y ya podemos llamar a su propiedad NombreASaludar directamente.

Sin embargo aún existe una forma mejor de hacerlo y es incluir en la página secundaria una directiva @PreviousPageType, así:

   1: <%@ PreviousPageType VirtualPath="~/Default.aspx" %>

Con esto le estamos indicando a la infraestructura de ASP.NET el tipo de la página original, por lo que la propiedad PreviousPage tendrá ya automáticamente el tipo correcto y nos ahorraremos incluso la conversión:

   1: if (Page.PreviousPage != null)
   2: {
   3:     lblMsg2.Text = this.PreviousPage.NombreASaludar;
   4: }
   5: else
   6: {
   7:     lblMsg2.Text = "No es Cross page";
   8: }

¡Ahora sí que es directo!

¿Cómo se distingue entre una transferencia y Cross Page Postback?

Todo lo anterior funciona tanto en transferencias como en envíos de datos cruzados entre páginas. Sin embargo en ocasiones será necesario que podamos distinguir entre unas y otras. ¿Cómo podemos hacerlo?

Las páginas tienen una propiedad llamada IsCrossPagePostBack que nos permite saber si se ha producido o no este tipo de acción. Esta propiedad será verdadera para la página anterior en el caso de un Cross page postback pero no en el caso de una transferencia, así que basta con poner esto para distinguirlas:

   1: if (Page.PreviousPage != null && Page.PreviousPage.IsCrossPagePostBack)

Fíjate que no hay peligro de que rompa la comparación cuando no es ni una cosa ni la otra ya que el operador lógico "AND" (&&) está cortocircuitado, es decir, si la primera condición no se cumple ya no se comprueba la segunda, así que aunque PreviousPage sea nulo la segunda comparación no provoca una excepción.

Dejo un ejemplo con todas las posibilidades implementadas para que lo puedas estudiar y ver las pequeñas diferencias entre unos casos y otros: CrossPagePostingYTransfer.zip (14,3 KB)

¡Espero que te sea ú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

José Manuel Alarcón José Manuel Alarcón
Fundador de campusMVP.es, el proyecto de referencia en formación on-line para programadores en lengua española. Autor de varios libros y cientos de artículos. Galardonado como MVP de Microsoft desde 2004. Gallego de Vigo, amante de la ciencia y la tecnología, la música y la lectura. Ayudando a la gente en Internet desde 1996.
Descarga GRATIS mi último libro (no técnico): "Tres Monos, Diez Minutos".
Banner

Comentarios (3) -

Spain José Francisco

Buen artículo,
En el escenario que estoy realizando (un poco más complicado que en el ejemplo) no consigo poder acceder a elementos de la página anterior.
El escenario es el siguiente. Hay una master page, todas las páginas utilizan esta master page. En la master page hay un formulario de búsqueda con una caja de texto y algunos checkboxs y un botón, configurado para que haga el Cross Page Posting a una página llamada buscador.aspx.
Mi intención es acceder a esos elementos (caja de texto y checks) para poder armar la consulta para la base de datos. Como comentas en tu artículos se hace un casting a una página, pero en el ejemplo descrito no se sabe desde que página se está haciendo el cross posting pues todas tienen dicho buscador (está en la pagina maestra), por lo tanto no se puede hacer casting, además los elementos estan en la master page.
He configurado algunas propiedades públicas en la master page que acceden a esos elementos, pero no consigo verlos.

Es un ejemplo de cross page posting pero un poco más complicado, ¿se podría hacer?

Responder

Spain José Manuel Alarcón

Hola José Francisco:

Claro que sí. No lo he probado (ahora mismo no tengo VS delante) pero bastaría con hacer un "doble acceso" a esas cajas de búsqueda. Me explico: en tu Master Page creas unas propiades públicas para acceder a los elementos del buscador. A éstas accedes desde las páginas derivadas (pondrás la directiva MasterType en tu página derivada para acceder directamente a las propiedades de la MasterPage).

Desde la página a la que va la búsqueda accedes a la Masterpage usando la propiedad "Master" de la página previa, es decir, si incluyes la directiva PreviousPage como muestro en el artículo, debería bastar con escribir algo como esto:

this.PreviousPage.Master.TuPropiedad

para acceder a cualquiera de ellas.

Prueba y nos cuentas si te ha funcionado.

Saludos!

JM

Responder

Spain José Francisco

Hola José Manuel,
No llega a funcionarme, no se si puede ser porque todas las páginas son hijas de la master page, pero las páginas no heredena de la típica Page de ASP.NET sino de una página Base (BasePage) que se encarga de la localización (en todas las páginas se puede cambiar el idoma de la página)
Esta página base:
using System.Globalization;
using System.Threading;
using System.Web.UI;

namespace Web
{
  
        /// <summary>
        /// BasePage for the common funtionality in all
        /// the web pages of the site.
        /// </summary>
        public class BasePage : Page
        {
            /// <summary>
            /// Default constructor
            /// </summary>
            public BasePage()
            {
                //
                // TODO: Add constructor logic here
                //
            }


            public const string LanguageSpanish = "ctl00$cphLanguageSelector$ucLangSelect$lnkBtnSpanish";
            public const string LanguageEnglish = "ctl00$cphLanguageSelector$ucLangSelect$lnkBtnEnglish";


            /// <summary>
            /// The name of the culture selection dropdown list in the common header.
            /// </summary>
            //public const string LanguageDropDownName = "ctl00$cphHeader$Header1$ddlLanguage";      

            /// <summary>
            /// The name of the PostBack event target field in a posted form.  You can use this to see which
            /// control triggered a PostBack:  Request.Form[PostBackEventTarget] .
            /// </summary>
            public const string PostBackEventTarget = "__EVENTTARGET";

            /// <SUMMARY>
            /// Overriding the InitializeCulture method to set the user selected
            /// option in the current thread. Note that this method is called much
            /// earlier in the Page lifecycle and we don't have access to any controls
            /// in this stage, so have to use Form collection.
            /// </SUMMARY>
            protected override void InitializeCulture()
            {
                ///<remarks><REMARKS>
                ///Check if PostBack occured. Cannot use IsPostBack in this method
                ///as this property is not set yet.
                ///</remarks>
                if (Request[PostBackEventTarget] != null)
                {
                    string controlID = Request[PostBackEventTarget];

                    if (controlID.Equals(LanguageSpanish))
                        SetCulture("es-ES", "es-ES");
                    else if (controlID.Equals(LanguageEnglish))
                        SetCulture("en-GB", "en-GB");
                }


                ///<remarks>
                ///Get the culture from the session if the control is tranferred to a
                ///new page in the same application.
                ///</remarks>
                if (Session["MyUICulture"] != null && Session["MyCulture"] != null)
                {
                    Thread.CurrentThread.CurrentUICulture = (CultureInfo)Session["MyUICulture"];
                    Thread.CurrentThread.CurrentCulture = (CultureInfo)Session["MyCulture"];
                }
                base.InitializeCulture();
            }

            /// <Summary>
            /// Sets the current UICulture and CurrentCulture based on
            /// the arguments
            /// </Summary>
            /// <PARAM name="name"></PARAM>
            /// <PARAM name="locale"></PARAM>
            protected void SetCulture(string name, string locale)
            {
                Thread.CurrentThread.CurrentUICulture = new CultureInfo(name);
                Thread.CurrentThread.CurrentCulture = new CultureInfo(locale);
                ///<remarks>
                ///Saving the current thread's culture set by the User in the Session
                ///so that it can be used across the pages in the current application.
                ///</remarks>
                Session["MyUICulture"] = Thread.CurrentThread.CurrentUICulture;
                Session["MyCulture"] = Thread.CurrentThread.CurrentCulture;
            }
    }
}

Así que creo que por esto no puedo acceder a la página Maestra
sí que funciona si utilizo
string menuSearch_field = ((TextBox)Page.PreviousPage.Master.FindControl("menuSearch_fieldp")).Text;
la otra forma,
pero cuando escribo this.PreviosPage.Master. no salen las propiedades públicas que he declarado en la MasterPage.
He añadido también en la página buscador.aspx la directiva <%@ MasterType VirtualPath="~/Main.Master" %>

Responder

Agregar comentario