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 crear 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 está formado por código situado bien en la propia página .aspx (solución WebMatrix), bien en un archivo independiente (solución Visual Studio.NET).
  2. A continuación, se ejecuta el código de presentación de la página .aspx para transformarlo en código HTML, que 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 sustituye 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]. En ocasiones, esto resulta realmente útil. Tomemos como ejemplo 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 hay que 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 incluyendo en el código de presentación la etiqueta

<%=uneListeHTML%>

Esta etiqueta se sustituirá 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 especialmente difícil de hacer y parece una solución elegante que evita incluir código de generación directamente en la parte de presentación de la página. En este caso, habría que insertar un bucle con comprobaciones en dicha parte, lo que la «contaminaría» en gran medida. No obstante, 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 puede querer intervenir en este código HTML para modificar el aspecto «visual» de la lista. Se verá obligado a trabajar en la parte [contrôleur] y, por lo tanto, a salir de su ámbito de competencia, con el riesgo que ello conlleva de introducir errores involuntariamente en el código.

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 tiene 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>

se sustituirá 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 programar orientada a objetos, lo cual resulta interesante. Volvamos a nuestro diseñador gráfico, que 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 no tiene que salir del código de presentación para realizar estas modificaciones. Se trata de una ventaja indudable con respecto al método anterior. Las bibliotecas de etiquetas de servidor facilitan, por tanto, la construcción de la parte de presentación de las páginas, lo que en los capítulos anteriores hemos denominado «interfaz de usuario». El objetivo de este capítulo es presentarlas. Veremos que, en ocasiones, 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. He aquí 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 es capaz de gestionar. De hecho, a lo largo del tiempo ha habido varias versiones del lenguaje HTML. Los navegadores actuales son compatibles con las versiones más recientes del lenguaje, algo que los navegadores más antiguos no pueden hacer. En función 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, herramientas avanzadas como Visual Studio.NET, WebMatrix, etc., permiten un diseño «al estilo Windows» de la interfaz web. Estas herramientas, aunque no son imprescindibles, suponen una ayuda decisiva para el desarrollador. Este diseña la interfaz web mediante componentes gráficos que coloca en dicha interfaz. Tiene acceso directo a las propiedades de cada uno de los componentes de la interfaz, que puede configurar a su antojo. Estas propiedades se traducirán en el código HTML de presentación de la interfaz en 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 supone 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 fase de diseño gráfico. En ese caso, un IDE ya no resulta útil. Basta con un simple editor de texto. Dependiendo de la forma de trabajar, se hace hincapié 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 va a 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 en 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 mediante un navegador. Por lo tanto, se realizará una solicitud GET a esta URL P. El servidor enviará la página P y, por lo tanto, el formulario F que contiene,
  3. el usuario lo rellenará 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 habrá podido ser modificado por la acción del usuario.
  4. A continuación, 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 dejan de funcionar. Ya no nos encontramos en un contexto de arquitectura MVC en el que una aplicación multipágina está controlada por una página concreta a la que hemos denominado «controlador de aplicación». En este tipo de arquitectura, el POST de los formularios tiene como destino el controlador y no 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 problema ya se ha tratado en el capítulo 2 y se invita al lector a consultarlo si desea saber cómo se abordó. 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 el componente. El controlador deberá hacer referencia a él 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
        ' se introduce 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 sistemáticamente este espacio de nombres:


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. En este caso, 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 ocasión 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 recomienda al lector que consulte la documentación sobre la clase [Label] para conocerlos. De aquí en adelante, siempre procederemos de esta manera. Para cada etiqueta, solo presentaremos las pocas propiedades que necesitemos.

7.3.2. Las pruebas

Colocamos los archivos (form1.aspx, form1.aspx.vb) en una carpeta <application-path> e iniciamos Cassini con los parámetros (<application-path>,/form1). A continuación, 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 puede observar 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 manualmente el código de presentación de la página [form1.aspx]. Este método es útil si se conocen las etiquetas. En ese caso, basta con un simple editor de texto para crear la interfaz de usuario. Para empezar, suele ser necesaria 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 vamos a crear la misma aplicación con la herramienta WebMatrix. Una vez iniciada 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 agrupa 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 comprobar que no está vacío:

Image

El inconveniente 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... Por otra parte, 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 denominamos «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. Volvemos a la pestaña [Design] para diseñar nuestra interfaz. En una ventana de herramientas situada a la izquierda de la ventana de diseño hay disponible una lista de componentes:

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 añadir 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 de la etiqueta HTML a la que representa. Anteriormente, nos referíamos a estas etiquetas como etiquetas HTML de servidor.

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 ha quedado 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 observa es que se ha perdido la etiqueta <script>. Se ha generado una etiqueta <asp:label>. Tiene el nombre [Label1] y el 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 recomienda al lector que consulte las propiedades del objeto [Label]. Hay dos que nos interesan aquí:

  • Texto: es el texto que debe mostrar la etiqueta; introducimos la cadena vacía (c.a.d. nada)
  • ID: es su identificador; introducimos 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 ya está terminada. Ahora nos queda escribir el código de control encargado de introducir la hora en la propiedad [Text] de [lblHeure]. Añadimos 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
        ' introduce 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 destacar 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 parte de presentación se declaran de forma implícita en la parte de código de control. Por lo tanto, declararlos provoca un error de compilación, que indica que el objeto ya está declarado. Ya 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 abre 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 intención de dar formato al texto que se desea insertar en el flujo HTML. De hecho, mientras que la clase [Label] permite aplicar formato mediante atributos como [BorderColor, BorderWidth, Font, ...], la clase [Literal] no cuenta con ninguno de estos atributos. El lector podrá 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 que 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, creada 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ón 2
botón de envío
3
lblInfo
Etiqueta
text=
mensaje informativo

El código generado por WebMatrix para esta parte 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 ha generado automáticamente. Se trata de una etiqueta de servidor HTML, c.a.d. Una etiqueta HTML clásica representada, no obstante, por un objeto que puede manipular el controlador. El código de la etiqueta <form> se generará a partir del valor que el controlador asigne a este objeto.

Añadamos en la parte del código correspondiente al controlador el procedimiento [Page_Init], que gestiona el evento [Init] de la página. En él colocamos el código que guarda la solicitud del cliente en el archivo [request.txt]. Lo necesitaremos para comprender 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 en la carpeta 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 incluido la cláusula [Handles MyBase.Init] tras la declaración del procedimiento [Page_Init]. De hecho, el evento [Init] del objeto [Page] tiene un controlador por defecto llamado [Page_Init]. Si se utiliza este nombre de controlador, la cláusula [Handles Page.Init] resulta innecesaria. Sin embargo, incluirla no provoca 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 convertido en la etiqueta HTML
    <form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">

Se han fijado 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 mismo. Dado que [__VIEWSTATE] forma parte del formulario, su valor se enviará junto con el resto al servidor. Esto permitirá al servidor saber qué componente del formulario ha cambiado de valor y, en su caso, tomar decisiones. Estas decisiones adoptarán la forma de eventos del tipo «el TextBox tal y 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 ha enviado el navegador para obtenerla. Recordemos que nuestra aplicación la almacena en el archivo [request.txt], en 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 ocurre nada. Sin embargo, sabemos que el formulario se ha enviado. Así lo indica el código HTML de la página. Esto queda confirmado por 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 ha realizado un POST a 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 ha hecho 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 ha utilizado. Lo utilizará para desencadenar un evento que podrá ser procesado por la página, una vez que esta se haya cargado.

7.5.4. Gestionar el evento Click de un objeto Button

Recordemos cómo funciona una página .aspx. Se trata de 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 denominados [Init] y [Load], en ese orden. El objeto [unePage] puede gestionarlos proporcionando los controladores de eventos [Page_Init] y [Page_Load]. A continuación, se generarán otros eventos. Tendremos ocasión 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 ha provocado dicho POST. Si la clase [unePage] ha definido un controlador para este evento, se invocará dicho controlador. 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], dentro del 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 añadido un nuevo atributo [onclick="Button1_Click"] a la etiqueta <asp:Button> de [Button1]. Este atributo indica el procedimiento encargado de gestionar 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)
        ' haz clic en el botón 1
        lblInfo.Text="Vous avez cliqué sur [Bouton1]"
    End Sub

    Sub Button2_Click(sender As Object, e As EventArgs)
        ' 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. Eventos del ciclo de vida de una aplicación ASP.NET

La documentación ASP.NET ofrece una lista de los eventos generados por el servidor a lo largo del ciclo de vida de una aplicación:

  • cuando la aplicación recibe la primera solicitud, 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, a continuación, generará dos eventos en dicho objeto: [Init] y, posteriormente, [Load]. Estos dos eventos pueden gestionarse mediante los procedimientos [Page_Init] y [Page_Load] de la página.
  • A partir de los valores enviados recibidos, el servidor generará otros eventos: [TextChanged] para un componente [TextBox] cuyo valor ha cambiado, [CheckedChanged] para un botón de radio cuyo valor ha cambiado, [SelectedIndexChanged] para una lista cuyo elemento seleccionado ha cambiado, etc. Tendremos ocasión 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 gestionarse mediante un procedimiento con el nombre O_E.
  • El orden de gestión de los eventos anteriores no está garantizado. Por lo tanto, los gestores no deben hacer ninguna suposición sobre dicho orden. Sin embargo, se garantiza que el evento [Click] del botón que ha provocado el POST se procesa en último lugar.
  • Una vez que la página está lista, el servidor la enviará al cliente. Antes de ello, genera el evento [PreRender], que puede ser gestionado 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 al margen de una solicitud del cliente:

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

Cabe destacar los siguientes puntos:

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

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

Tomemos como ejemplo 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 inmediatamente, tenemos dos soluciones:
    • puede ser procesado por el navegador mediante un script de JavaScript. En ese caso, el servidor no interviene. Para que esto sea posible, es necesario que la página pueda 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, se trata de una operación [submit]. Veremos que, en este caso, se utiliza un componente de servidor denominado [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 crear un [submit] tan pronto como se produzca 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] se debe a un cambio de selección en la lista desplegable. A continuación, el servidor generará el evento [SelectedIndexChanged], que la página podrá gestionar.
    • Si se desea procesarlo, pero no de forma inmediata, 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 JavaScript asociada. Por lo tanto, no ocurre nada cuando el usuario cambia la selección de la lista desplegable. Sin embargo, cuando valide el formulario mediante un botón [submit], por ejemplo, el servidor podrá reconocer que se ha producido 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á comprobar si el elemento seleccionado en la lista desplegable ha cambiado o no. En caso afirmativo, generará el evento [SelectedIndexChanged], que la página podrá gestionar a continuación. Para distinguir este mecanismo del anterior, algunos autores afirman que el evento «cambio de selección» se ha «almacenado en caché» cuando se produjo en el navegador. El servidor solo lo procesará 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 controlador de su evento [SelectedIndexChanged].

Una vez comprendido el mecanismo de gestión 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 inmediatamente el aspecto del formulario en el que se encuentra, en una aplicación web se dudará más a la hora de gestionar este evento de forma inmediata si implica un «post» del formulario al servidor y, por lo tanto, un intercambio de datos entre el cliente y el servidor. Por este motivo, 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 de servidor del formulario tienen su propiedad [AutoPostBack] establecida en [false]
  • el formulario tiene 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 controladores de los únicos eventos que se desean gestionar, normalmente el evento [Click] de 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
Text=
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 parte 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], hacemos doble clic en el componente [TextBox1]. A continuación, se genera la estructura básica del gestor de eventos [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 añadido el atributo [OnTextChanged="TextBox1_TextChanged"] a la etiqueta <asp:TextBox id="TextBox1"> para designar el controlador del evento [TextChanged] sobre [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 realizar un mejor seguimiento del 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 añadido el procedimiento [Page_Init] para guardar la solicitud del cliente tal y como se muestra 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. Cabe destacar los siguientes puntos:

  • hay tres campos ocultos: [__VIEWSTATE], con el que ya nos hemos encontrado, [__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 dado lugar a las etiquetas HTML <input type="text" ...>, que corresponden a los campos de entrada
  • la etiqueta de servidor <asp:textbox id="TextBox1" AutoPostBack="true" ...> ha dado lugar a una etiqueta <input type="text" ...> con un atributo [onchange="__doPostBack('TextBox1','')"]. Este atributo indica que, en caso de que cambie el contenido de [TextBox1], debe ejecutarse la función 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, a continuación, 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 saber qué componente ha generado el POST.

  • La etiqueta de servidor <asp:textbox id="TextBox2"...> ha dado lugar a una etiqueta <input type="text" ...> clásica porque su atributo [AutoPostBack] no estaba establecido 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

y, a continuación, coloquemos el cursor en el segundo campo de entrada. Inmediatamente, aparece una nueva página:

Image

¿Qué ha pasado? Cuando el cursor salió del primer campo de entrada, el navegador comprobó si su contenido había cambiado. Así era. Por lo tanto, generó, en el 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] en el 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 en el 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 y como lo ha recibido. Para ello, reasigna a los componentes el valor que el cliente ha enviado para ellos. En el caso de algunos componentes, el cliente no envía ningún valor. Es el caso, por ejemplo, de los componentes <asp:label>, que se traducen en 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. Dado que el campo oculto [__VIEWSTATE] es enviado por el navegador del cliente, el servidor es capaz de restaurar el estado anterior de todos los componentes del formulario. Solo le queda modificar aquellos cuyo valor haya sido alterado por el POST.

Veamos ahora en [request.txt] la solicitud que ha realizado 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. Volvamos a nuestro navegador e introduzcamos un texto en el segundo campo de entrada:

Image

Volvamos al campo de entrada n.º 1 para introducir un nuevo texto: esta vez no ocurre 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 gestiona el evento [Change], tal y como muestra su código HTML:

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

Por lo tanto, no se produce ningún evento al salir del campo de entrada n.º 2. Ahora introduzcamos 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, que devuelve la siguiente página:

Image

¿Qué ha ocurrido? En primer lugar, el navegador ha enviado 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 ha enviado. 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 como el cliente recuperará el formulario tal y como se ha enviado. A continuación, de nuevo 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 volvemos a modificar el campo de entrada n.º 1:

Image

Al sacar 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 ha ejecutado el procedimiento [TextBox1_Changed] en el servidor. El procedimiento [TextBox2_TextChanged] no se ha ejecutado precisamente porque el valor de [TextBox2] no ha cambiado. Por último, introduzcamos un nuevo texto en el campo n.º 2:

Image

A continuación, situemos el cursor en el campo n.º 1 y volvamos a colocarlo en el campo n.º 2. La página no cambia. ¿Por qué? Como no modificamos el valor del campo n.º 1, el evento del navegador [Change] no se produce al salir de este campo. Por lo tanto, el formulario no se envía al servidor. Así pues, 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 ningún POST. Si lo hubiera, el servidor detectaría que el contenido de [TextBox2] ha cambiado y debería reflejarlo 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. Hemos dicho que este campo representaba el estado del formulario y que, si se le devolvía dicho campo oculto, el servidor era capaz de 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 incluya en [__VIEWSTATE]. A veces, esto no es 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 una solicitud POST al servidor y obtenemos la siguiente respuesta:

Image

Escribamos un texto en el campo n.º 2, modifiquemos el del campo n.º 1 y volvamos al campo n.º 2 (en este 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 pasemos al campo n.º 2. Se envía una nueva solicitud POST. La respuesta del servidor es la siguiente:

Image

Esta vez hay un cambio. El servidor ha detectado un evento [TextChanged] en el campo n.º 2, ya que se ha modificado la hora de [lblInfo2]. Sin embargo, no se ha producido ningún cambio. Esto se debe a la propiedad [EnableViewState=false] de [TextBox2]. Esta propiedad hace que el servidor no haya introducido 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). A partir de ahí, concluyó que [TextBox2] había cambiado de valor y generó el evento [TextChanged] para [TextBox2]. Podemos comprobar este funcionamiento introduciendo 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 ha ocurrido. La hora y el contenido de [lblInfo2] muestran que el procedimiento [TextBox2_TextChanged] no se ha ejecutado. 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 vacío. Todo esto se consigue 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, se modificaba el contenido del campo 1 sin cambiar el del campo 2, lo que hacía que el procedimiento [TextBox2_TextChanged] no se ejecutara en el servidor, lo que 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 introducido. 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, sea cual sea el valor de su propiedad [EnableViewState], el estado de un componente no se guarda en el campo oculto [__VIEWSTATE]. Todo ocurre entonces como si su estado anterior fuera la cadena vacía.

Utilicemos ahora el cliente [curl] para poner de relieve otros mecanismos. En primer lugar, 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 difiere 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 ha detectado un evento [TextChanged] en [TextBox1], ya que ha generado el texto [lblInfo1]. La ausencia de [__VIEWSTATE] no le ha supuesto ningún problema. En su ausencia, supone que el valor anterior de un campo de entrada es la cadena vacía.
  • Ha sido capaz de volver a introducir el texto enviado para [TextBox1] en el atributo [value] de la etiqueta [TextBox1], de modo que el campo [TextBox1] vuelva a aparecer con el valor introducido. 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 ha podido 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, ha generado aquí el evento [TextChanged] sobre [TextBox1]. Volvamos a ejecutar 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 ha considerado que el valor anterior de [TextBox1] era la cadena vacía. Dado que el valor registrado de [TextBox1] también era una cadena vacía, no se generó el evento [TextChanged] sobre [TextBox1]. El procedimiento [TextBox1_TextChanged] no se ha ejecutado y, por lo tanto, el campo [lblInfo1] no ha recibido ningún valor nuevo. Se sabe que, en este caso, el componente conserva su valor anterior. Sin embargo, en este caso 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 ha asignado una cadena vacía a [lblInfo1]. En el caso de [TextBox2], el servidor ha comparado su valor enviado ([second texte]) con su valor anterior. Al no existir [__VIEWSTATE], dicho 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 ha ejecutado el procedimiento [TextBox2_TextChanged] y el campo [lblInfo2] ha recibido 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 ha provocado 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 enviados, 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 protegido y a un campo de entrada multilínea. 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 añadir 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]. En el servidor, gestionamos el evento de clic en el botón [btnAjouter]:

Sub btnAjouter_Click(sender As Object, e As EventArgs)
  ': se añade 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 POST de un formulario. Primero se ejecutan los procedimientos [Page_Init] y [Page_Load]. A continuación, se ejecutan todos los procedimientos de eventos almacenados en caché. Por último, se ejecuta el procedimiento que gestiona el evento que ha provocado el [POST], en este caso el procedimiento [btnAjouter_Click]. Cuando se ejecutan los gestores de eventos, todos los componentes de la página que tienen un valor en el POST han adoptado dicho 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] pasarán a formar parte del campo POST creado por el cliente. Asimismo, en el código anterior, [textbox1.text] tendrá como valor el valor enviado por el cliente, al igual que [textbox2.text]. El procedimiento [btnAjouter_Click] introduce en el campo [TextBox2] el valor enviado para [TextBox2] sumado al 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 añade 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 ha mostrado una 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 rellenaremos 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 elementos 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, de tipo [ListItem]
Items.Add
para añadir 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] tiene el valor «verdadero»
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] da lugar a 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) dará lugar a 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 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 de rellenado del menú desplegable [DropDownList1]:

    Sub Page_Load(sender As Object, e As EventArgs)
        ' se rellena 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, a continuación, 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 rellenado de esta lista en [Page_Load]? La respuesta depende del atributo [EnableViewState] del componente [DropDownList1]. Si este atributo tiene el valor «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 elementos:

  • 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 deben incluirse 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 volverán a calcular de todos modos. El objeto [Page], del que [form6.aspx] es una instancia, posee un atributo [IsPostBack] con valor booleano. Si este atributo es verdadero, significa que la página ha sido llamada por un POST. Si es falso, significa que la página ha sido 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 del [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 dicha lista en [false] para evitar un doble cálculo innecesario del contenido de la lista, salvo que sea necesario 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. A continuación, activará el evento [SelectedIndexChanged] en dicho 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]. Por otra parte, uno de ellos, de tipo [ListItem], tiene su atributo [Selected] establecido en «verdadero», que es el valor que ha enviado el navegador. Se puede acceder a este elemento de varias formas:

DropDownList1.SelectedItem
es el primer elemento [ListItem] de la lista cuyo atributo [Selected] tiene el valor «true»
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.º 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 rellena 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] deriva de la misma clase [ListControl] que la clase [DropDownList] analizada anteriormente. En ella se encuentran todas las propiedades y métodos vistos 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)
      ' Añadir 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 introducido en [txtSaisie] no es una cadena vacía o en blanco, se añade un nuevo elemento a la lista [ListBox1]. Sabemos que debemos añadir 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>]. En este caso, 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 añadido el contenido de [txtSaisie] a la lista [ListBox1], el campo [txTSaisie] se vacía.

Los clics en los botones [Effacer] se procesarán mediante los siguientes procedimientos:

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

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

Los clics en los botones de transferencia entre listas se gestionan 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)
      ' transferencia 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 que hacer algo?
      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 comprueba 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 comprueba para cada uno de ellos si su atributo [selected] es verdadero. Si es así, su atributo [selected] se establece en [false], a continuación se copia en 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 n.º 11. Tras 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 lleva el n.º 10 se nos escapa. Al recorrer los elementos de la lista l1 en sentido inverso, se evita 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=true
RadioButton1.Text=1
RadioButton2.Checked=false
RadioButton2.Text=2
RadioButton3.Checked=false
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 hace mediante el atributo [GroupName] de la clase [RadioButton]. En esta aplicación no es necesario mantener el estado de la página. Por lo tanto, añadimos 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>

Tenemos que escribir la rutina [btnEnvoyer_Click] para gestionar el evento [Click] de 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 especial, innovamos un poco:

<script runat="server">

    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
      ' se introduce información en el cuadro de lista
      for each c as control in FindControl("frmControls").controls
          ' ¿Se deriva el control 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 en árbol de controles. En nuestro ejemplo, la página contiene textos y controles de servidor. Los textos se consideran un control específico denominado [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 tiene 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=btnEnvoyer] de tipo [Button]

Un control tiene las siguientes propiedades:

[Control].Controls
devuelve la colección de controles secundarios 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") hace referencia al contenedor [HtmlForm]. Para acceder al botón de opción [RadioButton1], habrá que 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 introduce información en el cuadro de lista
      for each c as control in FindControl("frmControls").controls
          ' ¿El control 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 hay 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 muestra un ejemplo de ejecución.

7.10. Los componentes CheckBoxList y RadioButtonList

A veces, se desea que el usuario elija entre valores que se desconocen en el momento del diseño de la página. Estas opciones proceden de un archivo de configuración, de una base de datos, etc., y solo se conocen en el momento de la ejecución. Existen soluciones para este problema y las hemos encontrado. La lista de selección simple resulta 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 elevado, puede ser preferible utilizar botones de radio en lugar de la lista de selección simple o casillas de verificació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] derivan de la misma clase [ListControl] que las clases [DropDownList] y [ListBox] estudiadas anteriormente. Por lo tanto, encontraremos algunas de las propiedades y métodos que hemos visto en esas clases, 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, de tipo [ListItem]
Items.Add
para añadir 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 de 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 «text» y «value». 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 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 rellenan las listas si es la primera vez que se llama a la función
        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"}
                 ' rellenar 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
            ' rellenar 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 introduce 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 que hacer algo?
      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 se inicialicen 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 mostrada la página, el usuario marca algunas casillas y utiliza el botón [Envoyer]. A continuación, los valores del formulario se envían al propio formulario. Tras la ejecución de [Page_Load], se ejecuta el procedimiento [btnEnvoyer_Click]. Este recurre al procedimiento [affiche] para rellenar 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. Su función es similar a la del botón [Button]. De hecho, provoca un POST del lado del cliente gracias a una función de JavaScript asociada a ella. 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 gestores 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 en 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 la totalidad de las clases correspondientes a cada uno de los componentes de servidor. Este es el método más recomendable 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 ofrece en ocasiones ejemplos.

En este capítulo, hemos utilizado la técnica «todo en uno» de [WebMatrix], c.a.d, en la que se ha incluido 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 puede compilarse sin necesidad de ejecutar la aplicación web. Por otra parte, nuestros ejemplos —como ya hemos explicado al principio del capítulo— tenían un perfil muy concreto: consistían en una única página que era un formulario que se intercambiaban el cliente y el servidor 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 creado varias aplicaciones web. Todas ellas se han desarrollado siguiendo la arquitectura MVC (Modelo-Vista-Controlador), que divide la aplicación en bloques bien diferenciados y facilita su mantenimiento. 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 en profundidad: 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 rellena 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 haya rellenado el formulario [formulaire.aspx], este debe enviarse a [main.aspx] y no a [formulaire.aspx]. Esto simplemente no es posible si creamos la interfaz de usuario [formulaire.aspx] con componentes de servidor ASP. Para comprobarlo, creemos 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 tiene como destino el 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 imprescindible para todos los componentes de servidor ASP que recogen las entradas del usuario. Es como decir que no se pueden utilizar formularios del servidor ASP en una aplicación MVC. Es todo un descubrimiento. De hecho, uno de los puntos fuertes del marketing de ASP.NET es que se puede crear una aplicación web igual que 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 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 única página que actúa como controlador
  • las vistas se plasman en esta página mediante distintos contenedores, 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 vamos a poner en práctica 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] tendrá el siguiente aspecto:

La captura de pantalla de la izquierda que se muestra arriba es el formulario tal y como se presenta al cliente. Este lo rellena y lo valida mediante [Envoyer]. El servidor le devuelve una vista en la que se muestra una lista de los valores introducidos (captura de pantalla de la derecha). Un enlace permite al usuario volver al formulario. Este lo encuentra tal y 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 rellena y la envía mediante el botón [Envoyer]. A continuación, se envían los campos de entrada y el servidor asigna a los componentes del servidor el valor enviado o su estado [VIEWSTATE], si lo tuvieran. De este modo, al campo [txtSaisie] se le asigna el valor introducido 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.
  3. El usuario consulta esta nueva vista y utiliza el enlace [Retour au formulaire] para volver a ella. A continuación, se realiza una llamada POST a [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. En cualquier caso, no hay ningún campo [txtSaisie] enviado.
  4. El servidor recibe el POST y asigna a los componentes del servidor el valor enviado o su estado [VIEWSTATE], si lo tuvieran. 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 introducido por el usuario, debe tener la propiedad [EnableViewState=true].

El contenedor [panelinfo] tiene 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 introducidos por el usuario
LinkButton1
LinkButton
EnableViewState=false
enlace de vuelta al formulario

Durante 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 traduce al código HTML porque tiene la propiedad [Visible=false]. Sin embargo, como tiene la propiedad [EnableViewState=true], su valor se conservará de todos modos en el campo oculto [__VIEWSATE]. Por lo tanto, podremos recuperarlo, tal y como demostrarán las pruebas.

Ahora solo 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 veces siguientes, estos recuperan sus valores mediante el VIEWSTATE
         if IsPostBack then return
         ' inicialización del 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
         ' lista 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 almacenan 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))
   ' lista 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 un 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

Retomamos aquí una aplicación ya tratada 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, se utilizará 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 introducción de datos que permite calcular el impuesto de un usuario:

Image

El usuario rellena el formulario:

Image

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

Image

Vuelve al formulario mediante el enlace anterior. Lo encuentra tal y como lo había rellenado. Puede cometer errores al introducir los datos:

Image

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

Image

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

Image

A continuación, obtiene la vista [simulations] con una simulación más:

Image

Por último, si la fuente de datos no está disponible, se seña 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] agrupa 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 destacar que todos ellos se encuentran dentro de la etiqueta <form runat="server">. Esto es obligatorio, ya que, si queremos aprovechar las ventajas de los componentes de servidor, estos deben colocarse dentro de dicha etiqueta. Lo importante que hay que tener en cuenta es que aquí tenemos un único formulario que se intercambiará entre el cliente y el servidor web. Por lo tanto, nos encontramos efectivamente 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 de los impuestos
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
Código 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 repartido 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 añade el objeto a la aplicación
            Application("objImpot") = objImpot
            ' sin errores
            Application("erreur") = False
        Catch ex As Exception
            'se ha producido un error; se anota 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 realizada a la aplicación), se ejecuta el procedimiento [Application_Start]. Este procedimiento intenta crear un objeto de tipo [impot] tomando sus 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 se ha podido inicializar correctamente. Si la creación se realiza correctamente, 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 vaya a realizar. Estas se almacenarán en un objeto [ArrayList] asociado a la clave de sesión «simulations». Al iniciarse la sesión, esta clave se asocia a un objeto [ArrayList] vacío. La información necesaria para la aplicación se almacena 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] hace referencia a 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 comprueba el estado de la aplicación
        If CType(Application("erreur"), Boolean) Then
            ' La aplicación no ha podido 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
        ' sin errores: en la primera solicitud, se muestra el formulario
        If Not IsPostBack Then afficheFormulaire()
    End Sub

Recordemos que, cuando se ejecuta el procedimiento [Page_Load] en un cliente POST, todos los componentes del formulario tienen un valor: o bien el valor enviado por el cliente, si lo hay, o bien 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 va a generar para la lista de errores se coloca en el literal [erreursHTML]. El texto del enlace 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 se limita a hacer 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 una transición de POST a [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 comprueba la validez de los datos introducidos
        Dim erreurs As ArrayList = checkData()
        ' si hay errores, se indica
        If erreurs.Count <> 0 Then
            ' se muestra la página de errores
            afficheErreurs(erreurs, "Retour au formulaire")
            Exit Sub
        End If
        ' si no hay 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 añade 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 añade el resultado a las simulaciones existentes
        Dim simulations As ArrayList = CType(Session.Item("simulations"), ArrayList)
        simulations.Add(simulation)
        ' se incluyen 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], que devuelve una lista [ArrayList] con mensajes de error. Si la lista no está vacía, se muestra la vista [erreurs] y el procedimiento finaliza. Si los datos introducidos son válidos, se calcula el importe del impuesto mediante el objeto de tipo [impot] que se había almacenado en la aplicación al iniciarla. Esta nueva simulación se añade a la lista de simulaciones ya realizadas y se almacena en la sesión.

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


    Private Function checkData() As ArrayList
        ' Al principio 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

Por último, 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 «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 va a 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 vacío
        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 solo 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 introducidos por el usuario. Recordamos que, en la versión sin componentes de servidor, realizábamos nosotros mismos esta tarea de restauración.

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 podrá, 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 (<application-path>,/impots5). Se solicita la URL [http://impots5/main.aspx] con un navegador:

Image

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

Image

7.14.3. Ejemplo 3

En estos dos ejemplos hemos demostrado que es posible crear 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 solo tenían una página con 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 a sí mismos los valores del formulario no suponga ningún problema. Esto suele ocurrir muy a menudo 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 único controlador, sino varios.

Image

El controlador [main.aspx] actúa como controlador principal. Es al que 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. A continuación, cederá el control a uno de los controladores secundarios, el encargado de ejecutar la acción. A partir de ese momento, las comunicaciones se producen entre el cliente y ese controlador concreto. 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 única página, tal y como hemos explicado.

El hecho de que ya no haya un único controlador que elija las vistas que se envían al cliente presenta algunos inconvenientes. Tomemos como ejemplo la gestión de errores. Cualquiera de las acciones expuestas por la aplicación puede tener que mostrar una vista de errores. Cada controlador [applix.aspx] tendrá su propia vista [erreurs], ya que esta no es más que un contenedor específico de la página del controlador. No hay forma de disponer de una vista única [erreurs] que puedan utilizar todas las aplicaciones individuales. De hecho, dicha vista suele incluir un enlace de retorno 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 las desarrollan personas diferentes, se corre el riesgo de que las páginas de error tengan un aspecto distinto según la acción elegida por el usuario, lo que afecta a la homogeneidad de la aplicación en su conjunto. Veremos más adelante que ASP.NET ofrece una solución a este problema concreto de la vista compartida. Esta solución puede ser objeto de un nuevo componente de servidor que crearemos 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 gestionar es el problema del orden de las acciones. Cuando todas las solicitudes pasan por un único controlador, este puede comprobar que la acción solicitada sea compatible con la anterior. Este código de control se encuentra en un único lugar. En este caso, 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 está formada por tres marcos denominados «banner», «contents» y «main»:

La página [bandeau.htm], situada 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 que se debe 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 los identificadores MyBase.Load

' se recupera la acción a realizar

Dim acción As String

If Request.QueryString("action") Is Nothing Then

action = "label"

Else

action = Request.QueryString("action").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 «textbox1»

Server.Transfer("form4.aspx")

Case «textbox2»

Server.Transfer("form5.aspx")

Caso «dropdownlist»

Server.Transfer("form6.aspx")

Casilla «listbox»

Server.Transfer("form7.aspx")

Casilla «casas de marcar»

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")

End Select

End Sub

End Class

Nuestro controlador es sencillo. En función del valor del parámetro [action], transfiere el procesamiento de la solicitud a la página adecuada. No aporta ningún valor añadido respecto a una página HTML con enlaces. No obstante, bastaría con añadir 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 comprobar que se ha realizado dicha autenticación.