Skip to content

2. Una introducción rápida a ASP.NET

En este apartado, nuestro objetivo es presentar, mediante algunos ejemplos, los conceptos de ASP.NET que resultarán útiles más adelante en este documento. Esta introducción no aborda las complejidades de la comunicación cliente/servidor en una aplicación web. Para ello, puede consultar:

Esta introducción está dirigida a quienes desean iniciarse rápidamente, aceptando —al menos inicialmente— que algunos puntos potencialmente importantes puedan quedar sin explicar. El resto de este documento explorará estos puntos con mayor profundidad. Quienes estén familiarizados con ASP.NET pueden pasar directamente a la sección 3.

2.1. Un proyecto de ejemplo

2.1.1. Creación del proyecto

  • En [1], cree un nuevo proyecto con Visual Web Developer
  • En [2], seleccione un proyecto web de Visual C#
  • En [3], especifique que desea crear una aplicación web ASP.NET
  • En [4], asigne un nombre a la aplicación. Se creará una carpeta para el proyecto con este nombre.
  • En [5], especifique la carpeta principal de la carpeta del proyecto [4]
  • En [6], el proyecto creado
  • [Default.aspx] es una página web creada de forma predeterminada. Contiene etiquetas HTML y etiquetas ASP.NET
  • [Default.aspx.cs] contiene el código para gestionar los eventos desencadenados por el usuario en la página [Default.aspx] que se muestra en el navegador
  • [Default.aspx.designer.cs] contiene la lista de componentes ASP.NET para la página [Default.aspx]. Cada componente ASP.NET colocado en la página [Default.aspx] genera una declaración para ese componente en [Default.aspx.designer.cs].
  • [Web.config] es el archivo de configuración del proyecto ASP.NET.
  • [Referencias] es la lista de DLL utilizadas por el proyecto web. Estas DLL son bibliotecas de clases que el proyecto necesita utilizar. En [7] se encuentra la lista de DLL incluidas por defecto en las referencias del proyecto. La mayoría de ellas son innecesarias. Si el proyecto necesita utilizar una DLL que no aparece en [7], se puede añadir a través de [8].

2.1.2. La página [Default.aspx]

Si ejecutas el proyecto con [Ctrl-F5], se muestra la página [Default.aspx] en un navegador:

  • en [1], la URL del proyecto web. Visual Web Developer tiene un servidor web integrado que se inicia al ejecutar un proyecto. Escucha en un puerto aleatorio, en este caso el 1490. El puerto de escucha suele ser el 80. En [1], no se solicita ninguna página. En este caso, se muestra la página [Default.aspx], de ahí su nombre como página predeterminada.
  • En [2], la página [Default.aspx] está vacía.
  • En Visual Web Developer, la página [Default.aspx] [3] se puede crear visualmente (pestaña [Diseño]) o mediante etiquetas (pestaña [Código fuente])
  • En [4], la página [Default.aspx] en modo [Diseño]. Se crea arrastrando y soltando componentes que se encuentran en la caja de herramientas [5].

El modo [Código fuente] [6] permite acceder al código fuente de la página:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
  </div>
  </form>
</body>
</html>
  • La línea 1 es una directiva de ASP.NET que enumera ciertas propiedades de la página
    • La directiva Page se aplica a una página web. Existen otras directivas, como Application, WebService, etc., que se aplican a otros objetos de ASP.NET
    • El atributo CodeBehind especifica el archivo que gestiona los eventos de la página
    • El atributo Language especifica el lenguaje .NET utilizado por el archivo CodeBehind
    • El atributo Inherits especifica el nombre de la clase definida en el archivo CodeBehind
    • El atributo AutoEventWireUp="true" indica que la vinculación entre un evento en [Default.aspx] y su controlador en [Default.aspx.cs] se realiza mediante el nombre del evento. Por lo tanto, el evento Load de la página [Default.aspx] será gestionado por el método Page_Load de la clase Intro._Default definida por el atributo Inherits.
  • Las líneas 4–14 describen la página [Default.aspx] utilizando etiquetas:
    • Etiquetas HTML clásicas, como las etiquetas <body> o <div>
    • Etiquetas ASP.NET. Son las etiquetas con el atributo runat="server". Las etiquetas ASP.NET son procesadas por el servidor web antes de que la página se envíe al cliente. Se convierten en etiquetas HTML. Por lo tanto, el navegador del cliente recibe una página HTML estándar en la que ya no hay etiquetas ASP.NET.

La página [Default.aspx] se puede modificar directamente desde su código fuente. A veces, esto resulta más sencillo que utilizar el modo [Diseño]. Modificamos el código fuente de la siguiente manera:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Introduction ASP.NET</title>
</head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form id="form1" runat="server">
  <div>
  </div>
  </form>
</body>
</html>

En la línea 6, le damos un título a la página utilizando la etiqueta HTML <title>. En la línea 9, insertamos texto en el cuerpo (<body>) de la página. Si ejecutamos el proyecto (Ctrl-F5), obtenemos el siguiente resultado en el navegador:

 

2.1.3. Los archivos [Default.aspx.designer.cs] y [Default.aspx.cs]

El archivo [Default.aspx.designer.cs] declara los componentes de la página [Default.aspx]:


//------------------------------------------------------------------------------
// <auto-generated>
//      This code was generated by a tool.
//      Runtime version :2.0.50727.3603
//
//      Changes made to this file may cause incorrect behavior and will be lost if
//      the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
 
namespace Intro {
    
 
    public partial class _Default {
 
        /// <summary>
        /// Control form1.
        /// </summary>
        /// <remarks>
        /// Automatically generated field.
        /// To modify, move the field declaration from the designer file to the code-behind file.
        /// </remarks>
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
    }
}

Este archivo contiene la lista de componentes ASP.NET de la página [Default.aspx] que tienen un identificador. Se corresponden con las etiquetas de [Default.aspx] que tienen el atributo runat="server" y el atributo id. Por lo tanto, el componente de la línea 23 anterior se corresponde con la etiqueta


  <form id="form1" runat="server">

de [Default.aspx].

El desarrollador rara vez interactúa con el archivo [Default.aspx.designer.cs]. Sin embargo, este archivo resulta útil para determinar la clase de un componente específico. Como se muestra a continuación, el componente form1 es de tipo HtmlForm. El desarrollador puede entonces explorar esta clase para conocer sus propiedades y métodos. Los componentes de la página [Default.aspx] son utilizados por la clase del archivo [Default.aspx.cs]:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
 
namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
 
    }
  }
}

Obsérvese que la clase definida en los archivos [Default.aspx.cs] y [Default.aspx.designer.cs] es la misma (línea 10): Intro._Default. Es la palabra clave partial la que permite extender una declaración de clase a través de varios archivos, en este caso dos.

En la línea 10 anterior, vemos que la clase [_Default] extiende la clase [Page] y hereda sus eventos. Uno de ellos es el evento Load, que se produce cuando el servidor web carga la página. En la línea 12, el método Page_Load gestiona el evento Load de la página. Aquí es donde, por lo general, se inicializa la página antes de mostrarse en el navegador del cliente. En este caso, el método Page_Load no hace nada.

La clase asociada a una página web —en este caso, la clase Intro._Default— se crea al inicio de la solicitud del cliente y se elimina una vez que se ha enviado la respuesta al cliente. Por lo tanto, no puede utilizarse para almacenar información entre solicitudes. Para ello, debes recurrir al concepto de sesión de usuario.

2.2. Eventos de una página web ASP.NET

Estamos creando la siguiente página [Default.aspx]:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Introduction ASP.NET</title>
</head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form id="form1" runat="server">
  <div>
    <table>
      <tr>
        <td>
          Nom</td>
        <td>
          <asp:TextBox ID="TextBoxNom" runat="server"></asp:TextBox>
        </td>
        <td>
          &nbsp;</td>
      </tr>
      <tr>
        <td>
          Age</td>
        <td>
          <asp:TextBox ID="TextBoxAge" runat="server"></asp:TextBox>
        </td>
        <td>
          &nbsp;</td>
      </tr>
    </table>
  </div>
  <asp:Button ID="ButtonValider" runat="server" Text="Valider" />
  <hr />
  <p>
    Evénements traités par le serveur</p>
  <p>
    <asp:ListBox ID="ListBoxEvts" runat="server"></asp:ListBox>
  </p>
  </form>
</body>
</html>

La vista [Diseño] de la página es la siguiente:

 

El archivo [Default.aspx.designer.cs] es el siguiente:


namespace Intro {
    public partial class _Default {
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
        protected global::System.Web.UI.WebControls.TextBox TextBoxNom;
        protected global::System.Web.UI.WebControls.TextBox TextBoxAge;
        protected global::System.Web.UI.WebControls.Button ButtonValider;
        protected global::System.Web.UI.WebControls.ListBox ListBoxEvts;
    }
}

Esto contiene todos los componentes ASP.NET de la página [Default.aspx] que tienen un identificador.

Modificamos el archivo [Default.aspx.cs] de la siguiente manera:


using System;
 
namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Init(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Init", DateTime.Now.ToString("hh:mm:ss")));
    }
 
    protected void Page_Load(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
    }
 
    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
    }
  }
}

La clase [_Default] (línea 5) gestiona tres eventos:

  • el evento Init (línea 7), que se produce cuando se ha inicializado la página
  • el evento Load (línea 13), que se produce cuando el servidor web ha cargado la página. El evento Init se produce antes que el evento Load.
  • el evento Click del botón ButtonValider (línea 19), que se produce cuando el usuario hace clic en el botón [Validate]

Para gestionar cada uno de estos tres eventos, hay que añadir un mensaje al componente Listbox denominado ListBoxEvts. Este mensaje muestra la hora y el nombre del evento. Cada mensaje se coloca al principio de la lista. Por lo tanto, los mensajes que se encuentran al principio de la lista son los más recientes.

Cuando se ejecuta el proyecto, se muestra la siguiente página:

Podemos ver en [1] que los eventos Page_Init y Page_Load se produjeron en ese orden. Recuerda que el evento más reciente aparece en la parte superior de la lista. Cuando el navegador solicita la página [Default.aspx] directamente a través de su URL [2], lo hace utilizando un comando HTTP (Protocolo de Transferencia de Hipertexto) llamado GET. Una vez que la página se ha cargado en el navegador, el usuario activará eventos en la página. Por ejemplo, hará clic en el botón [Submit] [3]. Los eventos desencadenados por el usuario una vez que la página se ha cargado en el navegador inician una solicitud a la página [Default.aspx], pero esta vez utilizando un comando HTTP llamado POST. En resumen:

  • la carga inicial de una página P en un navegador se realiza mediante una operación HTTP GET
  • los eventos que se producen posteriormente en la página generan una nueva solicitud a la misma página P cada vez, pero esta vez utilizando un comando HTTP POST. Una página P puede determinar si se ha solicitado mediante un comando GET o POST, lo que le permite comportarse de forma diferente si es necesario, lo cual suele ser el caso.

Solicitud inicial de una página ASPX: GET

  • En [1], el navegador solicita la página ASPX mediante una solicitud HTTP GET sin parámetros.
  • En [2], el servidor web devuelve el código HTML de la página ASPX solicitada.

Gestión de un evento desencadenado en la página mostrada por el navegador: POST

  • En [1], cuando se produce un evento en la página HTML, el navegador solicita la página ASPX que se había recuperado previamente mediante una operación GET, esta vez utilizando una solicitud HTTP POST acompañada de parámetros. Estos parámetros son los valores de los componentes situados dentro de la etiqueta <form> de la página HTML mostrada por el navegador. Estos valores se denominan valores enviados por el cliente. La página ASPX los utilizará para procesar la solicitud del cliente.
  • En [2], el servidor web devuelve la salida HTML de la página ASPX solicitada inicialmente mediante POST, o de otra página si se ha producido una transferencia o redireccionamiento de página.

Volvamos a nuestra página de ejemplo:

  • En [2], la página se recuperó mediante una solicitud GET.
  • En [1], vemos los dos eventos que se produjeron durante esta solicitud GET

Si, en el ejemplo anterior, el usuario hace clic en el botón [Validate] [3], se solicitará la página [Default.aspx] mediante una solicitud POST. Esta solicitud POST irá acompañada de parámetros que son los valores de todos los componentes incluidos en la etiqueta <form> de la página [Default.aspx]: los dos cuadros de texto [TextBoxName, TextBoxAge], el botón [SubmitButton] y la lista [EventListBox]. Los valores enviados para los componentes son los siguientes:

  • TextBox: el valor introducido
  • Botón: el texto del botón, en este caso «Validate»
  • ListBox: el texto del mensaje seleccionado en el ListBox

En respuesta al POST, obtenemos la página [4]. Se trata de nuevo de la página [Default.aspx]. Este es un comportamiento normal, a menos que se produzca una transferencia de página o una redirección por parte de los controladores de eventos de la página. Podemos ver que se han producido dos nuevos eventos:

  • el evento Page_Load, que se produjo al cargarse la página
  • el evento ButtonValider_Click, que se producía al hacer clic en el botón [Validate]

Tenga en cuenta que:

  • el evento Page_Init no se produjo durante la operación HTTP POST, mientras que sí se produjo durante la operación HTTP GET
  • el evento Page_Load se produce siempre, tanto si se trata de un GET como de un POST. Es en este método donde generalmente necesitamos saber si estamos ante un GET o un POST.
  • Tras el POST, la página [Default.aspx] se envió de vuelta al cliente con los cambios realizados por los controladores de eventos. Esto ocurre siempre. Una vez que se han procesado los eventos de una página P, esa misma página P se envía de vuelta al cliente. Hay dos formas de romper esta regla. El último controlador de eventos ejecutado puede
    • transferir el flujo de ejecución a otra página P2.
    • redirigir el navegador del cliente a otra página P2.

En ambos casos, es la página P2 la que se devuelve al navegador. Los dos métodos presentan diferencias que comentaremos más adelante.

  • El evento ButtonValider_Click se produjo después del evento Page_Load. Por lo tanto, es este controlador el que puede decidir si transferir o redirigir a una página P2.
  • La lista de eventos [4] conservó los dos eventos mostrados durante la carga GET inicial de la página [Default.aspx]. Esto resulta sorprendente, dado que la página [Default.aspx] se recreó durante el POST. Deberíamos ver la página [Default.aspx] con sus valores de diseño y, por lo tanto, un ListBox vacío. La ejecución de los controladores Page_Load y ButtonValider_Click debería entonces rellenarlo con dos mensajes. Sin embargo, hay cuatro. Esto se explica por el mecanismo VIEWSTATE. Durante la solicitud GET inicial, el servidor web envía la página [Default.aspx] con una etiqueta HTML <input type="hidden" ...> denominada campo oculto (línea 10 a continuación).
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>
        Introduction ASP.NET
</title></head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form name="form1" method="post" action="default.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTMzMTEyNDMxMg9kFgICAw9kFgICBw8QZBAVAhMwNjoxNjozNjogUGFnZV9Mb2FkEzA2OjE2OjM2OiBQYWdlX0luaXQVAhMwNjoxNjozNjogUGFnZV9Mb2FkEzA2OjE2OjM2OiBQYWdlX0luaXQUKwMCZ2dkZGRW1AnTL8f/q7h2MXBLxctKD1UKfg==" />
</div>
..............................

En el campo de ID «__VIEWSTATE», el servidor web codifica los valores de todos los componentes de la página. Lo hace tanto para la solicitud GET inicial como para las solicitudes POST posteriores. Cuando se realiza una solicitud POST a la página P:

  • el navegador solicita la página P enviando los valores de todos los componentes dentro de la etiqueta <form> en su solicitud. Arriba, podemos ver que el componente «__VIEWSTATE» se encuentra dentro de la etiqueta <form>. Por lo tanto, su valor se envía al servidor durante una solicitud POST.
  • La página P se instancia e inicializa con los valores de su constructor
  • El componente «__VIEWSTATE» se utiliza para restaurar los valores que tenían los componentes cuando se envió la página P anteriormente. Así es como, por ejemplo, la lista de eventos [4] recupera los dos primeros mensajes que tenía cuando se envió en respuesta a la solicitud GET inicial del navegador.
  • A continuación, los componentes de la página P adoptan los valores enviados por el navegador. En este momento, el formulario de la página P se encuentra en el estado en el que el usuario lo envió.
  • Se procesa el evento Page_Load. Aquí, añade un mensaje a la lista de eventos [4].
  • Se gestiona el evento que desencadenó la solicitud POST. Aquí, ButtonValider_Click añade un mensaje a la lista de eventos [4].
  • Se devuelve la página P. Los componentes tienen los siguientes valores:
    • o bien el valor enviado, es decir, el valor que el componente tenía en el formulario cuando se envió al servidor
    • o un valor proporcionado por uno de los controladores de eventos.

En nuestro ejemplo,

  • los dos componentes TextBox conservarán sus valores enviados porque los controladores de eventos no los modifican
  • la lista de eventos [4] recupera su valor enviado, es decir, todos los eventos ya enumerados, más dos nuevos eventos creados por los métodos Page_Load y ButtonValider_Click.

El mecanismo VIEWSTATE se puede habilitar o deshabilitar a nivel de componente. Vamos a deshabilitarlo para el componente [ListBoxEvts]:

  • En [1], el VIEWSTATE del componente [ListBoxEvts] está desactivado. El del TextBox [2] está activado por defecto.
  • En [3], los dos eventos devueltos tras el GET inicial
  • En [4], hemos rellenado el formulario y hemos hecho clic en el botón [Validar]. Se enviará una solicitud POST a la página [Default.aspx].
  • En [6], el resultado devuelto tras hacer clic en el botón [Validar]
  • El mecanismo de VIEWSTATE habilitado explica por qué los cuadros de texto [7] conservaron los valores enviados en [4]
  • El mecanismo VIEWSTATE desactivado explica por qué el componente [ListBoxEvts] [8] no conservó su contenido [5].

2.3. Gestión de los valores enviados

Aquí nos centraremos en los valores enviados por los dos cuadros de texto cuando el usuario hace clic en el botón [Validar]. La página [Default.aspx] en modo [Diseño] cambia de la siguiente manera:

El código fuente del elemento añadido en [1] es el siguiente:


  <p>
    Eléments postés au serveur :
    <asp:Label ID="LabelPost" runat="server"></asp:Label>
</p>

Utilizaremos el componente [LabelPost] para mostrar los valores introducidos en los dos cuadros de texto [2]. El código del controlador de eventos [Default.aspx.cs] cambia de la siguiente manera:


using System;
 
namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Init(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Init", DateTime.Now.ToString("hh:mm:ss")));
    }
 
    protected void Page_Load(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // display name and age
      LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
    }
  }
}

En la línea 24, actualizamos el componente LabelPost:

  • LabelPost es de tipo [System.Web.UI.WebControls.Label] (véase Default.aspx.designer.cs). Su propiedad Text representa el texto que muestra el componente.
  • TextBoxName y TextBoxAge son de tipo [System.Web.UI.WebControls.TextBox]. La propiedad Text de un componente TextBox es el texto que se muestra en el campo de entrada.
  • El método Trim() elimina cualquier espacio que pueda preceder o seguir a una cadena

Como se ha explicado anteriormente, cuando se ejecuta el método ButtonValider_Click, los componentes de la página tienen los valores que tenían cuando el usuario envió la página. Por lo tanto, las propiedades Text de los dos TextBox contienen el texto introducido por el usuario en el navegador.

A continuación se muestra un ejemplo:

  • en [1], los valores enviados
  • en [2], la respuesta del servidor.
  • en [3], los TextBoxes han recuperado sus valores enviados a través del mecanismo VIEWSTATE activado
  • en [4], los mensajes del componente ListBoxEvts proceden de los métodos Page_Init, Page_Load y ButtonValider_Click, así como de un VIEWSTATE desactivado
  • en [5], el componente LabelPost obtuvo su valor a través del método ButtonValider_Click. Hemos recuperado con éxito los dos valores introducidos por el usuario en los dos TextBoxes [1].

Como se muestra arriba, el valor enviado para la edad es la cadena «yy», un valor no válido. Añadiremos a la página unos componentes llamados validadores. Se utilizan para verificar la validez de los datos enviados. Esta validez se puede verificar en dos lugares:

  • en el lado del cliente. Una opción de configuración del validador permite elegir si se realizan o no las comprobaciones en el navegador. En ese caso, las realiza el código JavaScript incrustado en la página HTML. Cuando el usuario envía los valores introducidos en el formulario, estos son comprobados primero por el código JavaScript. Si alguna de las comprobaciones falla, no se realiza el envío. Esto evita un viaje de ida y vuelta al servidor, lo que hace que la página sea más receptiva.
  • en el servidor. Mientras que la validación del lado del cliente puede ser opcional, la validación del lado del servidor es obligatoria independientemente de si se ha realizado la validación del lado del cliente. Esto se debe a que, cuando una página recibe los valores enviados, no tiene forma de saber si han sido validados por el cliente antes de ser enviados. Por lo tanto, en el lado del servidor, el desarrollador debe verificar siempre la validez de los datos enviados.

La página [Default.aspx] evoluciona de la siguiente manera:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Introduction ASP.NET</title>
</head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form id="form1" runat="server">
  <div>
    <table>
      <tr>
        <td>
          Nom</td>
        <td>
          <asp:TextBox ID="TextBoxNom" runat="server"></asp:TextBox>
        </td>
        <td>
          <asp:RequiredFieldValidator ID="RequiredFieldValidatorNom" runat="server" 
            ControlToValidate="TextBoxNom" Display="Dynamic" 
            ErrorMessage="Donnée obligatoire !"></asp:RequiredFieldValidator>
        </td>
      </tr>
      <tr>
        <td>
          Age</td>
        <td>
          <asp:TextBox ID="TextBoxAge" runat="server"></asp:TextBox>
        </td>
        <td>
          <asp:RequiredFieldValidator ID="RequiredFieldValidatorAge" runat="server" 
            ControlToValidate="TextBoxAge" Display="Dynamic" 
            ErrorMessage="Donnée obligatoire !"></asp:RequiredFieldValidator>
          <asp:RangeValidator ID="RangeValidatorAge" runat="server" 
            ControlToValidate="TextBoxAge" Display="Dynamic" 
            ErrorMessage="Tapez un nombre entre 1 et 150 !" MaximumValue="150" 
            MinimumValue="1" Type="Integer"></asp:RangeValidator>
        </td>
      </tr>
    </table>
  </div>
  <asp:Button ID="ButtonValider" runat="server" onclick="ButtonValider_Click" 
    Text="Valider" CausesValidation="False"/>
  <hr />
  <p>
    Evénements traités par le serveur</p>
  <p>
    <asp:ListBox ID="ListBoxEvts" runat="server" EnableViewState="False">
    </asp:ListBox>
  </p>
  <p>
    Eléments postés au serveur :
    <asp:Label ID="LabelPost" runat="server"></asp:Label>
  </p>
  <p>
    Eléments validés par le serveur :
    <asp:Label ID="LabelValidation" runat="server"></asp:Label>
  </p>
  <asp:Label ID="LabelErreursSaisie" runat="server" ForeColor="Red"></asp:Label>
  </form>
</body>
</html>

Se han añadido validadores en las líneas 20, 32 y 35. En la línea 58, se utiliza un control Label para mostrar los valores válidos enviados. En la línea 60, se utiliza un control Label para mostrar un mensaje de error si hay errores en la entrada.

La página [Default.aspx] en modo [Diseño] tiene este aspecto:

  • Los componentes [1] y [2] son del tipo RequiredFieldValidator. Este validador comprueba que un campo de entrada no esté vacío.
  • El componente [3] es un RangeValidator. Este validador comprueba que un campo de entrada contenga un valor comprendido entre dos límites.
  • En [4], las propiedades del validador [1].

Demostraremos los dos tipos de validadores utilizando sus etiquetas en el código de la página [Default.aspx]:


          <asp:RequiredFieldValidator ID="RequiredFieldValidatorNom" runat="server" 
            ControlToValidate="TextBoxNom" Display="Dynamic" 
ErrorMessage="Donnée obligatoire !"></asp:RequiredFieldValidator>
  • ID: el identificador del componente
  • ControlToValidate: el nombre del componente cuyo valor se está validando. En este caso, queremos asegurarnos de que el componente TextBoxNom no tenga un valor vacío (cadena vacía o una secuencia de espacios)
  • ErrorMessage: mensaje de error que se mostrará en el validador si los datos no son válidos.
  • EnableClientScript: un valor booleano que indica si el validador también debe ejecutarse en el lado del cliente. Este atributo tiene un valor predeterminado de True cuando no se establece explícitamente como se muestra arriba.
  • Display: modo de visualización del validador. Hay dos modos:
    • static (predeterminado): el validador ocupa espacio en la página aunque no muestre ningún mensaje de error
    • dinámico: el validador no ocupa espacio en la página si no muestra un mensaje de error.

          <asp:RangeValidator ID="RangeValidatorAge" runat="server" 
            ControlToValidate="TextBoxAge" Display="Dynamic" 
            ErrorMessage="Tapez un nombre entre 1 et 150 !" MaximumValue="150" 
            MinimumValue="1" Type="Integer"></asp:RangeValidator>
  • Type: el tipo de datos que se validan. En este caso, la edad es un número entero.
  • MinimumValue, MaximumValue: los límites entre los que debe estar el valor validado

La configuración del componente que activa el POST influye en el modo de validación. En este caso, dicho componente es el botón [Validate]:


  <asp:Button ID="ButtonValider" runat="server" onclick="ButtonValider_Click"  Text="Valider" CausesValidation="True" />
  • CausesValidation: establece el modo automático o el nombre de las validaciones del lado del servidor. Este atributo tiene el valor predeterminado «True» si no se especifica explícitamente. En este caso,
    • en el lado del cliente, se ejecutan los validadores con EnableClientScript establecido en True. La solicitud POST solo se envía si todos los validadores del lado del cliente tienen éxito.
    • En el lado del servidor, todos los validadores de la página se ejecutan automáticamente antes de que se procese el evento que ha desencadenado el POST. En este caso, se ejecutarían antes de que se llame al método ButtonValider_Click. Dentro de este método, puedes determinar si todas las validaciones se han realizado correctamente o no. Page.IsValid es «True» si todas han tenido éxito, «False» en caso contrario. En este último caso, puede detener el procesamiento del evento que ha desencadenado el POST. La página enviada se devuelve exactamente tal y como se envió. Los validadores que han fallado muestran entonces su mensaje de error (atributo ErrorMessage).

Si CausesValidation se establece en False, entonces

  • en el lado del cliente no se ejecuta ningún validador
  • en el lado del servidor, depende del desarrollador solicitar la ejecución de los validadores de la página. Esto se hace utilizando el método Page.Validate(). Dependiendo de los resultados de la validación, este método establece la propiedad Page.IsValid en «True» o «False».

En [Default.aspx.cs], el código del evento ButtonValider_Click queda de la siguiente manera:


protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // display name and age
      LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
      // is the page valid?
      Page.Validate();
      if (!Page.IsValid)
      {
        // global error msg
        LabelErreursSaisie.Text = "Veuillez corriger les erreurs de saisie...";
        LabelErreursSaisie.Visible = true;
        return;
      }
      // hide error msg
      LabelErreursSaisie.Visible = false;
      // displays validated name and age
      LabelValidation.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
}

Si el botón [Validate] tiene el atributo CausesValidation establecido en True y los validadores tienen el atributo EnableClientScript establecido en True, el método ButtonValider_Click solo se ejecuta cuando los valores enviados son válidos. Entonces, uno podría preguntarse cuál es el propósito del código que comienza en la línea 8. Es importante recordar que siempre es posible escribir un script del lado del cliente que envíe valores no verificados a la página [Default.aspx]. Por lo tanto, esta página debe volver a ejecutar siempre las comprobaciones de validez.

  • Línea 8: activa la ejecución de todos los validadores de la página. Si el botón [Validate] tiene el atributo CausesValidation establecido en True, esto se hace automáticamente y no es necesario repetirlo. Aquí hay redundancia.
  • Líneas 9-15: caso en el que uno de los validadores ha fallado
  • Líneas 16-19: caso en el que todos los validadores han pasado

A continuación se muestran dos ejemplos de ejecución:

  • En [1], un ejemplo de ejecución en el caso en que:
    • el botón [Validate] tiene la propiedad CausesValidation establecida en True
    • Los validadores tienen la propiedad EnableClientScript establecida en True

Los mensajes de error [2] fueron mostrados por los validadores ejecutados en el lado del cliente por el código JavaScript de la página. No se envió ninguna solicitud POST al servidor, como muestra la etiqueta de los elementos publicados [3].

  • En [4], un ejemplo de ejecución en el caso en que:
    • el botón [Validate] tiene la propiedad CausesValidation establecida en False
    • los validadores tienen su propiedad EnableClientScript establecida en False

Los mensajes de error [5] fueron mostrados por los validadores ejecutados en el lado del servidor. Como se muestra en [6], efectivamente se envió una solicitud POST al servidor. En [7], el mensaje de error mostrado por el método [ButtonValider_Click] en caso de errores de entrada.

  • En [8], un ejemplo obtenido con datos válidos. [9,10] muestran que los elementos enviados han sido validados. Al realizar pruebas repetidas, establezca la propiedad EnableViewState de la etiqueta [LabelValidation] en False para que el mensaje de validación no permanezca visible entre ejecuciones.

2.4. Gestión de datos en el ámbito de la aplicación

Repasemos la arquitectura de ejecución de una página ASPX:

La clase de la página ASPX se instancia al inicio de la solicitud del cliente y se destruye al final de la misma. Por lo tanto, no se puede utilizar para almacenar datos entre solicitudes. Es posible que desee almacenar dos tipos de datos:

  • datos compartidos por todos los usuarios de la aplicación web. Por lo general, se trata de datos de solo lectura. Se utilizan tres archivos para implementar este intercambio de datos:
    • [Web.Config]: el archivo de configuración de la aplicación
    • [Global.asax, Global.asax.cs]: le permiten definir una clase, denominada clase de aplicación global, cuya vida útil es la de la aplicación, así como controladores para determinados eventos de esa misma aplicación.

La clase de aplicación global permite definir datos que estarán disponibles para todas las solicitudes de todos los usuarios.

  • datos compartidos entre solicitudes del mismo cliente. Estos datos se almacenan en un objeto denominado «sesión». Nos referimos a esto como la sesión del cliente para denotar la memoria del cliente. Todas las solicitudes de un cliente tienen acceso a esta sesión. Pueden almacenar y leer información allí

Arriba, mostramos los tipos de memoria a los que tiene acceso una página ASPX:

  • la memoria de la aplicación, que contiene principalmente datos de solo lectura y es accesible para todos los usuarios.
  • la memoria de un usuario específico, o sesión, que contiene datos de lectura/escritura y es accesible para las solicitudes sucesivas del mismo usuario.
  • Aunque no se muestra arriba, existe una memoria de solicitud, o contexto de solicitud. La solicitud de un usuario puede ser procesada por varias páginas ASPX sucesivas. El contexto de solicitud permite que la Página 1 pase información a la Página 2.

Aquí nos interesan los datos del ámbito de la aplicación, que son compartidos por todos los usuarios. La clase global de la aplicación se puede crear de la siguiente manera:

  • En [1], añade un nuevo elemento al proyecto
  • En [2], añade la clase global de la aplicación
  • en [3], mantenga el nombre predeterminado [Global.asax] para el nuevo elemento
  • en [4], se han añadido dos nuevos archivos al proyecto
  • en [5], muestra el marcado de [Global.asax]

<%@ Application Codebehind="Global.asax.cs" Inherits="Intro.Global" Language="C#" %>
  • La etiqueta Application sustituye a la etiqueta Page que teníamos para [Default.aspx]. Identifica la clase de aplicación global
  • Codebehind: define el archivo en el que se define la clase global de la aplicación
  • Inherits: define el nombre de esta clase

La clase Intro.Global generada es la siguiente:


using System;
 
namespace Intro
{
  public class Global : System.Web.HttpApplication
  {
 
    protected void Application_Start(object sender, EventArgs e)
    {
 
    }
 
    protected void Session_Start(object sender, EventArgs e)
    {
 
    }
 
    protected void Application_BeginRequest(object sender, EventArgs e)
    {
 
    }
 
    protected void Application_AuthenticateRequest(object sender, EventArgs e)
    {
 
    }
 
    protected void Application_Error(object sender, EventArgs e)
    {
 
    }
 
    protected void Session_End(object sender, EventArgs e)
    {
 
    }
 
    protected void Application_End(object sender, EventArgs e)
    {
 
    }
  }
}
  • Línea 5: La clase de aplicación global deriva de la clase HttpApplication

La clase se genera con plantillas de controladores de eventos de la aplicación:

  • líneas 8 y 38: gestionar los eventos Application_Start (inicio de la aplicación) y Application_End (cierre de la aplicación cuando se detiene el servidor web o cuando el administrador la descarga)
  • líneas 13, 33: gestionar el evento Session_Start (inicio de una nueva sesión de cliente cuando llega un nuevo cliente o cuando caduca una sesión existente) y el evento Session_End (fin de una sesión de cliente, ya sea de forma explícita mediante programación o de forma implícita al exceder la duración permitida de la sesión).
  • Línea 28: gestiona el evento Application_Error (ocurrencia de una excepción no gestionada por el código de la aplicación y propagada hasta el servidor)
  • Línea 18: gestiona el evento Application_BeginRequest (llegada de una nueva solicitud).
  • Línea 23: Gestiona el evento Application_AuthenticateRequest (se produce cuando un usuario se ha autenticado).

El método [Application_Start] se utiliza a menudo para inicializar la aplicación basándose en la información contenida en [Web.Config]. El que se genera al crear el proyecto por primera vez tiene este aspecto:


<?xml version="1.0" encoding="utf-8"?>
 
<configuration>
    <configSections>
...
    </configSections>  
 
    <appSettings/>
    <connectionStrings/>
 
    <system.web>
...
    </system.web>
 
    <system.codedom>
....
    </system.codedom>
 
    <!-- 
        La section system.webServer est requise pour exécuter ASP.NET AJAX sur Internet
        Information Services 7.0.  Elle n'est pas nécessaire pour les versions précédentes d'IIS.
    -->
    <system.webServer>
...
    </system.webServer>
 
    <runtime>
....
    </runtime>
 
</configuration>

Para nuestra aplicación actual, este archivo es innecesario. Si lo eliminamos o le cambiamos el nombre, la aplicación seguirá funcionando con normalidad. Nos centraremos en las etiquetas de las líneas 8 y 9:

  • <appsettings> te permite definir un diccionario de información
  • <connectionStrings> permite definir cadenas de conexión a bases de datos

Considera el siguiente archivo [Web.config]:


<?xml version="1.0" encoding="utf-8"?>
 
<configuration>
    <configSections>
...
    </configSections>  
 
  <appSettings>
    <add key="cle1" value="valeur1"/>
    <add key="cle2" value="valeur2"/>
  </appSettings>
  <connectionStrings>
    <add connectionString="connectionString1" name="conn1"/>
  </connectionStrings>
 
    <system.web>
...
 

Este archivo puede ser utilizado por la siguiente clase de aplicación global:


using System;
using System.Configuration;
 
namespace Intro
{
  public class Global : System.Web.HttpApplication
  {
    public static string Param1 { get; set; }
    public static string Param2 { get; set; }
    public static string ConnString1 { get; set; }
    public static string Erreur { get; set; }
 
    protected void Application_Start(object sender, EventArgs e)
    {
      try
      {
        Param1 = ConfigurationManager.AppSettings["cle1"];
        Param2 = ConfigurationManager.AppSettings["cle2"];
        ConnString1 = ConfigurationManager.ConnectionStrings["conn1"].ConnectionString;
      }
      catch (Exception ex)
      {
        Erreur = string.Format("Erreur de configuration : {0}", ex.Message);
      }
    }
 
    protected void Session_Start(object sender, EventArgs e)
    {
 
    }
 
  }
}
  • líneas 8-11: cuatro propiedades estáticas P. Dado que la clase Global tiene la misma duración que la aplicación, cualquier solicitud realizada a la aplicación tendrá acceso a estas propiedades P mediante la sintaxis Global.P.
  • líneas 17-19: se puede acceder al archivo [Web.config] a través de la clase [System.Configuration.ConfigurationManager]
  • líneas 17-18: recupera los elementos de la etiqueta <appSettings> del archivo [Web.config] mediante el atributo key.
  • Línea 19: recupera los elementos de la etiqueta <connectionStrings> del archivo [Web.config] mediante el atributo name.

Se puede acceder a los atributos estáticos de las líneas 8-11 desde cualquier controlador de eventos en las páginas ASPX cargadas. Los utilizamos en el controlador [Page_Load] de la página [Default.aspx]:


    protected void Page_Load(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
      // retrieve information from the global application class
      LabelGlobal.Text = string.Format("Param1={0},Param2={1},ConnString1={2},Erreur={3}", Global.Param1, Global.Param2, Global.ConnString1, Global.Erreur);
}
  • Línea 6: Las cuatro propiedades estáticas de la clase global de la aplicación se utilizan para rellenar una nueva etiqueta en la página [Default.aspx]

En tiempo de ejecución, obtenemos el siguiente resultado:

Arriba, podemos ver que los parámetros de [web.config] se han recuperado correctamente. La clase global de la aplicación es el lugar adecuado para almacenar la información compartida por todos los usuarios.

2.5. Gestión de datos con ámbito de sesión

En este caso, nos interesa saber cómo almacenar información a lo largo de las solicitudes de un usuario determinado:

Cada usuario tiene su propia memoria, conocida como su sesión.

Hemos visto que la clase global de la aplicación tiene dos controladores para gestionar eventos:

  • Session_Start: inicio de una sesión
  • Session_end: fin de una sesión

El mecanismo de sesión funciona de la siguiente manera:

  • Cuando un usuario realiza su primera solicitud, el servidor web crea un token de sesión y se lo asigna al usuario. Este token es una cadena de caracteres única para cada usuario. El servidor lo envía en la respuesta a la primera solicitud del usuario.
  • En las solicitudes posteriores, el usuario (el navegador web) incluye el token de sesión asignado en su solicitud. Esto permite al servidor web reconocerlo.
  • Una sesión tiene un periodo de tiempo de espera. Cuando el servidor web recibe una solicitud de un usuario, calcula el tiempo transcurrido desde la solicitud anterior. Si este tiempo supera el tiempo de espera de la sesión, se crea una nueva sesión para el usuario. Los datos de la sesión anterior se pierden. Con el servidor web IIS (Internet Information Server) de Microsoft, las sesiones tienen una duración predeterminada de 20 minutos. El administrador del servidor web puede modificar este valor.
  • El servidor web sabe que está gestionando la primera solicitud de un usuario porque dicha solicitud no contiene un token de sesión. Es la única.

Cualquier página ASP.NET tiene acceso a la sesión del usuario a través de la propiedad Session de la página, de tipo [System.Web.SessionState.HttpSessionState]. Utilizaremos las siguientes propiedades P y métodos M de la clase HttpSessionState:

Nombre
Tipo
Role
Item[String key]
P
La sesión se puede estructurar como un diccionario. Elemento[clave] es el elemento de la sesión identificado por clave. En lugar de escribir [HttpSessionState].Elemento[clave], también se puede escribir [HttpSessionState].[clave].
Borrar
M
borra el diccionario de la sesión
Abandonar
M
finaliza la sesión. La sesión deja entonces de ser válida. Se iniciará una nueva sesión con la siguiente solicitud del usuario.

Como ejemplo de estado del usuario, contaremos el número de veces que un usuario hace clic en el botón [Enviar]. Para ello, necesitamos mantener un contador en la sesión del usuario.

La página [Default.aspx] cambia de la siguiente manera:

La clase de aplicación global [Global.asax.cs] cambia de la siguiente manera:


using System;
using System.Configuration;
 
namespace Intro
{
  public class Global : System.Web.HttpApplication
  {
    public static string Param1 { get; set; }
...
 
    protected void Application_Start(object sender, EventArgs e)
    {
...
    }
 
    protected void Session_Start(object sender, EventArgs e)
    {
      // query counter
      Session["nbRequêtes"] = 0;
    }
 
  }
}

En la línea 19, utilizamos la sesión del usuario para almacenar un contador de solicitudes identificado por la clave «nbRequests». Este contador se actualiza mediante el controlador [ButtonValider_Click] de la página [Default.aspx]:


using System;
 
namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
....
 
    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // post name and age are displayed
      LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
      // number of requests
      Session["nbRequêtes"] = (int)Session["nbRequêtes"] + 1;
      LabelNbRequetes.Text = Session["nbRequêtes"].ToString();
      // is the page valid?
      Page.Validate();
      if (!Page.IsValid)
      {
...
      }
...
    }
  }
}
  • línea 16: incrementa el contador de solicitudes
  • línea 17: el contador se muestra en la página

Aquí hay un ejemplo de ejecución:

2.6. Gestión de GET / POST en la carga de la página

Ya hemos mencionado que hay dos tipos de solicitudes a una página ASPX:

  • la solicitud inicial del navegador realizada con un comando HTTP GET. El servidor responde enviando la página solicitada. Supondremos que esta página es un formulario, es decir, que la página ASPX enviada contiene una etiqueta <form runat="server">.
  • Las solicitudes posteriores realizadas por el navegador en respuesta a determinadas acciones del usuario en el formulario. A continuación, el navegador realiza una solicitud HTTP POST.

Tanto si se trata de una solicitud GET como de una solicitud POST, se ejecuta el método [Page_Load]. Durante una solicitud GET, este método se utiliza normalmente para inicializar la página enviada al navegador del cliente. Posteriormente, a través del mecanismo VIEWSTATE, la página permanece inicializada y solo es modificada por los controladores de eventos que desencadenan solicitudes POST. No es necesario reinicializar la página en Page_Load. De ahí la necesidad de que este método determine si la solicitud del cliente es GET o POST.

Veamos el siguiente ejemplo. Añadimos una lista desplegable a la página [Default.aspx]. El contenido de esta lista se definirá en el controlador Page_Load para la solicitud GET:

La lista desplegable se declara en [Default.aspx.designer.cs] de la siguiente manera:


        protected global::System.Web.UI.WebControls.DropDownList DropDownListNoms;

Utilizaremos los siguientes métodos M y propiedades P de la clase [DropDownList]:

Nombre
Tipo
Rol
Elementos
P
la colección ListItemCollection de elementos ListItem de la lista desplegable
SelectedIndex
P
el índice, que comienza en 0, del elemento seleccionado en la lista desplegable cuando se envía el formulario
SelectedItem
P
el elemento de la lista seleccionado en la lista desplegable cuando se envía el formulario
SelectedValue
P
el valor de cadena del ListItem seleccionado en la lista desplegable cuando se envía el formulario. Definiremos este concepto de valor en breve.

La clase ListItem, que representa los elementos de una lista desplegable, se utiliza para generar las etiquetas <option> dentro de la etiqueta HTML <select>:

1
2
3
4
5
<select ....>
    <option value="val1">texte1</option>
    <option value="val2">texte2</option>
....
</select>

En la etiqueta <option>

  • text1 es el texto que se muestra en la lista desplegable
  • vali es el valor enviado por el navegador si texti es el texto seleccionado en la lista desplegable

Cada opción puede generarse mediante un objeto ListItem creado con el constructor ListItem(string text, string value).

En [Default.aspx.cs], el código del controlador [Page_Load] cambia de la siguiente manera:


    protected void Page_Load(object sender, EventArgs e)
    {
      // the event
      ...
      // retrieve information from the global application class
      ...
      // initialization of name combo only during initial GET
      if (!IsPostBack)
      {
        for (int i = 0; i < 3; i++)
        {
          DropDownListNoms.Items.Add(new ListItem("nom"+i,i.ToString()));
        }
      }
}
  • Línea 8: La clase Page tiene una propiedad booleana IsPostBack. Básicamente, esto significa que la solicitud del usuario es un POST. Por lo tanto, las líneas 10-13 solo se ejecutan en la solicitud GET inicial del cliente.
  • Línea 12: Añadimos un elemento de tipo ListItem(string text, string value) a la lista [DropDownListNames]. El texto que se mostrará para el elemento (i+1) será «names», y el valor enviado para este elemento si se selecciona será i.

El controlador [ButtonValider_Click] se modifica para mostrar el valor enviado por la lista desplegable:


    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // the event
...
      // display posted values
      LabelPost.Text = string.Format("nom={0}, age={1}, combo={2}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim(), DropDownListNoms.SelectedValue);
      // number of requests
...
}

Línea 6: El valor enviado para la lista [DropDownListNames] se obtiene mediante la propiedad SelectedValue de la lista. A continuación se muestra un ejemplo de ejecución:

  • en [1], el contenido de la lista desplegable tras el GET inicial y justo antes del primer POST
  • en [2], la página tras el primer POST.
  • en [3], el valor enviado para la lista desplegable. Esto se corresponde con el atributo «value» del elemento «ListItem» seleccionado en la lista.
  • en [4], la lista desplegable. Contiene los mismos elementos que tras la solicitud GET inicial. Esto se debe al mecanismo VIEWSTATE.

Para comprender la interacción entre el VIEWSTATE de la lista DropDownListNames y la prueba if (!IsPostBack) en el controlador Page_Load de [Default.aspx], se invita al lector a repetir la prueba anterior con las siguientes configuraciones:

Caso
DropDownListNames.EnableViewState
Prueba if(! IsPostBack) en Page_Load de [Default.aspx]
1
true
presente
2
falso
presente
3
verdadero
ausente
4
falso
ausente

Las distintas pruebas arrojan los siguientes resultados:

  1. Este es el caso descrito anteriormente
  2. La lista se rellena durante la solicitud GET inicial, pero no durante las solicitudes POST posteriores. Dado que EnableViewState está establecido en false, la lista queda vacía después de cada solicitud POST
  3. La lista se rellena tanto tras la solicitud GET inicial como durante las solicitudes POST posteriores. Dado que EnableViewState está establecido en true, hay 3 nombres tras la solicitud GET inicial, 6 nombres tras la primera solicitud POST, 9 nombres tras la segunda solicitud POST, ...
  4. La lista se rellena tanto tras el GET inicial como durante los POST posteriores. Dado que EnableViewState está establecido en false, la lista se rellena con solo 3 nombres para cada solicitud, ya sea el GET inicial o los POST posteriores. Observamos el mismo comportamiento que en el caso 1. Por lo tanto, hay dos formas de lograr el mismo resultado.

2.7. Gestión del VIEWSTATE de los elementos de una página ASPX

De forma predeterminada, todos los elementos de una página ASPX tienen la propiedad EnableViewState establecida en True. Cada vez que la página ASPX se envía al navegador del cliente, contiene el campo oculto __VIEWSTATE, cuyo valor es una cadena que codifica todos los valores de los componentes con la propiedad EnableViewState establecida en True. Para minimizar el tamaño de esta cadena, podemos intentar reducir el número de componentes con la propiedad EnableViewState establecida en True.

Repasemos cómo los componentes de una página ASPX obtienen sus valores tras una solicitud POST:

  1. Se instancia la página ASPX. Los componentes se inicializan con sus valores de diseño.
  2. El valor __VIEWSTATE enviado por el navegador se utiliza para asignar a los componentes los valores que tenían cuando la página ASPX se envió al navegador la vez anterior.
  3. Los valores enviados por el navegador se asignan a los componentes.
  4. Se ejecutan los controladores de eventos. Estos pueden modificar los valores de determinados componentes.

A partir de esta secuencia, podemos deducir que los componentes que:

  • sus valores son enviados
  • tienen sus valores modificados por un controlador de eventos

pueden tener su propiedad EnableViewState establecida en False, ya que su valor VIEWSTATE (paso 2) se modificará en el paso 3 o en el 4.

La lista de componentes de nuestra página está disponible en [Default.aspx.designer.cs]:


namespace Intro {
    public partial class _Default {
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
        protected global::System.Web.UI.WebControls.TextBox TextBoxNom;
        protected global::System.Web.UI.WebControls.RequiredFieldValidator RequiredFieldValidatorNom;
        protected global::System.Web.UI.WebControls.TextBox TextBoxAge;
        protected global::System.Web.UI.WebControls.RequiredFieldValidator RequiredFieldValidatorAge;
        protected global::System.Web.UI.WebControls.RangeValidator RangeValidatorAge;
        protected global::System.Web.UI.WebControls.DropDownList DropDownListNoms;
        protected global::System.Web.UI.WebControls.Button ButtonValider;
        protected global::System.Web.UI.WebControls.ListBox ListBoxEvts;
        protected global::System.Web.UI.WebControls.Label LabelPost;
        protected global::System.Web.UI.WebControls.Label LabelValidation;
        protected global::System.Web.UI.WebControls.Label LabelErreursSaisie;
        protected global::System.Web.UI.WebControls.Label LabelGlobal;
        protected global::System.Web.UI.WebControls.Label LabelNbRequetes;
    }
}

El valor de la propiedad EnableViewState para estos componentes podría ser el siguiente:

Componente
Valor establecido
EnableViewState
Por qué
Nombre del cuadro de texto
Valor introducido en el TextBox
Falso
se envía el valor del componente
TextBoxAge
igual
  
Nombre del validador de campo obligatorio
ninguno
Falso
No existe el concepto de valor del componente
Validador de campo obligatorio «Edad»
igual
  
Validador de rango de edad
igual
  
Etiqueta de publicación
ninguno
Falso
obtiene su valor de un controlador de eventos
Validación de etiqueta
igual
  
InputErrorLabel
igual
  
Etiqueta global
igual
  
Número de solicitudes de la etiqueta
igual
  
Nombres de la lista desplegable
«valor» del elemento seleccionado
True
Queremos conservar el contenido de la lista entre solicitudes sin tener que volver a generarlo
ListBoxEvts
«valor» del elemento seleccionado
Falso
El contenido de la lista se genera mediante un controlador de eventos
ButtonValidate
Etiqueta del botón
Falso
El componente conserva su valor de diseño

2.8. Redireccionamiento de una página a otra

Hasta ahora, las solicitudes GET y POST siempre devolvían la misma página [Default.aspx]. Consideraremos el caso en el que una solicitud es procesada por dos páginas ASPX sucesivas, [Default.aspx] y [Page1.aspx], y en el que esta última se devuelve al cliente. Además, veremos cómo la página [Default.aspx] puede pasar información a la página [Page1.aspx] a través de una memoria que llamaremos memoria de solicitud.

Creamos la página [Page1.aspx]:

  • En [1], añadimos un nuevo elemento al proyecto
  • En [2], añadimos un elemento [Formulario web] llamado [Page1.aspx] [3]
  • en [4], la página añadida
  • en [5], la página una vez creada

El código fuente de [Page1.aspx] es el siguiente:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page1.aspx.cs" Inherits="Intro.Page1" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>Page1</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h1>
      Page 1</h1>
    <asp:Label ID="Label1" runat="server"></asp:Label>
    <br />
    <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Default.aspx">Retour 
      vers page [Default]</asp:HyperLink>
  </div>
  </form>
</body>
</html>
  • Línea 13: una etiqueta que se utilizará para mostrar la información enviada por la página [Default.aspx]
  • Línea 15: un enlace HTML a la página [Default.aspx]. Cuando el usuario hace clic en este enlace, el navegador solicita la página [Default.aspx] mediante una operación GET. A continuación, la página [Default.aspx] se carga como si el usuario hubiera escrito su URL directamente en el navegador.

La página [Default.aspx] se ha mejorado con un nuevo componente LinkButton:

El código fuente de este nuevo componente es el siguiente:


  <asp:LinkButton ID="LinkButtonToPage1" runat="server" CausesValidation="False" 
    EnableViewState="False" onclick="LinkButtonToPage1_Click">Forward vers Page1</asp:LinkButton>
  • CausesValidation="False": Al hacer clic en el enlace se activará una solicitud POST a [Default.aspx]. El componente [LinkButton] se comporta como el componente [Button]. En este caso, no queremos que al hacer clic en el enlace se active la ejecución de los validadores.
  • EnableViewState="False": no es necesario conservar el estado del enlace entre solicitudes. Conserva sus valores de diseño.
  • onclick="LinkButtonToPage1_Click": nombre del método que, en [Defaul.aspx.cs], gestiona el evento Click del componente LinkButtonToPage1.

El código del controlador LinkButtonToPage1_Click es el siguiente:


  // to Page1
  protected void LinkButtonToPage1_Click(object sender, EventArgs e)
  {
    // we put information in context
    Context.Items["msg1"] = "Message de Default.aspx pour Page1";
    // pass the request to Page1
    Server.Transfer("Page1.aspx",true);
}

Línea 7: La solicitud se pasa a la página [Page1.aspx] utilizando el método [Server.Transfer]. El segundo parámetro del método, establecido en true, indica que toda la información enviada a [Default.aspx] durante la solicitud POST debe pasarse a [Page1.aspx]. Esto permite que [Page1.aspx], por ejemplo, acceda a los valores enviados a través de una colección llamada Request.Form. La línea 5 utiliza lo que se conoce como contexto de solicitud. Se accede a él a través de la propiedad Context de la clase Page. Este contexto puede servir como memoria compartida entre las diferentes páginas que gestionan la misma solicitud, en este caso [Default.aspx] y [Page1.aspx]. Para ello se utiliza el diccionario Items.

Cuando [Page1.aspx] se carga mediante la operación Server.Transfer("Page1.aspx", true), todo ocurre como si [Page1.aspx] hubiera sido llamada por una solicitud GET desde un navegador. El controlador Page_Load de [Page1.aspx] se ejecuta con normalidad. Lo utilizaremos para mostrar el mensaje colocado por [Default.aspx] en el contexto de la solicitud:


using System;
 
namespace Intro
{
  public partial class Page1 : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      Label1.Text = Context.Items["msg1"] as string;
    }
  }
}

Línea 9: El mensaje establecido por [Default.aspx] en el contexto de la solicitud se muestra en Label1.

A continuación se muestra un ejemplo de ejecución:

  • En la página [Default.aspx] [1], haz clic en el enlace [2] que te lleva a la página Page1
  • En [3], se muestra la página Page1
  • En [4], el mensaje creado en [Default.aspx] y mostrado por [Page1.aspx]
  • En [5], la URL que se muestra en el navegador es la de la página [Default.aspx]

2.9. Redireccionamiento de una página a otra

Aquí presentamos otra técnica que es funcionalmente similar a la anterior: cuando el usuario solicita la página [Default.aspx] mediante una solicitud POST, recibe otra página [Page2.aspx] como respuesta. En el método anterior, la solicitud del usuario era procesada sucesivamente por dos páginas: [Default.aspx] y [Page1.aspx]. En el método de redireccionamiento de páginas que presentamos ahora, hay dos solicitudes separadas desde el navegador:

  • En [1], el navegador envía una solicitud POST a la página [Default.aspx]. Esta página procesa la solicitud y envía al navegador lo que se conoce como una respuesta de redireccionamiento. Esta respuesta es un simple flujo HTTP (líneas de texto) que indica al navegador que redirija a otra URL [Page2.aspx]. [Default.aspx] no envía ningún contenido HTML en esta primera respuesta.
  • En [2], el navegador realiza una solicitud GET a la página [Page2.aspx]. A continuación, esta página se envía como respuesta al navegador.
  • Si la página [Default.aspx] desea pasar información a la página [Page2.aspx], puede hacerlo a través de la sesión del usuario. A diferencia del método anterior, aquí no se puede utilizar el contexto de la solicitud, ya que hay dos solicitudes independientes y, por lo tanto, dos contextos independientes. Por lo tanto, debemos utilizar la sesión del usuario para permitir que las páginas se comuniquen entre sí.

Al igual que hicimos con [Page1.aspx], añadimos la página [Page2.aspx] al proyecto:

  • en [1], se añadió [Page2.aspx] al proyecto
  • en [2], el aspecto visual de [Page2.aspx]
  • en [3], añadimos un componente LinkButton [4] a la página [Default.aspx], que redirigirá al usuario a [Page2.aspx].

El código fuente de [Page2.aspx] es similar al de [Page1.aspx]:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page2.aspx.cs" Inherits="Intro.Page2" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>Page2</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h1>
      Page 2</h1>
    <asp:Label ID="Label1" runat="server"></asp:Label>
    <br />
    <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Default.aspx">Retour 
      vers page [Default]</asp:HyperLink>
  </div>
  </form>
</body>
</html>

En [Default.aspx], al añadir el componente LinkButton se generó el siguiente código fuente:


<asp:LinkButton ID="LinkButtonToPage2" runat="server" 
    onclick="LinkButtonToPage2_Click">Redirection vers Page2</asp:LinkButton>

El controlador [LinkButtonToPage2_Click] gestiona la redirección a [Page2.aspx]. Su código en [Default.aspx.cs] es el siguiente:


    protected void LinkButtonToPage2_Click(object sender, EventArgs e)
    {
      // put a msg in the session
      Session["msg2"] = "Message de [Default.aspx] pour [Page2.aspx]";
      // the client is redirected to [Page2.aspx]
      Response.Redirect("Page2.aspx");
}
  • Línea 4: Establecemos un mensaje en la sesión del usuario
  • Línea 5: El objeto Response es una propiedad de todas las páginas ASPX. Representa la respuesta enviada al cliente. Dispone de un método Redirect que hace que la respuesta enviada al cliente sea una solicitud de redireccionamiento HTTP.

Cuando el navegador reciba el comando de redireccionamiento a [Page2.aspx], enviará una solicitud GET a esa página. En esa página, se ejecutará el método [Page_Load]. Lo utilizaremos para recuperar el mensaje almacenado por [Default.aspx] en la sesión y mostrarlo. El código para [Page2.aspx.cs] es el siguiente:


using System;

namespace Intro
{
  public partial class Page2 : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      // displays the msg set in the session by [Default.aspx]
      Label1.Text = Session["msg2"] as string;
    }
  }
}

Al ejecutarlo, se obtienen los siguientes resultados:

  • En [1], hacemos clic en el enlace de redireccionamiento de [Default.aspx]. Se envía una solicitud POST a la página [Default.aspx]
  • En [2], el navegador ha sido redirigido a [Page2.aspx]. Esto se puede ver en la URL que muestra el navegador. En el método anterior, esta URL era la de [Default.aspx] porque la única solicitud realizada por el navegador era a esa URL. Aquí, hay una primera solicitud POST a [Default.aspx], seguida de una segunda solicitud GET a [Page2.aspx] sin que el usuario lo sepa.
  • En [3], vemos que [Page2.aspx] ha recuperado correctamente el mensaje colocado por [Default.aspx] en la sesión.

2.10. Conclusión

Hemos presentado, mediante algunos ejemplos, los conceptos de ASP.NET que nos serán útiles en el resto de este documento. Esta introducción no abarca las sutilezas de la comunicación cliente/servidor en una aplicación web. Para ello, puede leer: