7. Componentes del servidor ASP - 1
7.1. Introduction
En este capítulo describimos la tecnología recomendada en ASP.NET para construir la interfaz de usuario. Sabemos que hay dos fases bien diferenciadas en el procesamiento de una página .aspx por parte del servidor web:
- en primer lugar, se ejecuta el controlador de la página. Este consiste en código ubicado ya sea en la propia página .aspx (solución WebMatrix) o en un archivo aparte (solución Visual Studio.NET).
- Luego, se ejecuta el código de presentación de la página .aspx para transformarlo en código HTML, el cual se envía al cliente.

ASP.NET ofrece tres bibliotecas de etiquetas para escribir el código de presentación de la página:
- las etiquetas clásicas HTML. Son las que hemos utilizado hasta ahora.
- las etiquetas de servidor HTML
- las etiquetas de formularios web
Independientemente de la biblioteca de etiquetas que se utilice, la función del controlador de página no cambia. Debe calcular el valor de los parámetros dinámicos que aparecen en el código de presentación. Hasta ahora, estos parámetros dinámicos eran sencillos: eran objetos de tipo [String]. Así, si en el código de presentación tenemos una etiqueta <%=nombre%>:
- el controlador de página declara una variable [nom] de tipo [String] y calcula su valor
- cuando el controlador de página ha terminado su trabajo y se ejecuta el código de presentación para generar la respuesta HTML, la etiqueta <%=nombre%> se reemplaza por el valor calculado por el código de control
Sabemos que la separación entre controlador y presentación es arbitraria y que se puede mezclar código de controlador y código de presentación en la misma página. Ya hemos explicado por qué no se recomienda este método y seguiremos respetando la separación entre control y presentación.
Las etiquetas [HTML serveur] y [WebForms] permiten introducir en el código de presentación objetos más complejos que el simple objeto [String]. Esto a veces resulta realmente útil. Tomemos el ejemplo de un formulario con una lista. Esta debe presentarse al cliente con un código HTML similar al siguiente:
<select name="uneListe" size="3">
<option value="opt1">option1</option>
<option value="opt2">option2</option>
<option value="opt3" selected>option3</option>
</select>
El contenido de la lista y la opción que se debe seleccionar son elementos dinámicos y, por lo tanto, deben ser generados por el controlador de la página. Ya nos hemos encontrado con este problema y lo hemos resuelto al incluir en el código de presentación la etiqueta
Esta etiqueta será reemplazada por el valor [String] de la variable [uneListeHTML]. Este valor, calculado por el controlador, deberá ser el código HTML de la lista, c.a.d. «<select name=..>...</select>». No es particularmente difícil de hacer y parece una solución elegante que evita colocar código de generación directamente en la parte de presentación de la página. Aquí habría que insertar un bucle con comprobaciones, lo que «contaminaría» considerablemente dicha parte. Sin embargo, este método presenta un inconveniente. La separación entre control y presentación de una página también sirve para delimitar dos ámbitos de competencia:
- el del desarrollador .NET, que se encarga del controlador de la página
- el del diseñador gráfico, que se encarga de la parte de presentación de la misma
Aquí vemos que se ha trasladado la generación del código HTML de la lista al controlador. El diseñador gráfico podría querer modificar este código HTML para cambiar el aspecto «visual» de la lista. Se vería obligado a trabajar en la parte [contrôleur] y, por lo tanto, a salirse de su ámbito de competencia, con el riesgo de introducir errores involuntarios en el código que ello conlleva.
Las bibliotecas de etiquetas de servidor resuelven este problema. Ofrecen un objeto que representa una lista HTML. Así, la biblioteca [WebForms] ofrece la siguiente etiqueta:
Esta etiqueta representa un objeto de tipo [ListBox] que puede ser manipulado por el controlador de página. Este objeto cuenta con propiedades para representar las diferentes opciones de la lista HTML y designar la opción seleccionada. Por lo tanto, el controlador de página asignará los valores adecuados a estas propiedades. Cuando se ejecute la parte de presentación, la etiqueta
será reemplazada por el código HTML que representa el objeto [uneListe], c.a.d. el código «<select ..>...</select>». Por el momento, no hay ninguna diferencia fundamental con el método anterior, salvo que se trata de una forma de codificar orientada a objetos, lo cual es interesante. Volvamos a nuestro diseñador gráfico, quien necesita modificar el «aspecto» de la lista. Las etiquetas de servidor tienen atributos de estilo (BackColor, Bordercolor, BorderWidth, ...) que permiten definir el aspecto visual del objeto HTML correspondiente. Así, podremos escribir:
<asp:ListBox id="ListBox1" runat="server" BackColor="#ffff99"></asp:ListBox></P>
La ventaja es que el diseñador gráfico puede permanecer en el código de presentación para realizar estas modificaciones. Esto representa una ventaja clara respecto al método anterior. Las bibliotecas de etiquetas de servidor, por lo tanto, facilitan la construcción de la parte de presentación de las páginas, lo que en los capítulos anteriores denominamos «interfaz de usuario». El objetivo de este capítulo es presentarlas. Veremos que a veces ofrecen objetos complejos, como calendarios o tablas vinculadas a fuentes de datos. Son extensibles, c.a.d, de modo que el usuario puede crear su propia biblioteca de etiquetas. Así, puede crear una etiqueta que genere un banner en una página. Todas las páginas que utilicen esta etiqueta tendrán entonces el mismo banner.
La generación HTML realizada para una etiqueta se adapta al tipo de navegador del cliente. Cuando este realiza una solicitud al servidor web, envía entre sus encabezados HTTP, un encabezado [User-Agent: xx] donde [xx] identifica al cliente. A continuación se muestra un ejemplo:
Con esta información, el servidor web puede conocer las capacidades del cliente, en particular el tipo de código HTML que este puede manejar. De hecho, a lo largo del tiempo, ha habido varias versiones del lenguaje HTML. Los navegadores recientes comprenden las versiones más recientes del lenguaje, algo que los navegadores más antiguos no pueden hacer. Dependiendo del encabezado HTTP [User-Agent:] que el cliente le haya enviado, el servidor le enviará una versión HTML que este pueda entender. Es una idea interesante y útil, ya que así el desarrollador no tiene que preocuparse por el tipo de navegador del cliente de su aplicación.
Por último, versiones avanzadas de IDE, como Visual Studio.NET, WebMatrix, etc., permiten un diseño de la interfaz web «al estilo de Windows». Estas herramientas, aunque no son imprescindibles, brindan una ayuda decisiva al desarrollador. El desarrollador diseña la interfaz web utilizando componentes gráficos que coloca en dicha interfaz. Tiene acceso directo a las propiedades de cada uno de los componentes de la interfaz, las cuales puede configurar a su gusto. Estas propiedades se traducirán en el código HTML de presentación de la interfaz como atributos de la etiqueta <asp:> del componente. La ventaja para el desarrollador es que no tiene que recordar ni la lista ni la sintaxis de los atributos de cada etiqueta. Esto representa una ventaja considerable cuando no se conocen a la perfección las bibliotecas de etiquetas de servidor que ofrece ASP.NET. Una vez dominada esta sintaxis, algunos desarrolladores pueden preferir codificar directamente las etiquetas en el código de presentación de la página sin pasar por la etapa de diseño gráfico. En ese caso, un IDE ya no es necesario. Basta con un simple editor de texto. Dependiendo de la forma de trabajar, el énfasis recae entonces en los componentes (uso de un IDE) o en las etiquetas (uso de un editor de texto). Existe equivalencia entre estos dos términos. El componente es el objeto que será manipulado por el código de control de la página. El IDE nos da acceso a sus propiedades en la fase de diseño. Los valores asignados a estas propiedades se traducen inmediatamente a los atributos de la etiqueta del componente en el código de presentación. En la fase de ejecución, el código de control de la página manipulará el componente y asignará valores a algunas de sus propiedades. El código de presentación generará el código HTML del componente utilizando, por un lado, los atributos establecidos en la fase de diseño para la etiqueta de servidor correspondiente y, por otro, los valores de las propiedades del componente calculados por el código de control.
7.2. El contexto de ejecución de los ejemplos
Ilustraremos el diseño de interfaces web basadas en componentes de servidor con programas cuyo contexto de ejecución será, en la mayoría de los casos, el siguiente:
- la aplicación web estará compuesta por una única página P que contiene un formulario F,
- el cliente realizará su primera solicitud directamente a esta página P. Esto consistirá en solicitar la URL de la página P con un navegador. Por lo tanto, se realizará una solicitud GET a esta URL P. El servidor entregará la página P y, por lo tanto, el formulario F que contiene,
- el usuario lo llenará y lo enviará, c.a.d, es decir, realizará una acción que obligará al navegador a enviar el formulario F al servidor. La operación POST del navegador seguirá dirigiéndose a la página P. El servidor volverá a enviar la página P con el formulario F, cuyo contenido puede haber sido modificado por la acción del usuario.
- Luego, se reanudarán los pasos 2 y 3.
Se trata de un proceso de ejecución muy particular, fuera del cual algunos conceptos que se exponen a continuación ya no funcionan. Ya no nos encontramos en un contexto de arquitectura MVC en el que una aplicación de múltiples páginas es controlada por una página específica a la que hemos llamado «controlador de aplicación». En este tipo de arquitectura, el POST de los formularios tiene como destino al controlador y no a los propios formularios. Sin embargo, veremos que crear un formulario con componentes de servidor implica que dicho formulario se envíe a sí mismo.
7.3. El componente Label
7.3.1. Uso
La etiqueta <asp:label> permite insertar texto dinámico en el código de presentación de una página. Por lo tanto, no hace nada más que la etiqueta <%=variable%> utilizada hasta ahora. El análisis de esta primera etiqueta nos permitirá descubrir el mecanismo de las etiquetas de servidor. Creamos una página con una parte de control [form1.aspx.vb] y una parte de presentación [form1.aspx]. Se trata de mostrar la hora:

Este tema ya se abordó en el capítulo 2 y se invita al lector a consultarlo si desea saber cómo se trató. El código de presentación [form1.aspx] es el siguiente:
<%@ page src="form1.aspx.vb" inherits="form1" AutoEventWireup="false" %>
<HTML>
<HEAD>
<title>Webforms</title>
</HEAD>
<body>
<asp:Label Runat="server" ID="lblHeure" />
</body>
</HTML>
Introducimos la etiqueta <asp:label>. En las bibliotecas de etiquetas, el atributo [runat="server"] es obligatorio. El atributo ID identifica al componente. El controlador deberá referenciarlo con este identificador. El código del controlador [form1.aspx.vb] es el siguiente:
Imports System.Web.UI.WebControls
Public Class form1
Inherits System.Web.UI.Page
Protected lblHeure As Label
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
' guarda la consulta actual en request.txt de la carpeta de la página
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' agrega la hora en lblHeure
lblHeure.Text = "Il est " + Date.Now.ToString("T")
End Sub
End Class
El controlador debe asignar un valor al objeto [lblHeure] de tipo [System.Web.UI.WebControls.Label]. Todos los objetos mostrados por las etiquetas <asp:> pertenecen al espacio de nombres [System.Web.UI.WebControls]. Por lo tanto, se puede importar este espacio de nombres de manera sistemática:
Imports System.Web.UI.WebControls
El objeto [Label] tiene varias propiedades, entre ellas la propiedad [Text], que representa el texto que se mostrará mediante la etiqueta <asp:label> correspondiente. Aquí, asignamos a esta propiedad la hora actual. Lo hacemos en el procedimiento [Form_Load] del controlador, que se ejecuta sistemáticamente. En el procedimiento [Form_Init], que también se ejecuta siempre, pero antes del procedimiento [Form_Load], almacenamos la solicitud del cliente en un archivo [request.txt] de la carpeta de la aplicación. Tendremos la oportunidad de examinar este archivo para comprender ciertos aspectos del funcionamiento de las páginas que utilizan etiquetas de servidor.
El objeto [Label] cuenta con numerosas propiedades, métodos y eventos. Se invita al lector a consultar la documentación sobre la clase [Label] para conocerlos. De aquí en adelante, siempre será así. Para cada etiqueta, solo presentaremos las pocas propiedades que necesitamos.
7.3.2. Las pruebas
Colocamos los archivos (form1.aspx, form1.aspx.vb) en una carpeta <ruta-de-la-aplicación> e iniciamos Cassini con los parámetros (<ruta-de-la-aplicación>,/form1). Luego solicitamos la URL [http://localhost/form1/form1.aspx]. Obtenemos el siguiente resultado:

El código HTML recibido por el navegador es el siguiente:
<HTML>
<HEAD>
<title>Webforms</title>
</HEAD>
<body>
<span id="lblHeure">Il est 19:39:37</span>
</body>
</HTML>
Se observa que la etiqueta del servidor
<asp:Label Runat="server" ID="lblHeure" />
se ha transformado en el siguiente código HTML:
Es la propiedad [Text] del objeto [lblHeure] la que se ha colocado entre las etiquetas <span> y </span>. La solicitud realizada por el cliente y almacenada en [request.txt] es la siguiente:
GET /form1/form1.aspx HTTP/1.1
Cache-Control: max-age=0
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
Todo muy normal.
7.3.3. Crear la aplicación con WebMatrix
Hemos creado el código de presentación de la página [form1.aspx] a mano. Este método se puede utilizar si se conocen las etiquetas. En ese caso, basta con un simple editor de texto para crear la interfaz de usuario. Para empezar, a menudo se necesita una herramienta de diseño gráfico combinada con la generación automática de código, ya que no se conoce la sintaxis de las etiquetas que se necesitan. Ahora crearemos la misma aplicación con la herramienta WebMatrix. Una vez que se inicia WebMatrix, seleccionamos la opción [File/New File]:

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]:


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

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

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

La ventana de herramientas ofrece acceso a dos tipos de componentes:
- los componentes [WebControls], que se traducen en etiquetas <asp:>
- los componentes [HTML Elements], que se traducen en etiquetas HTML clásicas. Sin embargo, se puede agregar a los atributos de una etiqueta HTML el atributo [runat="server"]. En este caso, el controlador puede acceder a la etiqueta HTML y a sus atributos a través de un objeto cuyas propiedades son las mismas que las de la etiqueta HTML que representa. Anteriormente, nos referimos a estas etiquetas como etiquetas de servidor HTML.
Hagamos doble clic en el componente [Label] de la lista de controles [WebControls]. En la pestaña [Design] obtenemos el siguiente resultado:

En la pestaña [All], el código quedó así:
<%@ Page Language="VB" %>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="Label1" runat="server">Label</asp:Label>
</body>
</html>
Lo primero que se nota es que se ha perdido la etiqueta <script>. Se ha generado una etiqueta <asp:label>. Tiene un nombre [Label1] y un valor [Label]. Volvamos a la pestaña [Design] para modificar estos dos valores. Hacemos clic una vez en el componente [Label] para que aparezca la ventana de propiedades de este componente, en la parte inferior derecha:

Se invita al lector a consultar las propiedades del objeto [Label]. Aquí nos interesan dos:
- Texto: es el texto que debe mostrar la etiqueta; colocamos la cadena vacía (c.a.d. nada)
- ID: es su identificador; ponemos lblHeure
La pestaña [Design] queda así:

y el código de [All] queda así:
<%@ Page Language="VB" %>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>
La parte de presentación de la página está terminada. Ahora nos queda escribir el código de control encargado de colocar la hora en la propiedad [Text] de [lblHeure]. Agregamos en la pestaña [All] el siguiente código:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' establece la hora en lblHeure
lblHeure.Text = "Il est " + Date.Now.ToString("T")
End Sub
</script>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>
Cabe señalar que en el código del controlador no se declara el objeto [lblHeure] como se había hecho anteriormente:
Protected lblHeure As New System.Web.UI.WebControls.Label
De hecho, todos los componentes de servidor <asp:> de la sección de presentación se declaran implícitamente en la sección de código de control. Por lo tanto, declararlos provoca un error de compilación, el cual indica que el objeto ya está declarado. Estamos listos para la ejecución. Seleccionamos la opción [View/Start] o el atajo [F5]. Cassini se inicia automáticamente con los siguientes parámetros:

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

A partir de ahora, utilizaremos principalmente la herramienta WebMatrix para facilitar la creación y las pruebas de los programas cortos que escribiremos.
7.4. El componente Literal
7.4.1. Uso
La etiqueta <asp:literal> permite insertar texto dinámico en el código de presentación de una página, al igual que la etiqueta <asp:label>. Su atributo principal es [Text], que representa el texto que se insertará tal cual en el flujo HTML de la página. Esta etiqueta es suficiente si no se tiene la intención de dar formato al texto que se desea insertar en el flujo HTML. De hecho, si bien la clase [Label] permite aplicar formato mediante atributos como [BorderColor, BorderWidth, Font, ...], la clase [Literal] no cuenta con ninguno de estos atributos. El lector puede reproducir íntegramente el ejemplo anterior sustituyendo el componente [Label] por un componente [Literal].
7.5. El componente Button
7.5.1. Uso
La etiqueta <asp:Button> permite insertar un botón de tipo [Submit] en un formulario, lo cual conlleva una gestión de eventos similar a la que se encuentra en las aplicaciones de Windows. Es este punto el que queremos profundizar aquí. Creamos la siguiente página [form3.aspx]:

Esta página, construida con WebMatrix, tiene tres componentes:
n.º | nombre | tipo | propiedades | función |
1 | Botón | text=Botón1 | botón de envío | |
2 | Botón | text=Botón2 | botón de envío | |
3 | Etiqueta | text= | mensaje informativo |
El código generado por WebMatrix para esta sección es el siguiente:
<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="Button1" runat="server" Text="Bouton1"></asp:Button>
<asp:Button id="Button2" runat="server" Text="Bouton2"></asp:Button>
</p>
<p>
<asp:Label id="lblInfo" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
Encontramos los componentes [Button] y [Label], utilizados en el diseño gráfico de la página, dentro de las etiquetas <asp:>. Cabe destacar la etiqueta <form runat="server">, que se generó automáticamente. Se trata de una etiqueta de servidor HTML, c.a.d. Una etiqueta HTML clásica, representada, sin embargo, por un objeto que el controlador puede manipular. El código de la etiqueta <form> se generará a partir del valor que el controlador le asigne a este objeto.
Agreguemos en la parte del código correspondiente al controlador el procedimiento [Page_Init] que maneja el evento [Init] de la página. Allí colocamos el código que guarda la solicitud del cliente en el archivo [request.txt]. Lo necesitaremos para entender el funcionamiento de los botones.
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' guarda la solicitud actual en request.txt del archivo de la página
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
</script>
Cabe señalar que no hemos colocado la cláusula [Handles MyBase.Init] después de la declaración del procedimiento [Page_Init]. De hecho, el evento [Init] del objeto [Page] tiene un manejador predeterminado llamado [Page_Init]. Si se utiliza este nombre de manejador, la cláusula [Handles Page.Init] se vuelve innecesaria. Sin embargo, incluirla no genera ningún error.
7.5.2. Pruebas
Ejecutamos la aplicación bajo WebMatrix mediante [F5]. Obtenemos la siguiente página:

El código HTML recibido por el navegador es el siguiente:
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />
<p>
<input type="submit" name="Button1" value="Bouton1" id="Button1" />
<input type="submit" name="Button2" value="Bouton2" id="Button2" />
</p>
<p>
<span id="lblInfo"></span>
</p>
</form>
</body>
</html>
Cabe destacar lo siguiente:
- la etiqueta <form runat="server"> se ha transformado en la etiqueta HTML
Se han establecido dos atributos: [method="post"] y [action="form3.aspx"]. ¿Se pueden asignar valores diferentes a estos atributos? Intentaremos aclarar este punto más adelante. Por ahora, basta con recordar que el formulario se enviará a la URL [form3.aspx]. También se han establecido otros dos atributos: [name, id]. En la mayoría de los casos, se ignoran. Sin embargo, si la página contiene código JavaScript que se ejecuta en el navegador, el atributo [name] de la etiqueta <form> resulta útil.
- Las etiquetas <asp:button> se han convertido en etiquetas HTML de botones [submit]. Por lo tanto, al hacer clic en cualquiera de estos botones se provocará un «post» del formulario [_ctl10] a la URL [form3.aspx].
- La etiqueta <asp:label> se ha convertido en una etiqueta HTML <span>
- Se ha generado un campo oculto [__VIEWSTATE] con un valor extraño:
Este campo representa, en forma codificada, el estado del formulario enviado al cliente. Este estado representa el valor de todos los componentes del formulario. Como [__VIEWSTATE] forma parte del formulario, su valor se enviará junto con el resto al servidor. Esto le permitirá al servidor saber qué componente del formulario ha cambiado de valor y, eventualmente, tomar decisiones. Estas decisiones tomarán la forma de eventos del tipo «el TextBox tal ha cambiado de valor».
7.5.3. Las solicitudes del cliente
Una vez obtenida la página [form3.aspx] en el navegador, volvamos a solicitarla y observemos la solicitud que envió el navegador para obtenerla. Recordemos que nuestra aplicación la almacena en el archivo [request.txt] dentro de la carpeta de la aplicación:
GET /form3.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
Aquí tenemos un GET clásico. Ahora hagamos clic en el botón [Bouton1] de la página en el navegador. Aparentemente no pasa nada. Sin embargo, sabemos que el formulario se ha enviado. Así lo indica el código HTML de la página. Esto se confirma con el nuevo contenido de [request.txt]:
POST /form3.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 80
Content-Type: application/x-www-form-urlencoded
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/form3.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__VIEWSTATE=dDwxNTY0NjIwMjUwOzs%2B2mcnJczeuvF2PEfvmtv7uiUhWUw%3D&Button1=Bouton1
El primer encabezado HTTP indica claramente que el cliente realizó un POST hacia la URL [/form3.aspx]. La última línea muestra los valores enviados:
- el valor del campo oculto __VIEWSTATE
- el valor del botón en el que se hizo clic
Si hacemos clic en [Bouton2], los valores enviados por el navegador son los siguientes:
El valor del campo oculto sigue siendo el mismo, pero es el valor de [Button2] el que se ha enviado. Por lo tanto, el servidor puede saber qué botón se utilizó. Lo utilizará para desencadenar un evento que podrá ser procesado por la página, una vez que esta se haya cargado.
7.5.4. Manejar el evento Click de un objeto Button
Recordemos cómo funciona una página .aspx. Esta es un objeto derivado de la clase [Page]. Llamemos a la clase derivada [unePage]. Cuando el servidor recibe una solicitud para dicha página, se instancia un objeto de tipo [unePage] mediante la operación new unePage(...). A continuación, el servidor genera dos eventos llamados [Init] y [Load] en ese orden. El objeto [unePage] puede manejarlos proporcionando los controladores de eventos [Page_Init] y [Page_Load]. Posteriormente se generarán otros eventos. Tendremos la oportunidad de volver sobre esto más adelante. Si la solicitud del cliente es un POST, el servidor generará el evento [Click] del botón que provocó ese POST. Si la clase [unePage] ha definido un controlador para este evento, este será invocado. Veamos este mecanismo con WebMatrix. En la pestaña [Design] de [form3.aspx], hagamos doble clic en el botón [Bouton1]. Esto nos lleva automáticamente a la pestaña [Code], al cuerpo de un procedimiento llamado [Button1_Click]. Para entenderlo mejor, vayamos a la pestaña [All] y veamos el código completo. Se han realizado las siguientes modificaciones:
<%@ Page Language="VB" %>
<script runat="server">
...
Sub Button1_Click(sender As Object, e As EventArgs)
End Sub
</script>
<html>
...
<body>
<form runat="server">
...
<asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
</form>
</body>
</html>
Se ha agregado un nuevo atributo [onclick="Button1_Click"] a la etiqueta <asp:Button> de [Button1]. Este atributo indica el procedimiento encargado de manejar el evento [Click] en el objeto [Button1], en este caso el procedimiento [Button1_Click]. Ahora solo nos queda escribirlo:
Sub Button1_Click(sender As Object, e As EventArgs)
' clic en el botón 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
El procedimiento inserta un mensaje informativo en la etiqueta [lblInfo]. Procedemos de la misma manera con el botón [Bouton2] para obtener la siguiente página nueva [form3.aspx]:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' guarda la solicitud actual en request.txt de la carpeta de la página
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Sub Button1_Click(sender As Object, e As EventArgs)
' haga clic en el botón 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
Sub Button2_Click(sender As Object, e As EventArgs)
' hace clic en el botón 2
lblInfo.Text="Vous avez cliqué sur [Bouton2]"
End Sub
</script>
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
<asp:Button id="Button2" onclick="Button2_Click" runat="server" Text="Bouton2" BorderStyle="None"></asp:Button>
</p>
<p>
<asp:Label id="lblInfo" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
Ejecutamos el proceso mediante [F5] para obtener la siguiente página:

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:

El código HTML recibido para esta respuesta es el siguiente:
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwO3Q8O2w8aTwxPjs+O2w8dDw7bDxpPDU+Oz47bDx0PHA8cDxsPFRleHQ7PjtsPFZvdXMgYXZleiBjbGlxdcOpIHN1ciBbQm91dG9uMV07Pj47Pjs7Pjs+Pjs+Pjs+4oO98Vd244kj0lPMXReWOwJ1WW0=" />
<p>
<input type="submit" name="Button1" value="Bouton1" id="Button1" />
<input type="submit" name="Button2" value="Bouton2" id="Button2" />
</p>
<p>
<span id="lblInfo">Vous avez cliqué sur [Bouton1]</span>
</p>
</form>
</body>
</html>
Podemos observar que el campo oculto [__VIEWSTATE] ha cambiado de valor. Esto refleja el cambio de valor del componente [lblInfo].
7.5.5. Los eventos del ciclo de vida de una aplicación ASP.NET
La documentación ASP.NET presenta la lista de eventos generados por el servidor durante el ciclo de vida de una aplicación:
- al recibir la primera solicitud de la aplicación, se generará el evento [Start] en el objeto [HttpApplication] de la aplicación. Este evento puede gestionarse mediante el procedimiento [Application_Start] del archivo [global.asax] de la aplicación.
A continuación, tendremos una serie de eventos que se repetirán para cada solicitud recibida:
- si la solicitud no ha enviado un token de sesión, se inicia una nueva sesión y se genera un evento [Start] en el objeto [Session] asociado a la solicitud. Este evento puede gestionarse mediante el procedimiento [Session_Start] del archivo [global.asax] de la aplicación.
- El servidor genera el evento [BeginRequest] en el objeto [HttpApplication]. Este evento puede gestionarse mediante el procedimiento [Application_BeginRequest] del archivo [global.asax] de la aplicación.
- El servidor carga la página solicitada por la consulta. Creará una instancia del objeto [Page] y luego generará dos eventos en ese objeto: [Init] y [Load]. Estos dos eventos pueden ser manejados por los procedimientos [Page_Init] y [Page_Load] de la página.
- A partir de los valores enviados que se hayan recibido, el servidor generará otros eventos: [TextChanged] para un componente [TextBox] cuyo valor ha cambiado, [CheckedChanged] para un botón de opción cuyo valor ha cambiado, [SelectedIndexChanged] para una lista cuyo elemento seleccionado ha cambiado, ... Tendremos la oportunidad de mencionar los principales eventos para cada uno de los componentes de servidor que vamos a presentar. Cada evento E en un objeto denominado O puede ser manejado por un procedimiento con el nombre O_E.
- El orden de manejo de los eventos anteriores no está garantizado. Por lo tanto, los manejadores no deben hacer ninguna suposición sobre dicho orden. Sin embargo, se garantiza que el evento [Click] del botón que provocó el POST se procesa en último lugar.
- Una vez que la página está lista, el servidor la enviará al cliente. Antes de hacerlo, genera el evento [PreRender], que puede ser manejado por el procedimiento [Page_PreRender] de la página.
- Una vez enviada la respuesta HTML al cliente, la página se descargará de la memoria. En ese momento se generarán dos eventos: [Unload] y [Disposed]. La página puede utilizar estos eventos para liberar recursos.
La aplicación también puede recibir eventos fuera de una solicitud del cliente:
- el evento [End] en un objeto [Session] de la aplicación ocurre cuando finaliza una sesión. Esto puede suceder a petición explícita del código de una página o porque se ha excedido el tiempo de vida asignado a la sesión. El procedimiento [Session_End] del archivo [global.asax] gestiona este evento. En él, por lo general, se liberan los recursos obtenidos en [Session_Start].
- El evento [End] en el objeto [HttpApplication] de la aplicación ocurre cuando la aplicación finaliza. Esto sucede, en particular, al detener el servidor web. El procedimiento [Application_End] del archivo [global.asax] gestiona este evento. En él, por lo general, se liberan los recursos obtenidos en [Application_Start].
Cabe destacar los siguientes puntos:
- el modelo de eventos anterior se basa en intercambios clásicos cliente-servidor HTTP. Esto se observa claramente al examinar los encabezados HTTP intercambiados.
- El procesamiento de los eventos anteriores siempre se realiza del lado del servidor. Por supuesto, al hacer clic en un botón, un script de JavaScript del lado del servidor puede procesarlo. Pero en ese caso no se trata de un evento del servidor y estamos ante una tecnología independiente de ASP.NET.
¿En qué momento se procesan los eventos, ya sea del lado del servidor (eventos relacionados con los componentes del servidor) o del lado del navegador mediante scripts de JavaScript?
Tomemos el ejemplo de una lista desplegable. Cuando el usuario cambia el elemento seleccionado en ella, el evento (cambio del elemento seleccionado) puede procesarse o no, y si se procesa, puede hacerlo en diferentes momentos.
- Si queremos procesarlo de inmediato, tenemos dos opciones:
- puede ser procesado por el navegador mediante un script de JavaScript. En ese caso, el servidor no interviene. Para que esto sea posible, la página debe poder reconstruirse con los valores presentes en ella.
- Puede ser procesado por el servidor. Para ello, solo hay una solución: el formulario debe enviarse al servidor para su procesamiento. Por lo tanto, tenemos una operación [submit]. Veremos que, en este caso, se utiliza un componente de servidor llamado [DropDownList] y se establece su atributo [AutoPostBack] en [true]. Esto significa que, en caso de que cambie el elemento seleccionado en la lista desplegable, el formulario debe enviarse inmediatamente al servidor. En este caso, el servidor genera, para el objeto [DropDownList], un código HTML asociado a una función de JavaScript encargada de ejecutar un [submit] tan pronto como ocurra el evento «cambio del elemento seleccionado». Este [submit] enviará el formulario al servidor y, en él, se incluirán campos ocultos para indicar que el [post] proviene de un cambio de selección en la lista desplegable. El servidor generará entonces el evento [SelectedIndexChanged], que la página podrá gestionar.
- Si se desea procesarlo, pero no de inmediato, se establece el atributo [AutoPostBack] del componente de servidor [DropDownList] en [false]. En este caso, el servidor genera, para el objeto [DropDownList], el código HTML clásico de una lista <select> sin ninguna función de JavaScript asociada. Por lo tanto, no ocurre nada cuando el usuario cambia la selección en la lista desplegable. Sin embargo, cuando valide el formulario mediante un botón [submit], por ejemplo, el servidor podrá reconocer que ha habido un cambio en la selección. De hecho, hemos visto que el formulario enviado al servidor tenía un campo oculto [__VIEWSTATE] que representaba, en forma codificada, el estado de todos los elementos del formulario enviado. Cuando el servidor recibe el nuevo formulario enviado por el cliente, podrá verificar si el elemento seleccionado en la lista desplegable ha cambiado o no. De ser así, generará el evento [SelectedIndexChanged], que la página podrá entonces gestionar. Para distinguir este mecanismo del anterior, algunos autores dicen que el evento «cambio de selección» se ha «almacenado en caché» cuando ocurrió en el navegador. El servidor lo procesará solo cuando el navegador le envíe el formulario, a menudo tras hacer clic en un botón [submit].
- Por último, si no se desea procesar el evento, se establece el atributo [AutoPostBack] del componente de servidor [DropDownList] en [false] y no se escribe el manejador de su evento [SelectedIndexChanged].
Una vez comprendido el mecanismo de manejo de eventos, el desarrollador no diseñará una aplicación web como si fuera una aplicación de Windows. De hecho, si bien un cambio de selección en un cuadro combinado de una aplicación de Windows puede utilizarse para modificar de inmediato el aspecto del formulario en el que se encuentra, en una aplicación web se dudará más en procesar este evento de inmediato si implica un «post» del formulario al servidor y, por lo tanto, un intercambio de datos entre el cliente y el servidor. Por eso, la propiedad [AutoPostBack] de los componentes de servidor se establece en [false] de forma predeterminada. Por otra parte, el mecanismo [AutoPostBack], que se basa en scripts de JavaScript generados automáticamente por el servidor web en el formulario enviado al cliente, solo puede utilizarse si se tiene la certeza de que el navegador del cliente ha autorizado la ejecución de scripts de JavaScript en su navegador. Por lo tanto, los formularios suelen construirse de la siguiente manera:
- los componentes del servidor del formulario tienen su propiedad [AutoPostBack] a [false]
- el formulario cuenta con uno o varios botones encargados de realizar la acción [POST] del formulario
- en el código del controlador de la página se escriben los manejadores de los únicos eventos que se desean manejar, por lo general el evento [Click] en uno de los botones.
7.6. El componente TextBox
7.6.1. Uso
La etiqueta <asp:TextBox> permite insertar un campo de entrada en el código de presentación de una página. Creamos una página [form4.aspx] para obtener la siguiente presentación:

4321Esta página, creada con WebMatrix, contiene los siguientes componentes:
n.º | nombre | tipo | propiedades | función |
1 | TextBox | AutoPostback=true Text= | campo de entrada | |
2 | TextBox | AutoPostback=false Texto= | campo de entrada | |
3 | Etiqueta | text= | mensaje informativo sobre el contenido de [TextBox1] | |
3 | Etiqueta | text= | mensaje informativo sobre el contenido de [TextBox2] |
El código generado por WebMatrix para esta sección es el siguiente:
<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form runat="server">
<p>
Texte 1 :
<asp:TextBox id="TextBox1" runat="server" AutoPostBack="True"></asp:TextBox>
</p>
<p>
Texte 2 :
<asp:TextBox id="TextBox2" runat="server"></asp:TextBox>
</p>
<p>
<asp:Label id="lblInfo1" runat="server"></asp:Label>
</p>
<p>
<asp:Label id="lblInfo2" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
En la pestaña [Design], haz doble clic en el componente [TextBox1]. A continuación, se genera el esqueleto del controlador del evento [TextChanged] de este objeto (pestaña [All]):
<%@ Page Language="VB" %>
<script runat="server">
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
End Sub
</script>
<html>
...
<body>
...
<asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
</p>
....
</form>
</body>
</html>
Se ha agregado el atributo [OnTextChanged="TextBox1_TextChanged"] a la etiqueta <asp:TextBox id="TextBox1"> para designar el controlador del evento [TextChanged] en [TextBox1]. Ahora vamos a escribir el procedimiento [TextBox1_Changed].
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
' cambio de texto
lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
End Sub
En el procedimiento, escribimos en la etiqueta [lblInfo1] un mensaje que señala el evento e indica el contenido de [TextBox1]. Hacemos lo mismo con [TextBox2]. También indicamos la hora para poder dar un mejor seguimiento al procesamiento de los eventos. El código final de [form4.aspx] es el siguiente:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' guarda la solicitud actual en request.txt de la carpeta de la página
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
' cambio de texto
lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
End Sub
Sub TextBox2_TextChanged(sender As Object, e As EventArgs)
' cambio de texto
lblInfo2.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox2]. Texte 2=["+textbox2.Text+"]"
End Sub
</script>
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form runat="server">
<p>
Texte 1 :
<asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
</p>
<p>
Texte 2 :
<asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged"></asp:TextBox>
</p>
<p>
<asp:Label id="lblInfo1" runat="server"></asp:Label>
</p>
<p>
<asp:Label id="lblInfo2" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
Hemos agregado el procedimiento [Page_Init] para guardar la solicitud del cliente como en el ejemplo anterior.
7.6.2. Pruebas
Ejecutamos la aplicación con el procedimiento WebMatrix a través de [F5]. Obtenemos la siguiente página:

El código HTML recibido por el navegador es el siguiente:
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u+/OrTD" />
<script language="javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform = document._ctl0;
theform.__EVENTTARGET.value = eventTarget;
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
</form>
</body>
</html>
Hay muchos elementos en este código que han sido generados automáticamente por el servidor. Destacamos los siguientes puntos:
- hay tres campos ocultos: [__VIEWSTATE], que ya hemos visto, [__EventTarget] y [__EventArgument]. Estos dos últimos campos se utilizan para gestionar el evento «change» del navegador en el campo de entrada [TextBox1]
- las etiquetas de servidor <asp:textbox> han generado las etiquetas HTML <input type="text" ...> que corresponden a los campos de entrada
- la etiqueta de servidor <asp:textbox id="TextBox1" AutoPostBack="true" ...> ha generado una etiqueta <input type="text" ...> con un atributo [onchange="__doPostBack('TextBox1','')"]. Este atributo indica que, en caso de que cambie el contenido de [TextBox1], se debe ejecutar la función de JavaScript [_doPostBack(...)]. Esta es la siguiente:
<script language="javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform = document._ctl0;
theform.__EVENTTARGET.value = eventTarget;
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
¿Qué hace la función anterior? Asigna un valor a cada uno de los dos campos ocultos [__EventTarget] y [__EventArgument] y luego envía el formulario. De este modo, el formulario se envía al servidor. Este es el efecto de [AutoPostBack]. El evento del navegador «change» provoca la ejecución del código «__doPostBack('TextBox1','')». De ello se deduce que, en el formulario enviado, el campo oculto [__EventTarget] tendrá el valor 'TextBox1' y el campo oculto [__EventArgument] tendrá el valor ''. Esto permitirá al servidor identificar el componente que generó el POST.
- La etiqueta de servidor <asp:textbox id="TextBox2"...> generó una etiqueta <input type="text" ...> convencional porque su atributo [AutoPostBack] no estaba configurado en [true].
- La etiqueta <form> indica que el formulario se enviará a [form4.aspx]:
Hagamos nuestra primera prueba. Escribamos un texto en el primer campo de entrada:

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

¿Qué pasó? Cuando el cursor salió del primer campo de entrada, el navegador verificó si el contenido de este había cambiado. Así fue. Por lo tanto, generó, del lado del navegador, el evento [change] en el campo HTML [TextBox1]. Entonces vimos que se ejecutaba una función de JavaScript que enviaba el formulario a [form4.aspx]. Por lo tanto, el servidor recargó esa página. Los valores enviados por el formulario permitieron al servidor, a su vez, detectar que el contenido de la etiqueta de servidor [TextBox1] había cambiado. Por lo tanto, se ejecutó el procedimiento [TextBox1_Changed] del lado del servidor. Este colocó un mensaje en la etiqueta [lblInfo1]. Una vez finalizado este procedimiento, se envió [form4.aspx] al navegador. Por eso ahora tenemos un texto en [lblInfo1]. Dicho esto, puede resultar sorprendente que haya algo en el campo de entrada [TextBox1]. De hecho, ningún procedimiento ejecutado del lado del servidor asigna un valor a este campo. Se trata de un mecanismo general de los formularios web ASP.NET: el servidor devuelve el formulario tal como lo recibió. Para ello, reasigna a los componentes el valor que el cliente envió para ellos. Para algunos componentes, el cliente no envía ningún valor. Este es el caso, por ejemplo, de los componentes <asp:label>, que se traducen a las etiquetas <span>. Recordemos que el formulario tiene un campo oculto que representa el estado del formulario cuando se envía al cliente. Este estado es la suma de los estados de todos los componentes del formulario, incluidos los componentes <asp:label>, si los hay. Como el campo oculto [__VIEWSTATE] es enviado por el navegador del cliente, el servidor puede restaurar el estado anterior de todos los componentes del formulario. Solo le queda modificar aquellos cuyos valores hayan sido alterados por el POST.
Veamos ahora en [request.txt] la solicitud que realizó el navegador:
POST /form4.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 137
Content-Type: application/x-www-form-urlencoded
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/form4.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u%2B%2FOrTD&TextBox1=premier+texte&TextBox2=
Se ve perfectamente el código POST, así como los parámetros enviados. Regresemos a nuestro navegador e ingresemos un texto en el segundo campo de entrada:

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

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

¿Qué pasó? En primer lugar, el navegador envió el formulario. Esto se refleja en la solicitud del cliente almacenada en [request.txt]:
POST /form4.aspx HTTP/1.1
....
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTt0PDtsPGk8MT47PjtsPHQ8O2w8aTwxPjtpPDU%2BOz47bDx0PHA8cDxsPFRleHQ7PjtsPHByZW1pZXIgdGV4dGU7Pj47Pjs7Pjt0PHA8cDxsPFRleHQ7PjtsPDE4OjQyOjI5OiBldnQgW1RleHRDaGFuZ2VkXSBzdXIgW1RleHRCb3gxXS4gVGV4dGUgMT1bcHJlbWllciB0ZXh0ZV07Pj47Pjs7Pjs%2BPjs%2BPjs%2BxLOermpUUUz5rTAa%2FFsjda6lVmo%3D&TextBox1=troisi%C3%A8me+texte&TextBox2=second+texte
El servidor comienza por restaurar los valores anteriores de los componentes mediante el campo oculto [__VIEWSTATE] que el cliente le envió. Gracias a los campos enviados [TextBox1] y [TextBox2], asignará a los componentes [TextBox1] y [TextBox2] los valores que se le han enviado. Es mediante este mecanismo que el cliente recuperará el formulario tal como fue enviado. Luego, siempre gracias a los campos enviados [__VIEWSTATE], [TextBox1] y [TextBox2], el servidor detectará que los valores de los campos de entrada [TextBox1] y [TextBox2] han cambiado. Por lo tanto, generará los eventos [TextChanged] para estos dos objetos. Se ejecutarán los procedimientos [TextBox1_TextChanged] y [TextBox2_TextChanged], y las etiquetas [labelInfo1] y [labelInfo2] recibirán un nuevo valor. A continuación, la página [form4.aspx], ya modificada, se envía de vuelta al cliente.
Ahora modificamos nuevamente el campo de entrada n.º 1:

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

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

Luego, coloquemos el cursor en el campo n.º 1 y volvamos a colocarlo en el campo n.º 2. La página no cambia. ¿Por qué? Porque no cambiamos el valor del campo n.º 1, el evento del navegador [Change] no se produce cuando salimos de ese campo. Por lo tanto, el formulario no se envía al servidor. Así que nada cambia en la página. Es precisamente el hecho de que el contenido de [lblInfo2] no cambie lo que nos indica que no existe POST. Si existiera uno, el servidor detectaría que el contenido de [TextBox2] ha cambiado y debería reflejar este hecho en [lblInfo2].
De este ejemplo se desprende que no tiene sentido asignar la propiedad [AutoPostBack] de un [TextBox] a [true]. Esto provoca un intercambio cliente-servidor innecesario la mayor parte del tiempo.
7.6.3. La función del campo __VIEWSTATE
Hemos visto que el servidor incluía sistemáticamente un campo oculto llamado __VIEWSTATE en el formulario que generaba. Mencionamos que este campo representaba el estado del formulario y que, si se le devolvía este campo oculto, el servidor podía reconstruir el valor anterior del formulario. El estado de un formulario es la suma de los estados de sus componentes. Cada uno de ellos tiene una propiedad [EnableViewState] con un valor booleano que indica si el estado del componente debe incluirse o no en el campo oculto [__VIEWSATE]. Por defecto, esta propiedad tiene el valor [true], lo que hace que el estado de todos los componentes de un formulario se coloque en [__VIEWSTATE]. A veces, esto no es lo deseable.
Hagamos algunas pruebas para comprender mejor la función de la propiedad [EnableViewState]. Establezcamos esta propiedad en [false] para los dos campos de entrada:
...
<asp:TextBox id="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged" AutoPostBack="True" EnableViewState="False"></asp:TextBox>
...
<asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged" EnableViewState="False"></asp:TextBox>
...
Ahora ejecutemos la aplicación y escribamos un primer texto en el campo n.º 1 antes de pasar al campo n.º 2. A continuación, se envía un POST al servidor y obtenemos la siguiente respuesta:

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

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

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

Eso es precisamente lo que ocurrió. La hora y el contenido de [lblInfo2] muestran que el procedimiento [TextBox2_TextChanged] no se ejecutó. Teniendo esto en cuenta, examinemos la propiedad [EnableViewState] de los cuatro componentes del formulario:
queremos mantener el estado de este componente para que el servidor sepa si ha cambiado o no | |
Lo mismo | |
No queremos mantener el estado de este componente. Queremos que el texto se recalcule con cada nuevo POST. Si no se recalcula, debe quedar en blanco. Todo esto se logra con [EnableViewState=false] | |
í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

ahora obtenemos:
version 2

En este paso, cambiábamos el contenido del campo 1 sin modificar el del campo 2, lo que hacía que el procedimiento [TextBox2_TextChanged] no se ejecutara en el servidor; esto implicaba que el campo [lblInfo2] no recibía un nuevo valor. Por lo tanto, se muestra con su valor anterior. En la versión 1 ([EnableViewState=true]), este valor anterior era el valor ingresado. En la versión 2 ([EnableViewState=false]), este valor anterior es la cadena vacía.
A veces no es necesario conservar el estado anterior de los componentes. En lugar de establecer [EnableViewState=false] para cada uno de ellos, se puede indicar que la página no debe mantener su estado. Esto se hace en la directiva [Page] del código de presentación:
En este caso, sin importar cuál sea el valor de su propiedad [EnableViewState], el estado de un componente no se guarda en el campo oculto [__VIEWSTATE]. Entonces, todo ocurre como si su estado anterior fuera la cadena vacía.
Ahora utilicemos el cliente [curl] para destacar otros mecanismos. Primero solicitamos la URL [http://localhost/form4.aspx]:
dos>curl --include --url http://localhost/form4.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Sun, 04 Apr 2004 17:51:14 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1077
Connection: Close
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u+/OrTD" />
<script language="javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform = document._ctl0;
theform.__EVENTTARGET.value = eventTarget;
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
</form>
</body>
</html>
Recibimos del servidor el código HTML de [form4.aspx]. No es diferente del que había recibido el navegador. Recordemos aquí la solicitud realizada por el navegador al enviar el formulario:
POST /form4.aspx HTTP/1.1
Connection: keep-alive
...
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u%2B%2FOrTD&TextBox1=premier+texte&TextBox2=
Hagamos lo mismo con POST, pero sin enviar el campo [__VIEWSTATE]:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=primer+texto --data TextBox2=
...................
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">19:57:48: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
..............
Cabe destacar lo siguiente:
- el servidor detectó un evento [TextChanged] en [TextBox1], ya que generó el texto [lblInfo1]. La ausencia de [__VIEWSTATE] no le afectó. En su ausencia, supone que el valor anterior de un campo de entrada es la cadena vacía.
- Pudo volver a colocar el texto enviado para [TextBox1] en el atributo [value] de la etiqueta [TextBox1] para que el campo [TextBox1] volviera a aparecer con el valor ingresado. Para ello, no necesita [__VIEWSTATE], sino solo el valor enviado para [TextBox1]
Ahora, volvamos a realizar la misma consulta sin cambiar nada. Obtenemos la siguiente respuesta nueva:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=primer+texto --data TextBox2=
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">20:05:47: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
Al no existir [__VIEWSTATE], el servidor no pudo detectar que el valor del campo [TextBox1] no había cambiado. Por lo tanto, actúa como si el valor anterior fuera la cadena vacía. Por lo tanto, generó aquí el evento [TextChanged] sobre [TextBox1]. Repitamos la misma consulta, pero esta vez con el campo [TextBox1] vacío y [TextBox2] no vacío:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox2=segundo+texto --data TextBox1=
......
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" value="second texte" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2">20:11:54: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
</p>
......
Al no existir [__VIEWSTATE], se consideró que el valor anterior de [TextBox1] era la cadena vacía. Dado que el valor registrado de [TextBox1] también era la cadena vacía, no se generó el evento [TextChanged] sobre [TextBox1]. El procedimiento [TextBox1_TextChanged] no se ejecutó y, por lo tanto, el campo [lblInfo1] no recibió un nuevo valor. Se sabe que, en este caso, el componente conserva su valor anterior. Sin embargo, aquí no es así: [lblInfo1] ha perdido su valor anterior. Esto se debe a que dicho valor se busca en [__VIEWSTATE]. Como este campo no existe, se le asignó la cadena vacía a [lblInfo1]. En el caso de [TextBox2], el servidor comparó su valor publicado, [second texte], con su valor anterior. Al no existir [__VIEWSTATE], ese valor anterior es igual a la cadena vacía. Dado que el valor publicado de [TextBox2] es diferente de la cadena vacía, se generó el evento [TextChanged] sobre [TextBox2]. Se ejecutó el procedimiento [TextBox2_TextChanged] y el campo [lblInfo2] recibió un nuevo valor.
Cabe preguntarse si los parámetros [__EVENTTARGET] y [__EVENTARGUMENT] son realmente útiles. Al no enviar estos parámetros, el servidor no sabrá qué evento provocó el [submit]. Probemos:
dos>curl --include --url http://localhost/form4.aspx --data TextBox2=segundo+texto --data TextBox1=primer+texto
..............................
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
</form>
.....................
Se observa que no se ha procesado ningún evento [TextChanged]. Además, los campos enviados [TextBox1] y [TextBox2] no recuperan los valores que se enviaron. De hecho, todo ocurre como si se hubiera ejecutado un GET. Todo vuelve a la normalidad si se incluye el campo [__EVENTTARGET] entre los campos procesados, aunque no tenga ningún valor:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET= --data TextBox2=segundo+texto --data TextBox1=primer+texto
.......
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" value="second texte" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">20:34:14: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2">20:34:14: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
</p>
........
7.6.4. Otras propiedades del componente TextBox
El componente de servidor [TextBox] también permite generar las etiquetas HTML <input type="password"..> y <textarea>..</textarea>, c.a.d. Estas etiquetas corresponden, respectivamente, a un campo de entrada protegida y a un campo de entrada de varias líneas. Esta generación se controla mediante la propiedad [TextMode] del componente [TextBox]. Tiene tres valores posibles:
valor | etiqueta HTML generada |
<input type="text" ...> | |
<textarea>...</textarea> | |
<input type="password ...> |
Analizamos el uso de estas propiedades con el siguiente ejemplo [form5.aspx]:

n.º | nombre | tipo | propiedades | función |
1 | Botón | botón [submit]: sirve para agregar el contenido de [TextBox1] al de [TextBox2] | ||
2 | TextBox | TextMode=Contraseña Text= | campo de entrada protegido | |
3 | TextBox | TextMode=Multilínea Text= | agrupa las entradas realizadas en [TextBox1] |
La propiedad [EnableViewState] de la página se establece en [false]. Del lado del servidor, manejamos el evento de clic en el botón [btnAjouter]:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
': se suma el contenido de [textBox1] al de [TextBox2]
textbox2.text=textbox2.text + textbox1.text+controlchars.crlf
End Sub
Para entender este código, hay que recordar cómo se procesa el evento POST de un formulario. Primero se ejecutan los procedimientos [Page_Init] y [Page_Load]. Luego vienen todos los procedimientos de eventos almacenados en caché. Finalmente, se ejecuta el procedimiento que maneja el evento que provocó el [POST], en este caso el procedimiento [btnAjouter_Click]. Cuando se ejecutan los manejadores de eventos, todos los componentes de la página que tienen un valor en el POST han adoptado ese valor. Los demás han recuperado su valor anterior si su propiedad [EnableViewState] era [true], o su valor de diseño si su propiedad [EnableViewState] era [false]. En este caso, los valores de los campos [TextBox1] y [TextBox2] formarán parte del campo POST creado por el cliente. Asimismo, en el código anterior, [textbox1.text] tendrá como valor el valor publicado por el cliente, al igual que [textbox2.text]. El procedimiento [btnAjouter_Click] coloca en el campo [TextBox2] el valor enviado para [TextBox2] sumado al valor enviado para [TextBox1], más el marcador de fin de línea [ControlChars.CrLf] definido en el espacio de nombres [Microsoft.VisualBasic]. No es necesario importar este espacio, ya que el servidor web lo importa de forma predeterminada.
El código final de [form5.aspx] es el siguiente:
<%@ Page Language="VB" EnableViewState="False" %>
<script runat="server">
Sub btnAjouter_Click(sender As Object, e As EventArgs)
': se suma el contenido de [textBox1] al de [TextBox2]
textbox2.text+=textbox1.text+controlchars.crlf
End Sub
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter" EnableViewState="False"></asp:Button>
<asp:TextBox id="TextBox1" runat="server" TextMode="Password" Width="353px" EnableViewState="False"></asp:TextBox>
</p>
<p>
<asp:TextBox id="TextBox2" runat="server" TextMode="MultiLine" Width="419px" Height="121px" EnableViewState="False"></asp:TextBox>
</p>
</form>
</body>
</html>
Un poco más arriba, se mostró la captura de pantalla de una ejecución.
7.7. El componente DropDownList
La etiqueta <asp:DropDownList> permite insertar una lista desplegable en el código de presentación de una página. Creamos una página [form6.aspx] para obtener la siguiente presentación:

n.º | nombre | tipo | propiedades | función |
1 | DropDownList | AutoPostback=true EnableViewState=true | lista desplegable | |
2 | Etiqueta | EnableViewState=false | mensaje informativo |
El código de presentación generado es el siguiente:
Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
</p>
<p>
<asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
</p>
</form>
</body>
</html>
Por el momento, la lista desplegable no contiene ningún elemento. La llenaremos en el procedimiento [Page_Load]. Para ello, debemos conocer algunas de las propiedades y métodos de la clase [DropDownList]:
colección de tipo [ListItemCollection] de los elementos de la lista desplegable. Los miembros de esta colección son de tipo [ListItem]. | |
número de elementos de la colección [Items] | |
elemento n.º i de la lista — del tipo [ListItem] | |
para agregar un nuevo elemento [ListItem] a la colección [Items] | |
para eliminar todos los elementos de la colección [Items] | |
para eliminar el elemento n.º i de la colección [Items] | |
Primer elemento [ListItem] de la colección [Items] cuya propiedad [Selected] es verdadera | |
N.º del elemento [SelectedItem] en la colección [Items] |
Los elementos de la colección [Items] de la clase [DropDownList] son de tipo [ListItem]. Cada elemento [ListItem] genera una etiqueta HTML <option>:
A continuación describimos algunas propiedades y métodos de la clase [ListItem]:
constructor: crea un elemento [ListItem] con las propiedades [texte] y [value]. Un elemento ListItem(T,V) generará la etiqueta HTML <option value="V">T</option>. Por lo tanto, la clase [ListITem] permite describir los elementos de una lista HTML | |
booleano. Si es verdadero, la opción correspondiente de la lista HTML tendrá el atributo [selected="selected"]. Este atributo le indica al navegador que el elemento correspondiente debe aparecer seleccionado en la lista HTML | |
el texto T de la opción HTML <option value="V" [selected="selected"]>T</option> | |
el valor V del atributo [Value] de la opción HTML <option value="V" [selected="selected"]>T</option> |
Tenemos suficiente información para escribir en el procedimiento [Page_Load] de la página el código para completar la lista desplegable [DropDownList1]:
Sub Page_Load(sender As Object, e As EventArgs)
' se completa el combo si es la primera vez que se nos llama
if not IsPostBack then
dim valeurs() as String = {"1","2","3","4"}
dim textes() as String = {"un","deux","trois","quatre"}
dim i as integer
for i=0 to valeurs.length-1
DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
next
end if
end sub
Una vez inicializado el componente [DropDownList1], su traducción HTML será la siguiente:
<select name="DropDownList1" id="DropDownList1" onchange="__doPostBack('DropDownList1','')" language="javascript">
<option value="1">un</option>
<option value="2">deux</option>
<option value="3">trois</option>
<option value="4">quatre</option>
</select>
Sabemos que el procedimiento [Page_Load] se ejecuta cada vez que se ejecuta la página [form6.aspx]. Esta última es llamada por primera vez por un GET, y luego por un POST cada vez que el usuario selecciona un nuevo elemento en la lista desplegable. ¿Es necesario ejecutar cada vez el código de llenado de esta lista en [Page_Load]? La respuesta depende del atributo [EnableViewState] del componente [DropDownList1]. Si este atributo está en verdadero, sabemos que el estado del componente [DropDownList1] se conservará a lo largo de las consultas en el campo oculto [__VIEWSTATE]. Este estado incluye dos cosas:
- la lista de todos los valores del menú desplegable
- el valor del elemento seleccionado en dicha lista
Por lo tanto, resulta tentador establecer la propiedad [EnableViewState] del componente [DropDownList1] en [true] para no tener que volver a calcular los valores que se deben incluir en la lista. El problema es que, dado que el procedimiento [Page_Load] se ejecuta cada vez que se solicita la página [form6.aspx], estos valores se calcularán de todos modos. El objeto [Page], del cual [form6.aspx] es una instancia, cuenta con un atributo [IsPostBack] con valor booleano. Si este atributo es verdadero, significa que la página fue llamada por un POST. Si es falso, significa que la página fue llamada por un GET. En nuestro sistema de ida y vuelta cliente-servidor, el cliente siempre solicita la misma página [form6.aspx] al servidor. La primera vez, la solicita con un GET; las siguientes veces, con un POST. De ello se deduce que la propiedad [IsPostBack] nos puede servir para detectar la primera llamada GET del cliente. Los valores de la lista desplegable solo se generan durante esta primera llamada. En las solicitudes siguientes, estos valores serán generados por el mecanismo de [VIEWSTATE]. En otras situaciones, el contenido de una lista puede variar de una solicitud a otra y, por lo tanto, debe recalcularse en cada una de ellas. En este caso, se establecerá el atributo [EnableViewState] de la lista en [false] para evitar un cálculo doble e innecesario del contenido de la lista, a menos que se necesite conocer los elementos seleccionados previamente en la lista, ya que esta información se conserva en el [VIEWSTATE].
El atributo [AutoPostBack] de la lista [DropDownList1] se ha establecido en verdadero. Esto significa que el navegador enviará el formulario tan pronto como detecte el evento «cambio del elemento seleccionado» en la lista desplegable. El servidor, a su vez, detectará, gracias al [VIEWSTATE] y a los valores enviados, que el elemento seleccionado en el componente [DropDownList1] ha cambiado. Entonces activará el evento [SelectedIndexChanged] en ese componente. Lo procesaremos con el siguiente procedimiento:
Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
' cambio de selección
lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
" valeur=" + dropdownlist1.selecteditem.value + _
" index="+ dropdownlist1.selectedindex.tostring
End Sub
Cuando se ejecuta este procedimiento, el objeto [DropDownList1] ha recuperado sus elementos de tipo [ListItem] gracias al [VIEWSTATE]. Además, uno de ellos, del tipo [ListItem], tiene su atributo [Selected] en «verdadero», que es el valor que el navegador ha enviado. Se puede acceder a este elemento de varias maneras:
es el primer elemento [ListItem] de la lista cuyo atributo [Selected] tiene el valor «verdadero» | |
corresponde a la parte [texte] de la etiqueta HTML del elemento <option value="...">texto</option> seleccionado por el usuario | |
corresponde a la parte [value] de la etiqueta HTML del elemento <option value="...">texto</option> seleccionado por el usuario | |
número en la colección [DropDownList1.Items] del primer elemento [ListItem] cuyo atributo [Selected] es verdadero |
El código final de [form6.aspx] es el siguiente:
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' se completa el menú desplegable si es la primera vez que se recibe la llamada
if not IsPostBack then
dim valeurs() as String = {"1","2","3","4"}
dim textes() as String = {"un","deux","trois","quatre"}
dim i as integer
for i=0 to valeurs.length-1
DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
next
end if
end sub
Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
' cambio de selección
lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
" valeur=" + dropdownlist1.selecteditem.value + _
" index="+ dropdownlist1.selectedindex.tostring
End Sub
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
</p>
<p>
<asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
</p>
</form>
</body>
</html>
7.8. El componente ListBox
La etiqueta <asp:ListBox> permite insertar una lista en el código de presentación de una página. Creamos [form7.aspx] para obtener la siguiente presentación:

n.º | nombre | tipo | propiedades | función |
1 | TextBox | EnableViewState=false | campo de entrada | |
2 | Botón | botón [submit] que transfiere a la Lista 1 el contenido de txtSaisie si este no está vacío. | ||
3 | ListBox | EnableViewState=true SelectionMode=Single | lista de valores de selección única | |
4 | ListBox | EnableViewState=true SelectionMode=Multiple | lista de valores de selección múltiple | |
5 | Botón | botón [submit] que transfiere a [liste 2] el elemento seleccionado de [liste 1] | ||
6 | Botón | botón [submit] que transfiere a [liste 1] los elementos seleccionados de [liste 2] |
El código de presentación generado es el siguiente:
<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
Tapez un texte pour l'inclure dans Liste 1 :
<asp:TextBox id="txtSaisie" runat="server" EnableViewState="False"></asp:TextBox>
</p>
<p>
<asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter"></asp:Button>
</p>
<p>
<table>
<tbody>
<tr>
<td>
<p align="center">
Liste 1
</p>
</td>
<td>
</td>
<td>
<p align="center">
Liste 2
</p>
</td>
</tr>
<tr>
<td>
<asp:ListBox id="ListBox1" runat="server"></asp:ListBox>
</td>
<td>
<p>
<asp:Button id="btn1vers2" onclick="btn1vers2_Click" runat="server" Text="-->"></asp:Button>
</p>
<p>
<asp:Button id="btn2vers1" onclick="btn2vers1_Click" runat="server" Text="<--"></asp:Button>
</p>
</td>
<td>
<p>
<asp:ListBox id="ListBox2" runat="server" SelectionMode="Multiple"></asp:ListBox>
</p>
</td>
</tr>
<tr>
<td>
<p align="center">
<asp:Button id="btnRaz1" onclick="btnRaz1_Click" runat="server" Text="Effacer"></asp:Button>
</p>
</td>
<td>
</td>
<td>
<p align="center">
<asp:Button id="btnRaz2" onclick="btnRaz2_Click" runat="server" Text="Effacer"></asp:Button>
</p>
</td>
</tr>
</tbody>
</table>
</p>
</form>
</body>
</html>
La clase [ListBox] se deriva de la misma clase [ListControl] que la clase [DropDownList] analizada anteriormente. En ella se encuentran todas las propiedades y métodos que vimos para [DropDownList], ya que en realidad pertenecían a [ListControl]. Aparece una nueva propiedad:
establece el modo de selección de la lista HTML <select> que se generará a partir del componente. Si SelectionMode = Single, solo se podrá seleccionar un elemento. Si SelectionMode = Multiple, se podrán seleccionar varios elementos. Para ello, se generará el atributo [multiple="multiple"] en la etiqueta <select> de la lista HTML. |
Procesemos los eventos. Al hacer clic en el botón [Ajouter], se ejecutará el siguiente procedimiento [btnAjouter_Click]:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' se agrega a la lista 1
dim texte as string=txtSaisie.text.trim
if texte<> "" then ListBox1.Items.Add(New ListItem(texte))
' borrar txtSaisie
txtSaisie.text=""
End Sub
Si el texto ingresado en [txtSaisie] no es una cadena vacía o en blanco, se agrega un nuevo elemento a la lista [ListBox1]. Sabemos que debemos agregar un elemento de tipo [ListItem]. Anteriormente, habíamos utilizado el constructor [ListItem(T as String, V as String)] para realizar una tarea similar. Dicho elemento da lugar a la etiqueta HTML [<option value="V">T</option>]. Aquí utilizamos el constructor [ListItem(T as String)], que genera las etiquetas HTML, [<option value="T">T</option>] y c.a.d. El texto [T] de la opción se toma para formar el valor de la opción. Una vez que se agrega el contenido de [txtSaisie] a la lista [ListBox1], se vacía el campo [txTSaisie].
Los clics en los botones [Effacer] serán procesados mediante los siguientes procedimientos:
Sub btnRaz1_Click(sender As Object, e As EventArgs)
' borrar lista 1
ListBox1.Items.Clear
End Sub
Sub btnRaz2_Click(sender As Object, e As EventArgs)
' borrar lista 2
ListBox2.Items.Clear
End Sub
Los clics en los botones de transferencia entre listas se procesan mediante los siguientes procedimientos:
Sub btn1vers2_Click(sender As Object, e As EventArgs)
' traslado del elemento seleccionado de la lista 1 a la lista 2
transfert(ListBox1,ListBox2)
End Sub
Sub btn2vers1_Click(sender As Object, e As EventArgs)
' traslado del elemento seleccionado de la lista 2 a la lista 1
transfert(ListBox2,ListBox1)
End Sub
sub transfert(l1 as listbox, l2 as listbox)
' transferencia de los elementos seleccionados de la lista 1 a la lista 2
' ¿Hay algo que hacer?
if l1.selectedindex=-1 then return
dim i as integer
' empezamos por el final
for i=l1.items.count-1 to 0 step -1
' ¿Seleccionado?
if l1.items(i).selected then
' ya no está seleccionado
l1.items(i).selected=false
' transferencia a l2
l2.items.add(l1.items(i))
' eliminación en l1
l1.items.removeAt(i)
end if
next
end sub
Dado que ambos botones realizan la misma función —transferir elementos de una lista a otra—, podemos reducirlo a un único procedimiento de transferencia con dos parámetros:
- l1 de tipo [ListBox], que es la lista de origen
- l2 de tipo [ListBox], que es la lista de destino
En primer lugar, se verifica si hay al menos un elemento seleccionado en la lista l1; de lo contrario, no hay nada que hacer. Para ello, se examina la propiedad [l1.selectedindex], que representa el número del primer elemento seleccionado en la lista. Si no hay ninguno, su valor es -1. Si hay al menos un elemento seleccionado en l1, se realiza la transferencia a l2. Para ello, se recorre toda la lista de elementos de l1 y se verifica para cada uno de ellos si su atributo [selected] es verdadero. Si es así, su atributo [selected] se cambia a [false], luego se copia a la lista l2 y, por último, se elimina de la lista l1. Esta eliminación provoca una renumeración de los elementos de la lista l1. Por eso se recorre la lista de elementos de l1 en orden inverso. Si se recorre en orden normal y se elimina el elemento n.º 10, el elemento n.º 11 pasa a ser el n.º 10 y el n.º 12, el elemento n.º 11. Después de procesar el elemento n.º 10, nuestro bucle en orden normal procesará el elemento n.º 11, que, según lo que acabamos de explicar, es el antiguo n.º 12. El que tenía el n.º 11 y ahora tiene el n.º 10 se nos escapa. Al recorrer los elementos de la lista l1 en sentido inverso, evitamos este problema.
7.9. Los componentes CheckBox, RadioButton
Las etiquetas <asp:RadioButton> y <asp:CheckBox> permiten insertar, respectivamente, un botón de opción y una casilla de selección en el código de presentación de una página. Creamos una página [form8.aspx] para obtener la siguiente presentación:

n.º | nombre | tipo | propiedades | función |
1 | RadioButton | RadioButton1.Checked=verdadero RadioButton1.Text=1 RadioButton2.Checked=false RadioButton2.Text=2 RadioButton3.Checked=falso RadioButton3.Text=3 para los 3 botones: GroupName=radio | botones de radio | |
2 | CheckBox | Checked=false para todos CheckBoxA.Text=A CheckBoxB.Text=B CheckBoxC.Text=C | casillas de selección | |
3 | Botón | botón [submit] | ||
4 | ListBox | Lista de información |
Para que el navegador trate los tres botones de opción como mutuamente excluyentes, es necesario agruparlos en un grupo de botones de opción. Esto se logra mediante el atributo [GroupName] de la clase [RadioButton]. No es necesario mantener el estado de la página en esta aplicación. Por lo tanto, le asignamos el atributo [EnableViewState="false"] a la página. El código de presentación es el siguiente:
<html>
<head>
</head>
<body>
<form id="frmControls" runat="server">
<h3>Cases à cocher
</h3>
<p>
<asp:RadioButton id="RadioButton1" runat="server" Checked="True" EnableViewState="False" GroupName="radio" Text="1"></asp:RadioButton>
<asp:RadioButton id="RadioButton2" runat="server" EnableViewState="False" GroupName="radio" Text="2"></asp:RadioButton>
<asp:RadioButton id="RadioButton3" runat="server" EnableViewState="False" GroupName="radio" Text="3"></asp:RadioButton>
</p>
<p>
<asp:CheckBox id="CheckBoxA" runat="server" EnableViewState="False" Text="A"></asp:CheckBox>
<asp:CheckBox id="CheckBoxB" runat="server" EnableViewState="False" Text="B"></asp:CheckBox>
<asp:CheckBox id="CheckBoxC" runat="server" EnableViewState="False" Text="C"></asp:CheckBox>
</p>
<p>
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
<asp:Button id="btnTree" onclick="btnTree_Click" runat="server" Text="Contrôles"></asp:Button>
</p>
<p>
<asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6" Height="131px"></asp:ListBox>
</p>
</form>
</body>
</html>
Debemos escribir la rutina [btnEnvoyer_Click] para manejar el evento [Click] en este botón. El estado de un botón de radio o de una casilla de selección viene dado por su atributo [Checked], un valor booleano que es verdadero si la casilla está marcada y falso en caso contrario. Por lo tanto, basta con escribir en la lista [lstInfos] el valor del atributo [Checked] de los seis botones de radio y casillas de selección. Como esto no presenta ninguna dificultad particular, innovamos un poco:
<script runat="server">
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' se coloca información en el cuadro de lista
for each c as control in FindControl("frmControls").controls
' ¿El control se deriva de CheckBox?
if TypeOf(c) is CheckBox then
lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
end if
next
End Sub
</script>
La página puede verse como una estructura de árbol de controles. En nuestro ejemplo, nuestra página contiene textos y controles de servidor. Los textos se consideran un control específico llamado [LiteralControl]. Cada texto da lugar a este control, incluso una secuencia de espacios entre dos controles. Cada control tiene un atributo ID que lo identifica. Es el atributo ID el que aparece en las etiquetas:
Si ignoramos los controles [LiteralControl], la página analizada cuenta con los siguientes controles:
- [HtmlForm], que es el formulario [ID=frmControls]. Este, a su vez, es un contenedor de controles. Contiene los siguientes controles:
-- [ID=RadioButton1] de tipo [RadioButton]
-- [ID=RadioButton2] de tipo [RadioButton]
-- [ID=RadioButton3] de tipo [RadioButton]
-- [ID=CheckBoxA] de tipo [CheckBox]
-- [ID=CheckBoxA] de tipo [CheckBox]
-- [ID=CheckBoxA] de tipo [CheckBox]
-- [ID=btnEnviar] de tipo [Button]
Un control tiene las siguientes propiedades:
devuelve la colección de controles hijos de [Control], si los hay | |
devuelve el control identificado por ID que se encuentra en la raíz del árbol de controles hijos de [Control]. En el ejemplo anterior: Page.FindControl("frmControls") se refiere al contenedor [HtmlForm]. Para acceder al botón de opción [RadioButton1], deberá escribir Page.FindControl("frmControls").FindControl("RadioButton1") | |
identificador de [Control] |
Volvamos al código del procedimiento [btnEnvoyer_Click]:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' se inserta información en el cuadro de lista
for each c as control in FindControl("frmControls").controls
' ¿El control se deriva de CheckBox?
if TypeOf(c) is CheckBox then
lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
end if
next
End Sub
Queremos mostrar el estado de los botones de opción y las casillas de selección que se encuentran en el formulario. Recorremos todos los controles del mismo. Si el control actual es de un tipo derivado de [CheckBox], mostramos su propiedad [Checked]. Dado que la clase [RadioButton] deriva de la clase [CheckBox], la comprobación es válida para ambos tipos de controles. La captura de pantalla que se muestra más arriba presenta un ejemplo de ejecución.
7.10. Los componentes CheckBoxList, RadioButtonList
A veces, se desea que el usuario elija entre valores que no se conocen al momento de diseñar la página. Estas opciones provienen de un archivo de configuración, de una base de datos, etc., y solo se conocen al momento de la ejecución. Existen soluciones para este problema y las hemos encontrado. La lista de selección simple es adecuada cuando el usuario solo puede elegir una opción, y la lista de selección múltiple cuando puede elegir varias. Desde un punto de vista estético, y si el número de opciones no es muy grande, es posible que se prefiera utilizar botones de radio en lugar de la lista de selección simple o casillas de selección en lugar de la lista de selección múltiple. Esto es posible con los componentes [CheckBoxList] y [RadioButtonList].
Las clases [CheckBoxList] y [RadioButtonList] se derivan de la misma clase [ListControl] que las clases [DropDownList] y [ListBox] analizadas anteriormente. Por lo tanto, encontraremos algunas de las propiedades y métodos que vimos para esas clases, las que en realidad pertenecían a [ListControl].
colección de tipo [ListItemCollection] de los elementos de la lista desplegable. Los miembros de esta colección son de tipo [ListItem]. | |
número de elementos de la colección [Items] | |
elemento n.º i de la lista — del tipo [ListItem] | |
para agregar un nuevo elemento [ListItem] a la colección [Items] | |
para eliminar todos los elementos de la colección [Items] | |
para eliminar el elemento n.º i de la colección [Items] | |
Primer elemento [ListItem] de la colección [Items] cuya propiedad [Selected] es verdadera | |
N.º del elemento anterior en la colección [Items] |
Algunas propiedades son específicas de las clases [CheckBoxList] y [RadioButtonList]:
[horizontal] o [vertical] para listas horizontales o verticales. |
Los elementos de la colección [Items] son del tipo [ListItem]. Cada elemento [ListItem] generará una etiqueta diferente dependiendo de si se trata de un objeto [CheckBoxList] o [RadioButtonList]:
O
A continuación describimos algunas propiedades y métodos de la clase [ListItem]:
constructor: crea un elemento [ListItem] con las propiedades «texto» y «valor». Un elemento ListItem(T,V) generará la etiqueta HTML <input type="checkbox" value="V">T o <input type="radio" value="V">T, según el caso. | |
booleano. Si es verdadero, la opción correspondiente de la lista HTML tendrá el atributo [selected="selected"]. Este atributo le indica al navegador que el elemento correspondiente debe aparecer seleccionado en la lista HTML | |
el texto T de la opción HTML <input type=".." value="V" [selected="selected"]>T | |
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]:

n.º | nombre | tipo | propiedades | función |
1 | RadioButtonList | EnableViewState=true RepeatDirection=horizontal | lista de botones de opción | |
2 | CheckBoxList | EnableViewState=true RepeatDirection=horizontal | lista de casillas de selección | |
3 | Botón | botón [submit] que muestra en [4] la lista de elementos seleccionados en ambas listas | ||
4 | ListBox | EnableViewState=false | lista de valores |
El código de presentación de la página es el siguiente:
<%@ Page Language="VB" autoeventwireup="false" %>
<script runat="server">
...
</script>
<html>
<head>
</head>
<body>
<form id="frmControls" runat="server">
<h3>Listes de cases à cocher
</h3>
<p>
<asp:RadioButtonList id="RadioButtonList1" runat="server" RepeatDirection="Horizontal"></asp:RadioButtonList>
</p>
<p>
<asp:CheckBoxList id="CheckBoxList1" runat="server" RepeatDirection="Horizontal"></asp:CheckBoxList>
</p>
<p>
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
</p>
<p>
<asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6"></asp:ListBox>
</p>
</form>
</body>
</html>
El código de control es el siguiente:
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs) handles MyBase.Load
' se completan las listas si es la primera vez que se llama
if not IsPostBack then
' textos para la lista RadioButton
dim textesRadio() as String = {"1","2","3","4"}
' textos para la lista RadioButton
dim textesCheckBox() as String = {"un","deux","trois","quatre"}
' relleno de la lista de opciones de radio
dim i as integer
for i=0 to textesRadio.length-1
RadioButtonList1.Items.Add(new ListItem(textesRadio(i)))
next
' selección del elemento n.º 1
RadioButtonList1.SelectedIndex=1
' relleno de la lista de casillas de selección
for i=0 to textesCheckBox.length-1
CheckBoxList1.Items.Add(new ListItem(textesCheckBox(i)))
next
end if
end sub
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' se ingresa información en el cuadro de lista lstinfos
affiche(RadioButtonList1)
affiche(CheckBoxList1)
End Sub
sub affiche(l1 as ListControl)
' muestra los valores de los elementos seleccionados de l1
' ¿Hay algo que hacer?
if l1.selectedindex=-1 then return
dim i as integer
' empezamos por el final
for i= 0 to l1.items.count-1
' ¿Seleccionado?
if l1.items(i).selected then
lstInfos.Items.Add("["+TypeName(l1)+"] ["+l1.items(i).text+"] sélectionné")
end if
next
end sub
</script>
En el procedimiento [Page_Load], que se ejecuta cada vez que se abre la página, se inicializan ambas listas. Para evitar que esto suceda cada vez, se utiliza la propiedad [IsPostBack] para que solo se haga la primera vez. En las siguientes ocasiones, las listas se regenerarán automáticamente mediante el mecanismo de [VIEWSTATE]. Una vez que se muestra la página, el usuario marca ciertas casillas y utiliza el botón [Envoyer]. Los valores del formulario se envían entonces al propio formulario. Tras la ejecución de [Page_Load], se ejecuta el procedimiento [btnEnvoyer_Click]. Este llama al procedimiento [affiche] para completar la lista [lstInfos]. Este último recibe como parámetro un objeto de tipo [ListControl], lo que permite enviarle indistintamente un objeto [RadioButtonList] o un objeto [CheckBoxList], clases derivadas de [ListControl]. La lista [lstInfos] puede tener su atributo [EnableViewState] establecido en [false], ya que no es necesario mantener su estado entre las diferentes consultas.
7.11. Los componentes Panel, LinkButton
La etiqueta <asp:panel> permite insertar un contenedor de controles en una página. La ventaja del contenedor es que algunas de sus propiedades se aplican a todos los controles que contiene. Este es el caso de su propiedad [Visible]. Esta propiedad existe para todos los controles de servidor. Si un contenedor tiene la propiedad [Visible=false], cada uno de sus controles se regirá por su propia propiedad [Visible]. Si tiene la propiedad [Visible=false], entonces ni el contenedor ni su contenido se muestran. Esto puede resultar más sencillo que gestionar la propiedad [Visible] de cada uno de los controles del contenedor.
La etiqueta <asp:LinkButton> permite insertar un enlace en el código de presentación de una página. Tiene una función similar a la del botón [Button]. De hecho, provoca un POST del lado del cliente gracias a una función de JavaScript que se le asocia. Creamos una página [form9.aspx] para obtener la presentación siguiente:

n.º | nombre | tipo | propiedades | función |
1 | Panel | EnableViewState=true | contenedor de controles | |
2 | ListBox | EnableViewState=true | una lista de tres valores | |
3 | LinkButton | EnableViewState=false | enlace para ocultar el contenedor |
Cuando el contenedor está oculto, aparece un nuevo enlace:

N.º | nombre | tipo | propiedades | función |
4 | LinkButton | EnableViewState=false | enlace para mostrar el contenedor |
El código de presentación de la página es el siguiente:
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:Panel id="Panel1" runat="server" BorderStyle="Ridge" BorderWidth="1px">
<p>
Conteneur
</p>
<p>
<asp:ListBox id="ListBox1" runat="server">
<asp:ListItem Value="1">un</asp:ListItem>
<asp:ListItem Value="2">deux</asp:ListItem>
<asp:ListItem Value="3" Selected="True">trois</asp:ListItem>
</asp:ListBox>
</p>
</asp:Panel>
</p>
<p>
<asp:LinkButton id="lnkVoir" onclick="lnkVoir_Click" runat="server">Voir le conteneur</asp:LinkButton>
</p>
<p>
<asp:LinkButton id="lnkCacher" onclick="lnkCacher_Click" runat="server">Cacher le conteneur</asp:LinkButton>
</p>
</form>
</body>
</html>
Cabe destacar que este código inicializa la lista [ListBox1] con tres valores. Los controladores de eventos [Clic] en ambos enlaces son los siguientes:
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
...
end sub
Sub lnkVoir_Click(sender As Object, e As EventArgs)
' muestra el contenedor 1
panel1.Visible=true
' cambia los enlaces
lnkVoir.visible=false
lnkCacher.visible=true
End Sub
Sub lnkCacher_Click(sender As Object, e As EventArgs)
' oculta el contenedor 1
panel1.Visible=false
' cambia los enlaces
lnkVoir.visible=true
lnkCacher.visible=false
End Sub
</script>
Utilizaremos el procedimiento [Page_Load] para inicializar el formulario. Lo haremos durante la primera consulta (IsPostBack=false):
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' la primera vez
if not IsPostBack then
' se muestra el contenedor
lnkVoir_Click(nothing,nothing)
end if
end sub
.....
</script>
7.12. Para continuar...
En los párrafos anteriores se han presentado varios componentes del servidor. En cada caso, solo se han mostrado algunas de sus propiedades. Para profundizar en el estudio de estos componentes, el lector puede hacerlo de varias maneras:
- descubrir las propiedades de un componente con un IDE, como por ejemplo el WebMatrix. Este, de hecho, expone las principales propiedades de los componentes utilizados en un formulario
- consultar la documentación de .NET para conocer todas las clases correspondientes a cada uno de los componentes de servidor. Este es el método preferido para un dominio total del componente. En ella se encontrará el árbol de clases que conduce a los componentes, así como las propiedades, métodos, constructores y eventos de cada una de ellas. Además, la documentación a veces proporciona ejemplos.
En este capítulo, hemos utilizado la técnica «todo en uno» de [WebMatrix], c.a.d, en la que se colocan el código de presentación y el código de control de una página en el mismo archivo. En general, no recomendamos este método, sino el denominado [codebehind] utilizado anteriormente, que coloca estos dos códigos en dos archivos separados. Recordamos que la ventaja de esta separación radica en que el código de control se puede compilar sin necesidad de ejecutar la aplicación web. Por otra parte, nuestros ejemplos —como lo explicamos desde el inicio del capítulo— tenían un perfil muy particular: consistían en una sola página que era un formulario que el cliente y el servidor intercambiaban en ciclos sucesivos de solicitud-respuesta, siendo la primera solicitud del cliente un GET, y las siguientes, POST.
7.13. Componentes del servidor y del controlador de la aplicación
En los capítulos anteriores, hemos desarrollado varias aplicaciones web. Todas ellas se construyeron siguiendo la arquitectura MVC (Modelo-Vista-Controlador), que divide la aplicación en bloques bien diferenciados y facilita su mantenimiento. En ese entonces, construíamos nuestras interfaces de usuario con etiquetas HTML estándar. Con lo que acabamos de ver, es natural querer utilizar ahora componentes de servidor. Retomemos un problema que ya hemos estudiado a fondo: el cálculo de un impuesto. Su arquitectura MVC era la siguiente:

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]:

El usuario completa el formulario:

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

En una aplicación MVC, toda solicitud debe pasar por el controlador, en este caso [main.aspx]. Esto significa que cuando el usuario ha completado el formulario [formulaire.aspx], este debe enviarse a [main.aspx] y no a [formulaire.aspx]. Esto simplemente no es posible si construimos la interfaz de usuario [formulaire.aspx] con componentes de servidor ASP. Para darnos cuenta de ello, construyamos un formulario [formtest.aspx] con un componente <asp:button>:
<%@ Page Language="VB" EnableViewState="false"%>
<html>
<head>
<title>test</title>
</head>
<body>
<form action="main.aspx" runat="server">
<p>
<asp:Button id="btnTest" runat="server" EnableViewState="false" Text="Test"></asp:Button>
</p>
</form>
</body>
</html>
Cabe destacar el atributo [action="main.aspx"] de la etiqueta <form...>. Ejecutemos esta aplicación. La página de presentación solo muestra un botón:

Veamos el código HTML enviado por el servidor:
<html>
<head>
<title>test</title>
</head>
<body>
<form name="_ctl0" method="post" action="formtest.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwtNTMwNzcxMzI0Ozs+" />
<p>
<input type="submit" name="btnTest" value="Test" id="btnTest" />
</p>
</form>
</body>
</html>
Se observa que el POST del formulario apunta al propio formulario [action="formtest.aspx"], mientras que en [formtest.aspx] habíamos escrito la etiqueta HTML del servidor:
El atributo [runat="server"] de la etiqueta <form> nos viene impuesto por el uso de los componentes del servidor. Se produce un error de compilación si no incluimos este atributo. Cuando lo incluimos, el atributo [action] de la etiqueta <form> se ignora. El servidor siempre genera un atributo [action] que apunta al propio formulario. De ello se deduce que, en una aplicación MVC, no se pueden utilizar formularios creados con la etiqueta <form ... runat="server">. Sin embargo, esta etiqueta es indispensable para todos los componentes de servidor ASP que recogen entradas del usuario. Es decir, no se pueden utilizar formularios del servidor ASP en una aplicación MVC. Es un gran descubrimiento. De hecho, uno de los puntos fuertes del marketing de ASP.NET es que se puede crear una aplicación web como si fuera una aplicación de Windows. Esto es cierto si nuestra aplicación no sigue la arquitectura MVC, pero lo es aún más si sí la sigue. Ahora bien, la arquitectura MVC parece un concepto fundamental del desarrollo web actual que resulta difícil de ignorar.
Es posible utilizar la arquitectura MVC junto con formularios con componentes ASP para aplicaciones con pocas vistas diferentes gracias al siguiente recurso:
- la aplicación consiste en una sola página que actúa como controlador
- las vistas se traducen en esta página como contenedores distintos, un contenedor por vista. Para mostrar una vista, se hace visible su contenedor y se ocultan los demás
Es una solución elegante que ahora implementaremos en algunos ejemplos
7.14. Ejemplos de aplicaciones MVC con componentes de servidor ASP
7.14.1. Ejemplo 1
En este primer ejemplo implementamos los componentes de servidor que hemos presentado. La página [form10.aspx] quedará así:
![]() | ![]() |
La captura de pantalla de la izquierda que se muestra arriba es el formulario tal como se le presenta al cliente. Este lo llena y lo valida mediante [Envoyer]. El servidor le devuelve una vista que muestra una lista de los valores ingresados (captura de pantalla de la derecha). Un enlace permite al usuario regresar al formulario. Este lo encuentra tal como lo validó. El código de presentación de [form10.aspx] es el siguiente:
<html>
<head>
<title>Exemple</title> <script language="javascript">
function effacer(){
alert("Vous avez cliqué sur [Effacer]")
}
</script>
</head>
<body>
<p>
Gestion d'un formulaire
</p>
<p>
<hr />
</p>
<form runat="server">
<p>
<asp:Panel id="panelinfo" runat="server" EnableViewState="False">
<p>
Liste des valeurs obtenues
</p>
<p>
<asp:ListBox id="lstInfos" runat="server" EnableViewState="False"></asp:ListBox>
</p>
<p>
<asp:LinkButton id="LinkButton1" onclick="LinkButton1_Click" runat="server">Retour au formulaire</asp:LinkButton>
</p>
<p>
<hr />
</p>
</asp:Panel>
</p>
<p>
<asp:Panel id="panelform" runat="server" >
<table>
<tbody>
<tr>
<td>
Etes-vous marié(e)</td>
<td>
<asp:RadioButton id="rdOui" runat="server" GroupName="rdmarie"></asp:RadioButton>
Oui<asp:RadioButton id="rdNon" runat="server" GroupName="rdmarie" Checked="True"></asp:RadioButton>
Non</td>
</tr>
<tr>
<td>
Cases à cocher</td>
<td>
<asp:CheckBox id="chk1" runat="server"></asp:CheckBox>
1<asp:CheckBox id="chk2" runat="server"></asp:CheckBox>
2<asp:CheckBox id="chk3" runat="server"></asp:CheckBox>
3</td>
</tr>
<tr>
<td>
Champ de saisie</td>
<td>
<asp:TextBox id="txtSaisie" runat="server" MaxLength="20" Columns="20"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Mot de passe</td>
<td>
<asp:TextBox id="txtmdp" runat="server" MaxLength="10" Columns="10" TextMode="Password"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Boîte de saisie</td>
<td>
<asp:TextBox id="txtArea" runat="server" Columns="20" TextMode="MultiLine" Rows="3"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Liste déroulante</td>
<td>
<asp:DropDownList id="cmbValeurs" runat="server"></asp:DropDownList>
</td>
</tr>
<tr>
<td>
Liste à choix unique</td>
<td>
<asp:ListBox id="lstSimple" runat="server"></asp:ListBox>
<asp:Button id="btnRazSimple" onclick="btnRazSimple_Click" runat="server" EnableViewState="False" Text="Raz"></asp:Button>
</td>
</tr>
<tr>
<td>
Liste à choix multiple</td>
<td>
<asp:ListBox id="lstMultiple" runat="server" SelectionMode="Multiple"></asp:ListBox>
<asp:Button id="razMultiple" onclick="razMultiple_Click" runat="server" EnableViewState="False" Text="Raz"></asp:Button>
</td>
</tr>
<tr>
<td>
Champ caché</td>
<td>
<asp:Label id="lblSecret" runat="server" visible="False"></asp:Label></td>
</tr>
<tr>
<td>
Bouton simple</td>
<td>
<input id="btnEffacer" onclick="effacer()" type="button" value="Effacer" /></td>
</tr>
<tr>
<td>
Bouton [reset]</td>
<td>
<input id="btnReset" type="reset" value="Rétablir" /></td>
</tr>
<tr>
<td>
Bouton [submit]</td>
<td>
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" EnableViewState="False" Text="Envoyer"></asp:Button>
</td>
</tr>
</tbody>
</table>
</asp:Panel>
</p>
</form>
</body>
</html>
La página tiene dos contenedores, uno para cada vista: [panelform] para la vista de formulario y [panelinfo] para la vista de información. La lista de componentes del contenedor [panelForm] es la siguiente:
nombre | tipo | propiedades | función |
Panel | EnableViewState=true | vista de formulario | |
RadioButton | EnableViewState=true GroupName=rdmarie | botones de opción | |
CheckBox | EnableViewState=true | casillas de selección | |
TextBox | EnableViewState=true | campo de entrada | |
TextBox | EnableViewState=true | campo de entrada protegido | |
TextBox | EnableViewState=true | campo de entrada de varias líneas | |
DropDownList | EnableViewState=true | lista desplegable | |
ListBox | EnableViewState=true SelectionMode=Single | lista de selección única | |
Botón | EnableViewState=false | deselecciona todos los elementos de lstSimple | |
ListBox | EnableViewState=true SelectionMode=Multiple | lista de selección múltiple | |
Botón | EnableViewState=false | deselecciona todos los elementos de lstMultiple | |
Etiqueta | EnableViewState=true Visible=false | campo oculto | |
HTML estándar | muestra una alerta | ||
Botón | EnableViewState=false | botón [submit] del formulario | |
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]:
- el cliente solicita por primera vez la página [form10.aspx]. Obtiene la vista de formulario
- , la llena y la envía con el botón [Envoyer]. Los campos de entrada se envían y el servidor asigna a los componentes del servidor el valor enviado o su estado [VIEWSTATE], si es que tenían uno. De este modo, al campo [txtSaisie] se le asigna el valor ingresado por el usuario. Además, en este paso, su estado [VIEWSTATE] no tiene ninguna utilidad. Como resultado de la operación, se envía la vista [informations], que en realidad sigue siendo la página [form10.aspx], pero con un contenedor diferente a la vista.
- El usuario consulta esta nueva vista y utiliza el enlace [Retour au formulaire] para regresar a ella. A continuación, se realiza un POST hacia [form10.aspx]. En ese momento, solo hay un valor enviado como máximo: el valor seleccionado por el usuario en la lista de información, información que no se utiliza posteriormente. De todos modos, no se envía ningún campo [txtSaisie].
- El servidor recibe el POST y asigna a los componentes del servidor el valor enviado o su estado [VIEWSTATE], si es que tenían uno. En este caso, [txtNom] no tiene ningún valor enviado. Si su atributo [EnableViewState] es [false], se le asignará la cadena vacía. Como queremos que tenga el valor ingresado por el usuario, debe tener la propiedad [EnableViewState=true].
El contenedor [panelinfo] cuenta con los siguientes controles:
nombre | tipo | propiedades | función |
Panel | EnableViewState=false | vista de información | |
ListBox | EnableViewState=false | lista de información que resume los valores ingresados por el usuario | |
LinkButton | EnableViewState=false | enlace de regreso al formulario |
Al momento de realizar las pruebas, si observamos el código HTML generado por el código de presentación anterior, nos puede sorprender el código generado para el campo oculto [lblSecret]:
El componente [lblSecret] no se convierte al código HTML porque tiene la propiedad [Visible=false]. Sin embargo, debido a que tiene la propiedad [EnableViewState=true], su valor se conservará de todos modos en el campo oculto [__VIEWSATE]. Por lo tanto, podremos recuperarlo, como lo demostrarán las pruebas.
Ahora nos queda escribir los controladores de eventos. En [Page_Load] inicializaremos el formulario:
Sub page_Load(sender As Object, e As EventArgs)
' la primera vez, se inicializan los elementos
' las siguientes veces, estos recuperan sus valores mediante el VIEWSTATE
if IsPostBack then return
' inicializar formulario
' panelinfo no se muestra
panelinfo.visible=false
' panel de formulario mostrado
panelform.visible=true
' botones de radio
rdNon.Checked=true
' casillas de selección
chk2.Checked=true
' campo de entrada
txtSaisie.Text="qqs mots"
' campo de contraseña
txtMdp.Text="ceciestsecret"
' cuadro de entrada
txtArea.Text="ligne"+ControlChars.CrLf+"ligne2"+ControlChars.CrLf
' menú desplegable
dim i as integer
for i=1 to 4
cmbValeurs.Items.Add(new ListItem("choix"+i.ToString,i.ToString))
next
cmbValeurs.SelectedIndex=1
' lista de selección simple
for i=1 to 7
lstSimple.Items.Add(new ListItem("simple"+i.ToString,i.ToString))
next
lstSimple.SelectedIndex=0
' lista de selección múltiple
for i=1 to 10
lstMultiple.Items.Add(new ListItem("multiple"+i.ToString,i.ToString))
next
lstMultiple.Items(0).Selected=true
lstMultiple.Items(2).Selected=true
' campo oculto
lblSecret.Text="secret"
End Sub
Al hacer clic en los botones [lstRazSimple] y [lstMultiple]:
Sub btnRazSimple_Click(sender As Object, e As EventArgs)
' borrar lista simple
lstSimple.SelectedIndex=-1
End Sub
Sub razMultiple_Click(sender As Object, e As EventArgs)
' borrar lista múltiple
lstMultiple.SelectedIndex=-1
End Sub
Al hacer clic en el botón [Envoyer]:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' el panel de información se muestra y el panel del formulario se oculta
panelinfo.Visible=true
panelform.visible=false
' se recuperan los valores enviados y se colocan en lstInfos
' botones de opción
dim info as string="état marital : "+iif(rdoui.checked,"marié"," non marié")
affiche(info)
' casillas de selección
info=" cases cochées : "+iif(chk1.checked,"1 oui","1 non")+","+ _
iif(chk2.checked,"2 oui","2 non")+","+iif(chk3.checked,"3 oui","3 non")
affiche(info)
' campo de entrada
affiche("champ de saisie : " + txtSaisie.Text.Trim)
' contraseña
affiche("mot de passe : " + txtMdp.Text.Trim)
' cuadro de texto
dim lignes() as String
lignes=new Regex("\r\n").Split(txtArea.Text.Trim)
dim i as integer
for i=0 to lignes.length-1
lignes(i)="["+lignes(i).Trim+"]"
next
affiche("Boîte de saisie : " + String.Join(",",lignes))
' menú desplegable
affiche("éléments sélectionnés dans combo : "+selection(cmbValeurs))
' lista simple
affiche("éléments sélectionnés dans liste simple : "+selection(lstSimple))
' lista múltiple
affiche("éléments sélectionnés dans liste multiple : "+selection(lstMultiple))
' campo oculto
affiche ("Champ caché : " + lblSecret.Text)
End Sub
sub affiche(msg as String)
' muestra mensaje en lstInfos
lstInfos.Items.Add(msg)
end sub
function selection(liste as ListControl) as string
' recorre los elementos de la lista
' para encontrar los que están seleccionados
dim i as integer
dim info as string=""
for i=0 to liste.Items.Count-1
if liste.Items(i).Selected then info+="[" + liste.Items(i).Text + "]"
next
return info
end function
Por último, al hacer clic en el enlace [Retour vers le formulaire ]:
Sub LinkButton1_Click(sender As Object, e As EventArgs)
' se muestra el formulario y se oculta el panel de información
panelform.visible=true
panelinfo.visible=false
End Sub
7.14.2. Ejemplo 2
Aquí retomamos una aplicación que ya se ha tratado anteriormente con formularios estándar HTML. La aplicación permite realizar simulaciones de cálculos de impuestos. Se basa en una clase [impot] que no volveremos a mencionar aquí. Esta clase necesita datos que encuentra en una fuente de datos OLEDB. Para el ejemplo, será una fuente ACCESS.
7.14.2.1. La estructura MVC de la aplicación
La estructura MVC de la aplicación es la siguiente:

Las tres vistas se incorporarán al código de presentación del controlador [main.aspx] en forma de contenedores. Por lo tanto, esta aplicación tiene una única página: [main.aspx].
7.14.2.2. Las vistas de la aplicación web
La vista [formulaire] es el formulario de ingreso de información que permite calcular el impuesto de un usuario:

El usuario llena el formulario:

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

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

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

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

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

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

7.14.2.3. El código de presentación de la aplicación
Recordemos que la página [main.aspx] reúne todas las vistas. Se trata de un único formulario con tres contenedores:
- [panelform] para la vista [formulaire]
- [panelerreurs] para la vista [erreurs]
- [panelsimulations] para la vista [simulations]
Volvemos a separar el código de presentación y el código de control en dos archivos distintos. El primero estará en [main.aspx] y el segundo en [main.aspx.vb]. El código de [main.aspx] es el siguiente:
<%@ page codebehind="main.aspx.vb" inherits="vs.main" AutoEventWireUp="false" %>
<HTML>
<HEAD>
<title>Calcul d'impôt </title>
</HEAD>
<body>
<P>Calcul de votre impôt</P>
<HR width="100%" SIZE="1">
<FORM id="Form1" runat="server">
<asp:panel id="panelform" Runat="server">
<TABLE id="Table1" cellSpacing="1" cellPadding="1" border="0">
<TR>
<TD height="19">Etes-vous marié(e)</TD>
<TD height="19">
<asp:RadioButton id="rdOui" runat="server" GroupName="rdMarie"></asp:RadioButton>Oui
<asp:RadioButton id="rdNon" runat="server" GroupName="rdMarie" Checked="True"></asp:RadioButton>Non</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD>
<asp:TextBox id="txtEnfants" runat="server" MaxLength="3" Columns="3"></asp:TextBox></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD>
<asp:TextBox id="txtSalaire" runat="server" MaxLength="10" Columns="10"></asp:TextBox></TD>
</TR>
</TABLE>
<P>
<asp:Button id="btnCalculer" runat="server" Text="Calculer"></asp:Button>
<asp:Button id="btnEffacer" runat="server" Text="Effacer"></asp:Button></P>
</asp:panel>
<asp:panel id="panelerreurs" runat="server">
<P>Les erreurs suivantes se sont produites :</P>
<P>
<asp:Literal id="erreursHTML" runat="server"></asp:Literal></P>
<P></P>
<asp:LinkButton id="lnkForm1" runat="server">Retour au formulaire</asp:LinkButton>
</asp:panel>
<asp:panel id="panelsimulations" runat="server">
<P>
<TABLE>
<TR>
<TH>
Marié</TH>
<TH>
Enfants</TH>
<TH>
Salaire annuel</TH>
<TH>
Impôt à payer (euro)</TH></TR>
<asp:Literal id="simulationsHTML" runat="server"></asp:Literal></TABLE>
<asp:LinkButton id="lnkForm2" runat="server">Retour au formulaire</asp:LinkButton></P>
</asp:panel>
</FORM>
</body>
</HTML>
Hemos delimitado los tres contenedores. Cabe señalar que todos se encuentran dentro de la etiqueta <form runat="server">. Esto es obligatorio, ya que para poder aprovechar las ventajas de los componentes de servidor, estos deben colocarse dentro de dicha etiqueta. Lo importante que hay que entender es que aquí tenemos un único formulario que se intercambiará entre el cliente y el servidor web. Por lo tanto, nos encontramos exactamente en la configuración utilizada a lo largo de todo este capítulo sobre los componentes de servidor. Detallamos a continuación los componentes de cada contenedor:
Contenedor [panelform]:
nombre | tipo | propiedades | función |
Panel | EnableViewState=true | vista de formulario | |
RadioButton | EnableViewState=true GroupName=rdmarie | botones de opción | |
TextBox | EnableViewState=true | número de hijos | |
TextBox | EnableViewState=true | salario anual | |
Botón | Botón [submit] del formulario: inicia el cálculo del impuesto | ||
Botón | Botón [submit] del formulario: borra el formulario |
Contenedor [panelerreurs]:
nombre | tipo | propiedades | función |
Panel | EnableViewState=true | vista de errores | |
LinkButton | EnableViewState=true | enlace al formulario | |
Literal | código HTML de la lista de errores |
Contenedor [panelsimulations]:
nombre | tipo | propiedades | función |
Panel | EnableViewState=true | vista de simulaciones | |
LinkButton | EnableViewState=true | enlace al formulario | |
Literal | código HTML de la lista de simulaciones en una tabla HTML |
7.14.2.4. El código de control de la aplicación
El código de control de la aplicación se encuentra distribuido en los archivos [global.asax.vb] y [main.aspx.vb]. El archivo [global.asax] se define de la siguiente manera:
El archivo [global.asax.vb] es el siguiente:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Collections
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' se crea un objeto de importación
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' se agrega el objeto a la aplicación
Application("objImpot") = objImpot
' sin errores
Application("erreur") = False
Catch ex As Exception
'se produjo un error; se registra en la aplicación
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' Inicio de sesión: se crea una lista de simulaciones vacía
Session.Item("simulations") = New ArrayList
End Sub
End Class
Cuando se inicia la aplicación (primera solicitud a la aplicación), se ejecuta el procedimiento [Application_Start]. Este procedimiento busca crear un objeto de tipo [impot] utilizando datos de una fuente OLEDB. Se recomienda al lector que consulte el capítulo 5, donde se definió esta clase, en caso de que la haya olvidado. La creación del objeto [impot] puede fallar si la fuente de datos no está disponible. En ese caso, el error se almacena en la aplicación para que todas las consultas posteriores sepan que este objeto no pudo inicializarse correctamente. Si la creación se realiza sin problemas, el objeto [impot] creado también se almacena en la aplicación. Será utilizado por todas las consultas de cálculo de impuestos. Cuando un cliente realiza su primera consulta, el procedimiento [Application_Start] crea una sesión para él. Esta sesión está destinada a almacenar las diferentes simulaciones de cálculo de impuestos que realizará. Estas se almacenarán en un objeto [ArrayList] asociado a la clave de sesión «simulaciones». Al iniciar la sesión, esta clave se asocia a un objeto [ArrayList] vacío. La información necesaria para la aplicación se coloca en su archivo de configuración [wenConfig]:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="chaineConnexion" value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\webforms\vs\impots5\impots.mdb" />
</appSettings>
</configuration>
La clave [chaineConnexion] identifica la cadena de conexión a la fuente OLEDB. La otra parte del código de control se encuentra en [main.aspx.vb]:
Imports System.Collections
Imports Microsoft.VisualBasic
Imports st.istia.univangers.fr
Imports System
Public Class main
Inherits System.Web.UI.Page
Protected WithEvents rdOui As System.Web.UI.WebControls.RadioButton
Protected WithEvents rdNon As System.Web.UI.WebControls.RadioButton
Protected WithEvents txtEnfants As System.Web.UI.WebControls.TextBox
Protected WithEvents txtSalaire As System.Web.UI.WebControls.TextBox
Protected WithEvents btnCalculer As System.Web.UI.WebControls.Button
Protected WithEvents btnEffacer As System.Web.UI.WebControls.Button
Protected WithEvents panelform As System.Web.UI.WebControls.Panel
Protected WithEvents lnkForm1 As System.Web.UI.WebControls.LinkButton
Protected WithEvents lnkForm2 As System.Web.UI.WebControls.LinkButton
Protected WithEvents panelerreurs As System.Web.UI.WebControls.Panel
Protected WithEvents panelsimulations As System.Web.UI.WebControls.Panel
Protected WithEvents simulationsHTML As System.Web.UI.WebControls.Literal
Protected WithEvents erreursHTML As System.Web.UI.WebControls.Literal
' variables locales
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub afficheFormulaire()
...
End Sub
Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
...
End Sub
Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
...
End Sub
Private Function checkData() As ArrayList
...
End Function
Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
....
End Sub
Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
...
End Sub
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
...
End Sub
Private Sub razForm()
...
End Sub
End Class
Le premier événement traité par le code est [Page_Load] :
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' En primer lugar, se revisa el estado de la aplicación
If CType(Application("erreur"), Boolean) Then
' la aplicación no pudo inicializarse
' se muestra la vista de errores
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
afficheErreurs(erreurs, "")
Exit Sub
End If
' No hay errores: en la primera solicitud, se muestra el formulario
If Not IsPostBack Then afficheFormulaire()
End Sub
Recordemos que cuando el procedimiento [Page_Load] se ejecuta en un cliente POST, todos los componentes del formulario tienen un valor: ya sea el valor enviado por el cliente, si lo hay, o el valor anterior del componente gracias al [VIEWSTATE]. En este formulario, todos los componentes tienen la propiedad [EnableViewState=true]. Antes de comenzar a procesar la solicitud, nos aseguramos de que la aplicación se haya inicializado correctamente. Si no es así, mostramos la vista [erreurs] mediante el procedimiento [afficheErreurs]. Si se trata de la primera consulta (IsPostBack=false), mostramos la vista [formulaire] con [afficheFormulaire].
El procedimiento que muestra la vista [erreurs] es el siguiente:
Private Sub afficheErreurs(ByRef erreurs As ArrayList, ByRef lien As String)
' se muestra el contenedor de errores
panelerreurs.Visible = True
Dim i As Integer
erreursHTML.Text = ""
For i = 0 To erreurs.Count - 1
erreursHTML.Text += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
Next
lnkForm1.Text = lien
' los demás contenedores están ocultos
panelform.Visible = False
panelsimulations.Visible = False
End Sub
El procedimiento tiene dos parámetros:
- una lista de mensajes de error en [erreurs]
- un texto de enlace en [lien]
El código HTML que se generará para la lista de errores se coloca en el literal [erreursHTML]. El texto del enlace, por su parte, se coloca en la propiedad [Text] del objeto [LinkButton] de la vista.
El procedimiento que muestra la vista [formulaire] es el siguiente:
Private Sub afficheFormulaire()
' muestra el formulario
panelform.Visible = True
' los demás contenedores están ocultos
panelerreurs.Visible = False
panelsimulations.Visible = False
End Sub
Este procedimiento simplemente hace visible el contenedor [panelform]. Los componentes se muestran con su valor actualizado o anterior (VIEWSTATE).
Cuando el usuario hace clic en el botón [Calculer] de la vista [formulaire], se realiza un POST hacia [main.aspx]. Se ejecuta el procedimiento [Page_Load] y, a continuación, el procedimiento [btnCalculer_Click]:
Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
' se verifica la validez de los datos ingresados
Dim erreurs As ArrayList = checkData()
' si hay errores, se reportan
If erreurs.Count <> 0 Then
' se muestra la página de errores
afficheErreurs(erreurs, "Retour au formulaire")
Exit Sub
End If
' sin errores: se calcula el impuesto
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
' se agrega el resultado a las simulaciones existentes
Dim simulation() As String = New String() {CType(IIf(rdOui.Checked, "oui", "non"), String), _
txtEnfants.Text.Trim, txtSalaire.Text.Trim, impot.ToString}
' se agrega el resultado a las simulaciones existentes
Dim simulations As ArrayList = CType(Session.Item("simulations"), ArrayList)
simulations.Add(simulation)
' se colocan las simulaciones en la sesión y el contexto
Session.Item("simulations") = simulations
' se muestra la página de resultados
afficheSimulations(simulations, "Retour au formulaire")
End Sub
El procedimiento comienza verificando la validez de los campos del formulario mediante el procedimiento [checkData], el cual devuelve una lista [ArrayList] de mensajes de error. Si la lista no está vacía, se muestra la vista [erreurs] y el procedimiento finaliza. Si los datos ingresados son válidos, se calcula el monto del impuesto mediante el objeto de tipo [impot] que se había almacenado en la aplicación al iniciarla. Esta nueva simulación se agrega a la lista de simulaciones ya realizadas y se almacena en la sesión.
La función [CheckData] verifica la validez de los datos. Devuelve una lista [ArrayList] de mensajes de error, que estará vacía si los datos son válidos:
Private Function checkData() As ArrayList
' Al inicio no hay errores
Dim erreurs As New ArrayList
' número de hijos
Try
Dim nbEnfants As Integer = CType(txtEnfants.Text, Integer)
If nbEnfants < 0 Then Throw New Exception
Catch
erreurs.Add("Le nombre d'enfants est incorrect")
End Try
' salario
Try
Dim salaire As Long = CType(txtSalaire.Text, Long)
If salaire < 0 Then Throw New Exception
Catch
erreurs.Add("Le salaire annuel est incorrect")
End Try
' se muestra la lista de errores
Return erreurs
End Function
Finalmente, la vista [simulations] se muestra mediante el siguiente procedimiento [afficheSimulations]:
Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
' muestra la vista de simulaciones
panelsimulations.Visible = True
' los demás contenedores están ocultos
panelerreurs.Visible = False
panelform.Visible = False
' contenido de la vista de simulaciones
' cada simulación es una matriz de 4 elementos de tipo cadena
Dim simulation() As String
Dim i, j As Integer
simulationsHTML.Text = ""
For i = 0 To simulations.Count - 1
simulation = CType(simulations(i), String())
simulationsHTML.Text += "<tr>"
For j = 0 To simulation.Length - 1
simulationsHTML.Text += "<td>" + simulation(j) + "</td>"
Next
simulationsHTML.Text += "</tr>" + ControlChars.CrLf
Next
' enlace
lnkForm2.Text = lien
End Sub
El procedimiento tiene dos parámetros:
- una lista de simulaciones en [simulations]
- un texto de enlace en [lien]
El código HTML que se generará para la lista de simulaciones se coloca en el literal [simulationsHTML]. El texto del enlace, por su parte, se coloca en la propiedad [Text] del objeto [LinkButton] de la vista.
Cuando el usuario hace clic en el botón [Effacer] de la vista [formulaire], se ejecuta el procedimiento [btnEffacer_click] (siempre después de [Page_Load]):
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
' muestra el formulario en blanco
razForm()
afficheFormulaire()
End Sub
Private Sub razForm()
' borra el formulario
rdOui.Checked = False
rdNon.Checked = True
txtEnfants.Text = ""
txtSalaire.Text = ""
End Sub
El código anterior es lo suficientemente sencillo como para no necesitar comentarios. Ahora nos queda gestionar el clic en los enlaces de las vistas [erreurs] y [simulations]:
Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
' muestra el formulario
afficheFormulaire()
End Sub
Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
' muestra el formulario
afficheFormulaire()
End Sub
Ambos procedimientos se limitan a mostrar la vista [formulaire]. Sabemos que los campos de esta vista obtendrán un valor que será, o bien el valor enviado para ellos, o bien su valor anterior. Como en este caso, el POST del cliente no envía ningún valor para los campos del formulario, estos recuperarán su valor anterior. Por lo tanto, el formulario se muestra con los valores ingresados por el usuario. Recordamos que, en la versión sin componentes de servidor, habíamos realizado esta tarea de restauración nosotros mismos.
7.14.2.5. Pruebas
Todos los archivos necesarios para la aplicación se encuentran en una carpeta <application-path>: ![]() | La carpeta [bin] contiene el archivo DLL con las clases [impot], [impotsData] y [impotsOLEDB] necesarias para la aplicación: |
El lector puede, si lo desea, volver a consultar el capítulo 5, donde se explica cómo crear el archivo [impot.dll] mencionado anteriormente. Una vez hecho esto, se inicia el servidor Cassini con los parámetros (<ruta-de-la-aplicación>,/impots5). Se solicita la URL [http://impots5/main.aspx] con un navegador:

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

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

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

El controlador [main.aspx] actúa como controlador principal. Es a él a quien llaman los enlaces de la página de inicio de la aplicación. Este podrá realizar operaciones comunes a todas las acciones posibles y, a continuación, ejecutará la acción específica asociada al enlace utilizado. Luego, le cederá el control a uno de los controladores secundarios, el encargado de ejecutar la acción. A partir de ese momento, las comunicaciones se realizan entre el cliente y ese controlador específico. Ya no se pasa por el controlador principal [main.aspx]. Por lo tanto, ya no nos encontramos en el marco MVC con un único controlador que filtra todas las solicitudes. Cada uno de los controladores mencionados anteriormente puede presentar varias vistas mediante el mecanismo de contenedores dentro de una sola página, tal como lo hemos explicado.
El hecho de que ya no haya un único controlador que elija las vistas que se enviarán al cliente presenta algunas desventajas. Tomemos como ejemplo el manejo de errores. Cada una de las acciones expuestas por la aplicación puede llegar a mostrar una vista de errores. Cada controlador [applix.aspx] tendrá su propia vista [erreurs], ya que esta es simplemente un contenedor específico de la página del controlador. No hay forma de tener una vista única [erreurs] que sea utilizada por todas las aplicaciones individuales. De hecho, dicha vista suele incluir un enlace de regreso al formulario con errores, y este debe restaurarse al estado en el que fue validado para que el usuario pueda corregir sus errores. Esta restauración se realiza mediante el mecanismo del [VIEWSTATE], que no funciona entre controladores diferentes. Si las aplicaciones son desarrolladas por diferentes personas, existe el riesgo de que las páginas de error tengan un aspecto diferente según la acción elegida por el usuario, lo que afecta la uniformidad de la aplicación en su conjunto. Veremos más adelante que ASP.NET ofrece una solución a este problema específico de la vista compartida. Esta solución puede ser objeto de un nuevo componente de servidor que construiremos nosotros mismos. Basta con utilizar este componente en las diferentes aplicaciones para garantizar la homogeneidad de la aplicación global. Más difícil de manejar es el problema del orden de las acciones. Cuando todas las solicitudes pasan por un único controlador, este puede verificar que la acción solicitada sea compatible con la anterior. Este código de control se encuentra en un solo lugar. Aquí, habrá que distribuirlo entre los distintos controladores, lo que complica el mantenimiento de la aplicación global.
Volvamos a nuestra aplicación anterior. Su página de inicio es una página clásica HTML:
<html>
<head>
<TITLE>Composants ASP Serveur</TITLE>
<meta name="pragma" content="no-cache">
</head>
<frameset rows="130,*" frameborder="0">
<frame name="banner" src="bandeau.htm" scrolling="no">
<frameset cols="200,*">
<frame name="contents" src="options.htm">
<frame name="main" src="main.htm">
</frameset>
<noframes>
<p id="p1">
Ce jeu de frames HTML affiche plusieurs pages Web. Pour afficher ce jeu de
frames, utilisez un navigateur Web qui prend en charge HTML 4.0 et version
ultérieure.
</p>
</noframes>
</frameset>
</html>
Esta página de inicio consta de tres marcos llamados «banner», «contents» y «main»:
![]() |
La página [bandeau.htm], ubicada en el marco [banner], es la siguiente:

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:
![]() | |
Todos los enlaces apuntan al controlador principal [main.aspx] con un parámetro [action] que indica la acción a realizar. Se solicita que el destino de los enlaces se muestre en el marco [main] (target="main").
La primera página que se muestra en el marco [main] es [main.htm]:
|
El controlador principal [main.aspx, main.aspx.vb] es el siguiente:
[main.aspx]
[main.aspx.vb]
Clase pública main
Heredado de System.Web.UI.Page
Sub privada Page_Load(ByVal remitente como System.Object, ByVal e como System.EventArgs) Con este identificador MyBase.Load
' se recupera la acción a realizar
Dim acción As String
Si Request.QueryString("action") es Nothing, entonces
acción = "label"
Else
acción = Request.QueryString("acción").ToString.ToLower
End If
' se ejecuta la acción
Select Case acción
Case "label"
Server.Transfer("form2.aspx")
Case "botón"
Server.Transfer("form3.aspx")
Case "cuadro de texto1"
Server.Transfer("form4.aspx")
Case "textbox2"
Server.Transfer("form5.aspx")
Caso «lista desplegable»
Server.Transfer("form6.aspx")
Campo «listbox»
Server.Transfer("form7.aspx")
Casilla «casilla de selección»
Server.Transfer("form8.aspx")
Caso «lista de casillas de selección»
Server.Transfer("form8b.aspx")
Casilla «panel»
Server.Transfer("form9.aspx")
Caso Else
Server.Transfer("form2.aspx")
Fin de la selección
Fin de Sub
Fin de clase
Nuestro controlador es sencillo. Según el valor del parámetro [action], transfiere el procesamiento de la solicitud a la página adecuada. No aporta ningún valor agregado en comparación con una página HTML con enlaces. Sin embargo, bastaría con agregar una página de autenticación para apreciar su utilidad. Si el usuario tuviera que autenticarse (nombre de usuario, contraseña) para acceder a las aplicaciones, el controlador [main.aspx] sería un buen lugar para verificar que se haya realizado dicha autenticación.




