Skip to content

7. Componentes del servidor ASP - 1

7.1. Introduction

En este capítulo describimos la tecnología recomendada en ASP.NET para construir la interfaz de usuario. Sabemos que hay dos fases bien diferenciadas en el procesamiento de una página .aspx por parte del servidor web:

  1. en primer lugar, se ejecuta el controlador de la página. Este consiste en código ubicado ya sea en la propia página .aspx (solución WebMatrix) o en un archivo aparte (solución Visual Studio.NET).
  2. Luego, se ejecuta el código de presentación de la página .aspx para transformarlo en código HTML, el cual se envía al cliente.

Image

ASP.NET ofrece tres bibliotecas de etiquetas para escribir el código de presentación de la página:

  1. las etiquetas clásicas HTML. Son las que hemos utilizado hasta ahora.
  2. las etiquetas de servidor HTML
  3. las etiquetas de formularios web

Independientemente de la biblioteca de etiquetas que se utilice, la función del controlador de página no cambia. Debe calcular el valor de los parámetros dinámicos que aparecen en el código de presentación. Hasta ahora, estos parámetros dinámicos eran sencillos: eran objetos de tipo [String]. Así, si en el código de presentación tenemos una etiqueta <%=nombre%>:

  • el controlador de página declara una variable [nom] de tipo [String] y calcula su valor
  • cuando el controlador de página ha terminado su trabajo y se ejecuta el código de presentación para generar la respuesta HTML, la etiqueta <%=nombre%> se reemplaza por el valor calculado por el código de control

Sabemos que la separación entre controlador y presentación es arbitraria y que se puede mezclar código de controlador y código de presentación en la misma página. Ya hemos explicado por qué no se recomienda este método y seguiremos respetando la separación entre control y presentación.

Las etiquetas [HTML serveur] y [WebForms] permiten introducir en el código de presentación objetos más complejos que el simple objeto [String]. Esto a veces resulta realmente útil. Tomemos el ejemplo de un formulario con una lista. Esta debe presentarse al cliente con un código HTML similar al siguiente:

<select name="uneListe" size="3">
    <option value="opt1">option1</option>
    <option value="opt2">option2</option>
    <option value="opt3" selected>option3</option>
</select>

El contenido de la lista y la opción que se debe seleccionar son elementos dinámicos y, por lo tanto, deben ser generados por el controlador de la página. Ya nos hemos encontrado con este problema y lo hemos resuelto al incluir en el código de presentación la etiqueta

<%=uneListeHTML%>

Esta etiqueta será reemplazada por el valor [String] de la variable [uneListeHTML]. Este valor, calculado por el controlador, deberá ser el código HTML de la lista, c.a.d. «<select name=..>...</select>». No es particularmente difícil de hacer y parece una solución elegante que evita colocar código de generación directamente en la parte de presentación de la página. Aquí habría que insertar un bucle con comprobaciones, lo que «contaminaría» considerablemente dicha parte. Sin embargo, este método presenta un inconveniente. La separación entre control y presentación de una página también sirve para delimitar dos ámbitos de competencia:

  • el del desarrollador .NET, que se encarga del controlador de la página
  • el del diseñador gráfico, que se encarga de la parte de presentación de la misma

Aquí vemos que se ha trasladado la generación del código HTML de la lista al controlador. El diseñador gráfico podría querer modificar este código HTML para cambiar el aspecto «visual» de la lista. Se vería obligado a trabajar en la parte [contrôleur] y, por lo tanto, a salirse de su ámbito de competencia, con el riesgo de introducir errores involuntarios en el código que ello conlleva.

Las bibliotecas de etiquetas de servidor resuelven este problema. Ofrecen un objeto que representa una lista HTML. Así, la biblioteca [WebForms] ofrece la siguiente etiqueta:

<asp:ListBox id="uneListe" runat="server"></asp:ListBox>

Esta etiqueta representa un objeto de tipo [ListBox] que puede ser manipulado por el controlador de página. Este objeto cuenta con propiedades para representar las diferentes opciones de la lista HTML y designar la opción seleccionada. Por lo tanto, el controlador de página asignará los valores adecuados a estas propiedades. Cuando se ejecute la parte de presentación, la etiqueta

<asp:ListBox id="uneListe" runat="server"></asp:ListBox>

será reemplazada por el código HTML que representa el objeto [uneListe], c.a.d. el código «<select ..>...</select>». Por el momento, no hay ninguna diferencia fundamental con el método anterior, salvo que se trata de una forma de codificar orientada a objetos, lo cual es interesante. Volvamos a nuestro diseñador gráfico, quien necesita modificar el «aspecto» de la lista. Las etiquetas de servidor tienen atributos de estilo (BackColor, Bordercolor, BorderWidth, ...) que permiten definir el aspecto visual del objeto HTML correspondiente. Así, podremos escribir:


                <asp:ListBox id="ListBox1" runat="server" BackColor="#ffff99"></asp:ListBox></P>

La ventaja es que el diseñador gráfico puede permanecer en el código de presentación para realizar estas modificaciones. Esto representa una ventaja clara respecto al método anterior. Las bibliotecas de etiquetas de servidor, por lo tanto, facilitan la construcción de la parte de presentación de las páginas, lo que en los capítulos anteriores denominamos «interfaz de usuario». El objetivo de este capítulo es presentarlas. Veremos que a veces ofrecen objetos complejos, como calendarios o tablas vinculadas a fuentes de datos. Son extensibles, c.a.d, de modo que el usuario puede crear su propia biblioteca de etiquetas. Así, puede crear una etiqueta que genere un banner en una página. Todas las páginas que utilicen esta etiqueta tendrán entonces el mismo banner.

La generación HTML realizada para una etiqueta se adapta al tipo de navegador del cliente. Cuando este realiza una solicitud al servidor web, envía entre sus encabezados HTTP, un encabezado [User-Agent: xx] donde [xx] identifica al cliente. A continuación se muestra un ejemplo:

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

Con esta información, el servidor web puede conocer las capacidades del cliente, en particular el tipo de código HTML que este puede manejar. De hecho, a lo largo del tiempo, ha habido varias versiones del lenguaje HTML. Los navegadores recientes comprenden las versiones más recientes del lenguaje, algo que los navegadores más antiguos no pueden hacer. Dependiendo del encabezado HTTP [User-Agent:] que el cliente le haya enviado, el servidor le enviará una versión HTML que este pueda entender. Es una idea interesante y útil, ya que así el desarrollador no tiene que preocuparse por el tipo de navegador del cliente de su aplicación.

Por último, versiones avanzadas de IDE, como Visual Studio.NET, WebMatrix, etc., permiten un diseño de la interfaz web «al estilo de Windows». Estas herramientas, aunque no son imprescindibles, brindan una ayuda decisiva al desarrollador. El desarrollador diseña la interfaz web utilizando componentes gráficos que coloca en dicha interfaz. Tiene acceso directo a las propiedades de cada uno de los componentes de la interfaz, las cuales puede configurar a su gusto. Estas propiedades se traducirán en el código HTML de presentación de la interfaz como atributos de la etiqueta <asp:> del componente. La ventaja para el desarrollador es que no tiene que recordar ni la lista ni la sintaxis de los atributos de cada etiqueta. Esto representa una ventaja considerable cuando no se conocen a la perfección las bibliotecas de etiquetas de servidor que ofrece ASP.NET. Una vez dominada esta sintaxis, algunos desarrolladores pueden preferir codificar directamente las etiquetas en el código de presentación de la página sin pasar por la etapa de diseño gráfico. En ese caso, un IDE ya no es necesario. Basta con un simple editor de texto. Dependiendo de la forma de trabajar, el énfasis recae entonces en los componentes (uso de un IDE) o en las etiquetas (uso de un editor de texto). Existe equivalencia entre estos dos términos. El componente es el objeto que será manipulado por el código de control de la página. El IDE nos da acceso a sus propiedades en la fase de diseño. Los valores asignados a estas propiedades se traducen inmediatamente a los atributos de la etiqueta del componente en el código de presentación. En la fase de ejecución, el código de control de la página manipulará el componente y asignará valores a algunas de sus propiedades. El código de presentación generará el código HTML del componente utilizando, por un lado, los atributos establecidos en la fase de diseño para la etiqueta de servidor correspondiente y, por otro, los valores de las propiedades del componente calculados por el código de control.

7.2. El contexto de ejecución de los ejemplos

Ilustraremos el diseño de interfaces web basadas en componentes de servidor con programas cuyo contexto de ejecución será, en la mayoría de los casos, el siguiente:

  1. la aplicación web estará compuesta por una única página P que contiene un formulario F,
  2. el cliente realizará su primera solicitud directamente a esta página P. Esto consistirá en solicitar la URL de la página P con un navegador. Por lo tanto, se realizará una solicitud GET a esta URL P. El servidor entregará la página P y, por lo tanto, el formulario F que contiene,
  3. el usuario lo llenará y lo enviará, c.a.d, es decir, realizará una acción que obligará al navegador a enviar el formulario F al servidor. La operación POST del navegador seguirá dirigiéndose a la página P. El servidor volverá a enviar la página P con el formulario F, cuyo contenido puede haber sido modificado por la acción del usuario.
  4. Luego, se reanudarán los pasos 2 y 3.

Se trata de un proceso de ejecución muy particular, fuera del cual algunos conceptos que se exponen a continuación ya no funcionan. Ya no nos encontramos en un contexto de arquitectura MVC en el que una aplicación de múltiples páginas es controlada por una página específica a la que hemos llamado «controlador de aplicación». En este tipo de arquitectura, el POST de los formularios tiene como destino al controlador y no a los propios formularios. Sin embargo, veremos que crear un formulario con componentes de servidor implica que dicho formulario se envíe a sí mismo.

7.3. El componente Label

7.3.1. Uso

La etiqueta <asp:label> permite insertar texto dinámico en el código de presentación de una página. Por lo tanto, no hace nada más que la etiqueta <%=variable%> utilizada hasta ahora. El análisis de esta primera etiqueta nos permitirá descubrir el mecanismo de las etiquetas de servidor. Creamos una página con una parte de control [form1.aspx.vb] y una parte de presentación [form1.aspx]. Se trata de mostrar la hora:

Image

Este tema ya se abordó en el capítulo 2 y se invita al lector a consultarlo si desea saber cómo se trató. El código de presentación [form1.aspx] es el siguiente:


<%@ page src="form1.aspx.vb" inherits="form1" AutoEventWireup="false" %>
<HTML>
    <HEAD>
        <title>Webforms</title>
    </HEAD>
    <body>
        <asp:Label Runat="server" ID="lblHeure" />
    </body>
</HTML>

Introducimos la etiqueta <asp:label>. En las bibliotecas de etiquetas, el atributo [runat="server"] es obligatorio. El atributo ID identifica al componente. El controlador deberá referenciarlo con este identificador. El código del controlador [form1.aspx.vb] es el siguiente:


Imports System.Web.UI.WebControls

Public Class form1
    Inherits System.Web.UI.Page

    Protected lblHeure As Label

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
        ' guarda la consulta actual en request.txt de la carpeta de la página
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub

    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' agrega la hora en lblHeure
        lblHeure.Text = "Il est " + Date.Now.ToString("T")
    End Sub

End Class

El controlador debe asignar un valor al objeto [lblHeure] de tipo [System.Web.UI.WebControls.Label]. Todos los objetos mostrados por las etiquetas <asp:> pertenecen al espacio de nombres [System.Web.UI.WebControls]. Por lo tanto, se puede importar este espacio de nombres de manera sistemática:


Imports System.Web.UI.WebControls

El objeto [Label] tiene varias propiedades, entre ellas la propiedad [Text], que representa el texto que se mostrará mediante la etiqueta <asp:label> correspondiente. Aquí, asignamos a esta propiedad la hora actual. Lo hacemos en el procedimiento [Form_Load] del controlador, que se ejecuta sistemáticamente. En el procedimiento [Form_Init], que también se ejecuta siempre, pero antes del procedimiento [Form_Load], almacenamos la solicitud del cliente en un archivo [request.txt] de la carpeta de la aplicación. Tendremos la oportunidad de examinar este archivo para comprender ciertos aspectos del funcionamiento de las páginas que utilizan etiquetas de servidor.

El objeto [Label] cuenta con numerosas propiedades, métodos y eventos. Se invita al lector a consultar la documentación sobre la clase [Label] para conocerlos. De aquí en adelante, siempre será así. Para cada etiqueta, solo presentaremos las pocas propiedades que necesitamos.

7.3.2. Las pruebas

Colocamos los archivos (form1.aspx, form1.aspx.vb) en una carpeta <ruta-de-la-aplicación> e iniciamos Cassini con los parámetros (<ruta-de-la-aplicación>,/form1). Luego solicitamos la URL [http://localhost/form1/form1.aspx]. Obtenemos el siguiente resultado:

Image

El código HTML recibido por el navegador es el siguiente:

<HTML>
    <HEAD>
        <title>Webforms</title>
    </HEAD>
    <body>
        <span id="lblHeure">Il est 19:39:37</span>
    </body>
</HTML>

Se observa que la etiqueta del servidor


        <asp:Label Runat="server" ID="lblHeure" />

se ha transformado en el siguiente código HTML:

        <span id="lblHeure">Il est 19:39:37</span>

Es la propiedad [Text] del objeto [lblHeure] la que se ha colocado entre las etiquetas <span> y </span>. La solicitud realizada por el cliente y almacenada en [request.txt] es la siguiente:

GET /form1/form1.aspx HTTP/1.1
Cache-Control: max-age=0
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

Todo muy normal.

7.3.3. Crear la aplicación con WebMatrix

Hemos creado el código de presentación de la página [form1.aspx] a mano. Este método se puede utilizar si se conocen las etiquetas. En ese caso, basta con un simple editor de texto para crear la interfaz de usuario. Para empezar, a menudo se necesita una herramienta de diseño gráfico combinada con la generación automática de código, ya que no se conoce la sintaxis de las etiquetas que se necesitan. Ahora crearemos la misma aplicación con la herramienta WebMatrix. Una vez que se inicia WebMatrix, seleccionamos la opción [File/New File]:

Image

Creamos una página ASP.NET llamada [form2.aspx]. Una vez validado el asistente anterior, aparece la ventana de diseño de la página [form2.aspx]:

Image

Image

Recordemos que WebMatrix coloca el código de control de la página y el de presentación en un mismo archivo, en este caso [form2.aspx]. La pestaña [All] muestra el contenido de este archivo de texto. Ya podemos observar que no está vacío:

Image

La desventaja de este tipo de herramientas es que a menudo generan código innecesario. Este es el caso aquí, donde WebMatrix ha generado una etiqueta <form> cuando no vamos a crear ningún formulario... Además, se puede observar que el documento no tiene la etiqueta <title>. Solucionamos estos dos problemas de inmediato para obtener la siguiente versión:

Image

Lo que llamamos el código de control se insertará entre las etiquetas <script> y </script>, lo que garantiza una separación, al menos visual, entre los dos tipos de código: control y presentación. Regresamos a la pestaña [Design] para diseñar nuestra interfaz. Hay una lista de componentes disponible en una ventana de herramientas a la izquierda de la ventana de diseño:

Image

La ventana de herramientas ofrece acceso a dos tipos de componentes:

  • los componentes [WebControls], que se traducen en etiquetas <asp:>
  • los componentes [HTML Elements], que se traducen en etiquetas HTML clásicas. Sin embargo, se puede agregar a los atributos de una etiqueta HTML el atributo [runat="server"]. En este caso, el controlador puede acceder a la etiqueta HTML y a sus atributos a través de un objeto cuyas propiedades son las mismas que las de la etiqueta HTML que representa. Anteriormente, nos referimos a estas etiquetas como etiquetas de servidor HTML.

Hagamos doble clic en el componente [Label] de la lista de controles [WebControls]. En la pestaña [Design] obtenemos el siguiente resultado:

Image

En la pestaña [All], el código quedó así:

<%@ Page Language="VB" %>
<html>
<head>
    <title>webforms</title>
</head>
<body>
    <asp:Label id="Label1" runat="server">Label</asp:Label>
</body>
</html>

Lo primero que se nota es que se ha perdido la etiqueta <script>. Se ha generado una etiqueta <asp:label>. Tiene un nombre [Label1] y un valor [Label]. Volvamos a la pestaña [Design] para modificar estos dos valores. Hacemos clic una vez en el componente [Label] para que aparezca la ventana de propiedades de este componente, en la parte inferior derecha:

Image

Se invita al lector a consultar las propiedades del objeto [Label]. Aquí nos interesan dos:

  • Texto: es el texto que debe mostrar la etiqueta; colocamos la cadena vacía (c.a.d. nada)
  • ID: es su identificador; ponemos lblHeure

La pestaña [Design] queda así:

Image

y el código de [All] queda así:

<%@ Page Language="VB" %>
<html>
<head>
    <title>webforms</title>
</head>
<body>
    <asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>

La parte de presentación de la página está terminada. Ahora nos queda escribir el código de control encargado de colocar la hora en la propiedad [Text] de [lblHeure]. Agregamos en la pestaña [All] el siguiente código:


<%@ Page Language="VB" %>

<script runat="server">
    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' establece la hora en lblHeure
        lblHeure.Text = "Il est " + Date.Now.ToString("T")
    End Sub
</script>

<html>
<head>
    <title>webforms</title>
</head>
<body>
    <asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>

Cabe señalar que en el código del controlador no se declara el objeto [lblHeure] como se había hecho anteriormente:


    Protected lblHeure As New System.Web.UI.WebControls.Label

De hecho, todos los componentes de servidor <asp:> de la sección de presentación se declaran implícitamente en la sección de código de control. Por lo tanto, declararlos provoca un error de compilación, el cual indica que el objeto ya está declarado. Estamos listos para la ejecución. Seleccionamos la opción [View/Start] o el atajo [F5]. Cassini se inicia automáticamente con los siguientes parámetros:

Image

Aceptamos estos valores. El navegador predeterminado del sistema se inicia automáticamente para solicitar la URL [http://localhost/form2.aspx]. Obtenemos el siguiente resultado:

Image

A partir de ahora, utilizaremos principalmente la herramienta WebMatrix para facilitar la creación y las pruebas de los programas cortos que escribiremos.

7.4. El componente Literal

7.4.1. Uso

La etiqueta <asp:literal> permite insertar texto dinámico en el código de presentación de una página, al igual que la etiqueta <asp:label>. Su atributo principal es [Text], que representa el texto que se insertará tal cual en el flujo HTML de la página. Esta etiqueta es suficiente si no se tiene la intención de dar formato al texto que se desea insertar en el flujo HTML. De hecho, si bien la clase [Label] permite aplicar formato mediante atributos como [BorderColor, BorderWidth, Font, ...], la clase [Literal] no cuenta con ninguno de estos atributos. El lector puede reproducir íntegramente el ejemplo anterior sustituyendo el componente [Label] por un componente [Literal].

7.5. El componente Button

7.5.1. Uso

La etiqueta <asp:Button> permite insertar un botón de tipo [Submit] en un formulario, lo cual conlleva una gestión de eventos similar a la que se encuentra en las aplicaciones de Windows. Es este punto el que queremos profundizar aquí. Creamos la siguiente página [form3.aspx]:

Image

Esta página, construida con WebMatrix, tiene tres componentes:

n.º
nombre
tipo
propiedades
función
1
Button1
Botón
text=Botón1
botón de envío
2
Button2
Botón
text=Botón2
botón de envío
3
lblInfo
Etiqueta
text=
mensaje informativo

El código generado por WebMatrix para esta sección es el siguiente:

<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
    <title>asp:button</title>
</head>
<body>
    <form runat="server">
        <p>
            <asp:Button id="Button1" runat="server" Text="Bouton1"></asp:Button>
            <asp:Button id="Button2" runat="server" Text="Bouton2"></asp:Button>
        </p>
        <p>
            <asp:Label id="lblInfo" runat="server"></asp:Label>
        </p>
    </form>
</body>
</html>

Encontramos los componentes [Button] y [Label], utilizados en el diseño gráfico de la página, dentro de las etiquetas <asp:>. Cabe destacar la etiqueta <form runat="server">, que se generó automáticamente. Se trata de una etiqueta de servidor HTML, c.a.d. Una etiqueta HTML clásica, representada, sin embargo, por un objeto que el controlador puede manipular. El código de la etiqueta <form> se generará a partir del valor que el controlador le asigne a este objeto.

Agreguemos en la parte del código correspondiente al controlador el procedimiento [Page_Init] que maneja el evento [Init] de la página. Allí colocamos el código que guarda la solicitud del cliente en el archivo [request.txt]. Lo necesitaremos para entender el funcionamiento de los botones.

<script runat="server">
    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) 
        ' guarda la solicitud actual en request.txt del archivo de la página
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub
</script>

Cabe señalar que no hemos colocado la cláusula [Handles MyBase.Init] después de la declaración del procedimiento [Page_Init]. De hecho, el evento [Init] del objeto [Page] tiene un manejador predeterminado llamado [Page_Init]. Si se utiliza este nombre de manejador, la cláusula [Handles Page.Init] se vuelve innecesaria. Sin embargo, incluirla no genera ningún error.

7.5.2. Pruebas

Ejecutamos la aplicación bajo WebMatrix mediante [F5]. Obtenemos la siguiente página:

Image

El código HTML recibido por el navegador es el siguiente:

<html>
<head>
    <title>asp:button</title>
</head>
<body>
    <form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />

        <p>
            <input type="submit" name="Button1" value="Bouton1" id="Button1" />
            <input type="submit" name="Button2" value="Bouton2" id="Button2" />

        </p>
        <p>
            <span id="lblInfo"></span>
        </p>
    </form>
</body>
</html>

Cabe destacar lo siguiente:

  • la etiqueta <form runat="server"> se ha transformado en la etiqueta HTML
    <form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">

Se han establecido dos atributos: [method="post"] y [action="form3.aspx"]. ¿Se pueden asignar valores diferentes a estos atributos? Intentaremos aclarar este punto más adelante. Por ahora, basta con recordar que el formulario se enviará a la URL [form3.aspx]. También se han establecido otros dos atributos: [name, id]. En la mayoría de los casos, se ignoran. Sin embargo, si la página contiene código JavaScript que se ejecuta en el navegador, el atributo [name] de la etiqueta <form> resulta útil.

  • Las etiquetas <asp:button> se han convertido en etiquetas HTML de botones [submit]. Por lo tanto, al hacer clic en cualquiera de estos botones se provocará un «post» del formulario [_ctl10] a la URL [form3.aspx].
  • La etiqueta <asp:label> se ha convertido en una etiqueta HTML <span>
  • Se ha generado un campo oculto [__VIEWSTATE] con un valor extraño:
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />

Este campo representa, en forma codificada, el estado del formulario enviado al cliente. Este estado representa el valor de todos los componentes del formulario. Como [__VIEWSTATE] forma parte del formulario, su valor se enviará junto con el resto al servidor. Esto le permitirá al servidor saber qué componente del formulario ha cambiado de valor y, eventualmente, tomar decisiones. Estas decisiones tomarán la forma de eventos del tipo «el TextBox tal ha cambiado de valor».

7.5.3. Las solicitudes del cliente

Una vez obtenida la página [form3.aspx] en el navegador, volvamos a solicitarla y observemos la solicitud que envió el navegador para obtenerla. Recordemos que nuestra aplicación la almacena en el archivo [request.txt] dentro de la carpeta de la aplicación:

GET /form3.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

Aquí tenemos un GET clásico. Ahora hagamos clic en el botón [Bouton1] de la página en el navegador. Aparentemente no pasa nada. Sin embargo, sabemos que el formulario se ha enviado. Así lo indica el código HTML de la página. Esto se confirma con el nuevo contenido de [request.txt]:

POST /form3.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 80
Content-Type: application/x-www-form-urlencoded
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/form3.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

__VIEWSTATE=dDwxNTY0NjIwMjUwOzs%2B2mcnJczeuvF2PEfvmtv7uiUhWUw%3D&Button1=Bouton1

El primer encabezado HTTP indica claramente que el cliente realizó un POST hacia la URL [/form3.aspx]. La última línea muestra los valores enviados:

  • el valor del campo oculto __VIEWSTATE
  • el valor del botón en el que se hizo clic

Si hacemos clic en [Bouton2], los valores enviados por el navegador son los siguientes:

__VIEWSTATE=dDwxNTY0NjIwMjUwOzs%2B2mcnJczeuvF2PEfvmtv7uiUhWUw%3D&Button2=Bouton2

El valor del campo oculto sigue siendo el mismo, pero es el valor de [Button2] el que se ha enviado. Por lo tanto, el servidor puede saber qué botón se utilizó. Lo utilizará para desencadenar un evento que podrá ser procesado por la página, una vez que esta se haya cargado.

7.5.4. Manejar el evento Click de un objeto Button

Recordemos cómo funciona una página .aspx. Esta es un objeto derivado de la clase [Page]. Llamemos a la clase derivada [unePage]. Cuando el servidor recibe una solicitud para dicha página, se instancia un objeto de tipo [unePage] mediante la operación new unePage(...). A continuación, el servidor genera dos eventos llamados [Init] y [Load] en ese orden. El objeto [unePage] puede manejarlos proporcionando los controladores de eventos [Page_Init] y [Page_Load]. Posteriormente se generarán otros eventos. Tendremos la oportunidad de volver sobre esto más adelante. Si la solicitud del cliente es un POST, el servidor generará el evento [Click] del botón que provocó ese POST. Si la clase [unePage] ha definido un controlador para este evento, este será invocado. Veamos este mecanismo con WebMatrix. En la pestaña [Design] de [form3.aspx], hagamos doble clic en el botón [Bouton1]. Esto nos lleva automáticamente a la pestaña [Code], al cuerpo de un procedimiento llamado [Button1_Click]. Para entenderlo mejor, vayamos a la pestaña [All] y veamos el código completo. Se han realizado las siguientes modificaciones:

<%@ Page Language="VB" %>
<script runat="server">

...
    Sub Button1_Click(sender As Object, e As EventArgs)

    End Sub

</script>
<html>
...
<body>
    <form runat="server">
...
            <asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
    </form>
</body>
</html>

Se ha agregado un nuevo atributo [onclick="Button1_Click"] a la etiqueta <asp:Button> de [Button1]. Este atributo indica el procedimiento encargado de manejar el evento [Click] en el objeto [Button1], en este caso el procedimiento [Button1_Click]. Ahora solo nos queda escribirlo:

    Sub Button1_Click(sender As Object, e As EventArgs)
        ' clic en el botón 1
        lblInfo.Text="Vous avez cliqué sur [Bouton1]"
    End Sub

El procedimiento inserta un mensaje informativo en la etiqueta [lblInfo]. Procedemos de la misma manera con el botón [Bouton2] para obtener la siguiente página nueva [form3.aspx]:

<%@ Page Language="VB" %>
<script runat="server">

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
        ' guarda la solicitud actual en request.txt de la carpeta de la página
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub

    Sub Button1_Click(sender As Object, e As EventArgs)
        ' haga clic en el botón 1
        lblInfo.Text="Vous avez cliqué sur [Bouton1]"
    End Sub

    Sub Button2_Click(sender As Object, e As EventArgs)
        ' hace clic en el botón 2
        lblInfo.Text="Vous avez cliqué sur [Bouton2]"
    End Sub

</script>
<html>
<head>
    <title>asp:button</title>
</head>
<body>
    <form runat="server">
        <p>
            <asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
            <asp:Button id="Button2" onclick="Button2_Click" runat="server" Text="Bouton2" BorderStyle="None"></asp:Button>
        </p>
        <p>
            <asp:Label id="lblInfo" runat="server"></asp:Label>
        </p>
    </form>
</body>
</html>

Ejecutamos el proceso mediante [F5] para obtener la siguiente página:

Image

Si observamos el código HTML recibido, veremos que no ha cambiado con respecto al de la versión anterior de la página:

<html>
<head>
    <title>asp:button</title>
</head>
<body>
    <form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />

        <p>
            <input type="submit" name="Button1" value="Bouton1" id="Button1" />
            <input type="submit" name="Button2" value="Bouton2" id="Button2" />
        </p>
        <p>
            <span id="lblInfo"></span>
        </p>
    </form>
</body>
</html>

Si hacemos clic en [Bouton1], obtenemos la siguiente respuesta:

Image

El código HTML recibido para esta respuesta es el siguiente:

<html>
<head>
    <title>asp:button</title>
</head>
<body>
    <form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwO3Q8O2w8aTwxPjs+O2w8dDw7bDxpPDU+Oz47bDx0PHA8cDxsPFRleHQ7PjtsPFZvdXMgYXZleiBjbGlxdcOpIHN1ciBbQm91dG9uMV07Pj47Pjs7Pjs+Pjs+Pjs+4oO98Vd244kj0lPMXReWOwJ1WW0=" />

        <p>
            <input type="submit" name="Button1" value="Bouton1" id="Button1" />
            <input type="submit" name="Button2" value="Bouton2" id="Button2" />
        </p>
        <p>
            <span id="lblInfo">Vous avez cliqué sur [Bouton1]</span>
        </p>
    </form>
</body>
</html>

Podemos observar que el campo oculto [__VIEWSTATE] ha cambiado de valor. Esto refleja el cambio de valor del componente [lblInfo].

7.5.5. Los eventos del ciclo de vida de una aplicación ASP.NET

La documentación ASP.NET presenta la lista de eventos generados por el servidor durante el ciclo de vida de una aplicación:

  • al recibir la primera solicitud de la aplicación, se generará el evento [Start] en el objeto [HttpApplication] de la aplicación. Este evento puede gestionarse mediante el procedimiento [Application_Start] del archivo [global.asax] de la aplicación.

A continuación, tendremos una serie de eventos que se repetirán para cada solicitud recibida:

  • si la solicitud no ha enviado un token de sesión, se inicia una nueva sesión y se genera un evento [Start] en el objeto [Session] asociado a la solicitud. Este evento puede gestionarse mediante el procedimiento [Session_Start] del archivo [global.asax] de la aplicación.
  • El servidor genera el evento [BeginRequest] en el objeto [HttpApplication]. Este evento puede gestionarse mediante el procedimiento [Application_BeginRequest] del archivo [global.asax] de la aplicación.
  • El servidor carga la página solicitada por la consulta. Creará una instancia del objeto [Page] y luego generará dos eventos en ese objeto: [Init] y [Load]. Estos dos eventos pueden ser manejados por los procedimientos [Page_Init] y [Page_Load] de la página.
  • A partir de los valores enviados que se hayan recibido, el servidor generará otros eventos: [TextChanged] para un componente [TextBox] cuyo valor ha cambiado, [CheckedChanged] para un botón de opción cuyo valor ha cambiado, [SelectedIndexChanged] para una lista cuyo elemento seleccionado ha cambiado, ... Tendremos la oportunidad de mencionar los principales eventos para cada uno de los componentes de servidor que vamos a presentar. Cada evento E en un objeto denominado O puede ser manejado por un procedimiento con el nombre O_E.
  • El orden de manejo de los eventos anteriores no está garantizado. Por lo tanto, los manejadores no deben hacer ninguna suposición sobre dicho orden. Sin embargo, se garantiza que el evento [Click] del botón que provocó el POST se procesa en último lugar.
  • Una vez que la página está lista, el servidor la enviará al cliente. Antes de hacerlo, genera el evento [PreRender], que puede ser manejado por el procedimiento [Page_PreRender] de la página.
  • Una vez enviada la respuesta HTML al cliente, la página se descargará de la memoria. En ese momento se generarán dos eventos: [Unload] y [Disposed]. La página puede utilizar estos eventos para liberar recursos.

La aplicación también puede recibir eventos fuera de una solicitud del cliente:

  • el evento [End] en un objeto [Session] de la aplicación ocurre cuando finaliza una sesión. Esto puede suceder a petición explícita del código de una página o porque se ha excedido el tiempo de vida asignado a la sesión. El procedimiento [Session_End] del archivo [global.asax] gestiona este evento. En él, por lo general, se liberan los recursos obtenidos en [Session_Start].
  • El evento [End] en el objeto [HttpApplication] de la aplicación ocurre cuando la aplicación finaliza. Esto sucede, en particular, al detener el servidor web. El procedimiento [Application_End] del archivo [global.asax] gestiona este evento. En él, por lo general, se liberan los recursos obtenidos en [Application_Start].

Cabe destacar los siguientes puntos:

  • el modelo de eventos anterior se basa en intercambios clásicos cliente-servidor HTTP. Esto se observa claramente al examinar los encabezados HTTP intercambiados.
  • El procesamiento de los eventos anteriores siempre se realiza del lado del servidor. Por supuesto, al hacer clic en un botón, un script de JavaScript del lado del servidor puede procesarlo. Pero en ese caso no se trata de un evento del servidor y estamos ante una tecnología independiente de ASP.NET.

¿En qué momento se procesan los eventos, ya sea del lado del servidor (eventos relacionados con los componentes del servidor) o del lado del navegador mediante scripts de JavaScript?

Tomemos el ejemplo de una lista desplegable. Cuando el usuario cambia el elemento seleccionado en ella, el evento (cambio del elemento seleccionado) puede procesarse o no, y si se procesa, puede hacerlo en diferentes momentos.

  • Si queremos procesarlo de inmediato, tenemos dos opciones:
    • puede ser procesado por el navegador mediante un script de JavaScript. En ese caso, el servidor no interviene. Para que esto sea posible, la página debe poder reconstruirse con los valores presentes en ella.
    • Puede ser procesado por el servidor. Para ello, solo hay una solución: el formulario debe enviarse al servidor para su procesamiento. Por lo tanto, tenemos una operación [submit]. Veremos que, en este caso, se utiliza un componente de servidor llamado [DropDownList] y se establece su atributo [AutoPostBack] en [true]. Esto significa que, en caso de que cambie el elemento seleccionado en la lista desplegable, el formulario debe enviarse inmediatamente al servidor. En este caso, el servidor genera, para el objeto [DropDownList], un código HTML asociado a una función de JavaScript encargada de ejecutar un [submit] tan pronto como ocurra el evento «cambio del elemento seleccionado». Este [submit] enviará el formulario al servidor y, en él, se incluirán campos ocultos para indicar que el [post] proviene de un cambio de selección en la lista desplegable. El servidor generará entonces el evento [SelectedIndexChanged], que la página podrá gestionar.
    • Si se desea procesarlo, pero no de inmediato, se establece el atributo [AutoPostBack] del componente de servidor [DropDownList] en [false]. En este caso, el servidor genera, para el objeto [DropDownList], el código HTML clásico de una lista <select> sin ninguna función de JavaScript asociada. Por lo tanto, no ocurre nada cuando el usuario cambia la selección en la lista desplegable. Sin embargo, cuando valide el formulario mediante un botón [submit], por ejemplo, el servidor podrá reconocer que ha habido un cambio en la selección. De hecho, hemos visto que el formulario enviado al servidor tenía un campo oculto [__VIEWSTATE] que representaba, en forma codificada, el estado de todos los elementos del formulario enviado. Cuando el servidor recibe el nuevo formulario enviado por el cliente, podrá verificar si el elemento seleccionado en la lista desplegable ha cambiado o no. De ser así, generará el evento [SelectedIndexChanged], que la página podrá entonces gestionar. Para distinguir este mecanismo del anterior, algunos autores dicen que el evento «cambio de selección» se ha «almacenado en caché» cuando ocurrió en el navegador. El servidor lo procesará solo cuando el navegador le envíe el formulario, a menudo tras hacer clic en un botón [submit].
    • Por último, si no se desea procesar el evento, se establece el atributo [AutoPostBack] del componente de servidor [DropDownList] en [false] y no se escribe el manejador de su evento [SelectedIndexChanged].

Una vez comprendido el mecanismo de manejo de eventos, el desarrollador no diseñará una aplicación web como si fuera una aplicación de Windows. De hecho, si bien un cambio de selección en un cuadro combinado de una aplicación de Windows puede utilizarse para modificar de inmediato el aspecto del formulario en el que se encuentra, en una aplicación web se dudará más en procesar este evento de inmediato si implica un «post» del formulario al servidor y, por lo tanto, un intercambio de datos entre el cliente y el servidor. Por eso, la propiedad [AutoPostBack] de los componentes de servidor se establece en [false] de forma predeterminada. Por otra parte, el mecanismo [AutoPostBack], que se basa en scripts de JavaScript generados automáticamente por el servidor web en el formulario enviado al cliente, solo puede utilizarse si se tiene la certeza de que el navegador del cliente ha autorizado la ejecución de scripts de JavaScript en su navegador. Por lo tanto, los formularios suelen construirse de la siguiente manera:

  • los componentes del servidor del formulario tienen su propiedad [AutoPostBack] a [false]
  • el formulario cuenta con uno o varios botones encargados de realizar la acción [POST] del formulario
  • en el código del controlador de la página se escriben los manejadores de los únicos eventos que se desean manejar, por lo general el evento [Click] en uno de los botones.

7.6. El componente TextBox

7.6.1. Uso

La etiqueta <asp:TextBox> permite insertar un campo de entrada en el código de presentación de una página. Creamos una página [form4.aspx] para obtener la siguiente presentación:

Image

4321Esta página, creada con WebMatrix, contiene los siguientes componentes:

n.º
nombre
tipo
propiedades
función
1
TextBox1
TextBox
AutoPostback=true
Text=
campo de entrada
2
TextBox2
TextBox
AutoPostback=false
Texto=
campo de entrada
3
lblInfo1
Etiqueta
text=
mensaje informativo sobre el contenido de [TextBox1]
3
lblInfo2
Etiqueta
text=
mensaje informativo sobre el contenido de [TextBox2]

El código generado por WebMatrix para esta sección es el siguiente:

<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
    <title>asp:textbox</title>
</head>
<body>
    <form runat="server">
        <p>
            Texte 1 :
            <asp:TextBox id="TextBox1" runat="server" AutoPostBack="True"></asp:TextBox>
        </p>
        <p>
            Texte 2 :
            <asp:TextBox id="TextBox2" runat="server"></asp:TextBox>
        </p>
        <p>
            <asp:Label id="lblInfo1" runat="server"></asp:Label>
        </p>
        <p>
            <asp:Label id="lblInfo2" runat="server"></asp:Label>
        </p>
    </form>
</body>
</html>

En la pestaña [Design], haz doble clic en el componente [TextBox1]. A continuación, se genera el esqueleto del controlador del evento [TextChanged] de este objeto (pestaña [All]):

<%@ Page Language="VB" %>
<script runat="server">

    Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
    End Sub
</script>
<html>
...
<body>
...
            <asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
        </p>
....
    </form>
</body>
</html>

Se ha agregado el atributo [OnTextChanged="TextBox1_TextChanged"] a la etiqueta <asp:TextBox id="TextBox1"> para designar el controlador del evento [TextChanged] en [TextBox1]. Ahora vamos a escribir el procedimiento [TextBox1_Changed].

    Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
      ' cambio de texto
      lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
    End Sub

En el procedimiento, escribimos en la etiqueta [lblInfo1] un mensaje que señala el evento e indica el contenido de [TextBox1]. Hacemos lo mismo con [TextBox2]. También indicamos la hora para poder dar un mejor seguimiento al procesamiento de los eventos. El código final de [form4.aspx] es el siguiente:

<%@ Page Language="VB" %>
<script runat="server">

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
        ' guarda la solicitud actual en request.txt de la carpeta de la página
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub

    Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
      ' cambio de texto
      lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
    End Sub

    Sub TextBox2_TextChanged(sender As Object, e As EventArgs)
      ' cambio de texto
      lblInfo2.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox2]. Texte 2=["+textbox2.Text+"]"
    End Sub

</script>
<html>
<head>
    <title>asp:textbox</title>
</head>
<body>
    <form runat="server">
        <p>
            Texte 1 :
            <asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
        </p>
        <p>
            Texte 2 :
            <asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged"></asp:TextBox>
        </p>
        <p>
            <asp:Label id="lblInfo1" runat="server"></asp:Label>
        </p>
        <p>
            <asp:Label id="lblInfo2" runat="server"></asp:Label>
        </p>
    </form>
</body>
</html>

Hemos agregado el procedimiento [Page_Init] para guardar la solicitud del cliente como en el ejemplo anterior.

7.6.2. Pruebas

Ejecutamos la aplicación con el procedimiento WebMatrix a través de [F5]. Obtenemos la siguiente página:

Image

El código HTML recibido por el navegador es el siguiente:

<html>
<head>
    <title>asp:textbox</title>
</head>
<body>
    <form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u+/OrTD" />

<script language="javascript">
<!--
    function __doPostBack(eventTarget, eventArgument) {
        var theform = document._ctl0;
        theform.__EVENTTARGET.value = eventTarget;
        theform.__EVENTARGUMENT.value = eventArgument;
        theform.submit();
    }
// -->
</script>

        <p>
            Texte 1 :
            <input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1"></span>

        </p>
        <p>
            <span id="lblInfo2"></span>
        </p>
    </form>
</body>
</html>

Hay muchos elementos en este código que han sido generados automáticamente por el servidor. Destacamos los siguientes puntos:

  • hay tres campos ocultos: [__VIEWSTATE], que ya hemos visto, [__EventTarget] y [__EventArgument]. Estos dos últimos campos se utilizan para gestionar el evento «change» del navegador en el campo de entrada [TextBox1]
  • las etiquetas de servidor <asp:textbox> han generado las etiquetas HTML <input type="text" ...> que corresponden a los campos de entrada
  • la etiqueta de servidor <asp:textbox id="TextBox1" AutoPostBack="true" ...> ha generado una etiqueta <input type="text" ...> con un atributo [onchange="__doPostBack('TextBox1','')"]. Este atributo indica que, en caso de que cambie el contenido de [TextBox1], se debe ejecutar la función de JavaScript [_doPostBack(...)]. Esta es la siguiente:
<script language="javascript">
<!--
    function __doPostBack(eventTarget, eventArgument) {
        var theform = document._ctl0;
        theform.__EVENTTARGET.value = eventTarget;
        theform.__EVENTARGUMENT.value = eventArgument;
        theform.submit();
    }
// -->
</script>

¿Qué hace la función anterior? Asigna un valor a cada uno de los dos campos ocultos [__EventTarget] y [__EventArgument] y luego envía el formulario. De este modo, el formulario se envía al servidor. Este es el efecto de [AutoPostBack]. El evento del navegador «change» provoca la ejecución del código «__doPostBack('TextBox1','')». De ello se deduce que, en el formulario enviado, el campo oculto [__EventTarget] tendrá el valor 'TextBox1' y el campo oculto [__EventArgument] tendrá el valor ''. Esto permitirá al servidor identificar el componente que generó el POST.

  • La etiqueta de servidor <asp:textbox id="TextBox2"...> generó una etiqueta <input type="text" ...> convencional porque su atributo [AutoPostBack] no estaba configurado en [true].
  • La etiqueta <form> indica que el formulario se enviará a [form4.aspx]:
    <form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">

Hagamos nuestra primera prueba. Escribamos un texto en el primer campo de entrada:

Image

luego coloquemos el cursor en el segundo campo de entrada. De inmediato, aparece una nueva página:

Image

¿Qué pasó? Cuando el cursor salió del primer campo de entrada, el navegador verificó si el contenido de este había cambiado. Así fue. Por lo tanto, generó, del lado del navegador, el evento [change] en el campo HTML [TextBox1]. Entonces vimos que se ejecutaba una función de JavaScript que enviaba el formulario a [form4.aspx]. Por lo tanto, el servidor recargó esa página. Los valores enviados por el formulario permitieron al servidor, a su vez, detectar que el contenido de la etiqueta de servidor [TextBox1] había cambiado. Por lo tanto, se ejecutó el procedimiento [TextBox1_Changed] del lado del servidor. Este colocó un mensaje en la etiqueta [lblInfo1]. Una vez finalizado este procedimiento, se envió [form4.aspx] al navegador. Por eso ahora tenemos un texto en [lblInfo1]. Dicho esto, puede resultar sorprendente que haya algo en el campo de entrada [TextBox1]. De hecho, ningún procedimiento ejecutado del lado del servidor asigna un valor a este campo. Se trata de un mecanismo general de los formularios web ASP.NET: el servidor devuelve el formulario tal como lo recibió. Para ello, reasigna a los componentes el valor que el cliente envió para ellos. Para algunos componentes, el cliente no envía ningún valor. Este es el caso, por ejemplo, de los componentes <asp:label>, que se traducen a las etiquetas <span>. Recordemos que el formulario tiene un campo oculto que representa el estado del formulario cuando se envía al cliente. Este estado es la suma de los estados de todos los componentes del formulario, incluidos los componentes <asp:label>, si los hay. Como el campo oculto [__VIEWSTATE] es enviado por el navegador del cliente, el servidor puede restaurar el estado anterior de todos los componentes del formulario. Solo le queda modificar aquellos cuyos valores hayan sido alterados por el POST.

Veamos ahora en [request.txt] la solicitud que realizó el navegador:

POST /form4.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 137
Content-Type: application/x-www-form-urlencoded
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/form4.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u%2B%2FOrTD&TextBox1=premier+texte&TextBox2=

Se ve perfectamente el código POST, así como los parámetros enviados. Regresemos a nuestro navegador e ingresemos un texto en el segundo campo de entrada:

Image

Volvamos al campo de entrada n.º 1 para escribir un nuevo texto: esta vez no pasa nada. ¿Por qué? Porque el campo de entrada [TextBox2] no tiene su propiedad [AutoPostBack] en [true], por lo que la etiqueta <input type="text"...> generada para él no maneja el evento [Change], como lo muestra su código HTML:

            <input name="TextBox2" type="text" id="TextBox2" />

Por lo tanto, no ocurre ningún evento al salir del campo de entrada n.º 2. Ahora ingresemos un nuevo texto en el campo n.º 1:

Image

Salgamos del campo de entrada n.º 1. Inmediatamente se detecta el evento [Change] en este campo y el formulario se envía al servidor, el cual devuelve la siguiente página:

Image

¿Qué pasó? En primer lugar, el navegador envió el formulario. Esto se refleja en la solicitud del cliente almacenada en [request.txt]:

POST /form4.aspx HTTP/1.1
....
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTt0PDtsPGk8MT47PjtsPHQ8O2w8aTwxPjtpPDU%2BOz47bDx0PHA8cDxsPFRleHQ7PjtsPHByZW1pZXIgdGV4dGU7Pj47Pjs7Pjt0PHA8cDxsPFRleHQ7PjtsPDE4OjQyOjI5OiBldnQgW1RleHRDaGFuZ2VkXSBzdXIgW1RleHRCb3gxXS4gVGV4dGUgMT1bcHJlbWllciB0ZXh0ZV07Pj47Pjs7Pjs%2BPjs%2BPjs%2BxLOermpUUUz5rTAa%2FFsjda6lVmo%3D&TextBox1=troisi%C3%A8me+texte&TextBox2=second+texte

El servidor comienza por restaurar los valores anteriores de los componentes mediante el campo oculto [__VIEWSTATE] que el cliente le envió. Gracias a los campos enviados [TextBox1] y [TextBox2], asignará a los componentes [TextBox1] y [TextBox2] los valores que se le han enviado. Es mediante este mecanismo que el cliente recuperará el formulario tal como fue enviado. Luego, siempre gracias a los campos enviados [__VIEWSTATE], [TextBox1] y [TextBox2], el servidor detectará que los valores de los campos de entrada [TextBox1] y [TextBox2] han cambiado. Por lo tanto, generará los eventos [TextChanged] para estos dos objetos. Se ejecutarán los procedimientos [TextBox1_TextChanged] y [TextBox2_TextChanged], y las etiquetas [labelInfo1] y [labelInfo2] recibirán un nuevo valor. A continuación, la página [form4.aspx], ya modificada, se envía de vuelta al cliente.

Ahora modificamos nuevamente el campo de entrada n.º 1:

Image

Al retirar el cursor de entrada del campo 1, se produce el evento [Change] en el navegador. A continuación, se desarrolla la secuencia de eventos ya explicada (envío desde el navegador al servidor, ..., envío de la respuesta del servidor). Se obtiene la siguiente respuesta:

Image

Por la hora que aparece en cada uno de los mensajes, vemos que solo se ejecutó el procedimiento [TextBox1_Changed] en el servidor. El procedimiento [TextBox2_TextChanged] no se ejecutó precisamente porque el valor de [TextBox2] no cambió. Por último, introduzcamos un nuevo texto en el campo n.º 2:

Image

Luego, coloquemos el cursor en el campo n.º 1 y volvamos a colocarlo en el campo n.º 2. La página no cambia. ¿Por qué? Porque no cambiamos el valor del campo n.º 1, el evento del navegador [Change] no se produce cuando salimos de ese campo. Por lo tanto, el formulario no se envía al servidor. Así que nada cambia en la página. Es precisamente el hecho de que el contenido de [lblInfo2] no cambie lo que nos indica que no existe POST. Si existiera uno, el servidor detectaría que el contenido de [TextBox2] ha cambiado y debería reflejar este hecho en [lblInfo2].

De este ejemplo se desprende que no tiene sentido asignar la propiedad [AutoPostBack] de un [TextBox] a [true]. Esto provoca un intercambio cliente-servidor innecesario la mayor parte del tiempo.

7.6.3. La función del campo __VIEWSTATE

Hemos visto que el servidor incluía sistemáticamente un campo oculto llamado __VIEWSTATE en el formulario que generaba. Mencionamos que este campo representaba el estado del formulario y que, si se le devolvía este campo oculto, el servidor podía reconstruir el valor anterior del formulario. El estado de un formulario es la suma de los estados de sus componentes. Cada uno de ellos tiene una propiedad [EnableViewState] con un valor booleano que indica si el estado del componente debe incluirse o no en el campo oculto [__VIEWSATE]. Por defecto, esta propiedad tiene el valor [true], lo que hace que el estado de todos los componentes de un formulario se coloque en [__VIEWSTATE]. A veces, esto no es lo deseable.

Hagamos algunas pruebas para comprender mejor la función de la propiedad [EnableViewState]. Establezcamos esta propiedad en [false] para los dos campos de entrada:

...
            <asp:TextBox id="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged" AutoPostBack="True" EnableViewState="False"></asp:TextBox>
...
            <asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged" EnableViewState="False"></asp:TextBox>
...

Ahora ejecutemos la aplicación y escribamos un primer texto en el campo n.º 1 antes de pasar al campo n.º 2. A continuación, se envía un POST al servidor y obtenemos la siguiente respuesta:

Image

Escribamos un texto en el campo n.º 2, modifiquemos el del campo n.º 1 y luego regresemos al campo n.º 2 (en ese orden). Se envía una nueva solicitud POST al servidor y obtenemos la siguiente respuesta:

Image

Por ahora, todo sigue igual que antes. Ahora modifiquemos el contenido del campo n.º 1 y luego pasemos al campo n.º 2. Se envía un nuevo POST. La respuesta del servidor es la siguiente:

Image

Esta vez, hay un cambio. El servidor detectó un evento [TextChanged] en el campo n.º 2, ya que se modificó la hora de [lblInfo2]. Sin embargo, no hubo ningún cambio. Esto se debe a la propiedad [EnableViewState=false] de [TextBox2]. Esta propiedad hace que el servidor no haya incluido en el campo [__VIEWSTATE] del formulario el estado anterior de [TextBox2]. Esto equivale a asignarle la cadena vacía como estado anterior. Cuando se produjo el POST debido al cambio en el contenido de [TextBox1], el servidor comparó el valor actual de [TextBox2] —que era [deux]— con su valor anterior (la cadena vacía). Concluyó que [TextBox2] había cambiado de valor y generó el evento [TextChanged] para [TextBox2]. Podemos validar este funcionamiento colocando la cadena vacía en [TextBox2]. Según lo que acabamos de explicar, el servidor no debería generar el evento [TextChanged] para [TextBox2]. Probemos:

Image

Eso es precisamente lo que ocurrió. La hora y el contenido de [lblInfo2] muestran que el procedimiento [TextBox2_TextChanged] no se ejecutó. Teniendo esto en cuenta, examinemos la propiedad [EnableViewState] de los cuatro componentes del formulario:

TextBox1
queremos mantener el estado de este componente para que el servidor sepa si ha cambiado o no
TextBox2
Lo mismo
lblInfo1
No queremos mantener el estado de este componente. Queremos que el texto se recalcule con cada nuevo POST. Si no se recalcula, debe quedar en blanco. Todo esto se logra con [EnableViewState=false]
lblInfo2 
ídem

Nuestra página de presentación queda así:

...
            <asp:TextBox id="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged" AutoPostBack="True"></asp:TextBox>
...
            <asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged"></asp:TextBox>
....
            <asp:Label id="lblInfo1" runat="server" enableviewstate="False"></asp:Label>
...
            <asp:Label id="lblInfo2" runat="server" enableviewstate="False"></asp:Label>
...

Repitamos la misma serie de pruebas que antes. Donde obtuvimos la pantalla

version 1

Image

ahora obtenemos:

version 2

Image

En este paso, cambiábamos el contenido del campo 1 sin modificar el del campo 2, lo que hacía que el procedimiento [TextBox2_TextChanged] no se ejecutara en el servidor; esto implicaba que el campo [lblInfo2] no recibía un nuevo valor. Por lo tanto, se muestra con su valor anterior. En la versión 1 ([EnableViewState=true]), este valor anterior era el valor ingresado. En la versión 2 ([EnableViewState=false]), este valor anterior es la cadena vacía.

A veces no es necesario conservar el estado anterior de los componentes. En lugar de establecer [EnableViewState=false] para cada uno de ellos, se puede indicar que la página no debe mantener su estado. Esto se hace en la directiva [Page] del código de presentación:

<%@ Page Language="VB" EnableViewState="False" %>

En este caso, sin importar cuál sea el valor de su propiedad [EnableViewState], el estado de un componente no se guarda en el campo oculto [__VIEWSTATE]. Entonces, todo ocurre como si su estado anterior fuera la cadena vacía.

Ahora utilicemos el cliente [curl] para destacar otros mecanismos. Primero solicitamos la URL [http://localhost/form4.aspx]:

dos>curl --include --url http://localhost/form4.aspx

HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Sun, 04 Apr 2004 17:51:14 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1077
Connection: Close


<html>
<head>
    <title>asp:textbox</title>
</head>
<body>
    <form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u+/OrTD" />

<script language="javascript">
<!--
    function __doPostBack(eventTarget, eventArgument) {
        var theform = document._ctl0;
        theform.__EVENTTARGET.value = eventTarget;
        theform.__EVENTARGUMENT.value = eventArgument;
        theform.submit();
    }
// -->
</script>

        <p>
            Texte 1 :
            <input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1"></span>
        </p>
        <p>
            <span id="lblInfo2"></span>
        </p>
    </form>
</body>
</html>

Recibimos del servidor el código HTML de [form4.aspx]. No es diferente del que había recibido el navegador. Recordemos aquí la solicitud realizada por el navegador al enviar el formulario:

POST /form4.aspx HTTP/1.1
Connection: keep-alive
...
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u%2B%2FOrTD&TextBox1=premier+texte&TextBox2=

Hagamos lo mismo con POST, pero sin enviar el campo [__VIEWSTATE]:

dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=primer+texto --data TextBox2=

...................
        <p>
            Texte 1 :
            <input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1">19:57:48: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
        </p>
        <p>
            <span id="lblInfo2"></span>
        </p>
..............

Cabe destacar lo siguiente:

  • el servidor detectó un evento [TextChanged] en [TextBox1], ya que generó el texto [lblInfo1]. La ausencia de [__VIEWSTATE] no le afectó. En su ausencia, supone que el valor anterior de un campo de entrada es la cadena vacía.
  • Pudo volver a colocar el texto enviado para [TextBox1] en el atributo [value] de la etiqueta [TextBox1] para que el campo [TextBox1] volviera a aparecer con el valor ingresado. Para ello, no necesita [__VIEWSTATE], sino solo el valor enviado para [TextBox1]

Ahora, volvamos a realizar la misma consulta sin cambiar nada. Obtenemos la siguiente respuesta nueva:

dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=primer+texto --data TextBox2=


        <p>
            Texte 1 :
            <input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1">20:05:47: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
        </p>
        <p>
            <span id="lblInfo2"></span>
        </p>

Al no existir [__VIEWSTATE], el servidor no pudo detectar que el valor del campo [TextBox1] no había cambiado. Por lo tanto, actúa como si el valor anterior fuera la cadena vacía. Por lo tanto, generó aquí el evento [TextChanged] sobre [TextBox1]. Repitamos la misma consulta, pero esta vez con el campo [TextBox1] vacío y [TextBox2] no vacío:

dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox2=segundo+texto --data TextBox1=
......
        <p>
            Texte 1 :
            <input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" value="second texte" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1"></span>
        </p>
        <p>
            <span id="lblInfo2">20:11:54: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
        </p>
......

Al no existir [__VIEWSTATE], se consideró que el valor anterior de [TextBox1] era la cadena vacía. Dado que el valor registrado de [TextBox1] también era la cadena vacía, no se generó el evento [TextChanged] sobre [TextBox1]. El procedimiento [TextBox1_TextChanged] no se ejecutó y, por lo tanto, el campo [lblInfo1] no recibió un nuevo valor. Se sabe que, en este caso, el componente conserva su valor anterior. Sin embargo, aquí no es así: [lblInfo1] ha perdido su valor anterior. Esto se debe a que dicho valor se busca en [__VIEWSTATE]. Como este campo no existe, se le asignó la cadena vacía a [lblInfo1]. En el caso de [TextBox2], el servidor comparó su valor publicado, [second texte], con su valor anterior. Al no existir [__VIEWSTATE], ese valor anterior es igual a la cadena vacía. Dado que el valor publicado de [TextBox2] es diferente de la cadena vacía, se generó el evento [TextChanged] sobre [TextBox2]. Se ejecutó el procedimiento [TextBox2_TextChanged] y el campo [lblInfo2] recibió un nuevo valor.

Cabe preguntarse si los parámetros [__EVENTTARGET] y [__EVENTARGUMENT] son realmente útiles. Al no enviar estos parámetros, el servidor no sabrá qué evento provocó el [submit]. Probemos:

dos>curl --include --url http://localhost/form4.aspx --data TextBox2=segundo+texto --data TextBox1=primer+texto
..............................
        <p>
            Texte 1 :
            <input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1"></span>
        </p>
        <p>
            <span id="lblInfo2"></span>
        </p>
    </form>
.....................

Se observa que no se ha procesado ningún evento [TextChanged]. Además, los campos enviados [TextBox1] y [TextBox2] no recuperan los valores que se enviaron. De hecho, todo ocurre como si se hubiera ejecutado un GET. Todo vuelve a la normalidad si se incluye el campo [__EVENTTARGET] entre los campos procesados, aunque no tenga ningún valor:

dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET= --data TextBox2=segundo+texto --data TextBox1=primer+texto
.......
        <p>
            Texte 1 :
            <input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" value="second texte" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1">20:34:14: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
        </p>
        <p>
            <span id="lblInfo2">20:34:14: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
        </p>
........

7.6.4. Otras propiedades del componente TextBox

El componente de servidor [TextBox] también permite generar las etiquetas HTML <input type="password"..> y <textarea>..</textarea>, c.a.d. Estas etiquetas corresponden, respectivamente, a un campo de entrada protegida y a un campo de entrada de varias líneas. Esta generación se controla mediante la propiedad [TextMode] del componente [TextBox]. Tiene tres valores posibles:

valor
etiqueta HTML generada
SingleLine
<input type="text" ...>
MultiLine
<textarea>...</textarea>
Password
<input type="password ...>

Analizamos el uso de estas propiedades con el siguiente ejemplo [form5.aspx]:

Image

n.º
nombre
tipo
propiedades
función
1
btnAjouter
Botón
 
botón [submit]: sirve para agregar el contenido de [TextBox1] al de [TextBox2]
2
TextBox1
TextBox
TextMode=Contraseña
Text=
campo de entrada protegido
3
TextBox2
TextBox
TextMode=Multilínea
Text=
agrupa las entradas realizadas en [TextBox1]

La propiedad [EnableViewState] de la página se establece en [false]. Del lado del servidor, manejamos el evento de clic en el botón [btnAjouter]:

Sub btnAjouter_Click(sender As Object, e As EventArgs)
  ': se suma el contenido de [textBox1] al de [TextBox2]
  textbox2.text=textbox2.text + textbox1.text+controlchars.crlf
End Sub

Para entender este código, hay que recordar cómo se procesa el evento POST de un formulario. Primero se ejecutan los procedimientos [Page_Init] y [Page_Load]. Luego vienen todos los procedimientos de eventos almacenados en caché. Finalmente, se ejecuta el procedimiento que maneja el evento que provocó el [POST], en este caso el procedimiento [btnAjouter_Click]. Cuando se ejecutan los manejadores de eventos, todos los componentes de la página que tienen un valor en el POST han adoptado ese valor. Los demás han recuperado su valor anterior si su propiedad [EnableViewState] era [true], o su valor de diseño si su propiedad [EnableViewState] era [false]. En este caso, los valores de los campos [TextBox1] y [TextBox2] formarán parte del campo POST creado por el cliente. Asimismo, en el código anterior, [textbox1.text] tendrá como valor el valor publicado por el cliente, al igual que [textbox2.text]. El procedimiento [btnAjouter_Click] coloca en el campo [TextBox2] el valor enviado para [TextBox2] sumado al valor enviado para [TextBox1], más el marcador de fin de línea [ControlChars.CrLf] definido en el espacio de nombres [Microsoft.VisualBasic]. No es necesario importar este espacio, ya que el servidor web lo importa de forma predeterminada.

El código final de [form5.aspx] es el siguiente:

<%@ Page Language="VB" EnableViewState="False" %>
<script runat="server">

    Sub btnAjouter_Click(sender As Object, e As EventArgs)
      ': se suma el contenido de [textBox1] al de [TextBox2]
      textbox2.text+=textbox1.text+controlchars.crlf
    End Sub

</script>
<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            <asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter" EnableViewState="False"></asp:Button>
            <asp:TextBox id="TextBox1" runat="server" TextMode="Password" Width="353px" EnableViewState="False"></asp:TextBox>
        </p>
        <p>
            <asp:TextBox id="TextBox2" runat="server" TextMode="MultiLine" Width="419px" Height="121px" EnableViewState="False"></asp:TextBox>
        </p>
    </form>
</body>
</html>

Un poco más arriba, se mostró la captura de pantalla de una ejecución.

7.7. El componente DropDownList

La etiqueta <asp:DropDownList> permite insertar una lista desplegable en el código de presentación de una página. Creamos una página [form6.aspx] para obtener la siguiente presentación:

Image

n.º
nombre
tipo
propiedades
función
1
DropDownList1
DropDownList
AutoPostback=true
EnableViewState=true
lista desplegable
2
lblInfo
Etiqueta
EnableViewState=false
mensaje informativo

El código de presentación generado es el siguiente:

Page Language="VB" %>
<script runat="server">

</script>
<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            <asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
        </p>
        <p>
            <asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
        </p>
    </form>
</body>
</html>

Por el momento, la lista desplegable no contiene ningún elemento. La llenaremos en el procedimiento [Page_Load]. Para ello, debemos conocer algunas de las propiedades y métodos de la clase [DropDownList]:

Items
colección de tipo [ListItemCollection] de los elementos de la lista desplegable. Los miembros de esta colección son de tipo [ListItem].
Items.Count
número de elementos de la colección [Items]
Items(i)
elemento n.º i de la lista — del tipo [ListItem]
Items.Add
para agregar un nuevo elemento [ListItem] a la colección [Items]
Items.Clear
para eliminar todos los elementos de la colección [Items]
Items.RemoveAt(i)
para eliminar el elemento n.º i de la colección [Items]
SelectedItem
Primer elemento [ListItem] de la colección [Items] cuya propiedad [Selected] es verdadera
SelectedIndex
N.º del elemento [SelectedItem] en la colección [Items]

Los elementos de la colección [Items] de la clase [DropDownList] son de tipo [ListItem]. Cada elemento [ListItem] genera una etiqueta HTML <option>:

<option value="V" [selected="selected"]>T</option>

A continuación describimos algunas propiedades y métodos de la clase [ListItem]:

ListItem(String texte, String value)
constructor: crea un elemento [ListItem] con las propiedades [texte] y [value]. Un elemento ListItem(T,V) generará la etiqueta HTML <option value="V">T</option>. Por lo tanto, la clase [ListITem] permite describir los elementos de una lista HTML
Selected
booleano. Si es verdadero, la opción correspondiente de la lista HTML tendrá el atributo [selected="selected"]. Este atributo le indica al navegador que el elemento correspondiente debe aparecer seleccionado en la lista HTML
Text
el texto T de la opción HTML <option value="V" [selected="selected"]>T</option>
Value
el valor V del atributo [Value] de la opción HTML <option value="V" [selected="selected"]>T</option>

Tenemos suficiente información para escribir en el procedimiento [Page_Load] de la página el código para completar la lista desplegable [DropDownList1]:

    Sub Page_Load(sender As Object, e As EventArgs)
        ' se completa el combo si es la primera vez que se nos llama
        if not IsPostBack then
          dim valeurs() as String = {"1","2","3","4"}
            dim textes() as  String = {"un","deux","trois","quatre"}

            dim i as integer
            for i=0 to valeurs.length-1
                DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
            next
        end if
    end sub

Una vez inicializado el componente [DropDownList1], su traducción HTML será la siguiente:

<select name="DropDownList1" id="DropDownList1" onchange="__doPostBack('DropDownList1','')" language="javascript">

    <option value="1">un</option>
    <option value="2">deux</option>
    <option value="3">trois</option>
    <option value="4">quatre</option>

</select>

Sabemos que el procedimiento [Page_Load] se ejecuta cada vez que se ejecuta la página [form6.aspx]. Esta última es llamada por primera vez por un GET, y luego por un POST cada vez que el usuario selecciona un nuevo elemento en la lista desplegable. ¿Es necesario ejecutar cada vez el código de llenado de esta lista en [Page_Load]? La respuesta depende del atributo [EnableViewState] del componente [DropDownList1]. Si este atributo está en verdadero, sabemos que el estado del componente [DropDownList1] se conservará a lo largo de las consultas en el campo oculto [__VIEWSTATE]. Este estado incluye dos cosas:

  • la lista de todos los valores del menú desplegable
  • el valor del elemento seleccionado en dicha lista

Por lo tanto, resulta tentador establecer la propiedad [EnableViewState] del componente [DropDownList1] en [true] para no tener que volver a calcular los valores que se deben incluir en la lista. El problema es que, dado que el procedimiento [Page_Load] se ejecuta cada vez que se solicita la página [form6.aspx], estos valores se calcularán de todos modos. El objeto [Page], del cual [form6.aspx] es una instancia, cuenta con un atributo [IsPostBack] con valor booleano. Si este atributo es verdadero, significa que la página fue llamada por un POST. Si es falso, significa que la página fue llamada por un GET. En nuestro sistema de ida y vuelta cliente-servidor, el cliente siempre solicita la misma página [form6.aspx] al servidor. La primera vez, la solicita con un GET; las siguientes veces, con un POST. De ello se deduce que la propiedad [IsPostBack] nos puede servir para detectar la primera llamada GET del cliente. Los valores de la lista desplegable solo se generan durante esta primera llamada. En las solicitudes siguientes, estos valores serán generados por el mecanismo de [VIEWSTATE]. En otras situaciones, el contenido de una lista puede variar de una solicitud a otra y, por lo tanto, debe recalcularse en cada una de ellas. En este caso, se establecerá el atributo [EnableViewState] de la lista en [false] para evitar un cálculo doble e innecesario del contenido de la lista, a menos que se necesite conocer los elementos seleccionados previamente en la lista, ya que esta información se conserva en el [VIEWSTATE].

El atributo [AutoPostBack] de la lista [DropDownList1] se ha establecido en verdadero. Esto significa que el navegador enviará el formulario tan pronto como detecte el evento «cambio del elemento seleccionado» en la lista desplegable. El servidor, a su vez, detectará, gracias al [VIEWSTATE] y a los valores enviados, que el elemento seleccionado en el componente [DropDownList1] ha cambiado. Entonces activará el evento [SelectedIndexChanged] en ese componente. Lo procesaremos con el siguiente procedimiento:

    Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
      ' cambio de selección
      lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
        " valeur=" + dropdownlist1.selecteditem.value + _
        " index="+ dropdownlist1.selectedindex.tostring
    End Sub

Cuando se ejecuta este procedimiento, el objeto [DropDownList1] ha recuperado sus elementos de tipo [ListItem] gracias al [VIEWSTATE]. Además, uno de ellos, del tipo [ListItem], tiene su atributo [Selected] en «verdadero», que es el valor que el navegador ha enviado. Se puede acceder a este elemento de varias maneras:

DropDownList1.SelectedItem
es el primer elemento [ListItem] de la lista cuyo atributo [Selected] tiene el valor «verdadero»
DropDownList1.SelectedItem.Text
corresponde a la parte [texte] de la etiqueta HTML del elemento <option value="...">texto</option> seleccionado por el usuario
DropDownList1.SelectedItem.Value
corresponde a la parte [value] de la etiqueta HTML del elemento <option value="...">texto</option> seleccionado por el usuario
DropdDownList1.SelectedIndex
número en la colección [DropDownList1.Items] del primer elemento [ListItem] cuyo atributo [Selected] es verdadero

El código final de [form6.aspx] es el siguiente:

<%@ Page Language="VB" %>
<script runat="server">

    Sub Page_Load(sender As Object, e As EventArgs)
        ' se completa el menú desplegable si es la primera vez que se recibe la llamada
        if not IsPostBack then
          dim valeurs() as String = {"1","2","3","4"}
            dim textes() as  String = {"un","deux","trois","quatre"}

            dim i as integer
            for i=0 to valeurs.length-1
                DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
            next
        end if
    end sub

    Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
      ' cambio de selección
      lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
        " valeur=" + dropdownlist1.selecteditem.value + _
        " index="+ dropdownlist1.selectedindex.tostring
    End Sub

</script>
<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            <asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
        </p>
        <p>
            <asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
        </p>
    </form>
</body>
</html>

7.8. El componente ListBox

La etiqueta <asp:ListBox> permite insertar una lista en el código de presentación de una página. Creamos [form7.aspx] para obtener la siguiente presentación:

Image

n.º
nombre
tipo
propiedades
función
1
txtSaisie
TextBox
EnableViewState=false
campo de entrada
2
btnAjouter
Botón
 
botón [submit] que transfiere a la Lista 1 el contenido de txtSaisie si este no está vacío.
3
ListBox1
ListBox
EnableViewState=true
SelectionMode=Single
lista de valores de selección única
4
ListBox2
ListBox
EnableViewState=true
SelectionMode=Multiple
lista de valores de selección múltiple
5
btn1vers2
Botón
 
botón [submit] que transfiere a [liste 2] el elemento seleccionado de [liste 1]
6
btn2vers1
Botón
 
botón [submit] que transfiere a [liste 1] los elementos seleccionados de [liste 2]

El código de presentación generado es el siguiente:

<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            Tapez un texte pour l'inclure dans Liste 1 :
            <asp:TextBox id="txtSaisie" runat="server" EnableViewState="False"></asp:TextBox>
        </p>
        <p>
            <asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter"></asp:Button>
        </p>
        <p>
            <table>
                <tbody>
                    <tr>
                        <td>
                            <p align="center">
                                Liste 1
                            </p>
                        </td>
                        <td>
                        </td>
                        <td>
                            <p align="center">
                                Liste 2
                            </p>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <asp:ListBox id="ListBox1" runat="server"></asp:ListBox>
                        </td>
                        <td>
                            <p>
                                <asp:Button id="btn1vers2" onclick="btn1vers2_Click" runat="server" Text="-->"></asp:Button>
                            </p>
                            <p>
                                <asp:Button id="btn2vers1" onclick="btn2vers1_Click" runat="server" Text="<--"></asp:Button>
                            </p>
                        </td>
                        <td>
                            <p>
                                <asp:ListBox id="ListBox2" runat="server" SelectionMode="Multiple"></asp:ListBox>
                            </p>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <p align="center">
                                <asp:Button id="btnRaz1" onclick="btnRaz1_Click" runat="server" Text="Effacer"></asp:Button>
                            </p>
                        </td>
                        <td>
                        </td>
                        <td>
                            <p align="center">
                                <asp:Button id="btnRaz2" onclick="btnRaz2_Click" runat="server" Text="Effacer"></asp:Button>
                            </p>
                        </td>
                    </tr>
                </tbody>
            </table>
        </p>
    </form>
</body>
</html>

La clase [ListBox] se deriva de la misma clase [ListControl] que la clase [DropDownList] analizada anteriormente. En ella se encuentran todas las propiedades y métodos que vimos para [DropDownList], ya que en realidad pertenecían a [ListControl]. Aparece una nueva propiedad:

SelectionMode
establece el modo de selección de la lista HTML <select> que se generará a partir del componente. Si SelectionMode = Single, solo se podrá seleccionar un elemento. Si SelectionMode = Multiple, se podrán seleccionar varios elementos. Para ello, se generará el atributo [multiple="multiple"] en la etiqueta <select> de la lista HTML.

Procesemos los eventos. Al hacer clic en el botón [Ajouter], se ejecutará el siguiente procedimiento [btnAjouter_Click]:

    Sub btnAjouter_Click(sender As Object, e As EventArgs)
      ' se agrega a la lista 1
      dim texte as string=txtSaisie.text.trim
      if texte<> "" then ListBox1.Items.Add(New ListItem(texte))
      ' borrar txtSaisie
      txtSaisie.text=""
    End Sub

Si el texto ingresado en [txtSaisie] no es una cadena vacía o en blanco, se agrega un nuevo elemento a la lista [ListBox1]. Sabemos que debemos agregar un elemento de tipo [ListItem]. Anteriormente, habíamos utilizado el constructor [ListItem(T as String, V as String)] para realizar una tarea similar. Dicho elemento da lugar a la etiqueta HTML [<option value="V">T</option>]. Aquí utilizamos el constructor [ListItem(T as String)], que genera las etiquetas HTML, [<option value="T">T</option>] y c.a.d. El texto [T] de la opción se toma para formar el valor de la opción. Una vez que se agrega el contenido de [txtSaisie] a la lista [ListBox1], se vacía el campo [txTSaisie].

Los clics en los botones [Effacer] serán procesados mediante los siguientes procedimientos:

    Sub btnRaz1_Click(sender As Object, e As EventArgs)
      ' borrar lista 1
      ListBox1.Items.Clear
    End Sub

    Sub btnRaz2_Click(sender As Object, e As EventArgs)
      ' borrar lista 2
      ListBox2.Items.Clear
    End Sub

Los clics en los botones de transferencia entre listas se procesan mediante los siguientes procedimientos:

    Sub btn1vers2_Click(sender As Object, e As EventArgs)
      ' traslado del elemento seleccionado de la lista 1 a la lista 2
      transfert(ListBox1,ListBox2)
    End Sub

    Sub btn2vers1_Click(sender As Object, e As EventArgs)
      ' traslado del elemento seleccionado de la lista 2 a la lista 1
      transfert(ListBox2,ListBox1)
    End Sub

    sub transfert(l1 as listbox, l2 as listbox)
      ' transferencia de los elementos seleccionados de la lista 1 a la lista 2
       ' ¿Hay algo que hacer?
      if l1.selectedindex=-1 then return
      dim i as integer
      ' empezamos por el final
      for i=l1.items.count-1 to 0 step -1
        ' ¿Seleccionado?
        if l1.items(i).selected then
            ' ya no está seleccionado
            l1.items(i).selected=false
          ' transferencia a l2
          l2.items.add(l1.items(i))
          ' eliminación en l1
          l1.items.removeAt(i)
        end if
      next
    end sub

Dado que ambos botones realizan la misma función —transferir elementos de una lista a otra—, podemos reducirlo a un único procedimiento de transferencia con dos parámetros:

  • l1 de tipo [ListBox], que es la lista de origen
  • l2 de tipo [ListBox], que es la lista de destino

En primer lugar, se verifica si hay al menos un elemento seleccionado en la lista l1; de lo contrario, no hay nada que hacer. Para ello, se examina la propiedad [l1.selectedindex], que representa el número del primer elemento seleccionado en la lista. Si no hay ninguno, su valor es -1. Si hay al menos un elemento seleccionado en l1, se realiza la transferencia a l2. Para ello, se recorre toda la lista de elementos de l1 y se verifica para cada uno de ellos si su atributo [selected] es verdadero. Si es así, su atributo [selected] se cambia a [false], luego se copia a la lista l2 y, por último, se elimina de la lista l1. Esta eliminación provoca una renumeración de los elementos de la lista l1. Por eso se recorre la lista de elementos de l1 en orden inverso. Si se recorre en orden normal y se elimina el elemento n.º 10, el elemento n.º 11 pasa a ser el n.º 10 y el n.º 12, el elemento n.º 11. Después de procesar el elemento n.º 10, nuestro bucle en orden normal procesará el elemento n.º 11, que, según lo que acabamos de explicar, es el antiguo n.º 12. El que tenía el n.º 11 y ahora tiene el n.º 10 se nos escapa. Al recorrer los elementos de la lista l1 en sentido inverso, evitamos este problema.

7.9. Los componentes CheckBox, RadioButton

Las etiquetas <asp:RadioButton> y <asp:CheckBox> permiten insertar, respectivamente, un botón de opción y una casilla de selección en el código de presentación de una página. Creamos una página [form8.aspx] para obtener la siguiente presentación:

Image

n.º
nombre
tipo
propiedades
función
1
RadioButton1
RadioButton2
RadioButton3
RadioButton
RadioButton1.Checked=verdadero
RadioButton1.Text=1
RadioButton2.Checked=false
RadioButton2.Text=2
RadioButton3.Checked=falso
RadioButton3.Text=3
para los 3 botones: GroupName=radio
botones de radio
2
CheckBoxA
CheckBoxB
CheckBoxC
CheckBox
Checked=false para todos
CheckBoxA.Text=A
CheckBoxB.Text=B CheckBoxC.Text=C
casillas de selección
3
btnEnvoyer
Botón
 
botón [submit]
4
lstInfos
ListBox
 
Lista de información

Para que el navegador trate los tres botones de opción como mutuamente excluyentes, es necesario agruparlos en un grupo de botones de opción. Esto se logra mediante el atributo [GroupName] de la clase [RadioButton]. No es necesario mantener el estado de la página en esta aplicación. Por lo tanto, le asignamos el atributo [EnableViewState="false"] a la página. El código de presentación es el siguiente:

<html>
<head>
</head>
<body>
    <form id="frmControls" runat="server">
        <h3>Cases à cocher 
        </h3>
        <p>
            <asp:RadioButton id="RadioButton1" runat="server" Checked="True" EnableViewState="False" GroupName="radio" Text="1"></asp:RadioButton>
            <asp:RadioButton id="RadioButton2" runat="server" EnableViewState="False" GroupName="radio" Text="2"></asp:RadioButton>
            &nbsp;<asp:RadioButton id="RadioButton3" runat="server" EnableViewState="False" GroupName="radio" Text="3"></asp:RadioButton>
        </p>
        <p>
            <asp:CheckBox id="CheckBoxA" runat="server" EnableViewState="False" Text="A"></asp:CheckBox>
            <asp:CheckBox id="CheckBoxB" runat="server" EnableViewState="False" Text="B"></asp:CheckBox>
            <asp:CheckBox id="CheckBoxC" runat="server" EnableViewState="False" Text="C"></asp:CheckBox>
        </p>
        <p>
            <asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
            <asp:Button id="btnTree" onclick="btnTree_Click" runat="server" Text="Contrôles"></asp:Button>
        </p>
        <p>
            <asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6" Height="131px"></asp:ListBox>
        </p>
    </form>
</body>
</html>

Debemos escribir la rutina [btnEnvoyer_Click] para manejar el evento [Click] en este botón. El estado de un botón de radio o de una casilla de selección viene dado por su atributo [Checked], un valor booleano que es verdadero si la casilla está marcada y falso en caso contrario. Por lo tanto, basta con escribir en la lista [lstInfos] el valor del atributo [Checked] de los seis botones de radio y casillas de selección. Como esto no presenta ninguna dificultad particular, innovamos un poco:

<script runat="server">

    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
      ' se coloca información en el cuadro de lista
      for each c as control in FindControl("frmControls").controls
          ' ¿El control se deriva de CheckBox?
          if TypeOf(c) is CheckBox then
              lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
          end if
      next
    End Sub

</script>

La página puede verse como una estructura de árbol de controles. En nuestro ejemplo, nuestra página contiene textos y controles de servidor. Los textos se consideran un control específico llamado [LiteralControl]. Cada texto da lugar a este control, incluso una secuencia de espacios entre dos controles. Cada control tiene un atributo ID que lo identifica. Es el atributo ID el que aparece en las etiquetas:

            <asp:CheckBox id="CheckBoxA" runat="server" ></asp:CheckBox>

Si ignoramos los controles [LiteralControl], la página analizada cuenta con los siguientes controles:

  • [HtmlForm], que es el formulario [ID=frmControls]. Este, a su vez, es un contenedor de controles. Contiene los siguientes controles:

-- [ID=RadioButton1] de tipo [RadioButton]

-- [ID=RadioButton2] de tipo [RadioButton]

-- [ID=RadioButton3] de tipo [RadioButton]

-- [ID=CheckBoxA] de tipo [CheckBox]

-- [ID=CheckBoxA] de tipo [CheckBox]

-- [ID=CheckBoxA] de tipo [CheckBox]

-- [ID=btnEnviar] de tipo [Button]

Un control tiene las siguientes propiedades:

[Control].Controls
devuelve la colección de controles hijos de [Control], si los hay
[Control].FindControl(ID)
devuelve el control identificado por ID que se encuentra en la raíz del árbol de controles hijos de [Control]. En el ejemplo anterior: Page.FindControl("frmControls") se refiere al contenedor [HtmlForm]. Para acceder al botón de opción [RadioButton1], deberá escribir
Page.FindControl("frmControls").FindControl("RadioButton1")
[Control].ID
identificador de [Control]

Volvamos al código del procedimiento [btnEnvoyer_Click]:

    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
      ' se inserta información en el cuadro de lista
      for each c as control in FindControl("frmControls").controls
          ' ¿El control se deriva de CheckBox?
          if TypeOf(c) is CheckBox then
              lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
          end if
      next
    End Sub

Queremos mostrar el estado de los botones de opción y las casillas de selección que se encuentran en el formulario. Recorremos todos los controles del mismo. Si el control actual es de un tipo derivado de [CheckBox], mostramos su propiedad [Checked]. Dado que la clase [RadioButton] deriva de la clase [CheckBox], la comprobación es válida para ambos tipos de controles. La captura de pantalla que se muestra más arriba presenta un ejemplo de ejecución.

7.10. Los componentes CheckBoxList, RadioButtonList

A veces, se desea que el usuario elija entre valores que no se conocen al momento de diseñar la página. Estas opciones provienen de un archivo de configuración, de una base de datos, etc., y solo se conocen al momento de la ejecución. Existen soluciones para este problema y las hemos encontrado. La lista de selección simple es adecuada cuando el usuario solo puede elegir una opción, y la lista de selección múltiple cuando puede elegir varias. Desde un punto de vista estético, y si el número de opciones no es muy grande, es posible que se prefiera utilizar botones de radio en lugar de la lista de selección simple o casillas de selección en lugar de la lista de selección múltiple. Esto es posible con los componentes [CheckBoxList] y [RadioButtonList].

Las clases [CheckBoxList] y [RadioButtonList] se derivan de la misma clase [ListControl] que las clases [DropDownList] y [ListBox] analizadas anteriormente. Por lo tanto, encontraremos algunas de las propiedades y métodos que vimos para esas clases, las que en realidad pertenecían a [ListControl].

Items
colección de tipo [ListItemCollection] de los elementos de la lista desplegable. Los miembros de esta colección son de tipo [ListItem].
Items.Count
número de elementos de la colección [Items]
Items(i)
elemento n.º i de la lista — del tipo [ListItem]
Items.Add
para agregar un nuevo elemento [ListItem] a la colección [Items]
Items.Clear
para eliminar todos los elementos de la colección [Items]
Items.RemoveAt(i)
para eliminar el elemento n.º i de la colección [Items]
SelectedItem
Primer elemento [ListItem] de la colección [Items] cuya propiedad [Selected] es verdadera
SelectedIndex
N.º del elemento anterior en la colección [Items]

Algunas propiedades son específicas de las clases [CheckBoxList] y [RadioButtonList]:

RepeatDirection
[horizontal] o [vertical] para listas horizontales o verticales.

Los elementos de la colección [Items] son del tipo [ListItem]. Cada elemento [ListItem] generará una etiqueta diferente dependiendo de si se trata de un objeto [CheckBoxList] o [RadioButtonList]:

<input type="checkbox" [selected="selected"] value="V">Texte

O

<input type="radio" [selected="selected"] value="V">Texte

A continuación describimos algunas propiedades y métodos de la clase [ListItem]:

ListItem(String texte, String value)
constructor: crea un elemento [ListItem] con las propiedades «texto» y «valor». Un elemento ListItem(T,V) generará la etiqueta HTML <input type="checkbox" value="V">T o <input type="radio" value="V">T, según el caso.
Selected
booleano. Si es verdadero, la opción correspondiente de la lista HTML tendrá el atributo [selected="selected"]. Este atributo le indica al navegador que el elemento correspondiente debe aparecer seleccionado en la lista HTML
Text
el texto T de la opción HTML <input type=".." value="V" [selected="selected"]>T
Value
el valor del atributo Value de la opción HTML <input type=".." value="V" [selected="selected"]>T

Nos proponemos crear la siguiente página [form8b.aspx]:

Image

n.º
nombre
tipo
propiedades
función
1
RadioButtonList1
RadioButtonList
EnableViewState=true
RepeatDirection=horizontal
lista de botones de opción
2
CheckBoxList1
CheckBoxList
EnableViewState=true
RepeatDirection=horizontal
lista de casillas de selección
3
btnEnvoyer
Botón
 
botón [submit] que muestra en [4] la lista de elementos seleccionados en ambas listas
4
lstInfos
ListBox
EnableViewState=false
lista de valores

El código de presentación de la página es el siguiente:

<%@ Page Language="VB" autoeventwireup="false" %>
<script runat="server">
...
</script>
<html>
<head>
</head>
<body>
    <form id="frmControls" runat="server">
        <h3>Listes de cases à cocher
        </h3>
        <p>
            <asp:RadioButtonList id="RadioButtonList1" runat="server" RepeatDirection="Horizontal"></asp:RadioButtonList>
        </p>
        <p>
            <asp:CheckBoxList id="CheckBoxList1" runat="server" RepeatDirection="Horizontal"></asp:CheckBoxList>
        </p>
        <p>
            <asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
        </p>
        <p>
            <asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6"></asp:ListBox>
        </p>
    </form>
</body>
</html>

El código de control es el siguiente:

<script runat="server">

    Sub Page_Load(sender As Object, e As EventArgs) handles MyBase.Load
        ' se completan las listas si es la primera vez que se llama
        if not IsPostBack then
            ' textos para la lista RadioButton
          dim textesRadio() as String = {"1","2","3","4"}
          ' textos para la lista RadioButton
          dim textesCheckBox() as  String = {"un","deux","trois","quatre"}
                 ' relleno de la lista de opciones de radio
            dim i as integer
            for i=0 to textesRadio.length-1
                RadioButtonList1.Items.Add(new ListItem(textesRadio(i)))
            next
             ' selección del elemento n.º 1
            RadioButtonList1.SelectedIndex=1
            ' relleno de la lista de casillas de selección
            for i=0 to textesCheckBox.length-1
                CheckBoxList1.Items.Add(new ListItem(textesCheckBox(i)))
            next
        end if
    end sub


    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
      ' se ingresa información en el cuadro de lista lstinfos
      affiche(RadioButtonList1)
      affiche(CheckBoxList1)
    End Sub

    sub affiche(l1 as ListControl)
      ' muestra los valores de los elementos seleccionados de l1
       ' ¿Hay algo que hacer?
      if l1.selectedindex=-1 then return
      dim i as integer
      ' empezamos por el final
      for i= 0 to l1.items.count-1
        ' ¿Seleccionado?
        if l1.items(i).selected then
          lstInfos.Items.Add("["+TypeName(l1)+"] ["+l1.items(i).text+"] sélectionné")
        end if
      next
    end sub

</script>

En el procedimiento [Page_Load], que se ejecuta cada vez que se abre la página, se inicializan ambas listas. Para evitar que esto suceda cada vez, se utiliza la propiedad [IsPostBack] para que solo se haga la primera vez. En las siguientes ocasiones, las listas se regenerarán automáticamente mediante el mecanismo de [VIEWSTATE]. Una vez que se muestra la página, el usuario marca ciertas casillas y utiliza el botón [Envoyer]. Los valores del formulario se envían entonces al propio formulario. Tras la ejecución de [Page_Load], se ejecuta el procedimiento [btnEnvoyer_Click]. Este llama al procedimiento [affiche] para completar la lista [lstInfos]. Este último recibe como parámetro un objeto de tipo [ListControl], lo que permite enviarle indistintamente un objeto [RadioButtonList] o un objeto [CheckBoxList], clases derivadas de [ListControl]. La lista [lstInfos] puede tener su atributo [EnableViewState] establecido en [false], ya que no es necesario mantener su estado entre las diferentes consultas.

7.11. Los componentes Panel, LinkButton

La etiqueta <asp:panel> permite insertar un contenedor de controles en una página. La ventaja del contenedor es que algunas de sus propiedades se aplican a todos los controles que contiene. Este es el caso de su propiedad [Visible]. Esta propiedad existe para todos los controles de servidor. Si un contenedor tiene la propiedad [Visible=false], cada uno de sus controles se regirá por su propia propiedad [Visible]. Si tiene la propiedad [Visible=false], entonces ni el contenedor ni su contenido se muestran. Esto puede resultar más sencillo que gestionar la propiedad [Visible] de cada uno de los controles del contenedor.

La etiqueta <asp:LinkButton> permite insertar un enlace en el código de presentación de una página. Tiene una función similar a la del botón [Button]. De hecho, provoca un POST del lado del cliente gracias a una función de JavaScript que se le asocia. Creamos una página [form9.aspx] para obtener la presentación siguiente:

Image

n.º
nombre
tipo
propiedades
función
1
Panel1
Panel
EnableViewState=true
contenedor de controles
2
ListBox1
ListBox
EnableViewState=true
una lista de tres valores
3
lnkCacher
LinkButton
EnableViewState=false
enlace para ocultar el contenedor

Cuando el contenedor está oculto, aparece un nuevo enlace:

Image

N.º
nombre
tipo
propiedades
función
4
lnkVoir
LinkButton
EnableViewState=false
enlace para mostrar el contenedor

El código de presentación de la página es el siguiente:

<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            <asp:Panel id="Panel1" runat="server" BorderStyle="Ridge" BorderWidth="1px">
                <p>
                    Conteneur 
                </p>
                <p>
                    <asp:ListBox id="ListBox1" runat="server">
                        <asp:ListItem Value="1">un</asp:ListItem>
                        <asp:ListItem Value="2">deux</asp:ListItem>
                        <asp:ListItem Value="3" Selected="True">trois</asp:ListItem>
                    </asp:ListBox>
                </p>
            </asp:Panel>
        </p>
        <p>
            <asp:LinkButton id="lnkVoir" onclick="lnkVoir_Click" runat="server">Voir le conteneur</asp:LinkButton>
        </p>
        <p>
            <asp:LinkButton id="lnkCacher" onclick="lnkCacher_Click" runat="server">Cacher le conteneur</asp:LinkButton>
        </p>
    </form>
</body>
</html>

Cabe destacar que este código inicializa la lista [ListBox1] con tres valores. Los controladores de eventos [Clic] en ambos enlaces son los siguientes:

<%@ Page Language="VB" %>
<script runat="server">

    Sub Page_Load(sender As Object, e As EventArgs)
...
    end sub

    Sub lnkVoir_Click(sender As Object, e As EventArgs)
        ' muestra el contenedor 1
        panel1.Visible=true
         ' cambia los enlaces
        lnkVoir.visible=false
        lnkCacher.visible=true
    End Sub

    Sub lnkCacher_Click(sender As Object, e As EventArgs)
        ' oculta el contenedor 1
        panel1.Visible=false
         ' cambia los enlaces
        lnkVoir.visible=true
        lnkCacher.visible=false
    End Sub
</script>

Utilizaremos el procedimiento [Page_Load] para inicializar el formulario. Lo haremos durante la primera consulta (IsPostBack=false):

<%@ Page Language="VB" %>
<script runat="server">

    Sub Page_Load(sender As Object, e As EventArgs)
        ' la primera vez
        if not IsPostBack then
            ' se muestra el contenedor
            lnkVoir_Click(nothing,nothing)
        end if
    end sub
.....
</script>

7.12. Para continuar...

En los párrafos anteriores se han presentado varios componentes del servidor. En cada caso, solo se han mostrado algunas de sus propiedades. Para profundizar en el estudio de estos componentes, el lector puede hacerlo de varias maneras:

  • descubrir las propiedades de un componente con un IDE, como por ejemplo el WebMatrix. Este, de hecho, expone las principales propiedades de los componentes utilizados en un formulario
  • consultar la documentación de .NET para conocer todas las clases correspondientes a cada uno de los componentes de servidor. Este es el método preferido para un dominio total del componente. En ella se encontrará el árbol de clases que conduce a los componentes, así como las propiedades, métodos, constructores y eventos de cada una de ellas. Además, la documentación a veces proporciona ejemplos.

En este capítulo, hemos utilizado la técnica «todo en uno» de [WebMatrix], c.a.d, en la que se colocan el código de presentación y el código de control de una página en el mismo archivo. En general, no recomendamos este método, sino el denominado [codebehind] utilizado anteriormente, que coloca estos dos códigos en dos archivos separados. Recordamos que la ventaja de esta separación radica en que el código de control se puede compilar sin necesidad de ejecutar la aplicación web. Por otra parte, nuestros ejemplos —como lo explicamos desde el inicio del capítulo— tenían un perfil muy particular: consistían en una sola página que era un formulario que el cliente y el servidor intercambiaban en ciclos sucesivos de solicitud-respuesta, siendo la primera solicitud del cliente un GET, y las siguientes, POST.

7.13. Componentes del servidor y del controlador de la aplicación

En los capítulos anteriores, hemos desarrollado varias aplicaciones web. Todas ellas se construyeron siguiendo la arquitectura MVC (Modelo-Vista-Controlador), que divide la aplicación en bloques bien diferenciados y facilita su mantenimiento. En ese entonces, construíamos nuestras interfaces de usuario con etiquetas HTML estándar. Con lo que acabamos de ver, es natural querer utilizar ahora componentes de servidor. Retomemos un problema que ya hemos estudiado a fondo: el cálculo de un impuesto. Su arquitectura MVC era la siguiente:

Image

La aplicación tiene dos vistas: [formulaire.aspx] y [erreurs.aspx]. La vista [formulaire.aspx] se muestra cuando se solicita por primera vez la URL [main.aspx]:

Image

El usuario completa el formulario:

Image

y utiliza el botón [Calculer] para obtener la siguiente respuesta:

Image

En una aplicación MVC, toda solicitud debe pasar por el controlador, en este caso [main.aspx]. Esto significa que cuando el usuario ha completado el formulario [formulaire.aspx], este debe enviarse a [main.aspx] y no a [formulaire.aspx]. Esto simplemente no es posible si construimos la interfaz de usuario [formulaire.aspx] con componentes de servidor ASP. Para darnos cuenta de ello, construyamos un formulario [formtest.aspx] con un componente <asp:button>:

<%@ Page Language="VB" EnableViewState="false"%>
<html>
<head>
    <title>test</title>
</head>
<body>
    <form action="main.aspx" runat="server">
        <p>
            <asp:Button id="btnTest" runat="server" EnableViewState="false" Text="Test"></asp:Button>
        </p>
    </form>
</body>
</html>

Cabe destacar el atributo [action="main.aspx"] de la etiqueta <form...>. Ejecutemos esta aplicación. La página de presentación solo muestra un botón:

Image

Veamos el código HTML enviado por el servidor:

<html>
<head>
    <title>test</title>
</head>
<body>
    <form name="_ctl0" method="post" action="formtest.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwtNTMwNzcxMzI0Ozs+" />

        <p>
            <input type="submit" name="btnTest" value="Test" id="btnTest" />
        </p>

    </form>
</body>
</html>

Se observa que el POST del formulario apunta al propio formulario [action="formtest.aspx"], mientras que en [formtest.aspx] habíamos escrito la etiqueta HTML del servidor:

    <form action="main.aspx" runat="server">

El atributo [runat="server"] de la etiqueta <form> nos viene impuesto por el uso de los componentes del servidor. Se produce un error de compilación si no incluimos este atributo. Cuando lo incluimos, el atributo [action] de la etiqueta <form> se ignora. El servidor siempre genera un atributo [action] que apunta al propio formulario. De ello se deduce que, en una aplicación MVC, no se pueden utilizar formularios creados con la etiqueta <form ... runat="server">. Sin embargo, esta etiqueta es indispensable para todos los componentes de servidor ASP que recogen entradas del usuario. Es decir, no se pueden utilizar formularios del servidor ASP en una aplicación MVC. Es un gran descubrimiento. De hecho, uno de los puntos fuertes del marketing de ASP.NET es que se puede crear una aplicación web como si fuera una aplicación de Windows. Esto es cierto si nuestra aplicación no sigue la arquitectura MVC, pero lo es aún más si sí la sigue. Ahora bien, la arquitectura MVC parece un concepto fundamental del desarrollo web actual que resulta difícil de ignorar.

Es posible utilizar la arquitectura MVC junto con formularios con componentes ASP para aplicaciones con pocas vistas diferentes gracias al siguiente recurso:

  • la aplicación consiste en una sola página que actúa como controlador
  • las vistas se traducen en esta página como contenedores distintos, un contenedor por vista. Para mostrar una vista, se hace visible su contenedor y se ocultan los demás

Es una solución elegante que ahora implementaremos en algunos ejemplos

7.14. Ejemplos de aplicaciones MVC con componentes de servidor ASP

7.14.1. Ejemplo 1

En este primer ejemplo implementamos los componentes de servidor que hemos presentado. La página [form10.aspx] quedará así:

La captura de pantalla de la izquierda que se muestra arriba es el formulario tal como se le presenta al cliente. Este lo llena y lo valida mediante [Envoyer]. El servidor le devuelve una vista que muestra una lista de los valores ingresados (captura de pantalla de la derecha). Un enlace permite al usuario regresar al formulario. Este lo encuentra tal como lo validó. El código de presentación de [form10.aspx] es el siguiente:

<html>
<head>
    <title>Exemple</title> <script language="javascript">
        function effacer(){
            alert("Vous avez cliqué sur [Effacer]")
        }
    </script>
</head>
<body>
    <p>
        Gestion d'un formulaire
    </p>
    <p>
        <hr />
    </p>
    <form runat="server">
        <p>
            <asp:Panel id="panelinfo" runat="server" EnableViewState="False">
                <p>
                    Liste des valeurs obtenues
                </p>
                <p>
                    <asp:ListBox id="lstInfos" runat="server" EnableViewState="False"></asp:ListBox>
                </p>
                <p>
                    <asp:LinkButton id="LinkButton1" onclick="LinkButton1_Click" runat="server">Retour au formulaire</asp:LinkButton>
                </p>
                <p>
                    <hr />
                </p>
            </asp:Panel>
        </p>
        <p>
            <asp:Panel id="panelform" runat="server" >
                <table>
                    <tbody>
                        <tr>
                            <td>
                                Etes-vous marié(e)</td>
                            <td>
                                <asp:RadioButton id="rdOui" runat="server"  GroupName="rdmarie"></asp:RadioButton>
                                Oui<asp:RadioButton id="rdNon" runat="server"  GroupName="rdmarie" Checked="True"></asp:RadioButton>
                                Non</td>
                        </tr>
                        <tr>
                            <td>
                                Cases à cocher</td>
                            <td>
                                <asp:CheckBox id="chk1" runat="server"></asp:CheckBox>
                                1<asp:CheckBox id="chk2" runat="server"></asp:CheckBox>
                                2<asp:CheckBox id="chk3" runat="server"></asp:CheckBox>
                                3</td>
                        </tr>
                        <tr>
                            <td>
                                Champ de saisie</td>
                            <td>
                                <asp:TextBox id="txtSaisie" runat="server"  MaxLength="20" Columns="20"></asp:TextBox>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Mot de passe</td>
                            <td>
                                <asp:TextBox id="txtmdp" runat="server"  MaxLength="10" Columns="10" TextMode="Password"></asp:TextBox>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Boîte de saisie</td>
                            <td>
                                <asp:TextBox id="txtArea" runat="server"  Columns="20" TextMode="MultiLine" Rows="3"></asp:TextBox>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Liste déroulante</td>
                            <td>
                                <asp:DropDownList id="cmbValeurs" runat="server"></asp:DropDownList>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Liste à choix unique</td>
                            <td>
                                <asp:ListBox id="lstSimple" runat="server"></asp:ListBox>
                                <asp:Button id="btnRazSimple" onclick="btnRazSimple_Click" runat="server" EnableViewState="False" Text="Raz"></asp:Button>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Liste à choix multiple</td>
                            <td>
                                <asp:ListBox id="lstMultiple" runat="server" SelectionMode="Multiple"></asp:ListBox>
                                <asp:Button id="razMultiple" onclick="razMultiple_Click" runat="server" EnableViewState="False" Text="Raz"></asp:Button>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Champ caché</td>
                            <td>
                                <asp:Label id="lblSecret" runat="server" visible="False"></asp:Label></td>
                        </tr>
                        <tr>
                            <td>
                                Bouton simple</td>
                            <td>
                                <input id="btnEffacer" onclick="effacer()" type="button" value="Effacer" /></td>
                        </tr>
                        <tr>
                            <td>
                                Bouton [reset]</td>
                            <td>
                                <input id="btnReset" type="reset" value="Rétablir" /></td>
                        </tr>
                        <tr>
                            <td>
                                Bouton [submit]</td>
                            <td>
                                <asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" EnableViewState="False" Text="Envoyer"></asp:Button>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </asp:Panel>
        </p>
    </form>
</body>
</html>

La página tiene dos contenedores, uno para cada vista: [panelform] para la vista de formulario y [panelinfo] para la vista de información. La lista de componentes del contenedor [panelForm] es la siguiente:

nombre
tipo
propiedades
función
panelform
Panel
EnableViewState=true
vista de formulario
rdOui
rdNon
RadioButton
EnableViewState=true
GroupName=rdmarie
botones de opción
chk1
chk2
chk3
CheckBox
EnableViewState=true
casillas de selección
txtSaisie
TextBox
EnableViewState=true
campo de entrada
txtMdp
TextBox
EnableViewState=true
campo de entrada protegido
txtArea
TextBox
EnableViewState=true
campo de entrada de varias líneas
cmbValeurs
DropDownList
EnableViewState=true
lista desplegable
lstSimple
ListBox
EnableViewState=true
SelectionMode=Single
lista de selección única
btnRazSimple
Botón
EnableViewState=false
deselecciona todos los elementos de lstSimple
lstMultiple
ListBox
EnableViewState=true
SelectionMode=Multiple
lista de selección múltiple
btnRazMultiple
Botón
EnableViewState=false
deselecciona todos los elementos de lstMultiple
lblSecret
Etiqueta
EnableViewState=true
Visible=false
campo oculto
btnEffacer
HTML estándar
 
muestra una alerta
btnEnvoyer
Botón
EnableViewState=false
botón [submit] del formulario
btnReset
HTML estándar
 
botón [reset] del formulario

La función de [VIEWSTATE] para los componentes es importante en este caso. Todos los componentes, excepto los botones, deben tener la propiedad [EnableViewState=true]. Para entender el motivo, hay que recordar cómo funciona la aplicación. Supongamos que el campo [txtSaisie] tiene la propiedad [EnableViewState=false]:

  1. el cliente solicita por primera vez la página [form10.aspx]. Obtiene la vista de formulario
  2. , la llena y la envía con el botón [Envoyer]. Los campos de entrada se envían y el servidor asigna a los componentes del servidor el valor enviado o su estado [VIEWSTATE], si es que tenían uno. De este modo, al campo [txtSaisie] se le asigna el valor ingresado por el usuario. Además, en este paso, su estado [VIEWSTATE] no tiene ninguna utilidad. Como resultado de la operación, se envía la vista [informations], que en realidad sigue siendo la página [form10.aspx], pero con un contenedor diferente a la vista.
  3. El usuario consulta esta nueva vista y utiliza el enlace [Retour au formulaire] para regresar a ella. A continuación, se realiza un POST hacia [form10.aspx]. En ese momento, solo hay un valor enviado como máximo: el valor seleccionado por el usuario en la lista de información, información que no se utiliza posteriormente. De todos modos, no se envía ningún campo [txtSaisie].
  4. El servidor recibe el POST y asigna a los componentes del servidor el valor enviado o su estado [VIEWSTATE], si es que tenían uno. En este caso, [txtNom] no tiene ningún valor enviado. Si su atributo [EnableViewState] es [false], se le asignará la cadena vacía. Como queremos que tenga el valor ingresado por el usuario, debe tener la propiedad [EnableViewState=true].

El contenedor [panelinfo] cuenta con los siguientes controles:

nombre
tipo
propiedades
función
panelinfo
Panel
EnableViewState=false
vista de información
lstInfos
ListBox
EnableViewState=false
lista de información que resume los valores ingresados por el usuario
LinkButton1
LinkButton
EnableViewState=false
enlace de regreso al formulario

Al momento de realizar las pruebas, si observamos el código HTML generado por el código de presentación anterior, nos puede sorprender el código generado para el campo oculto [lblSecret]:

                    <tr>
                        <td>
                            Champ caché</td>
                        <td></td>
                    </tr>

El componente [lblSecret] no se convierte al código HTML porque tiene la propiedad [Visible=false]. Sin embargo, debido a que tiene la propiedad [EnableViewState=true], su valor se conservará de todos modos en el campo oculto [__VIEWSATE]. Por lo tanto, podremos recuperarlo, como lo demostrarán las pruebas.

Ahora nos queda escribir los controladores de eventos. En [Page_Load] inicializaremos el formulario:

Sub page_Load(sender As Object, e As EventArgs)
         ' la primera vez, se inicializan los elementos
          ' las siguientes veces, estos recuperan sus valores mediante el VIEWSTATE
         if IsPostBack then return
         ' inicializar formulario
          ' panelinfo no se muestra
         panelinfo.visible=false
         ' panel de formulario mostrado
         panelform.visible=true
         ' botones de radio
         rdNon.Checked=true
         ' casillas de selección
         chk2.Checked=true
          ' campo de entrada
         txtSaisie.Text="qqs mots"
          ' campo de contraseña
         txtMdp.Text="ceciestsecret"
         ' cuadro de entrada
         txtArea.Text="ligne"+ControlChars.CrLf+"ligne2"+ControlChars.CrLf
         ' menú desplegable
         dim i as integer
         for i=1 to 4
             cmbValeurs.Items.Add(new ListItem("choix"+i.ToString,i.ToString))
         next
         cmbValeurs.SelectedIndex=1
         ' lista de selección simple
         for i=1 to 7
             lstSimple.Items.Add(new ListItem("simple"+i.ToString,i.ToString))
         next
         lstSimple.SelectedIndex=0
         ' lista de selección múltiple
         for i=1 to 10
             lstMultiple.Items.Add(new ListItem("multiple"+i.ToString,i.ToString))
         next
         lstMultiple.Items(0).Selected=true
         lstMultiple.Items(2).Selected=true
         ' campo oculto
         lblSecret.Text="secret"
End Sub

Al hacer clic en los botones [lstRazSimple] y [lstMultiple]:

Sub btnRazSimple_Click(sender As Object, e As EventArgs)
    ' borrar lista simple
    lstSimple.SelectedIndex=-1
End Sub

Sub razMultiple_Click(sender As Object, e As EventArgs)
    ' borrar lista múltiple
    lstMultiple.SelectedIndex=-1
End Sub

Al hacer clic en el botón [Envoyer]:

Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
    ' el panel de información se muestra y el panel del formulario se oculta
  panelinfo.Visible=true
  panelform.visible=false
  ' se recuperan los valores enviados y se colocan en lstInfos
   ' botones de opción
  dim info as string="état marital : "+iif(rdoui.checked,"marié"," non marié")
  affiche(info)
   ' casillas de selección
  info=" cases cochées : "+iif(chk1.checked,"1 oui","1 non")+","+ _
      iif(chk2.checked,"2 oui","2 non")+","+iif(chk3.checked,"3 oui","3 non")
  affiche(info)
   ' campo de entrada
  affiche("champ de saisie : " + txtSaisie.Text.Trim)
   ' contraseña
  affiche("mot de passe : " + txtMdp.Text.Trim)
   ' cuadro de texto
  dim lignes() as String
  lignes=new Regex("\r\n").Split(txtArea.Text.Trim)
  dim i as integer
  for i=0 to lignes.length-1
      lignes(i)="["+lignes(i).Trim+"]"
  next
  affiche("Boîte de saisie : " + String.Join(",",lignes))
   ' menú desplegable
  affiche("éléments sélectionnés dans combo : "+selection(cmbValeurs))
   ' lista simple
  affiche("éléments sélectionnés dans liste simple : "+selection(lstSimple))
   ' lista múltiple
  affiche("éléments sélectionnés dans liste multiple : "+selection(lstMultiple))
   ' campo oculto
  affiche ("Champ caché : " + lblSecret.Text)
End Sub

sub affiche(msg as String)
    ' muestra mensaje en lstInfos
    lstInfos.Items.Add(msg)
end sub

       function selection(liste as ListControl) as string
           ' recorre los elementos de la lista
            ' para encontrar los que están seleccionados
           dim i as integer
           dim info as string=""
           for i=0 to liste.Items.Count-1
               if liste.Items(i).Selected then info+="[" + liste.Items(i).Text + "]"
           next
           return info
       end function

Por último, al hacer clic en el enlace [Retour vers le formulaire ]:

Sub LinkButton1_Click(sender As Object, e As EventArgs)
    ' se muestra el formulario y se oculta el panel de información
    panelform.visible=true
    panelinfo.visible=false
End Sub

7.14.2. Ejemplo 2

Aquí retomamos una aplicación que ya se ha tratado anteriormente con formularios estándar HTML. La aplicación permite realizar simulaciones de cálculos de impuestos. Se basa en una clase [impot] que no volveremos a mencionar aquí. Esta clase necesita datos que encuentra en una fuente de datos OLEDB. Para el ejemplo, será una fuente ACCESS.

7.14.2.1. La estructura MVC de la aplicación

La estructura MVC de la aplicación es la siguiente:

Image

Las tres vistas se incorporarán al código de presentación del controlador [main.aspx] en forma de contenedores. Por lo tanto, esta aplicación tiene una única página: [main.aspx].

7.14.2.2. Las vistas de la aplicación web

La vista [formulaire] es el formulario de ingreso de información que permite calcular el impuesto de un usuario:

Image

El usuario llena el formulario:

Image

Utiliza el botón [Envoyer] para solicitar el cálculo de su impuesto. Obtiene la siguiente vista [simulations]:

Image

Regresa al formulario mediante el enlace anterior. Lo encuentra tal como lo había ingresado. Puede cometer errores al ingresar los datos:

Image

Estos se le señalan mediante la vista [erreurs]:

Image

Regresa al formulario mediante el enlace anterior. Lo encuentra tal como lo había ingresado. Puede realizar nuevas simulaciones:

Image

A continuación, obtiene la vista [simulations] con una simulación adicional:

Image

Por último, si la fuente de datos no está disponible, se le indica al usuario en la vista [erreurs]:

Image

7.14.2.3. El código de presentación de la aplicación

Recordemos que la página [main.aspx] reúne todas las vistas. Se trata de un único formulario con tres contenedores:

  • [panelform] para la vista [formulaire]
  • [panelerreurs] para la vista [erreurs]
  • [panelsimulations] para la vista [simulations]

Volvemos a separar el código de presentación y el código de control en dos archivos distintos. El primero estará en [main.aspx] y el segundo en [main.aspx.vb]. El código de [main.aspx] es el siguiente:


<%@ page codebehind="main.aspx.vb" inherits="vs.main" AutoEventWireUp="false" %>
<HTML>
    <HEAD>
        <title>Calcul d'impôt </title>
    </HEAD>
    <body>
        <P>Calcul de votre impôt</P>
        <HR width="100%" SIZE="1">
        <FORM id="Form1" runat="server">
            <asp:panel id="panelform" Runat="server">
                <TABLE id="Table1" cellSpacing="1" cellPadding="1" border="0">
                    <TR>
                        <TD height="19">Etes-vous marié(e)</TD>
                        <TD height="19">
                            <asp:RadioButton id="rdOui" runat="server" GroupName="rdMarie"></asp:RadioButton>Oui
                            <asp:RadioButton id="rdNon" runat="server" GroupName="rdMarie" Checked="True"></asp:RadioButton>Non</TD>
                    </TR>
                    <TR>
                        <TD>Nombre d'enfants</TD>
                        <TD>
                            <asp:TextBox id="txtEnfants" runat="server" MaxLength="3" Columns="3"></asp:TextBox></TD>
                    </TR>
                    <TR>
                        <TD>Salaire annuel (euro)</TD>
                        <TD>
                            <asp:TextBox id="txtSalaire" runat="server" MaxLength="10" Columns="10"></asp:TextBox></TD>
                    </TR>
                </TABLE>
                <P>
                    <asp:Button id="btnCalculer" runat="server" Text="Calculer"></asp:Button>
                    <asp:Button id="btnEffacer" runat="server" Text="Effacer"></asp:Button></P>
            </asp:panel>
          <asp:panel id="panelerreurs" runat="server">
                <P>Les erreurs suivantes se sont produites :</P>
                <P>
                    <asp:Literal id="erreursHTML" runat="server"></asp:Literal></P>
                <P></P>
                <asp:LinkButton id="lnkForm1" runat="server">Retour au formulaire</asp:LinkButton>
            </asp:panel>
          <asp:panel id="panelsimulations" runat="server">
                <P>
                    <TABLE>
                        <TR>
                            <TH>
                                Marié</TH>
                            <TH>
                                Enfants</TH>
                            <TH>
                                Salaire annuel</TH>
                            <TH>
                                Impôt à payer (euro)</TH></TR>
                        <asp:Literal id="simulationsHTML" runat="server"></asp:Literal></TABLE>
                    <asp:LinkButton id="lnkForm2" runat="server">Retour au formulaire</asp:LinkButton></P>
            </asp:panel>
      </FORM>
    </body>
</HTML>

Hemos delimitado los tres contenedores. Cabe señalar que todos se encuentran dentro de la etiqueta <form runat="server">. Esto es obligatorio, ya que para poder aprovechar las ventajas de los componentes de servidor, estos deben colocarse dentro de dicha etiqueta. Lo importante que hay que entender es que aquí tenemos un único formulario que se intercambiará entre el cliente y el servidor web. Por lo tanto, nos encontramos exactamente en la configuración utilizada a lo largo de todo este capítulo sobre los componentes de servidor. Detallamos a continuación los componentes de cada contenedor:

Contenedor [panelform]:

nombre
tipo
propiedades
función
panelform
Panel
EnableViewState=true
vista de formulario
rdOui
rdNon
RadioButton
EnableViewState=true
GroupName=rdmarie
botones de opción
txtEnfants
TextBox
EnableViewState=true
número de hijos
txtSalaire
TextBox
EnableViewState=true
salario anual
btnCalculer
Botón
 
Botón [submit] del formulario: inicia el cálculo del impuesto
btnEffacer
Botón
 
Botón [submit] del formulario: borra el formulario

Contenedor [panelerreurs]:

nombre
tipo
propiedades
función
panelerreurs
Panel
EnableViewState=true
vista de errores
lnkForm1
LinkButton
EnableViewState=true
enlace al formulario
erreursHTML
Literal
 
código HTML de la lista de errores

Contenedor [panelsimulations]:

nombre
tipo
propiedades
función
panelsimulations
Panel
EnableViewState=true
vista de simulaciones
lnkForm2
LinkButton
EnableViewState=true
enlace al formulario
simulationsHTML
Literal
 
código HTML de la lista de simulaciones en una tabla HTML

7.14.2.4. El código de control de la aplicación

El código de control de la aplicación se encuentra distribuido en los archivos [global.asax.vb] y [main.aspx.vb]. El archivo [global.asax] se define de la siguiente manera:

<%@ Application src="Global.asax.vb" Inherits="Global" %>

El archivo [global.asax.vb] es el siguiente:


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Collections

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' se crea un objeto de importación
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
            ' se agrega el objeto a la aplicación
            Application("objImpot") = objImpot
            ' sin errores
            Application("erreur") = False
        Catch ex As Exception
            'se produjo un error; se registra en la aplicación
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Inicio de sesión: se crea una lista de simulaciones vacía
        Session.Item("simulations") = New ArrayList
    End Sub
End Class

Cuando se inicia la aplicación (primera solicitud a la aplicación), se ejecuta el procedimiento [Application_Start]. Este procedimiento busca crear un objeto de tipo [impot] utilizando datos de una fuente OLEDB. Se recomienda al lector que consulte el capítulo 5, donde se definió esta clase, en caso de que la haya olvidado. La creación del objeto [impot] puede fallar si la fuente de datos no está disponible. En ese caso, el error se almacena en la aplicación para que todas las consultas posteriores sepan que este objeto no pudo inicializarse correctamente. Si la creación se realiza sin problemas, el objeto [impot] creado también se almacena en la aplicación. Será utilizado por todas las consultas de cálculo de impuestos. Cuando un cliente realiza su primera consulta, el procedimiento [Application_Start] crea una sesión para él. Esta sesión está destinada a almacenar las diferentes simulaciones de cálculo de impuestos que realizará. Estas se almacenarán en un objeto [ArrayList] asociado a la clave de sesión «simulaciones». Al iniciar la sesión, esta clave se asocia a un objeto [ArrayList] vacío. La información necesaria para la aplicación se coloca en su archivo de configuración [wenConfig]:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key="chaineConnexion" value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\webforms\vs\impots5\impots.mdb" />
    </appSettings>
</configuration>

La clave [chaineConnexion] identifica la cadena de conexión a la fuente OLEDB. La otra parte del código de control se encuentra en [main.aspx.vb]:


Imports System.Collections
Imports Microsoft.VisualBasic
Imports st.istia.univangers.fr
Imports System

Public Class main
    Inherits System.Web.UI.Page

    Protected WithEvents rdOui As System.Web.UI.WebControls.RadioButton
    Protected WithEvents rdNon As System.Web.UI.WebControls.RadioButton
    Protected WithEvents txtEnfants As System.Web.UI.WebControls.TextBox
    Protected WithEvents txtSalaire As System.Web.UI.WebControls.TextBox
    Protected WithEvents btnCalculer As System.Web.UI.WebControls.Button
    Protected WithEvents btnEffacer As System.Web.UI.WebControls.Button
    Protected WithEvents panelform As System.Web.UI.WebControls.Panel
    Protected WithEvents lnkForm1 As System.Web.UI.WebControls.LinkButton
    Protected WithEvents lnkForm2 As System.Web.UI.WebControls.LinkButton
    Protected WithEvents panelerreurs As System.Web.UI.WebControls.Panel
    Protected WithEvents panelsimulations As System.Web.UI.WebControls.Panel
    Protected WithEvents simulationsHTML As System.Web.UI.WebControls.Literal
    Protected WithEvents erreursHTML As System.Web.UI.WebControls.Literal

    ' variables locales

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
    End Sub

    Private Sub afficheFormulaire()
...
    End Sub

    Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
...
    End Sub

    Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
...
    End Sub

    Private Function checkData() As ArrayList
...
    End Function

    Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
....
    End Sub

    Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
...
    End Sub

    Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
...
    End Sub

    Private Sub razForm()
...
    End Sub
End Class

Le premier événement traité par le code est [Page_Load] :

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' En primer lugar, se revisa el estado de la aplicación
        If CType(Application("erreur"), Boolean) Then
            ' la aplicación no pudo inicializarse
            ' se muestra la vista de errores
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
            afficheErreurs(erreurs, "")
            Exit Sub
        End If
        ' No hay errores: en la primera solicitud, se muestra el formulario
        If Not IsPostBack Then afficheFormulaire()
    End Sub

Recordemos que cuando el procedimiento [Page_Load] se ejecuta en un cliente POST, todos los componentes del formulario tienen un valor: ya sea el valor enviado por el cliente, si lo hay, o el valor anterior del componente gracias al [VIEWSTATE]. En este formulario, todos los componentes tienen la propiedad [EnableViewState=true]. Antes de comenzar a procesar la solicitud, nos aseguramos de que la aplicación se haya inicializado correctamente. Si no es así, mostramos la vista [erreurs] mediante el procedimiento [afficheErreurs]. Si se trata de la primera consulta (IsPostBack=false), mostramos la vista [formulaire] con [afficheFormulaire].

El procedimiento que muestra la vista [erreurs] es el siguiente:


    Private Sub afficheErreurs(ByRef erreurs As ArrayList, ByRef lien As String)
        ' se muestra el contenedor de errores
        panelerreurs.Visible = True
        Dim i As Integer
        erreursHTML.Text = ""
        For i = 0 To erreurs.Count - 1
            erreursHTML.Text += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
        Next
        lnkForm1.Text = lien
        ' los demás contenedores están ocultos
        panelform.Visible = False
        panelsimulations.Visible = False
    End Sub

El procedimiento tiene dos parámetros:

  • una lista de mensajes de error en [erreurs]
  • un texto de enlace en [lien]

El código HTML que se generará para la lista de errores se coloca en el literal [erreursHTML]. El texto del enlace, por su parte, se coloca en la propiedad [Text] del objeto [LinkButton] de la vista.

El procedimiento que muestra la vista [formulaire] es el siguiente:


    Private Sub afficheFormulaire()
        ' muestra el formulario
        panelform.Visible = True
        ' los demás contenedores están ocultos
        panelerreurs.Visible = False
        panelsimulations.Visible = False
    End Sub

Este procedimiento simplemente hace visible el contenedor [panelform]. Los componentes se muestran con su valor actualizado o anterior (VIEWSTATE).

Cuando el usuario hace clic en el botón [Calculer] de la vista [formulaire], se realiza un POST hacia [main.aspx]. Se ejecuta el procedimiento [Page_Load] y, a continuación, el procedimiento [btnCalculer_Click]:


    Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
        ' se verifica la validez de los datos ingresados
        Dim erreurs As ArrayList = checkData()
        ' si hay errores, se reportan
        If erreurs.Count <> 0 Then
            ' se muestra la página de errores
            afficheErreurs(erreurs, "Retour au formulaire")
            Exit Sub
        End If
        ' sin errores: se calcula el impuesto
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
        ' se agrega el resultado a las simulaciones existentes
        Dim simulation() As String = New String() {CType(IIf(rdOui.Checked, "oui", "non"), String), _
         txtEnfants.Text.Trim, txtSalaire.Text.Trim, impot.ToString}
        ' se agrega el resultado a las simulaciones existentes
        Dim simulations As ArrayList = CType(Session.Item("simulations"), ArrayList)
        simulations.Add(simulation)
        ' se colocan las simulaciones en la sesión y el contexto
        Session.Item("simulations") = simulations
        ' se muestra la página de resultados
        afficheSimulations(simulations, "Retour au formulaire")
    End Sub

El procedimiento comienza verificando la validez de los campos del formulario mediante el procedimiento [checkData], el cual devuelve una lista [ArrayList] de mensajes de error. Si la lista no está vacía, se muestra la vista [erreurs] y el procedimiento finaliza. Si los datos ingresados son válidos, se calcula el monto del impuesto mediante el objeto de tipo [impot] que se había almacenado en la aplicación al iniciarla. Esta nueva simulación se agrega a la lista de simulaciones ya realizadas y se almacena en la sesión.

La función [CheckData] verifica la validez de los datos. Devuelve una lista [ArrayList] de mensajes de error, que estará vacía si los datos son válidos:


    Private Function checkData() As ArrayList
        ' Al inicio no hay errores
        Dim erreurs As New ArrayList
        ' número de hijos
        Try
            Dim nbEnfants As Integer = CType(txtEnfants.Text, Integer)
            If nbEnfants < 0 Then Throw New Exception
        Catch
            erreurs.Add("Le nombre d'enfants est incorrect")
        End Try
        ' salario
        Try
            Dim salaire As Long = CType(txtSalaire.Text, Long)
            If salaire < 0 Then Throw New Exception
        Catch
            erreurs.Add("Le salaire annuel est incorrect")
        End Try
        ' se muestra la lista de errores
        Return erreurs
    End Function

Finalmente, la vista [simulations] se muestra mediante el siguiente procedimiento [afficheSimulations]:


    Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
        ' muestra la vista de simulaciones
        panelsimulations.Visible = True
        ' los demás contenedores están ocultos
        panelerreurs.Visible = False
        panelform.Visible = False
        ' contenido de la vista de simulaciones
        ' cada simulación es una matriz de 4 elementos de tipo cadena
        Dim simulation() As String
        Dim i, j As Integer
        simulationsHTML.Text = ""
        For i = 0 To simulations.Count - 1
            simulation = CType(simulations(i), String())
            simulationsHTML.Text += "<tr>"
            For j = 0 To simulation.Length - 1
                simulationsHTML.Text += "<td>" + simulation(j) + "</td>"
            Next
            simulationsHTML.Text += "</tr>" + ControlChars.CrLf
        Next
        ' enlace
        lnkForm2.Text = lien
    End Sub

El procedimiento tiene dos parámetros:

  • una lista de simulaciones en [simulations]
  • un texto de enlace en [lien]

El código HTML que se generará para la lista de simulaciones se coloca en el literal [simulationsHTML]. El texto del enlace, por su parte, se coloca en la propiedad [Text] del objeto [LinkButton] de la vista.

Cuando el usuario hace clic en el botón [Effacer] de la vista [formulaire], se ejecuta el procedimiento [btnEffacer_click] (siempre después de [Page_Load]):


    Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
        ' muestra el formulario en blanco
        razForm()
        afficheFormulaire()
    End Sub

    Private Sub razForm()
        ' borra el formulario
        rdOui.Checked = False
        rdNon.Checked = True
        txtEnfants.Text = ""
        txtSalaire.Text = ""
    End Sub

El código anterior es lo suficientemente sencillo como para no necesitar comentarios. Ahora nos queda gestionar el clic en los enlaces de las vistas [erreurs] y [simulations]:


    Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
        ' muestra el formulario
        afficheFormulaire()
    End Sub

    Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
        ' muestra el formulario
        afficheFormulaire()
    End Sub

Ambos procedimientos se limitan a mostrar la vista [formulaire]. Sabemos que los campos de esta vista obtendrán un valor que será, o bien el valor enviado para ellos, o bien su valor anterior. Como en este caso, el POST del cliente no envía ningún valor para los campos del formulario, estos recuperarán su valor anterior. Por lo tanto, el formulario se muestra con los valores ingresados por el usuario. Recordamos que, en la versión sin componentes de servidor, habíamos realizado esta tarea de restauración nosotros mismos.

7.14.2.5. Pruebas

Todos los archivos necesarios para la aplicación se encuentran en una carpeta <application-path>:
La carpeta [bin] contiene el archivo DLL con las clases [impot], [impotsData] y [impotsOLEDB] necesarias para la aplicación:

El lector puede, si lo desea, volver a consultar el capítulo 5, donde se explica cómo crear el archivo [impot.dll] mencionado anteriormente. Una vez hecho esto, se inicia el servidor Cassini con los parámetros (<ruta-de-la-aplicación>,/impots5). Se solicita la URL [http://impots5/main.aspx] con un navegador:

Image

Si cambiamos el nombre del archivo ACCESS [impots.mdb] a [impots1.mdb], obtendremos la siguiente página:

Image

7.14.3. Ejemplo 3

En estos dos ejemplos hemos demostrado que es posible desarrollar aplicaciones web que sigan la arquitectura MVC utilizando componentes de servidor. El último ejemplo muestra que la solución con componentes de servidor es más sencilla que la que utiliza etiquetas HTML estándar. Nuestros dos ejemplos contaban con una sola página que incluía varias vistas dentro de la misma página. Se puede tener una arquitectura MVC con varios formularios ASP de servidor, siempre y cuando el hecho de que estos envíen los valores del formulario a sí mismos no represente un problema. Esto suele ocurrir con frecuencia en aplicaciones con menú. Veamos el siguiente ejemplo:

Image

Hemos reunido en una misma página enlaces a las aplicaciones que hemos escrito hasta ahora. Este tipo de aplicación se adapta bien a una arquitectura MVC. Simplemente, ya no hay un solo controlador, sino varios.

Image

El controlador [main.aspx] actúa como controlador principal. Es a él a quien llaman los enlaces de la página de inicio de la aplicación. Este podrá realizar operaciones comunes a todas las acciones posibles y, a continuación, ejecutará la acción específica asociada al enlace utilizado. Luego, le cederá el control a uno de los controladores secundarios, el encargado de ejecutar la acción. A partir de ese momento, las comunicaciones se realizan entre el cliente y ese controlador específico. Ya no se pasa por el controlador principal [main.aspx]. Por lo tanto, ya no nos encontramos en el marco MVC con un único controlador que filtra todas las solicitudes. Cada uno de los controladores mencionados anteriormente puede presentar varias vistas mediante el mecanismo de contenedores dentro de una sola página, tal como lo hemos explicado.

El hecho de que ya no haya un único controlador que elija las vistas que se enviarán al cliente presenta algunas desventajas. Tomemos como ejemplo el manejo de errores. Cada una de las acciones expuestas por la aplicación puede llegar a mostrar una vista de errores. Cada controlador [applix.aspx] tendrá su propia vista [erreurs], ya que esta es simplemente un contenedor específico de la página del controlador. No hay forma de tener una vista única [erreurs] que sea utilizada por todas las aplicaciones individuales. De hecho, dicha vista suele incluir un enlace de regreso al formulario con errores, y este debe restaurarse al estado en el que fue validado para que el usuario pueda corregir sus errores. Esta restauración se realiza mediante el mecanismo del [VIEWSTATE], que no funciona entre controladores diferentes. Si las aplicaciones son desarrolladas por diferentes personas, existe el riesgo de que las páginas de error tengan un aspecto diferente según la acción elegida por el usuario, lo que afecta la uniformidad de la aplicación en su conjunto. Veremos más adelante que ASP.NET ofrece una solución a este problema específico de la vista compartida. Esta solución puede ser objeto de un nuevo componente de servidor que construiremos nosotros mismos. Basta con utilizar este componente en las diferentes aplicaciones para garantizar la homogeneidad de la aplicación global. Más difícil de manejar es el problema del orden de las acciones. Cuando todas las solicitudes pasan por un único controlador, este puede verificar que la acción solicitada sea compatible con la anterior. Este código de control se encuentra en un solo lugar. Aquí, habrá que distribuirlo entre los distintos controladores, lo que complica el mantenimiento de la aplicación global.

Volvamos a nuestra aplicación anterior. Su página de inicio es una página clásica HTML:

<html>
    <head>
        <TITLE>Composants ASP Serveur</TITLE>
        <meta name="pragma" content="no-cache">
    </head>
    <frameset rows="130,*" frameborder="0">
        <frame name="banner" src="bandeau.htm" scrolling="no">
        <frameset cols="200,*">
            <frame name="contents" src="options.htm">
            <frame name="main" src="main.htm">
        </frameset>
        <noframes>
            <p id="p1">
                Ce jeu de frames HTML affiche plusieurs pages Web. Pour afficher ce jeu de 
                frames, utilisez un navigateur Web qui prend en charge HTML 4.0 et version 
                ultérieure.
            </p>
        </noframes>
    </frameset>
</html>

Esta página de inicio consta de tres marcos llamados «banner», «contents» y «main»:

La página [bandeau.htm], ubicada en el marco [banner], es la siguiente:

Image

Su código HTML es el siguiente:


<html>
    <head>
        <META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE" />
        <title>bandeau</title>
    </head>
    <body>
        <P>
            <TABLE>
                <TR>
                    <TD><IMG alt="logo université d'angers" src="univ01.gif"></TD>
                    <TD>Composants serveurs ASP</TD>
                </TR>
            </TABLE>
        </P>
        <HR>
    </body>
</html>

La página [options.htm] se encuentra en el banner [contents]. Se trata de un conjunto de enlaces:


<html>
    <head>
        <meta http-equiv="pragma" content="no-cache" />
        <title>options</title>
    </head>
    <body bgcolor="Gold">
        <ul>
            <li>
                <a href="main.aspx?action=label" 
                 target="main">Label</a>
            <li>
                <a href="main.aspx?action=button" 
                 target="main">Button</a>
            <li>
                <a href="main.aspx?action=textbox1" 
                 target="main">TextBox-1</a></li>
            <li>
                <a href="main.aspx?action=textbox2" 
                 target="main">TextBox-2</a></li>
            <li>
                <a href="main.aspx?action=dropdownlist" 
                 target="main">DropDownList</a></li>
            <li>
                <a href="main.aspx?action=listbox" 
                 target="main">ListBox</a></li>
            <li>
                <a href="main.aspx?action=casesacocher" 
                  target="main">CheckBox</a> et<br>
                RadioButton</li>
            <li>
                <a href="main.aspx?action=listecasesacocher" 
                  target="main">CheckBoxList</a> et<br>
                RadioButtonList</a></li>
            <li>
                <a href="main.aspx?action=panel" 
                  target="main">Panel</a></li>
        </ul>
    </body>
</html>

Todos los enlaces apuntan al controlador principal [main.aspx] con un parámetro [action] que indica la acción a realizar. Se solicita que el destino de los enlaces se muestre en el marco [main] (target="main").

La primera página que se muestra en el marco [main] es [main.htm]:


<html>
    <head>
        <title>main</title>
    </head>
    <body>
        <P>Choisissez une option pour tester découvrir un type de composant serveur...</P>
    </body>
</html>

El controlador principal [main.aspx, main.aspx.vb] es el siguiente:

[main.aspx]

<%@ page src="main.aspx.vb" inherits="main" autoeventwireup="false" %>

[main.aspx.vb]

Clase pública main

Heredado de System.Web.UI.Page

Sub privada Page_Load(ByVal remitente como System.Object, ByVal e como System.EventArgs) Con este identificador MyBase.Load

' se recupera la acción a realizar

Dim acción As String

Si Request.QueryString("action") es Nothing, entonces

acción = "label"

Else

acción = Request.QueryString("acción").ToString.ToLower

End If

' se ejecuta la acción

Select Case acción

Case "label"

Server.Transfer("form2.aspx")

Case "botón"

Server.Transfer("form3.aspx")

Case "cuadro de texto1"

Server.Transfer("form4.aspx")

Case "textbox2"

Server.Transfer("form5.aspx")

Caso «lista desplegable»

Server.Transfer("form6.aspx")

Campo «listbox»

Server.Transfer("form7.aspx")

Casilla «casilla de selección»

Server.Transfer("form8.aspx")

Caso «lista de casillas de selección»

Server.Transfer("form8b.aspx")

Casilla «panel»

Server.Transfer("form9.aspx")

Caso Else

Server.Transfer("form2.aspx")

Fin de la selección

Fin de Sub

Fin de clase

Nuestro controlador es sencillo. Según el valor del parámetro [action], transfiere el procesamiento de la solicitud a la página adecuada. No aporta ningún valor agregado en comparación con una página HTML con enlaces. Sin embargo, bastaría con agregar una página de autenticación para apreciar su utilidad. Si el usuario tuviera que autenticarse (nombre de usuario, contraseña) para acceder a las aplicaciones, el controlador [main.aspx] sería un buen lugar para verificar que se haya realizado dicha autenticación.