Presentación del tutorial de ASP

318
Presentación del tutorial de ASP.NET El tutorial de ASP.NET comprende un conjunto de ejemplos y comentarios relacionados acerca de ASP.NET, diseñado para explicar rápidamente a los programadores la sintaxis, la arquitectura y la eficacia del marco de programación para aplicaciones Web de ASP.NET. Los ejemplos del tutorial se diseñaron en forma de ilustraciones breves y fáciles de comprender de las características de ASP.NET. Cuando haya completado el tutorial, estará familiarizado con: La sintaxis de ASP.NET. Aunque algunos de los elementos de la sintaxis de ASP.NET serán familiares para los programadores veteranos de ASP, otros son exclusivos del nuevo marco de trabajo. Los ejemplos del tutorial describen cada elemento en detalle. La arquitectura y las características de ASP.NET. En el tutorial se describen las características de ASP.NET, que permiten a los programadores crear aplicaciones interactivas de primer nivel con mucho menos esfuerzo que antes. Los mejores procedimientos. Los ejemplos del tutorial explican las mejores formas de utilizar la eficacia de ASP.NET y la forma de evitar posibles errores al hacerlo. Nivel de conocimientos necesario para poder seguir el tutorial Si no tiene experiencia en programación de páginas, este tutorial no es lo más aconsejable. Debe tener conocimientos acerca de HTML y de terminología general de programación Web. No necesita experiencia previa en ASP, pero debe conocer los conceptos básicos relacionados con páginas Web interactivas, como "formulario", "secuencia de comando" y "acceso a datos". Trabajar con los ejemplos del tutorial Es mejor estudiar los ejemplos del tutorial en el orden en que se presentan. Cada ejemplo se basa en conceptos tratados en el ejemplo anterior. La serie empieza con el envío de un formulario sencillo y continúa con más ejemplos hasta desarrollar escenarios de aplicación integrados. ¿Qué es ASP.NET? ASP.NET es un marco de trabajo de programación generado en Common Language Runtime que puede utilizarse en un servidor para generar eficaces aplicaciones Web. ASP.NET ofrece varias ventajas importantes acerca de los modelos de programación Web anteriores: Mejor rendimiento. ASP.NET es un código de Common Language Runtime compilado que se ejecuta en el servidor. A diferencia de sus predecesores, ASP.NET puede aprovechar las ventajas del enlace anticipado, la compilación just-in-time, la optimización nativa y los servicios de caché desde el primer momento. Esto supone un incremento espectacular del rendimiento antes de siquiera escribir una línea de código. Compatibilidad con herramientas de primer nivel. El marco de trabajo de ASP.NET se complementa con un diseñador y una caja de herramientas muy completos en el entorno integrado de programación (Integrated Development Environment, IDE) de Visual Studio. La edición WYSIWYG, los controles de servidor de arrastrar y colocar y la implementación automática son sólo algunas de las características que proporciona esta eficaz herramienta. Eficacia y flexibilidad. Debido a que ASP.NET se basa en Common Language Runtime, la eficacia y la flexibilidad de toda esa plataforma se encuentra disponible para los programadores de aplicaciones Web. La biblioteca de clases de .NET Framework, la Mensajería y las soluciones de Acceso a datos se encuentran accesibles desde el Web de manera uniforme. ASP.NET es también independiente del lenguaje, por lo que puede elegir el lenguaje que mejor se adapte a la aplicación o dividir la aplicación en varios lenguajes. Además, la interoperabilidad de Common Language Runtime garantiza que la inversión existente en programación basada en COM se conserva al migrar a ASP.NET. Simplicidad. ASP.NET facilita la realización de tareas comunes, desde el sencillo envío de formularios y la autenticación del cliente hasta la implementación y la configuración de sitios. Por ejemplo, el marco de trabajo de página de ASP.NET permite generar interfaces de usuario, que separan claramente la lógica de aplicación del código de presentación, y controlar eventos en un sencillo modelo de procesamiento de formularios de tipo Visual Basic. Además, Common Language Runtime simplifica la programación, con servicios de código administrado como el recuento de referencia automático y el recolector de elementos no utilizados. Facilidad de uso. ASP.NET emplea un sistema de configuración jerárquico, basado en texto, que simplifica la aplicación de la configuración al entorno de servidor y las aplicaciones Web. Debido a que la información de configuración se almacena como texto sin formato, se puede

Transcript of Presentación del tutorial de ASP

Page 1: Presentación del tutorial de ASP

Presentación del tutorial de ASP.NET

El tutorial de ASP.NET comprende un conjunto de ejemplos y comentarios relacionados acerca de ASP.NET, diseñado para explicar rápidamente a los programadores la sintaxis, la arquitectura y la eficacia del marco de programación para aplicaciones Web de ASP.NET. Los ejemplos del tutorial se diseñaron en forma de ilustraciones breves y fáciles de comprender de las características de ASP.NET. Cuando haya completado el tutorial, estará familiarizado con:

La sintaxis de ASP.NET. Aunque algunos de los elementos de la sintaxis de ASP.NET serán familiares para los programadores veteranos de ASP, otros son exclusivos del nuevo marco de trabajo. Los ejemplos del tutorial describen cada elemento en detalle.

La arquitectura y las características de ASP.NET. En el tutorial se describen las características de ASP.NET, que permiten a los programadores crear aplicaciones interactivas de primer nivel con mucho menos esfuerzo que antes.

Los mejores procedimientos. Los ejemplos del tutorial explican las mejores formas de utilizar la eficacia de ASP.NET y la forma de evitar posibles errores al hacerlo.

Nivel de conocimientos necesario para poder seguir el tutorialSi no tiene experiencia en programación de páginas, este tutorial no es lo más aconsejable. Debe tener conocimientos acerca de HTML y de terminología general de programación Web. No necesita experiencia previa en ASP, pero debe conocer los conceptos básicos relacionados con páginas Web interactivas, como "formulario", "secuencia de comando" y "acceso a datos".

Trabajar con los ejemplos del tutorialEs mejor estudiar los ejemplos del tutorial en el orden en que se presentan. Cada ejemplo se basa en conceptos tratados en el ejemplo anterior. La serie empieza con el envío de un formulario sencillo y continúa con más ejemplos hasta desarrollar escenarios de aplicación integrados.

¿Qué es ASP.NET?

ASP.NET es un marco de trabajo de programación generado en Common Language Runtime que puede utilizarse en un servidor para generar eficaces aplicaciones Web. ASP.NET ofrece varias ventajas importantes acerca de los modelos de programación Web anteriores:

Mejor rendimiento. ASP.NET es un código de Common Language Runtime compilado que se ejecuta en el servidor. A diferencia de sus predecesores, ASP.NET puede aprovechar las ventajas del enlace anticipado, la compilación just-in-time, la optimización nativa y los servicios de caché desde el primer momento. Esto supone un incremento espectacular del rendimiento antes de siquiera escribir una línea de código.

Compatibilidad con herramientas de primer nivel. El marco de trabajo de ASP.NET se complementa con un diseñador y una caja de herramientas muy completos en el entorno integrado de programación (Integrated Development Environment, IDE) de Visual Studio. La edición WYSIWYG, los controles de servidor de arrastrar y colocar y la implementación automática son sólo algunas de las características que proporciona esta eficaz herramienta.

Eficacia y flexibilidad. Debido a que ASP.NET se basa en Common Language Runtime, la eficacia y la flexibilidad de toda esa plataforma se encuentra disponible para los programadores de aplicaciones Web. La biblioteca de clases de .NET Framework, la Mensajería y las soluciones de Acceso a datos se encuentran accesibles desde el Web de manera uniforme. ASP.NET es también independiente del lenguaje, por lo que puede elegir el lenguaje que mejor se adapte a la aplicación o dividir la aplicación en varios lenguajes. Además, la interoperabilidad de Common Language Runtime garantiza que la inversión existente en programación basada en COM se conserva al migrar a ASP.NET.

Simplicidad. ASP.NET facilita la realización de tareas comunes, desde el sencillo envío de formularios y la autenticación del cliente hasta la implementación y la configuración de sitios. Por ejemplo, el marco de trabajo de página de ASP.NET permite generar interfaces de usuario, que separan claramente la lógica de aplicación del código de presentación, y controlar eventos en un sencillo modelo de procesamiento de formularios de tipo Visual Basic. Además, Common Language Runtime simplifica la programación, con servicios de código administrado como el recuento de referencia automático y el recolector de elementos no utilizados.

Facilidad de uso. ASP.NET emplea un sistema de configuración jerárquico, basado en texto, que simplifica la aplicación de la configuración al entorno de servidor y las aplicaciones Web. Debido a que la información de configuración se almacena como texto sin formato, se puede aplicar la nueva configuración sin la ayuda de herramientas de administración local. Esta filosofía de "administración local cero" se extiende asimismo a la implementación de las aplicaciones ASP.NET Framework. Una aplicación ASP.NET Framework se implementa en un servidor sencillamente mediante la copia de los archivos necesarios al servidor. No se requiere el reinicio del servidor, ni siquiera para implementar o reemplazar el código compilado en ejecución.

Escalabilidad y disponibilidad. ASP.NET se ha diseñado teniendo en cuenta la escalabilidad, con características diseñadas específicamente a medida, con el fin de mejorar el rendimiento en entornos agrupados y de múltiples procesadores. Además, el motor de tiempo de ejecución de ASP.NET controla y administra los procesos de cerca, por lo que si uno no se comporta adecuadamente (filtraciones, bloqueos), se puede crear un proceso nuevo en su lugar, lo que ayuda a mantener la aplicación disponible constantemente para controlar solicitudes.

Posibilidad de personalización y extensibilidad. ASP.NET presenta una arquitectura bien diseñada que permite a los programadores insertar su código en el nivel adecuado. De hecho, es posible extender o reemplazar cualquier subcomponente del motor de tiempo de ejecución de ASP.NET con su propio componente escrito personalizado. La implementación de la autenticación personalizada o de los servicios de estado nunca ha sido más fácil.

Seguridad. Con la autenticación de Windows integrada y la configuración por aplicación, se puede tener la completa seguridad de que las aplicaciones están a salvo.

El resto del tutorial presenta ejemplos prácticos de estos conceptos.

Compatibilidad de lenguajes

Page 2: Presentación del tutorial de ASP

La Plataforma Microsoft .NET ofrece actualmente compatibilidad integrada para tres lenguajes: C#, Visual Basic y JScript.

En los ejercicios y los ejemplos de código de este tutorial se muestra cómo utilizar C#, Visual Basic y JScript para generar aplicaciones .NET. Para obtener más información sobre la sintaxis de los demás lenguajes, consulte la documentación completa del Kit de desarrollo de software (SDK) de .NET Framework.

La siguiente tabla ayuda a comprender los ejemplos de código de este tutorial así como las diferencias entre los tres lenguajes:

Declaraciones de variables

Dim x As IntegerDim s As StringDim s1, s2 As StringDim o 'Implicitly ObjectDim obj As New Object()Public name As String

Instrucciones

Response.Write("foo")

Comentarios

' This is a comment

' This' is' a' multiline' comment

Obtener acceso a propiedades indizadas

Dim s, value As Strings = Request.QueryString("Name")value = Request.Cookies("Key").Value'Note that default non-indexed properties'must be explicitly named in VB

Declarar propiedades indizadas

' Default Indexed PropertyPublic Default ReadOnly Property DefaultProperty(Name As String) As String Get Return CStr(lookuptable(name)) End GetEnd Property

Declarar propiedades sencillas

Public Property Name As String

Get ...

Page 3: Presentación del tutorial de ASP

Return ... End Get

Set ... = Value End Set

End Property

Declarar y utilizar una enumeración

' Declare the EnumerationPublic Enum MessageSize

Small = 0 Medium = 1 Large = 2End Enum

' Create a Field or PropertyPublic MsgSize As MessageSize

' Assign to the property using the Enumeration valuesMsgSize = small

Enumerar una colección

Dim S As StringFor Each S In Coll ...Next

Declarar y utilizar métodos

' Declare a void return functionSub VoidFunction() ...End Sub

' Declare a function that returns a valueFunction StringFunction() As String ... Return CStr(val)End Function

' Declare a function that takes and returns valuesFunction ParmFunction(a As String, b As String) As String ... Return CStr(A & B)End Function

' Use the FunctionsVoidFunction()Dim s1 As String = StringFunction()Dim s2 As String = ParmFunction("Hello", "World!")

Page 4: Presentación del tutorial de ASP

Atributos personalizados

' Stand-alone attribute<STAThread>

' Attribute with parameters<DllImport("ADVAPI32.DLL")>

' Attribute with named parameters<DllImport("KERNEL32.DLL", CharSet:=CharSet.Auto)>

Matrices

Dim a(2) As String a(0) = "1" a(1) = "2" a(2) = "3" Dim a(2,2) As String a(0,0) = "1" a(1,0) = "2" a(2,0) = "3"

Inicialización

Dim s As String = "Hello World"Dim i As Integer = 1Dim a() As Double = { 3.00, 4.00, 5.00 }

Instrucciones if

If Not (Request.QueryString = Nothing) ...End If

Instrucciones case

Select Case FirstName Case "John" ... Case "Paul" ... Case "Ringo" ... Case Else ...End Select

Bucles for

Dim I As Integer For I = 0 To 2 a(I) = "test" Next

Bucles while

Dim I As IntegerI = 0Do While I < 3 Console.WriteLine(I.ToString())

Page 5: Presentación del tutorial de ASP

I += 1Loop

Control de excepciones

Try ' Code that throws exceptionsCatch E As OverflowException ' Catch a specific exceptionCatch E As Exception ' Catch the generic exceptionsFinally ' Execute some cleanup codeEnd Try

Concatenación de cadenas

' Using StringsDim s1, s2 As Strings2 = "hello"s2 &= " world"s1 = s2 & " !!!"' Using StringBuilder class for performanceDim s3 As New StringBuilder()s3.Append("hello")s3.Append(" world")s3.Append(" !!!")

Delegados de controlador de eventos

Sub MyButton_Click(Sender As Object, E As EventArgs)...End Sub

Declarar eventos

' Create a public eventPublic Event MyEvent(Sender as Object, E as EventArgs)' Create a method for firing the eventProtected Sub OnMyEvent(E As EventArgs) RaiseEvent MyEvent(Me, E)End Sub

Agregar o quitar controladores de eventos en eventos

AddHandler Control.Change, AddressOf Me.ChangeEventHandlerRemoveHandler Control.Change, AddressOf Me.ChangeEventHandler

Conversión de tipos

Dim obj As MyObjectDim iObj As IMyObjectobj = Session("Some Value")iObj = CType(obj, IMyObject)

Conversión

Dim i As IntegerDim s As String

Page 6: Presentación del tutorial de ASP

Dim d As Doublei = 3s = i.ToString()d = CDbl(s)

' See also CDbl(...), CStr(...), ...

Definición de clase con herencia

Imports System

Namespace MySpace

Public Class Foo : Inherits Bar

Dim x As Integer

Public Sub New() MyBase.New() x = 4 End Sub

Public Sub Add(x As Integer) Me.x = Me.x + x End Sub

Overrides Public Function GetNum() As Integer Return x End Function

End Class

End Namespace

' vbc /out:libraryvb.dll /t:library' library.vb

Implementar una interfaz

Public Class MyClass : Implements IEnumerable ...

Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator ... End FunctionEnd Class

Definición de clase con un método principal

Imports System

Public Class ConsoleVB

Public Sub New() MyBase.New() Console.WriteLine("Object Created") End Sub

Public Shared Sub Main() Console.WriteLine("Hello World") Dim cvb As New ConsoleVB

Page 7: Presentación del tutorial de ASP

End Sub

End Class

' vbc /out:consolevb.exe /t:exe console.vb

Módulo estándar

Imports System

Public Module ConsoleVB

Public Sub Main() Console.WriteLine("Hello World") End Sub

End Module

' vbc /out:consolevb.exe /t:exe console.vb

Web Forms ASP.NET Presentación de formularios Web¿Qué son los formularios Web de ASP.NET?

El marco de trabajo de la página de formularios Web de ASP.NET es un modelo de programación escalable de Common Language Runtime que puede utilizarse en el servidor para generar páginas Web dinámicamente.

Concebido como una evolución lógica de ASP (ASP.NET proporciona compatibilidad sintáctica con las páginas existentes), el marco de trabajo de formularios Web ASP.NET se ha diseñado específicamente para tratar varias deficiencias clave del modelo anterior. En particular, proporciona:

Capacidad para crear y utilizar controles de la interfaz de usuario reutilizables que puedan encapsular funcionalidades comunes y, así, reducir la cantidad de código que tiene que escribir el programador de una página.

Capacidad para que los programadores puedan estructurar limpiamente la lógica de la página de forma ordenada (no revuelta).

Capacidad para que las herramientas de desarrollo proporcionen un fuerte soporte de diseño WYSIWYG (Lo que ve es lo que se imprime) a las páginas (el código ASP existente es opaco para las herramientas).

Es esta sección se proporciona un recorrido con código de gran nivel por algunas de las funciones claves de formularios Web de ASP.NET. En las siguientes secciones, se profundizará en detalles más concretos.

Escribir la página de formularios Web

Las páginas de formularios Web de ASP.NET consisten en archivos de texto con una extensión de nombre de archivo .aspx. Pueden implementarse por todo un árbol de directorio raíz virtual IIS. Cuando un explorador cliente solicita recursos .aspx, el motor en tiempo de ejecución de ASP.NET analiza y compila el archivo de destino en una clase de .NET Framework. Esta clase puede utilizarse, a continuación, para procesar de forma dinámica las solicitudes entrantes. (Debe observarse que el archivo .aspx sólo se compila la primera que se tiene acceso al mismo; la instancia de tipo compilada se vuelve a utilizar en múltiples solicitudes).

Una página de ASP.NET puede crearse tomando simplemente un archivo HTML existente y cambiando la extensión del nombre de archivo a .aspx (no se necesita ninguna modificación del código). En el siguiente ejemplo se muestra una página HTML sencilla que recoge una preferencia de categoría y un nombre de usuario, y a continuación realiza una devolución de formulario a la página de origen cuando se hace clic sobre el botón:

<html> <head>

<link rel="stylesheet" href="intro.css"> </head>

<body>

<center>

Page 8: Presentación del tutorial de ASP

<form action="intro1.aspx" method="post">

<h3> Nombre: <input id="Name" type=text>

Categoría: <select id="Category" size=1> <option>psychology</option> <option>business</option> <option>popular_comp</option> </select>

<input type=submit value="Búsqueda"> </h3> </form>

</center>

</body></html>

Importante: Debe observarse que no sucede nada cuando se hace clic en el botón Lookup. Esto sucede porque el archivo .aspx sólo contiene HTML estático (sin contenido dinámico). Así pues, el mismo HTML se envía de vuelta al cliente en cada viaje a la página, lo que conlleva una pérdida de los contenidos de los campos del formulario (el cuadro de texto y la lista desplegable) entre solicitudes.

Utilizar bloques de representación ASP <%%>

ASP.NET proporciona compatibilidad sintáctica con páginas ASP existentes. Esto incluye compatibilidad para bloques de representación de código <% %> que pueden entremezclarse con contenido HTML dentro de un archivo .aspx. Estos bloques de código se ejecutan de arriba a abajo en tiempo de representación de página.

En el siguiente ejemplo se muestra cómo se pueden utilizar bloques de representación <% %> para ascender en bucle por un bloque HTML (aumentando el tamaño de fuente cada vez):

<%@ Page Language="VB" %><html> <head>

<link rel="stylesheet"href="intro.css"> </head>

<body>

<center>

<form action="intro2.aspx" method="post">

<h3> Nombre: <input id="Nombre" type=text>

Categoría: <select id="Categoría" size=1> <option>psychology</option> <option>business</option> <option>popular_comp</option> </select>

</h3>

<input type=submit value="Búsqueda">

<p>

<% Dim I As Integer For I = 0 to 7 %> <font size="<%=I%>"> Bienvenido a ASP.NET </font> <br>

Page 9: Presentación del tutorial de ASP

<% Next %>

</form>

</center>

</body></html>

Importante: A diferencia de ASP, el código que se utiliza en los bloques <% %> anteriores realmente se compila, no se interpreta mediante un motor de secuencias de comandos. Esto produce un mejor rendimiento de la ejecución en tiempo de ejecución.

Los programadores de páginas ASP.NET pueden utilizar bloques de código <% %> para modificar dinámicamente resultados HTML más de lo que se puede actualmente con ASP. En el siguiente ejemplo se muestra cómo se pueden utilizar bloques de código <% %> para interpretar resultados devueltos desde un cliente.

<%@ Page Language="VB" %>

<html> <head>

<link rel="stylesheet"href="intro.css"> </head>

<body>

<center>

<form action="intro3.aspx">

<h3> Nombre: <input name="Nombre" type=text value="<%=Request.QueryString("Nombre")%>">

Categoría: <select name="Categoría" size=1>

<% Dim I As Integer Dim Values(2) As String Values(0) = "psychology" Values(1) = "business" Values(2) = "popular_comp"

For I = 0 To Values.Length - 1 %>

<% If (Request.QueryString("Categoría") = Values(i)) %> <option selected> <% Else %> <option> <% End If %> <%=Values(i)%> </option>

<% Next %>

</select> </h3>

<input type=submit name="Búsqueda" value="Búsqueda">

<p>

Page 10: Presentación del tutorial de ASP

<% If (Not Request.QueryString("Búsqueda") = Nothing) %>

Hola <%=Request.QueryString("Nombre") %>, ha seleccionado: <%=Request.QueryString("Categoría") %>

<% End If %>

</form>

</center>

</body></html>

Importante: Mientras que los bloques de código <% %> proporcionan una forma potente de manipular de forma personalizada el resultado de texto devuelto desde una página ASP.NET, no proporcionan un modelo de programación HTML limpio. Tal y como se muestra en el ejemplo anterior, los programadores que sólo utilizan bloques de código <% %> deben administrar de forma personalizada el estado de página entre acciones de ida y vuelta e interpretar valores expuestos.

Introducción a controles de servidor ASP.NET

Además de (o en vez de) utilizar bloques de código <% %> para programar contenido dinámico, los programadores de páginas ASP.NET pueden utilizar controles de servidor ASP.NET para programar páginas Web. Los controles de servidor se declaran dentro de un archivo .aspx mediante etiquetas personalizadas o etiquetas HTML intrínsecas que contienen un valor de atributo runat="server". Las etiquetas HTML intrínsecas las controla uno de los controles del espacio de nombres System.Web.UI.HtmlControls. A cualquier etiqueta que no esté explícitamente asignada a uno de los controles se le asigna el tipo de System.Web.UI.HtmlControls.HtmlGenericControl.

En el siguiente ejemplo se utilizan cuatro controles de servidor: <form runat=server>, <asp:textbox runat=server>, <asp:dropdownlist runat=server>, y <asp:button runat=server>. En tiempo de ejecución, estos controles de servidor generan contenido HTML automáticamente.

<html> <head>

<link rel="stylesheet"href="intro.css"> </head>

<body>

<center>

<form action="intro4.aspx" method="post" runat=server>

<h3> Nombre: <asp:textbox id="Name" runat="server"/>

Categoría: <asp:dropdownlist id="Category" runat=server> <asp:listitem >psychology</asp:listitem> <asp:listitem >business</asp:listitem> <asp:listitem >popular_comp</asp:listitem> </asp:dropdownlist> </h3>

<asp:button text="Búsqueda" runat="server"/>

</form>

</center>

</body></html>

Page 11: Presentación del tutorial de ASP

Importante: Debe tenerse en cuenta que estos controles de servidor mantienen automáticamente cualquier valor introducido por el cliente entre acciones de ida y vuelta al servidor. El estado del control no se almacena en el servidor, sino en un campo del formulario <input type="hidden"> que recibe acciones de ida y vuelta entre solicitudes. Hay que tener en cuenta que no se necesita ninguna secuencia de comando en el cliente.

Además de admitir controles estándar de entrada HTML, ASP.NET permite a los programadores utilizar controles personalizados enriquecidos en las páginas. En el siguiente ejemplo se demuestra cómo puede utilizarse el control <asp:adrotator> para mostrar dinámicamente en la página anuncios en rotación.

<html> <head>

<link rel="stylesheet"href="intro.css"> </head>

<body>

<center>

<form action="intro5.aspx" method="post" runat="server">

<asp:adrotator AdvertisementFile="ads.xml" BorderColor="black" BorderWidth=1 runat="server"/>

<h3> Nombre: <asp:textbox id="Name" runat="server"/>

Categoría: <asp:dropdownlist id="Category" runat=server> <asp:listitem >psychology</asp:listitem> <asp:listitem >business</asp:listitem> <asp:listitem >popular_comp</asp:listitem> </asp:dropdownlist> </h3>

<asp:button text="Búsqueda" runat="server"/>

</form>

</center>

</body></html>

Importante: Se puede encontrar un listado detallado de todos los controles de servidor integrados en la sección Referencia de controles de formularios Web de este tutorial.

Controlar eventos de controles de servidor

Cada control de servidor ASP.NET puede exponer un modelo de objeto con propiedades, métodos y eventos. Los programadores de ASP.NET pueden utilizar este modelo de objeto para modificar e interactuar limpiamente con la página.

En el siguiente ejemplo se muestra cómo un programador de páginas ASP.NET puede controlar el evento OnClick desde el control <asp:button runat=server> para manipular la propiedad Text del control <asp:label runat=server>.

<html> <head>

<link rel="stylesheet"href="intro.css"> </head>

Page 12: Presentación del tutorial de ASP

<script language="VB" runat=server>

Sub SubmitBtn_Click(Sender As Object, E As EventArgs) Message.Text = "Hola " & Name.Text & ", ha selecionado: " & Category.SelectedItem.Text End Sub

</script>

<body>

<center>

<form action="intro6.aspx" method="post" runat="server">

<asp:adrotator AdvertisementFile="ads.xml" BorderColor="black" BorderWidth=1 runat="server"/>

<h3> Nombre: <asp:textbox id="Name" runat="server"/>

Categoría: <asp:dropdownlist id="Category" runat=server> <asp:listitem >psychology</asp:listitem> <asp:listitem >business</asp:listitem> <asp:listitem >popular_comp</asp:listitem> </asp:dropdownlist> </h3>

<asp:button text="Búsqueda" OnClick="SubmitBtn_Click" runat="server"/>

<p>

<asp:label id="Message" runat="server"/>

</form>

</center>

</body></html>

Este simple ejemplo resulta equivalente al ejemplo "Intro3" que se mostró antes en esta misma sección. No obstante, debe observarse que el código es mucho más limpio y sencillo en esta nueva versión basada en el control de servidor.

Utilizar controles de servidor personalizados

ASP.NET incluye 45 controles de servidor integrados que se pueden utilizar fuera del cuadro (para obtener más detalles, vea Referencia de controles de formularios Web). Además de utilizar los controles integrados de ASP.NET, los programadores también pueden utilizar controles desarrollados por otros fabricantes.

En el siguiente ejemplo se muestra un control Calendar sencillo. El control Calendar se declara en la página mediante una etiqueta <acme:calendar runat=server>. Debe observarse que la directiva <% Register %> al principio de la página es la responsable de registrar el prefijo de etiqueta XML con el espacio de nombres de código "Acme" de la implementación del control. El localizador de páginas de ASP.NET utilizará entonces este espacio de nombres para cargar la instancia de la clase del control Calendar en tiempo de ejecución.

<%@ Register TagPrefix="Acme" Namespace="AcmeVB" Assembly="AcmeVB" %>

<html> <head>

<link rel="stylesheet"href="intro.css"> </head>

Page 13: Presentación del tutorial de ASP

<script language="VB" runat=server>

Sub SubmitBtn_Click(Sender As Object, E As EventArgs) Message.Text = "Hola " & Name.Text & ", ha selecionado: " & Category.SelectedItem.Text & " el: " & MyCal.Date.ToShortDateString() End Sub

</script>

<body>

<center>

<form action="intro7.aspx" method="post" runat="server">

<asp:adrotator AdvertisementFile="ads.xml" BorderColor="black" BorderWidth=1 runat="server"/>

<h3> Nombre: <asp:textbox id="Name" runat="server"/>

Categoría: <asp:dropdownlist id="Category" runat=server> <asp:listitem >psychology</asp:listitem> <asp:listitem >business</asp:listitem> <asp:listitem >popular_comp</asp:listitem> </asp:dropdownlist> </h3>

<asp:button text="Búsqueda" OnClick="SubmitBtn_Click" runat="server"/>

<p>

<Acme:Calendar id="MyCal" runat=server/>

<p>

<asp:label id="Message" runat="server"/>

</form>

</center>

</body></html>

El control Calendar de este ejemplo se ha diseñado para realizar procesos similares a los de alto nivel en Internet Explorer 5.5 y procesos de bajo nivel en todos los demás exploradores. El olfateo de este explorador no es, en absoluto, más complejo que el que proporcionan los controles de servidor integrados de ASP.NET. Para los exploradores Internet Explorer 5.5, genera resultados DHTML. Dicho resultado no necesita acciones de ida y vuelta al servidor cuando se realizan selecciones de día y desplazamientos de mes. Para el resto de exploradores, el control genera HTML 3.2 estándar. HTML 3.2 necesita acciones de ida y vuelta al servidor para controlar interacciones del usuario en el cliente.

Importante: El código que escribe el programador de una página es idéntico, sin importar si se empleo un explorador de alto o bajo nivel para tener acceso a la página. El mismo control Calendar encapsula toda la lógica necesaria para controlar ambos escenarios.

Listas, datos y enlace de datos

ASP.NET incluye un conjunto integrado de controles de lista y cuadrícula de datos. Se pueden utilizar para proporcionar una interfaz de usuario personalizada basada en consultas a una base de datos o a otro origen de datos. En el siguiente ejemplo se muestra cómo se puede utilizar un control <asp:datagrid runat=server> para enlazar mediante datos información de libros recogida mediante una consulta a una base de datos de SQL.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

Page 14: Presentación del tutorial de ASP

<html> <head>

<link rel="stylesheet"href="intro.css"> </head>

<script language="VB" runat=server>

Sub SubmitBtn_Click(Sender As Object, E As EventArgs)

Dim DS As DataSet Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") MyCommand = New SqlDataAdapter("select * from Titles where type='" + Category.SelectedItem.Value + "'", myConnection)

DS = new DataSet() MyCommand.Fill(ds, "Titles")

MyList.DataSource = ds.Tables("Titles").DefaultView MyList.DataBind()

End Sub

</script>

<body>

<center>

<form action="intro8.aspx" method="post" runat="server">

<asp:adrotator AdvertisementFile="ads.xml" BorderColor="black" BorderWidth=1 runat="server"/>

<h3> Nombre: <asp:textbox id="Name" runat="server"/>

Categoría: <asp:dropdownlist id="Category" runat=server> <asp:listitem >psychology</asp:listitem> <asp:listitem >business</asp:listitem> <asp:listitem >popular_comp</asp:listitem> </asp:dropdownlist> </h3>

<asp:button text="Búsqueda" OnClick="SubmitBtn_Click" runat="server"/>

<p>

<ASP:DataGrid id="MyList" HeaderStyle-BackColor="#aaaadd" BackColor="#ccccff" runat="server"/>

</form>

</center>

</body>

</html>

Page 15: Presentación del tutorial de ASP

El control DataGrid <asp:datagrid runat=server> proporciona una forma sencilla de visualizar rápidamente resultados de datos mediante una interfaz de usuario tradicional de control de cuadrícula. Como alternativa, los programadores de ASP.NET pueden utilizar el control DataList <asp:DataList runat=server> y una plantilla ItemTemplate personalizada para personalizar información de datos, tal y como se muestra en el siguiente ejemplo.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html> <head>

<link rel="stylesheet"href="intro.css"> </head>

<script language="VB" runat=server>

Sub SubmitBtn_Click(Sender As Object, E As EventArgs)

Dim DS As DataSet Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") MyCommand = New SqlDataAdapter("select * from Titles where type='" + Category.SelectedItem.Value + "'", myConnection)

DS = new DataSet() MyCommand.Fill(ds, "Titles")

MyList.DataSource = ds.Tables("Titles").DefaultView MyList.DataBind()

End Sub

</script>

<body>

<center>

<form action="intro9.aspx" method="post" runat="server">

<asp:adrotator AdvertisementFile="ads.xml" BorderColor="black" BorderWidth=1 runat="server"/>

<h3> Nombre: <asp:textbox id="Name" runat="server"/>

Categoría: <asp:dropdownlist id="Category" runat=server> <asp:listitem >psychology</asp:listitem> <asp:listitem >business</asp:listitem> <asp:listitem >popular_comp</asp:listitem> </asp:dropdownlist> </h3>

<asp:button text="Búsqueda" OnClick="SubmitBtn_Click" runat="server"/>

<p>

<asp:datalist id="MyList" repeatcolumns="2" borderwidth="0" runat="server">

<ItemTemplate>

<table> <tr>

Page 16: Presentación del tutorial de ASP

<td> <img src='<%# DataBinder.Eval(Container.DataItem, "title_id", "/quickstart/aspplus/images/title-{0}.gif") %>'> </td>

<td width=250 valign=top>

<b><%# DataBinder.Eval(Container.DataItem, "title") %></b>

<br><br>

Precio: <%# DataBinder.Eval(Container.DataItem, "price", "${0}") %> </td>

</tr> </table>

</ItemTemplate>

</asp:datalist>

</form>

</center>

</body>

</html>

Debe observarse que el control <asp:datalist runat=server> permite a los usuarios finales controlar exactamente la estructura y el diseño de cada elemento de la lista (mediante la propiedad de plantillas ItemTemplate). El control también controla automáticamente el ajuste de contenido en dos columnas (los usuarios pueden controlar el número de columnas mediante la propiedad RepeatColumns de la lista de datos).

En el siguiente ejemplo se proporciona una visión alternativa del control <asp:datalist runat=server>.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html> <head>

<link rel="stylesheet"href="intro.css"> </head>

<script language="VB" runat=server>

Sub SubmitBtn_Click(Sender As Object, E As EventArgs)

Dim DS As DataSet Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") MyCommand = New SqlDataAdapter("select * from Titles where type='" + Category.SelectedItem.Value + "'", myConnection)

DS = new DataSet() MyCommand.Fill(ds, "Titles")

Page 17: Presentación del tutorial de ASP

MyList.DataSource = ds.Tables("Titles").DefaultView MyList.DataBind()

End Sub

</script>

<body>

<center>

<form action="intro10.aspx" method="post" runat="server">

<asp:adrotator AdvertisementFile="ads.xml" BorderColor="black" BorderWidth=1 runat="server"/>

<h3> Nombre: <asp:textbox id="Name" runat="server"/>

Categoría: <asp:dropdownlist id="Category" runat=server> <asp:listitem >psychology</asp:listitem> <asp:listitem >business</asp:listitem> <asp:listitem >popular_comp</asp:listitem> </asp:dropdownlist>

</h3>

<asp:button text="Búsqueda" OnClick="SubmitBtn_Click" runat="server"/>

<p>

<asp:datalist id="MyList" layout="flow" showfooter=true borderwidth=0 runat=server>

<HeaderTemplate>

<table cellpadding=1 cellspacing=0 > <tr> <td colspan=4> <b><font face="Verdana" size=3>Lista de productos </font></b> </td> </tr> <tr> <td colspan=4 height=5 bgcolor="000000"></td> </tr>

</HeaderTemplate>

<ItemTemplate> <tr> <td colspan=3 style="font-size:10pt"> <b><%# DataBinder.Eval(Container.DataItem, "title_id") %></b> <span> <%# DataBinder.Eval(Container.DataItem, "title") %> </span> </td> <td align=right style="font-size:10pt"> <b><%# DataBinder.Eval(Container.DataItem, "price", "${0}") %> </b> </td>

</tr> </ItemTemplate>

<SeparatorTemplate> <tr> <td colspan=4 height=1 bgcolor="000000"></td> </tr> </SeparatorTemplate>

Page 18: Presentación del tutorial de ASP

<FooterTemplate> <tr> <td colspan=4 height=5 bgcolor="000000"></td> </tr> </table> </FooterTemplate>

</asp:datalist>

</form>

</center>

</body>

</html>

Debe observarse que el control, el modelo de datos y el usuario de la página son los mismos que en el ejemplo anterior. La única diferencia consiste en que, en este caso, las plantillas alternativas se proporcionan al código de forma declarativa.

Controles de validación de formulario

El marco de trabajo de la página de formularios Web de ASP.NET proporciona un conjunto de controles de servidor de validación que proporcionan a su vez un modo sencillo a la vez que potente de comprobar errores en los formularios de entrada y, en caso necesario, mostrar mensajes al usuario.

Los controles de validación se agregan a una página ASP.NET con otros controles de servidor. Existen controles para tipos concretos de validación, como la comprobación de intervalos o la coincidencia de modelos, además de RequiredFieldValidator, que se asegura de que un usuario omita un campo de entrada.

En el siguiente ejemplo se muestra cómo utilizar dos controles <asp:requiredfieldvalidator runat=server> en una página para validar los contenidos de los controles TextBox y DropDownList.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html> <head>

<link rel="stylesheet"href="intro.css"> </head>

<script language="VB" runat=server>

Sub SubmitBtn_Click(Sender As Object, E As EventArgs)

Dim DS As DataSet Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") MyCommand = New SqlDataAdapter("select * from Titles where type='" + Category.SelectedItem.Value + "'", myConnection)

DS = new DataSet() MyCommand.Fill(ds, "Titles")

MyList.DataSource = ds.Tables("Titles").DefaultView MyList.DataBind()

End Sub

Page 19: Presentación del tutorial de ASP

</script>

<body>

<center>

<form action="intro11.aspx" method="post" runat="server">

<asp:adrotator AdvertisementFile="ads.xml" BorderColor="black" BorderWidth=1 runat="server"/>

<table> <tr> <td> Nombre: </td> <td> <asp:textbox id="Name" runat="server"/> </td> <td> <asp:RequiredFieldValidator ControlToValidate="Name" Display="Dynamic" errormessage="Debe escribir su nombre" runat=server/> </td> </tr> <tr> <td> Categoría: </td> <td> <asp:dropdownlist id="Category" width=220 runat=server> <asp:listitem><!--Seleccionar categoría--></asp:listitem> <asp:listitem >psychology</asp:listitem> <asp:listitem >business</asp:listitem> <asp:listitem >popular_comp</asp:listitem> </asp:dropdownlist> </td> <td> <asp:RequiredFieldValidator ControlToValidate="Category" Display="Dynamic" InitialValue="<!--Seleccionar categoría-->" errormessage="Debe seleccionar una categoría" runat=server/> </td> </tr> <tr> <td></td> <td><asp:button text="Búsqueda" OnClick="SubmitBtn_Click" runat="server"/></td> </tr> </table>

<p>

<asp:datalist id="MyList" repeatcolumns="2" borderwidth="0" runat="server">

<ItemTemplate>

<table> <tr>

<td> <img src='<%# DataBinder.Eval(Container.DataItem, "title_id", "/quickstart/aspplus/images/title-{0}.gif") %>'> </td>

<td width=250 valign=top>

<b><%# DataBinder.Eval(Container.DataItem, "title") %></b>

<br><br>

Precio: <%# DataBinder.Eval(Container.DataItem, "price", "${0}") %> </td>

</tr> </table>

Page 20: Presentación del tutorial de ASP

</ItemTemplate>

</asp:datalist>

</form>

</center>

</body>

</html>

Debe observarse que los controles de validación disponen de compatibilidad con clientes de alto y bajo nivel. Los exploradores de alto nivel realizan la validación en el cliente (mediante JavaScript y DHTML) y en el servidor. Los exploradores de bajo nivel sólo realizan la validación en el servidor. El modelo de programación de los dos escenarios es idéntico.

Debe observarse que los programadores de páginas ASP.NET pueden activar opcionalmente la propiedad Page.IsValid en tiempo de ejecución para determinar si todos los controles de validación de una página son válidos en ese momento. Esto proporciona un modo simple de determinar si se continúa con la lógica empresarial o no. En el siguiente ejemplo se realiza una comprobación Page.IsValid antes de ejecutar una búsqueda de la categoría especificada en la base de datos.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html> <head>

<link rel="stylesheet"href="intro.css"> </head>

<script language="VB" runat=server>

Sub SubmitBtn_Click(Sender As Object, E As EventArgs)

If (Page.IsValid)

Dim DS As DataSet Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") MyCommand = New SqlDataAdapter("select * from Titles where type='" + Category.SelectedItem.Value + "'", myConnection)

DS = new DataSet() MyCommand.Fill(ds, "Titles")

MyList.DataSource = ds.Tables("Titles").DefaultView MyList.DataBind()

End If

End Sub

</script>

<body>

<center>

<form action="intro12.aspx" method="post" runat="server">

Page 21: Presentación del tutorial de ASP

<asp:adrotator AdvertisementFile="ads.xml" BorderColor="black" BorderWidth=1 runat="server"/>

<table> <tr> <td> Nombre: </td> <td> <asp:textbox id="Name" runat="server"/> </td> <td> <asp:RequiredFieldValidator ControlToValidate="Name" Display="Dynamic" errormessage="Debe escribir su nombre" runat=server/> </td> </tr> <tr> <td> Categoría: </td> <td> <asp:dropdownlist id="Category" width=220 runat=server> <asp:listitem><!--Seleccionar categoría--></asp:listitem> <asp:listitem >psychology</asp:listitem> <asp:listitem >business</asp:listitem> <asp:listitem >popular_comp</asp:listitem> </asp:dropdownlist> </td> <td> <asp:RequiredFieldValidator ControlToValidate="Category" Display="Dynamic" InitialValue="<!--Seleccionar categoría-->" errormessage="Debe seleccionar una categoría" runat=server/> </td> </tr> <tr> <td></td> <td><asp:button text="Búsqueda" OnClick="SubmitBtn_Click" runat="server"/></td> </tr> </table>

<p>

<asp:datalist id="MyList" repeatcolumns="2" borderwidth="0" runat="server">

<ItemTemplate>

<table> <tr>

<td> <img src='<%# DataBinder.Eval(Container.DataItem, "title_id", "/quickstart/aspplus/images/title-{0}.gif") %>'> </td>

<td width=250 valign=top>

<b><%# DataBinder.Eval(Container.DataItem, "title") %></b>

<br><br>

Precio: <%# DataBinder.Eval(Container.DataItem, "price", "${0}") %> </td>

</tr> </table>

</ItemTemplate>

</asp:datalist>

</form>

</center>

</body>

Page 22: Presentación del tutorial de ASP

</html>

Formularios Web de código subyacente

ASP.NET admite dos métodos para crear páginas dinámicas. El primero es el método que se ha mostrado en los ejemplos anteriores, en el que el código de página se declara físicamente en el archivo .aspx de origen. La forma alternativa (conocida como método de código subyacente) permite que el código de página esté más claramente separado del contenido HTML en un archivo completamente independiente.

En el siguiente ejemplo se muestra el uso del método de código subyacente para escribir código de páginas ASP.NET.

<%@ Page Inherits="MyCodeBehind" Src="Intro13.vb" %>

<html> <head>

<link rel="stylesheet" href="intro.css"> </head>

<body>

<center>

<form action="intro13.aspx" method="post" runat="server">

<asp:adrotator AdvertisementFile="ads.xml" BorderColor="black" BorderWidth=1 runat="server"/>

<table> <tr> <td> Nombre: </td> <td> <asp:textbox id="Name" runat="server"/> </td> <td> <asp:RequiredFieldValidator ControlToValidate="Name" Display="Dynamic" errormessage="Debe escribir su nombre" runat=server/> </td> </tr> <tr> <td> Categoría: </td> <td> <asp:dropdownlist id="Category" width=220 runat=server> <asp:listitem><!--Seleccionar categoría--></asp:listitem> <asp:listitem >psychology</asp:listitem> <asp:listitem >business</asp:listitem> <asp:listitem >popular_comp</asp:listitem> </asp:dropdownlist> </td> <td> <asp:RequiredFieldValidator ControlToValidate="Category" Display="Dynamic" InitialValue="<!--Seleccionar categoría-->" errormessage="Debe seleccionar una categoría" runat=server/> </td> </tr> <tr> <td></td> <td><asp:button text="Búsqueda" OnClick="SubmitBtn_Click" runat="server"/></td> </tr> </table>

<p>

<asp:datalist id="MyList" repeatcolumns="2" borderwidth="0" runat="server">

<ItemTemplate>

<table> <tr>

Page 23: Presentación del tutorial de ASP

<td> <img src='<%# DataBinder.Eval(Container.DataItem, "title_id", "/quickstart/aspplus/images/title-{0}.gif") %>'> </td>

<td width=250 valign=top>

<b><%# DataBinder.Eval(Container.DataItem, "title") %></b>

<br><br>

Precio: <%# DataBinder.Eval(Container.DataItem, "price", "${0}") %> </td>

</tr> </table>

</ItemTemplate>

</asp:datalist>

</form>

</center>

</body>

</html>

Resumen de la sección

1. Los formularios Web ASP.NET proporcionan una forma fácil y potente de generar interfaces de usuario Web dinámicas. 2. Las páginas de formularios Web de ASP.NET pueden dirigirse a cualquier explorador cliente (no existe ningún requisito de

cookies o de biblioteca de secuencias de comandos). 3. Las páginas de formularios Web de ASP.NET proporciona compatibilidad sintáctica con páginas ASP existentes. 4. Los controles de servidor de ASP.NET proporcionan una forma sencilla de encapsular funcionalidades comunes. 5. ASP.NET incluye 45 controles de servidor integrados. Los programadores también pueden utilizar los controles generados por

otros. 6. Los controles de servidor ASP.NET pueden proyectar automáticamente HTML de alto y bajo nivel. 7. Las plantillas ASP.NET proporcionan una forma sencilla de personalizar la apariencia y sensación de controles de servidor de

lista. 8. Los controles de validación de ASP.NET proporcionan una forma sencilla de realizar validaciones de datos declarativas en el

cliente o en el servidor.

Trabajar con controles de servidor

En esta sección del tutorial, se muestra algunos conceptos básicos comunes y acciones comunes realizadas por usuarios finales al utilizar controles de servidor ASP.NET en una página.

Declarar controles de servidor

Los controles de servidor ASP.NET se identifican en una página mediante etiquetas declarativas que contienen un atributo runat="server". En el siguiente ejemplo se declaran tres controles de servidor <asp:label runat="server"> y se personaliza las propiedades de texto y estilo de cada uno por separado.

<html>

<body>

Page 24: Presentación del tutorial de ASP

<h3><font face="Verdana">Declarar controles de servidor</font></h3>

Este ejemplo muestra cómo declarar el control de servidor &lt;asp:label&gt; y manipular sus propiedades en una página.

<p>

<hr>

<asp:label id="Message1" font-size="16" font-bold="true" forecolor="red" runat=server>Primer mensaje</asp:label>

<br>

<asp:label id="Message2" font-size="20" font-italic="true" forecolor="blue" runat=server>Segundo mensaje</asp:label>

<br>

<asp:label id="Message3" font-size="24" font-underline="true" forecolor="green" runat=server>Tercer mensaje</asp:label>

</body>

</html>

Manipular controles de servidor

Se puede identificar mediante programación un control de servidor individual de ASP.NET dentro de una página proporcionándolo con un atributo id. Se puede utilizar la referencia id para manipular mediante programación el modelo de objeto del control de servidor en tiempo de ejecución. En el siguiente ejemplo se muestra cómo puede un programador de páginas establecer una propiedad Text del control <asp:label runat="server"> en el evento Page_Load.

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs) Message.Text = "Fecha y hora en la que obtuvo acceso por última vez a esta página: " & DateTime.Now End Sub

</script>

<body>

<h3><font face="Verdana">Manipular controles de servidor</font></h3>

Este ejemplo muestra cómo manipular el control de servidor &lt;asp:label&gt; del evento Page_Load para representar la hora actual.

<p>

<hr>

<asp:label id="Message" font-size="24" font-bold="true" runat=server/>

</body>

</html>

Page 25: Presentación del tutorial de ASP

Controlar eventos de acción del control

Los controles de servidor ASP.NET pueden exponer y provocar opcionalmente eventos, que pueden controlar los programadores de páginas. Un programador de página puede cumplir con esto mediante la conexión de un evento con un control (en el que el nombre de atributo de un evento indica el nombre del evento y el valor del atributo indica el nombre de un método al que llamar). Por ejemplo, en el siguiente ejemplo de código se demuestra cómo conectar un evento OnClick a un control de botón.

html>

<script language="VB" runat="server">

Sub EnterBtn_Click(Sender As Object, E As EventArgs) Message.Text = "Hola " & Name.Text & ", esto es ASP.NET" End Sub

</script>

<body>

<h3><font face="Verdana">Administrar eventos de acción de control</font></h3>

<p>

Este ejemplo muestra cómo obtener acceso al control de servidor &lt;asp:textbox&gt; del evento "Click" de &lt;asp:button&gt; y utilizar el contenido para modificar el texto de &lt;asp:label&gt;.

<p>

<hr>

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

<font face="Verdana">

Escribir su nombre: <asp:textbox id="Name" runat=server/> <asp:button text="Entrar" Onclick="EnterBtn_Click" runat=server/>

<p>

<asp:label id="Message" runat=server/>

</font>

</form>

</body>

</html>

Controlar múltiples eventos de acción del control

Los controladores de eventos proporcionan a los programadores de páginas una forma limpia de estructurar lógicamente en una página ASP.NET. Por ejemplo, en el siguiente ejemplo se demuestra cómo conectar y controlar cuatro eventos de botón en una única página.

<html>

<script language="VB" runat="server">

Page 26: Presentación del tutorial de ASP

Sub AddBtn_Click(Sender As Object, E As EventArgs)

If Not (AvailableFonts.SelectedIndex = -1)

InstalledFonts.Items.Add(New ListItem(AvailableFonts.SelectedItem.Value)) AvailableFonts.Items.Remove(AvailableFonts.SelectedItem.Value) End If End Sub

Sub AddAllBtn_Click(Sender As Object, E As EventArgs)

Do While Not (AvailableFonts.Items.Count = 0)

InstalledFonts.Items.Add(New ListItem(AvailableFonts.Items(0).Value)) AvailableFonts.Items.Remove(AvailableFonts.Items(0).Value) Loop End Sub

Sub RemoveBtn_Click(Sender As Object, E As EventArgs)

If Not (InstalledFonts.SelectedIndex = -1)

AvailableFonts.Items.Add(New ListItem(InstalledFonts.SelectedItem.Value)) InstalledFonts.Items.Remove(InstalledFonts.SelectedItem.Value) End If End Sub

Sub RemoveAllBtn_Click(Sender As Object, E As EventArgs)

Do While Not (InstalledFonts.Items.Count = 0)

AvailableFonts.Items.Add(New ListItem(InstalledFonts.Items(0).Value)) InstalledFonts.Items.Remove(InstalledFonts.Items(0).Value) Loop End Sub

</script>

<body>

<h3><font face="Verdana">Administrar eventos múltiples de acción de control</font></h3>

<p>

Este ejemplo muestra cómo administrar eventos múltiples de acción de control provocados por controles diferentes &lt;asp:button&gt;.

<p>

<hr>

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

<table> <tr> <td> Fuentes disponibles </td> <td> <!-- Filler --> </td> <td>

Page 27: Presentación del tutorial de ASP

Fuentes instaladas </td> </tr> <tr> <td> <asp:listbox id="AvailableFonts" width="100px" runat=server> <asp:listitem>Roman</asp:listitem> <asp:listitem>Arial Black</asp:listitem> <asp:listitem>Garamond</asp:listitem> <asp:listitem>Somona</asp:listitem> <asp:listitem>Symbol</asp:listitem> </asp:listbox> </td> <td> <!-- Filler --> </td> <td> <asp:listbox id="InstalledFonts" width="100px" runat=server> <asp:listitem>Times</asp:listitem> <asp:listitem>Helvetica</asp:listitem> <asp:listitem>Arial</asp:listitem> </asp:listbox> </td> </tr> <tr> <td> <!-- Filler --> </td> <td> <asp:button text="<<" OnClick="RemoveAllBtn_Click" runat=server/> <asp:button text="<" OnClick="RemoveBtn_Click" runat=server/> <asp:button text=">" OnClick="AddBtn_Click" runat=server/> <asp:button text=">>" OnClick="AddAllBtn_Click" runat=server/> </td> <td> <!-- Filler --> </td> </tr> </table>

</form>

</body>

</html>

Realizar desplazamientos de página (escenario 1)

Desplazamiento por múltiples páginas es un escenario habitual en prácticamente todas las aplicaciones Web. En el siguiente ejemplo se demuestra cómo utilizar el control <asp:hyperlink runat=server> para desplazarse a otra página (pasando parámetros de cadenas de consulta personalizadas por el camino). En el ejemplo se demuestra, pues, cómo obtener acceso fácilmente a dichos parámetros de cadenas de consulta desde la página de destino.

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

Dim RandomGenerator As Random

Page 28: Presentación del tutorial de ASP

RandomGenerator = New Random(DateTime.Now.Millisecond)

Dim RandomNum As Integer RandomNum = RandomGenerator.Next(0, 3)

Select RandomNum

Case 0: Name.Text = "Scott"

Case 1: Name.Text = "Fred"

Case 2: Name.Text = "Adam"

End Select

AnchorLink.NavigateUrl = "controls_navigationtarget.aspx?name=" & System.Web.HttpUtility.UrlEncode(Name.Text) End Sub

</script>

<body>

<h3><font face="Verdana">Realizar exploración de páginas (escenario 1)</font></h3>

<p>

Este ejemplo muestra cómo generar una etiqueta delimitadora HTML que hace que el cliente explore una nueva página cuando hace clic en el explorador.

<p>

<hr>

<p>

<asp:hyperlink id="AnchorLink" font-size=24 runat=server> Hola <asp:label id="Name" runat=server/> hacer clic en este vínculo </asp:hyperlink>

</body>

</html>

Realizar desplazamientos de página (escenario 2)

No todos los escenarios de desplazamiento de páginas se inician a través de hipervínculos en el cliente. Las redirecciones y desplazamientos en el cliente también puede iniciarlas desde el servidor un programador de páginas ASP.NET llamando al método Response.Redirect(url). Esto suele realizarse cuando se necesita la validación en el servidor para alguna entrada del cliente antes de que el desplazamiento sea realmente efectivo.

En el siguiente ejemplo se demuestra cómo utilizar el método Response.Redirect para pasar parámetros a otra página de destino. También se demuestra cómo obtener acceso fácilmente a dichos parámetros desde la página de destino.

<html>

<script language="VB" runat="server">

Page 29: Presentación del tutorial de ASP

Sub EnterBtn_Click(Sender As Object, E As EventArgs)

' Navigate to a new page (passing name as a querystring argument) if ' user has entered a valid name value in the <asp:textbox>

If Not (Name.Text = "") Response.Redirect("Controls_NavigationTarget.aspx?name=" & System.Web.HttpUtility.UrlEncode(Name.Text)) Else Message.Text = "Escribir su nombre en el cuadro de texto" End If End Sub

</script>

<body>

<h3><font face="Verdana">Realizar exploración de páginas (escenario 2)</font></h3>

<p>

Este ejemplo muestra cómo explorar una nueva página desde el evento &lt;asp:button&gt; Click, pasando &lt;asp:textbox&gt; un valor como argumento de la cadena de consulta (una vez que se ha especificado el valor del cuadro de texto).

<p>

<hr>

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

<font face="Verdana">

Escribir su nombre: <asp:textbox id="Name" runat=server/> <asp:button text="Entrar" Onclick="EnterBtn_Click" runat=server/>

<p>

<asp:label id="Message" forecolor="red" font-bold="true" runat=server/>

</font>

</form>

</body>

</html>

Aplicar estilos a controles

El Web es un entorno flexible para interfaces de usuarios con variaciones extremas en la apariencia y sensación de diferentes sitios Web. La amplia adopción de hojas de estilo en cascada (CSS) es la gran responsable de los ricos diseños que se encuentran en el Web. Todos los controles de servidor HTML de ASP.NET y los controles de servidores Web se han diseñado para proporcionar compatibilidad de primera clase con los estilos de CSS. En esta sección se comenta cómo utilizar estilos en unión con controles de servidor y se demuestra el preciso control que se ejerce sobre la apariencia y sensación de los formularios Web que proporciona.

Aplicar estilos a controles HTML

Las etiquetas HTML estándar admiten CSS a través de un atributo de estilo que se puede establecer en una lista de pares atributo-valor delimitada por punto y coma. Para obtener más información acerca de los atributos CSS admitidos por el explorador Internet Explorer, vea la

Page 30: Presentación del tutorial de ASP

página Referencia de atributos CSS del sitio Web Workshop de MSDN. Todos los controles de servidor HTML de ASP.NET pueden aceptar estilos exactamente del mismo modo que las etiquetas HTML estándar. En el siguiente ejemplo se muestra un número de estilos aplicados a varios controles de servidor HTML. Si se visualiza el código fuente en la página devuelta al cliente, se verá que estos estilos se pasan al explorador del procesamiento del control.

<html>

<body>

<h3><font face="verdana">Aplicación de estilos a controles HTML</font></h3>

<p><font face="verdana"><h4>Intervalo con estilo</h4></font><p>

<span style="font: 12pt verdana; color:orange;font-weight:700" runat="server"> Texto literal dentro de un control Span con estilo </span>

<p><font face="verdana"><h4>Botón con estilo</h4></font><p>

<button style="font: 8pt verdana;background-color:lightgreen;border-color:black;width:100" runat="server">Hacer clic aquí</button>

<p><font face="verdana"><h4>Entrada de texto con estilo</h4></font><p>

Escriba texto: <p> <input type="text" value="Un, dos, tres" style="font: 14pt verdana;background-color:yellow;border-style:dashed;border-color:red;width:300;" runat="server"/>

<p><font face="verdana"><h4>Entrada de selección con estilo</h4></font><p>

Seleccione un elemento: <p> <select style="font: 14pt verdana;background-color:lightblue;color:purple;" runat="server"> <option>Elemento 1</option> <option>Elemento 2</option> <option>Elemento 3</option> </select>

<p><font face="verdana"><h4>Botones de opción con estilo</h4></font><p>

Seleccione una opción: <p> <span style="font: 16 pt verdana;font-weight:300"> <input type="radio" name="Mode" checked style="width:50;background-color:red;zoom:200%" runat="server"/>Opción 1<br> <input type="radio" name="Mode" style="width:50;background-color:red;zoom:200%" runat="server"/>Opción 2<br> <input type="radio" name="Mode" style="width:50;background-color:red;zoom:200%" runat="server"/>Opción 3 </span>

</body></html>

CSS también define un atributo de clase que se puede establecer en una definición de estilo CSS incluida en una sección <style>...</style> del documento. Los atributos de clase facilitan la definición de estilos y la aplicación de los mismos a varias etiquetas sin tener que volver a definir el mismo estilo. Los estilos de controles de servidor HTML también se pueden establecer de esta forma, como se demuestra en el siguiente ejemplo.

<html><head>

<style>

Page 31: Presentación del tutorial de ASP

.spanstyle { font: 12pt verdana; font-weight:700; color:orange; }

.buttonstyle { font: 8pt verdana; background-color:lightgreen; border-color:black; width:100 } .inputstyle { font: 14pt verdana; background-color:yellow; border-style:dashed; border-color:red; width:300; } .selectstyle { font: 14pt verdana; background-color:lightblue; color:purple; } .radiostyle { width:50; background-color:red; zoom:200% }

</style>

</head><body>

<h3><font face="verdana">Aplicación de estilos a controles HTML</font></h3>

<p><font face="verdana"><h4>Intervalo con estilo</h4></font><p>

<span class="spanstyle" runat="server"> Texto literal dentro de un control Span con estilo </span>

<p><font face="verdana"><h4>Botón con estilo</h4></font><p>

<button class="buttonstyle" runat="server">Hacer clic aquí</button>

<p><font face="verdana"><h4>Entrada de texto con estilo</h4></font><p>

Escriba texto: <p> <input type="text" value="Un, dos, tres" class="inputstyle" runat="server"/>

<p><font face="verdana"><h4>Entrada de selección con estilo</h4></font><p>

Seleccione un elemento: <p> <select class="selectstyle" runat="server">

Page 32: Presentación del tutorial de ASP

<option>Elemento 1</option> <option>Elemento 2</option> <option>Elemento 3</option> </select>

<p><font face="verdana"><h4>Botones de opción con estilo</h4></font><p>

Seleccione una opción: <p> <span style="font: 16 pt verdana;font-weight:300"> <input type="radio" name="Mode" checked class="radiostyle" runat="server"/>Opción 1<br> <input type="radio" name="Mode" class="radiostyle" runat="server"/>Opción 2<br> <input type="radio" name="Mode" class="radiostyle" runat="server"/>Opción 3 </span>

</body></html>

Cuando se analiza una página ASP.NET, la información de estilo se llena en una propiedad Style (de tipo CssStyleCollection) de la clase System.Web.UI.HtmlControls.HtmlControl. Básicamente, esta propiedad consiste en un diccionario que expone los estilos del control como colección de valores indizada por cadenas para cada clave de atributo de estilo. Por ejemplo, se puede utilizar el siguiente código para

establecer y, en consecuencia, recuperar el atributo de estilo width en un control de servidor HtmlInputText.

<script language="VB" runat="server" >

Sub Page_Load(Sender As Object, E As EventArgs) MyText.Style("width") = "90px" Response.Write(MyText.Style("width")) End Sub

</script>

<input type="text" id="MyText" runat="server"/>

En el siguiente ejemplo se muestra cómo se puede manipular mediante programación el estilo de un control de servidor HTML mediante la propiedad de colección Style.

<html>

<script language="VB" runat="server">

Sub Page_Load(Src As Object, E As EventArgs)

Message.InnerHtml &= "<h5>Obteniendo acceso a los estilos...</h5>"

Message.InnerHtml &= "Color del intervalo: " & MySpan.Style("color") & "<br>" Message.InnerHtml &= "Ancho del cuadro de texto: " & MyText.Style("ancho") & "<p>"

Message.InnerHtml &= "La colección de estilos de MySelect es: <br>"

Dim Keys As IEnumerator

Keys = MySelect.Style.Keys.GetEnumerator()

Do While (Keys.MoveNext())

Dim Key As String Key = CStr(Keys.Current)

Page 33: Presentación del tutorial de ASP

Message.InnerHtml &= "<img src='/quickstart/images/bullet.gif'>&nbsp;&nbsp;" Message.InnerHtml &= Key & "=" & MySelect.Style(Key) & "<br>" Loop End Sub

Sub Submit_Click(Src As Object, E As EventArgs)

Message.InnerHtml &= "<h5>Modificando estilos...</h5>"

MySpan.Style("color") = ColorSelect.Value MyText.Style("ancho") = "600"

Message.InnerHtml &= "Color del intervalo: " & MySpan.Style("color") & "<br>" Message.InnerHtml &= "Ancho del cuadro de texto: " & MyText.Style("ancho") End Sub

</script>

<body>

<form runat="server">

<h3><font face="verdana">Obtener acceso a estilos mediante programación</font></h3>

<div style="font: 8pt verdana;background-color:cccccc;border-color:black;border-width:1;border-style:solid;padding:1,10,25,10"> <span id="Message" EnableViewState="false" runat="server"/> <p> Seleccione un color para el control Span: <p> <select id="ColorSelect" style="font: 11pt verdana;font-weight:700;" runat="server"> <option>red</option> <option>green</option> <option>blue</option> </select> <input type="submit" runat="server" Value="Cambiar estilo" OnServerClick="Submit_Click"> </div>

<p><font face="verdana"><h4>Control Span con estilo</h4></font><p>

<span id="MySpan" style="font: 12pt verdana; color:orange;font-weight:700" runat="server"> Texto literal dentro de un control Span con estilo </span>

<p><font face="verdana"><h4>Botón con estilo</h4></font><p>

<button id="MyButton" style="font: 8pt verdana;background-color:lightgreen;border-color:black;width:100" runat="server">Hacer clic aquí</button>

<p><font face="verdana"><h4>Entrada de texto con estilo</h4></font><p>

Escriba texto: <p> <input id="MyText" type="text" value="Un, dos, tres" style="font: 14pt verdana;background-color:yellow;border-style:dashed;border-color:red;width:300;" runat="server"/>

<p><font face="verdana"><h4>Entrada de selección con estilo</h4></font><p>

Seleccione un elemento: <p> <select id="MySelect" style="font: 14pt verdana;background-color:lightblue;color:purple;" runat="server"> <option>Elemento 1</option> <option>Elemento 2</option> <option>Elemento 3</option> </select>

Page 34: Presentación del tutorial de ASP

<p><font face="verdana"><h4>Botones de opción con estilo</h4></font><p>

Seleccione una opción: <p> <span style="font: 16 pt verdana;font-weight:300"> <input id="MyRadio1" type="radio" name="Mode" checked style="width:50;background-color:red;zoom:200%" runat="server"/>Opción 1<br> <input id="MyRadio2" type="radio" name="Mode" style="width:50;background-color:red;zoom:200%" runat="server"/>Opción 2<br> <input id="MyRadio3" type="radio" name="Mode" style="width:50;background-color:red;zoom:200%" runat="server"/>Opción 3 </span>

</form>

</body></html>

Aplicar estilos a controles de servidor Web

Los controles de servidor Web proporcionan un nivel adicional de compatibilidad con estilos mediante la adición de varias propiedades con establecimiento inflexible de tipos para la configuración del estilo habitual, como el color de fondo y de primer plano, el nombre y tamaño de fuente, el ancho, el alto, etc. Estas propiedades de estilo representan un subconjunto de comportamientos de estilo disponible en HTML y se representan como propiedades "planas" expuestas directamente en la clase base System.Web.UI.WebControls.WebControl. La ventaja de utilizar estas propiedades es que proporcionan comprobación de tipo en tiempo de compilación y finalización de instrucciones en herramientas de programación como Microsoft Visual Studio .NET.

En el siguiente ejemplo se muestra un control WebCalendar al que se aplican varios estilos (se incluye un calendario sin estilos aplicados para comparar). Debe observarse que al establecer una propiedad que es un tipo de clase, como Font, se necesita utilizar la sintaxis de subpropiedad PropertyName-SubPropertyName.

html>

<body>

<form runat="server">

<h3><font face="verdana">Aplicación de estilos a controles Web</font></h3>

<p><font face="verdana"><h4>Propiedades de estilo</h4></font><p>

<b>Sin estilo:</b> <p> <ASP:Calendar runat="server" /> <p>

<b>Estilo: (Puede que, el estilo no sea el más <i>correcto</i> pero al menos los calcetines van a juego...)</b> <p> <ASP:Calendar runat="server"

BackColor="Beige" ForeColor="Brown" BorderWidth="3" BorderStyle="Solid" BorderColor="Black" Height="450" Width="450" Font-Size="12pt" Font-Name="Tahoma,Arial" Font-Underline="false" CellSpacing=2 CellPadding=2 ShowGridLines=true

Page 35: Presentación del tutorial de ASP

/>

</form>

</body></html>

El espacio de nombres System.Web.UI.WebControls incluye una clase base Style que encapsula atributos de estilo comunes (clases de estilo adicionales, como TableStyle y TableItemStyle, heredadas desde esta clase base común). Numerosos controles de servidor Web exponen propiedades de este tipo para especificar el estilo de elementos de procesamiento individuales del control. Por ejemplo, el control WebCalendar expone muchas de esas propiedades de estilo: DayStyle, WeekendDayStyle, TodayDayStyle, SelectedDayStyle, OtherMonthDayStyle y NextPrevStyle. Se pueden establecer propiedades individuales de estos estilos mediante la sintaxis de subpropiedad PropertyName-SubPropertyName, tal y como se muestra en el siguiente ejemplo.

<html>

<body>

<form runat="server">

<h3><font face="verdana">Aplicación de estilos a controles Web</font></h3>

<p><font face="verdana"><h4>Subpropiedades de estilo</h4></font><p>

<ASP:Calendar runat="server"

BackColor="Beige" ForeColor="Brown" BorderWidth="3" BorderStyle="Solid" BorderColor="Black" Height="450" Width="450" Font-Size="12pt" Font-Name="Tahoma,Arial" Font-Underline="false" CellSpacing=2 CellPadding=2 ShowGridLines=true

TitleStyle-BorderColor="darkolivegreen" TitleStyle-BorderWidth="3" TitleStyle-BackColor="olivedrab" TitleStyle-Height="50px"

DayHeaderStyle-BorderColor="darkolivegreen" DayHeaderStyle-BorderWidth="3" DayHeaderStyle-BackColor="olivedrab" DayHeaderStyle-ForeColor="black" DayHeaderStyle-Height="20px"

DayStyle-Width="50px" DayStyle-Height="50px"

TodayDayStyle-BorderWidth="3"

WeekEndDayStyle-BackColor="palegoldenrod" WeekEndDayStyle-Width="50px" WeekEndDayStyle-Height="50px"

SelectedDayStyle-BorderColor="firebrick"

Page 36: Presentación del tutorial de ASP

SelectedDayStyle-BorderWidth="3"

OtherMonthDayStyle-Width="50px" OtherMonthDayStyle-Height="50px" />

</form>

</body></html>

Una sintaxis ligeramente diferente permite que se declare cada propiedad Style como un elemento secundario anidado en etiquetas de control de servidor Web.

<ASP:Calendar ... runat="server"> <TitleStyle BorderColor="darkolivegreen" BorderWidth="3" BackColor="olivedrab" Height="50px" /></ASP:Calendar>

En el siguiente ejemplo se muestra una sintaxis alternativa, pero es funcionalmente equivalente al anterior.

<html>

<body>

<form runat="server">

<h3><font face="verdana">Aplicación de estilos a controles Web</font></h3>

<p><font face="verdana"><h4>Subpropiedades de estilo</h4></font><p>

<ASP:Calendar id="MyCalendar" runat="server"

BackColor="Beige" ForeColor="Brown" BorderWidth="3" BorderStyle="Solid" BorderColor="Black" Height="450" Width="450" Font-Size="12pt" Font-Name="Tahoma,Arial" Font-Underline="false" CellSpacing=2 CellPadding=2 ShowGridLines=true >

<TitleStyle BorderColor="darkolivegreen" BorderWidth="3" BackColor="olivedrab" Height="50px" />

<DayHeaderStyle BorderColor="darkolivegreen" BorderWidth="3" BackColor="olivedrab" ForeColor="black" Height="20px" />

<WeekEndDayStyle BackColor="palegoldenrod" Width="50px" Height="50px" />

<DayStyle Width="50px" Height="50px" />

<TodayDayStyle BorderWidth="3" />

<SelectedDayStyle BorderColor="firebrick" BorderWidth="3" />

Page 37: Presentación del tutorial de ASP

<OtherMonthDayStyle Width="50px" Height="50px" />

</ASP:Calendar>

</form>

</body></html>

Como sucede con los controles de servidor HTML, se pueden aplicar estilos a controles de servidor Web mediante una definición de clase CSS. La clase base WebControl expone una propiedad String denominada CssClass para establecer la clase de estilo:

<html><head>

<style> .calstyle { position:absolute; left:25%; top:20% } </style>

</head><body>

<form runat="server">

<h3><font face="verdana">Aplicación de estilos a controles Web</font></h3>

<p><font face="verdana"><h4>Propiedad CssClass</h4></font><p>

<ASP:Calendar CssClass="calstyle" runat="server"

BackColor="Beige" ForeColor="Brown" BorderWidth="3" BorderStyle="Solid" BorderColor="Black" Height="450" Width="450" Font-Size="12pt" Font-Name="Tahoma,Arial" Font-Underline="false" CellSpacing=2 CellPadding=2 ShowGridLines=true

TitleStyle-BorderColor="darkolivegreen" TitleStyle-BorderWidth="3" TitleStyle-BackColor="olivedrab" TitleStyle-Height="50px"

DayHeaderStyle-BorderColor="darkolivegreen" DayHeaderStyle-BorderWidth="3" DayHeaderStyle-BackColor="olivedrab" DayHeaderStyle-ForeColor="black" DayHeaderStyle-Height="20px"

DayStyle-Width="50px" DayStyle-Height="50px"

TodayDayStyle-BorderWidth="3"

Page 38: Presentación del tutorial de ASP

WeekEndDayStyle-BackColor="palegoldenrod" WeekEndDayStyle-Width="50px" WeekEndDayStyle-Height="50px"

SelectedDayStyle-BorderColor="firebrick" SelectedDayStyle-BorderWidth="3"

OtherMonthDayStyle-Width="50px" OtherMonthDayStyle-Height="50px" />

</form>

</body></html>

Si se establece un atributo en un control de servidor que no se corresponde con ninguna propiedad con establecimiento inflexible de tipos, el atributo y el valor se llenan en la colección Attributes del control. De forma predeterminada, los controles de servidor procesarán estos atributos no modificados en el HTML devuelto al cliente del explorador solicitante. Esto significa que los atributos de estilo y de clase se pueden establecer directamente en controles de servidor Web en vez de utilizar las propiedades con establecimiento inflexible de tipo. Mientras que esto requiere entender algo del procesamiento real del control, también puede constituir una forma flexible de aplicar estilos. Resulta especialmente útil con los controles estándar de entrada de formulario, tal y como se muestra en el siguiente ejemplo.

<html>

<head>

<style>

.beige { background-color:beige }

</style>

</head>

<body>

<form runat="server">

<h3><font face="verdana">Aplicación de estilos a controles Web</font></h3>

<p><font face="verdana"><h4>Estilos CSS Expando</h4></font><p>

<table style="font: 10pt verdana; background-color:tan" cellspacing=15> <tr> <td><b>Iniciar sesión: </b></td> <td><ASP:TextBox runat="server" class="beige" style="font-weight:700;"/></td> </tr> <tr> <td><b>Contraseña: </b></td> <td><ASP:TextBox TextMode="Password" runat="server" class="beige"/></td> </tr> <tr> <td><b>Seleccionar una vista: </b></td> <td> <ASP:DropDownList class="beige" runat="server"> <ASP:ListItem>Escritorio predeterminado</ASP:ListItem> <ASP:ListItem>Mi cartera de acciones</ASP:ListItem> <ASP:ListItem>Mi lista de contactos</ASP:ListItem> </ASP:DropDownList>

Page 39: Presentación del tutorial de ASP

</td> </tr> <tr> <td>&nbsp;</td> <td><ASP:Button Text="Enviar" runat="server" class="beige"/></td> </tr> </table>

</form>

</body></html>

Los estilos de controles de servidor Web también se pueden establecer mediante programación con el método ApplyStyle de la clase base

WebControl, como en el código que se muestra a continuación.

<script language="VB" runat="server"> Sub Page_Load(Src As Object, E As EventArgs) Dim MyStyle As New Style MyStyle.BorderColor = Color.Black MyStyle.BorderStyle = BorderStyle.Dashed MyStyle.BorderWidth = New Unit(1)

MyLogin.ApplyStyle (MyStyle) MyPassword.ApplyStyle (MyStyle) MySubmit.ApplyStyle (MyStyle) End Sub</script>

Login: <ASP:TextBox id="MyLogin" runat="server" />/<p/>Password: <ASP:TextBox id="MyPassword" TextMode="Password" runat="server" />View: <ASP:DropDownList id="MySelect" runat="server"> ... </ASP:DropDownList>

En el siguiente ejemplo se muestra el código anterior.

<%@ Import Namespace="System.Drawing" %>

<html>

<head>

<style>

.beige { background-color:beige }

</style>

</head>

<script language="VB" runat="server">

Sub Page_Load(Src As Object, E As EventArgs)

Dim MyStyle As Style

MyStyle = New Style() MyStyle.BorderColor = Color.Black

Page 40: Presentación del tutorial de ASP

MyStyle.BorderStyle = BorderStyle.Dashed MyStyle.BorderWidth = New Unit(1)

MyLogin.ApplyStyle (MyStyle) MyPassword.ApplyStyle (MyStyle) MySubmit.ApplyStyle (MyStyle) End Sub

</script>

<body>

<form runat="server">

<h3><font face="verdana">Aplicación de estilos a controles Web</font></h3>

<p><font face="verdana"><h4>Aplicación de estilos mediante programación</h4></font><p>

<table style="font: 10pt verdana; background-color:tan" cellspacing=15> <tr> <td><b>Iniciar sesión: </b></td> <td><ASP:TextBox id="MyLogin" runat="server" class="beige" style="font-weight:700;"/></td> </tr> <tr> <td><b>Contraseña: </b></td> <td><ASP:TextBox id="MyPassword" TextMode="Password" runat="server" class="beige"/></td> </tr> <tr> <td><b>Seleccionar una vista: </b></td> <td> <ASP:DropDownList id="MySelect" class="beige" runat="server"> <ASP:ListItem>Escritorio predeterminado</ASP:ListItem> <ASP:ListItem>Mi cartera de acciones</ASP:ListItem> <ASP:ListItem>Mi lista de contactos</ASP:ListItem> </ASP:DropDownList> </td> </tr> <tr> <td>&nbsp;</td> <td><ASP:Button id="MySubmit" Text="Enviar" runat="server" class="beige"/></td> </tr> </table>

</form>

</body></html>

Resumen de la sección

1. Los controles de servidor HTML de ASP.NET y las familias de controles de servidores Web proporcionan compatibilidad de primera clase con los estilos de CSS.

2. Los estilos pueden aplicarse estableciendo los atributos de estilo o de clase de un control. Se puede acceder a esta configuración mediante programación a través de la colección Attributes del control. En el caso de controles de servidor HTML, los valores individuales para claves de atributo de estilo se pueden recuperar desde la colección Style del control.

3. La configuración de estilo más habitual se expone en los controles de servidor Web como propiedades con establecimiento inflexible de tipos del mismo control.

4. El espacio de nombres System.Web.UI.WebControls incluye una clase base Style que encapsula atributos de estilo comunes. Muchos controles de servidor Web exponen propiedades de este tipo para controlar elementos de procesamiento individuales.

5. Los estilos se pueden establecer mediante programación en controles de servidor Web mediante el método ApplyStyle de la clase base WebControl.

Page 41: Presentación del tutorial de ASP

Validación de formularios de control de servidor

Introducción a la validación

El marco de trabajo de los formularios Web incluye un conjunto de controles de servidor de validación que proporcionan un modo sencillo a la vez que potente de comprobar errores en los formularios de entrada y, en caso necesario, mostrar mensajes al usuario.

Los controles de validación se agregan a una página de formularios Web con otros controles de servidor. Existen controles para tipos concretos de validación, como la comprobación de intervalos o la coincidencia de modelos, además de RequiredFieldValidator, que se asegura de que un usuario omita un campo de entrada. Se puede adjuntar más de un control de validación para un control de entrada. Por ejemplo, se puede especificar que se necesita una entrada y que debe contener un intervalo específico de valores.

Los controles de validación funcionan con un subconjunto limitado de controles de servidor HTML y Web. Para cada control, una propiedad específica contiene el valor que se va a validar. En la siguiente tabla se enumeran los controles de entrada que pueden validarse.

Control Propiedad de validación

HtmlInputText Value

HtmlTextArea Value

HtmlSelect Value

HtmlInputFile Value

TextBox Text

ListBox SelectedItem.Value

DropDownList SelectedItem.Value

RadioButtonList SelectedItem.Value

Tipos de controles de validación

El formulario de validación más sencillo es un campo requerido. Si el usuario introduce cualquier valor en un campo, es válido. Si todos los campos de la página son válidos, la página es válida. En el siguiente ejemplo se muestra cómo utilizar el control RequiredFieldValidator.

<html><head>

<script language="VB" runat="server">

Sub ValidateBtn_Click(sender As Object, e As EventArgs) If (Page.IsValid) Then lblOutput.Text = "Página válida" Else lblOutput.Text = "Algunos campos requeridos están vacíos" End If End Sub

</script>

</head><body>

<h3><font face="Verdana">Ejemplo simple RequiredField Validator</font></h3><p>

<form runat="server">

<table bgcolor="#eeeeee" cellpadding=10> <tr valign="top"> <td colspan=3> <asp:Label ID="lblOutput" Text="Rellenar los campos requeridos a continuación" ForeColor="red" Font-Name="Verdana" Font-Size="10" runat=server /><br>

Page 42: Presentación del tutorial de ASP

</td> </tr>

<tr> <td colspan=3> <font face=Verdana size=2><b>Información de la tarjeta de crédito</b></font> </td> </tr> <tr> <td align=right> <font face=Verdana size=2>Tipo de tarjeta:</font> </td> <td> <ASP:RadioButtonList id=RadioButtonList1 RepeatLayout="Flow" runat=server> <asp:ListItem>MasterCard</asp:ListItem> <asp:ListItem>Visa</asp:ListItem> </ASP:RadioButtonList> </td> <td align=middle rowspan=1> <asp:RequiredFieldValidator id="RequiredFieldValidator1" ControlToValidate="RadioButtonList1" Display="Static" InitialValue="" Width="100%" runat=server> * </asp:RequiredFieldValidator> </td> </tr> <tr> <td align=right> <font face=Verdana size=2>Número de tarjeta:</font> </td> <td> <ASP:TextBox id=TextBox1 runat=server /> </td> <td> <asp:RequiredFieldValidator id="RequiredFieldValidator2" ControlToValidate="TextBox1" Display="Static" Width="100%" runat=server> * </asp:RequiredFieldValidator>

</td> </tr> <tr> <td align=right> <font face=Verdana size=2>Fecha de caducidad:</font> </td> <td> <ASP:DropDownList id=DropDownList1 runat=server> <asp:ListItem></asp:ListItem> <asp:ListItem >06/00</asp:ListItem> <asp:ListItem >07/00</asp:ListItem> <asp:ListItem >08/00</asp:ListItem> <asp:ListItem >09/00</asp:ListItem> <asp:ListItem >10/00</asp:ListItem> <asp:ListItem >11/00</asp:ListItem> <asp:ListItem >01/01</asp:ListItem> <asp:ListItem >02/01</asp:ListItem> <asp:ListItem >03/01</asp:ListItem> <asp:ListItem >04/01</asp:ListItem> <asp:ListItem >05/01</asp:ListItem> <asp:ListItem >06/01</asp:ListItem>

Page 43: Presentación del tutorial de ASP

<asp:ListItem >07/01</asp:ListItem> <asp:ListItem >08/01</asp:ListItem> <asp:ListItem >09/01</asp:ListItem> <asp:ListItem >10/01</asp:ListItem> <asp:ListItem >11/01</asp:ListItem> <asp:ListItem >12/01</asp:ListItem> </ASP:DropDownList> </td> <td> <asp:RequiredFieldValidator id="RequiredFieldValidator3" ControlToValidate="DropDownList1" Display="Static" InitialValue="" Width="100%" runat=server> * </asp:RequiredFieldValidator> </td> <td> </tr> <tr> <td></td> <td> <ASP:Button id=Button1 text="Validar" OnClick="ValidateBtn_Click" runat=server /> </td> <td></td> </tr> </table>

</form>

</body></html>

También hay controles de validación para tipos de validación específicos, como la comprobación de intervalos o la coincidencia de modelos. En la siguiente tabla se enumeran los controles de validación.

Nombre del control Descripción

RequiredFieldValidator Se asegura de que el usuario no omita ninguna entrada.

CompareValidator Compara una entrada de usuario con un valor constante o un valor de propiedad de otro control mediante un operador de comparación (menor que, igual a, mayor qué, entre otros).

RangeValidator Comprueba que una entrada de usuario se encuentra entre los límites superior e inferior especificados. Se pueden comprobar los intervalos entre pares de números, caracteres alfabéticos o fechas. Los límites se pueden expresar como constantes.

RegularExpressionValidator Comprueba que la entrada coincide con un patrón definido por una expresión regular. Este tipo de validación permite comprobar secuencias de caracteres previsibles, como las de los números de la seguridad social, las direcciones de correo electrónico, los números de teléfono y los códigos postales, entre otras.

CustomValidator Comprueba la entrada del usuario mediante lógica de validación que codifica el usuario. Este tipo de validación permite comprobar los valores derivados en tiempo de ejecución.

ValidationSummary Muestra los errores de validación en forma de resumen para todos los validadores de la página.

Validación en el cliente

Los controles de validación siempre realizan una comprobación de validación en el código del servidor. No obstante, si el usuario trabaja con un explorador que admite DHTML, los controles de validación también pueden realizar validaciones mediante secuencias de comandos del cliente. Con validación en el cliente, se detectan algunos errores en el cliente cuando se envía el formulario al servidor. Si resulta que alguno de los validadores es un error, se cancela el envío del formulario al servidor y se muestra la propiedad Text del validador. Esto permite al usuario corregir lo escrito antes de enviar el formulario al servidor. Los valores de campo se revalidan en cuanto el campo que contiene el error pierde el foco, proporcionando así al usuario una rica experiencia de validación interactiva.

Page 44: Presentación del tutorial de ASP

Debe observarse que el marco de trabajo de la página de formularios Web siempre realiza la validación en el servidor, incluso cuando ya se ha realizado en el cliente. De esta forma, se evita que los usuarios puedan omitir la validación mediante la suplantación de otro usuario o de una transacción previamente aprobada.

La validación en el cliente se ha habilitado de forma predeterminada. Si el cliente es capaz, la validación de alto nivel se realiza automáticamente. Para deshabilitar la validación en el cliente, establezca la propiedad ClientTarget de la página en "Downlevel" ("Uplevel" fuerza la validación en el cliente).

<%@ Page ClientTarget=UpLevel %>

<html><head>

<script language="VB" runat="server">

Sub Page_Load If Not IsPostBack 'Validate intially to force *s to appear before the first round-trip Validate() End If End Sub

Sub ValidateBtn_Click(sender As Object, e As EventArgs) If (Page.IsValid) Then lblOutput.Text = "Página válida" Else lblOutput.Text = "Algunos campos requeridos están vacíos" End If End Sub

</script>

</head><body>

<h3><font face="Verdana">Ejemplo RequiredFieldValidator de cliente</font></h3><p>

<form runat="server">

<table bgcolor="#eeeeee" cellpadding=10> <tr valign="top"> <td colspan=3> <asp:Label ID="lblOutput" Name="lblOutput" Text="Rellenar los campos requeridos a continuación" ForeColor="red" Font-Name="Verdana" Font-Size="10" runat=server /><br> </td> </tr>

<tr> <td colspan=3> <font face=Verdana size=2><b>Información de la tarjeta de crédito</b></font> </td> </tr> <tr> <td align=right> <font face=Verdana size=2>Tipo de tarjeta:</font> </td> <td> <ASP:RadioButtonList id=RadioButtonList1 RepeatLayout="Flow" onclick="ClientOnChange();" runat=server> <asp:ListItem>MasterCard</asp:ListItem>

Page 45: Presentación del tutorial de ASP

<asp:ListItem>Visa</asp:ListItem> </ASP:RadioButtonList> </td> <td align=middle rowspan=1> <asp:RequiredFieldValidator id="RequiredFieldValidator1" runat="server" ControlToValidate="RadioButtonList1" ErrorMessage="*" Display="Static" InitialValue="" Width="100%"> </asp:RequiredFieldValidator> </td> </tr> <tr> <td align=right> <font face=Verdana size=2>Número de tarjeta:</font> </td> <td> <ASP:TextBox id=TextBox1 onchange="ClientOnChange();" runat=server /> </td> <td> <asp:RequiredFieldValidator id="RequiredFieldValidator2" runat="server" ControlToValidate="TextBox1" ErrorMessage="*" Display="Static" Width="100%"> </asp:RequiredFieldValidator>

</td> </tr> <tr> <td align=right> <font face=Verdana size=2>Fecha de caducidad:</font> </td> <td> <ASP:DropDownList id=DropDownList1 onchange="ClientOnChange();" runat=server> <asp:ListItem></asp:ListItem> <asp:ListItem >06/00</asp:ListItem> <asp:ListItem >07/00</asp:ListItem> <asp:ListItem >08/00</asp:ListItem> <asp:ListItem >09/00</asp:ListItem> <asp:ListItem >10/00</asp:ListItem> <asp:ListItem >11/00</asp:ListItem> <asp:ListItem >01/01</asp:ListItem> <asp:ListItem >02/01</asp:ListItem> <asp:ListItem >03/01</asp:ListItem> <asp:ListItem >04/01</asp:ListItem> <asp:ListItem >05/01</asp:ListItem> <asp:ListItem >06/01</asp:ListItem> <asp:ListItem >07/01</asp:ListItem> <asp:ListItem >08/01</asp:ListItem> <asp:ListItem >09/01</asp:ListItem> <asp:ListItem >10/01</asp:ListItem> <asp:ListItem >11/01</asp:ListItem> <asp:ListItem >12/01</asp:ListItem> </ASP:DropDownList> </td> <td> <asp:RequiredFieldValidator id="RequiredFieldValidator3" runat="server" ControlToValidate="DropDownList1" ErrorMessage="*" Display="Static" InitialValue=""

Page 46: Presentación del tutorial de ASP

Width="100%"> </asp:RequiredFieldValidator> </td> <td> </tr> <tr> <td></td> <td> <ASP:Button id=Button1 text="Validar" OnClick="ValidateBtn_Click" runat="server" /> </td> <td></td> </tr> </table>

</form>

<script language=javascript><!-- function ClientOnChange() { if (typeof(Page_Validators) == "sin definir") return; document.all["lblOutput"].innerText = Page_IsValid ? "Página válida" : "Algunos campos requeridos están vacíos"; }// --></script>

</body></html>

Visualizar errores de validación

Cuando se procesa la entrada del usuario (por ejemplo, cuando se envía el formulario), el marco de trabajo de la página de formularios Web pasa la entrada del usuario al control o controles de validación asociados. Los controles de validación comprueban la entrada del usuario y establecen una propiedad para indicar si la entrada pasó la prueba de validación. Tras haber procesado todos los controles de validación, se establece la propiedad IsValid de la página; si cualquiera de los controles muestra que se produjo un error en la prueba de la validación, toda la página se establecerá como no válida.

Si un control de validación es un error, un mensaje de error puede aparecer en la página por ese control de validación o en un control ValidationSummary en cualquier otra parte de la página. Se visualiza el control ValidationSummary cuando la propiedad IsValid de la página es false. Sondea cada control de validación de la página y agrega mensajes de texto expuestos por cada uno. En el siguiente ejemplo se muestra cómo visualizar errores con ValidationSummary.

<%@ Page clienttarget=downlevel %>

<html><head>

<script language="VB" runat="server">

Sub ListFormat_SelectedIndexChanged(sender As Object, e As EventArgs)

' Change display mode of the validator summary when a new option ' is selected from the "ListFormat" dropdownlist

valSum.DisplayMode = ListFormat.SelectedIndex End Sub

</script>

Page 47: Presentación del tutorial de ASP

</head><body>

<h3><font face="Verdana">Ejemplo ValidationSummary</font></h3><p>

<form runat="server">

<table cellpadding=10> <tr> <td> <table bgcolor="#eeeeee" cellpadding=10>

<tr> <td colspan=3> <font face=Verdana size=2><b>Información de la tarjeta de crédito</b></font> </td> </tr> <tr> <td align=right> <font face=Verdana size=2>Tipo de tarjeta:</font> </td> <td> <ASP:RadioButtonList id=RadioButtonList1 RepeatLayout="Flow" runat=server> <asp:ListItem>MasterCard</asp:ListItem> <asp:ListItem>Visa</asp:ListItem> </ASP:RadioButtonList> </td> <td align=middle rowspan=1> <asp:RequiredFieldValidator id="RequiredFieldValidator1" ControlToValidate="RadioButtonList1" ErrorMessage="Tipo de tarjeta. " Display="Static" InitialValue="" Width="100%" runat=server> * </asp:RequiredFieldValidator> </td> </tr> <tr> <td align=right> <font face=Verdana size=2>Número de tarjeta:</font> </td> <td> <ASP:TextBox id=TextBox1 runat=server /> </td> <td> <asp:RequiredFieldValidator id="RequiredFieldValidator2" ControlToValidate="TextBox1" ErrorMessage="Número de tarjeta. " Display="Static" Width="100%" runat=server> * </asp:RequiredFieldValidator>

</td> </tr> <tr> <td align=right> <font face=Verdana size=2>Fecha de caducidad:</font> </td> <td> <ASP:DropDownList id=DropDownList1 runat=server> <asp:ListItem></asp:ListItem>

Page 48: Presentación del tutorial de ASP

<asp:ListItem >06/00</asp:ListItem> <asp:ListItem >07/00</asp:ListItem> <asp:ListItem >08/00</asp:ListItem> <asp:ListItem >09/00</asp:ListItem> <asp:ListItem >10/00</asp:ListItem> <asp:ListItem >11/00</asp:ListItem> <asp:ListItem >01/01</asp:ListItem> <asp:ListItem >02/01</asp:ListItem> <asp:ListItem >03/01</asp:ListItem> <asp:ListItem >04/01</asp:ListItem> <asp:ListItem >05/01</asp:ListItem> <asp:ListItem >06/01</asp:ListItem> <asp:ListItem >07/01</asp:ListItem> <asp:ListItem >08/01</asp:ListItem> <asp:ListItem >09/01</asp:ListItem> <asp:ListItem >10/01</asp:ListItem> <asp:ListItem >11/01</asp:ListItem> <asp:ListItem >12/01</asp:ListItem> </ASP:DropDownList> </td> <td> <asp:RequiredFieldValidator id="RequiredFieldValidator3" ControlToValidate="DropDownList1" ErrorMessage="Fecha de caducidad. " Display="Static" InitialValue="" Width="100%" runat=server> * </asp:RequiredFieldValidator> </td> <td> </tr> <tr> <td></td> <td> <ASP:Button id=Button1 text="Validar" runat=server /> </td> <td></td> </tr> </table> </td> <td valign=top> <table cellpadding=20><tr><td> <asp:ValidationSummary ID="valSum" runat="server" HeaderText="Escribir un valor en los siguientes campos:" Font-Name="verdana" Font-Size="12" /> </td></tr></table> </td> </tr></table>

<font face="verdana" size="-1">Seleccionar el tipo de presentación de resumen de validación que desee: </font>

<asp:DropDownList id="ListFormat" AutoPostBack=true OnSelectedIndexChanged="ListFormat_SelectedIndexChanged" runat=server > <asp:ListItem>Lista</asp:ListItem> <asp:ListItem selected>Lista con viñetas</asp:ListItem> <asp:ListItem>Un párrafo</asp:ListItem></asp:DropDownList>

Page 49: Presentación del tutorial de ASP

</form>

</body></html>

Trabajar con CompareValidator

El control de servidor CompareValidator compara los valores de dos controles. CompareValidator utiliza tres propiedades clave para realizar la validación. ControlToValidate y ControlToCompare contienen los valores que se van a comparar. Operator define el tipo de comparación que se va a realizar, como por ejemplo Equal o Not Equal. CompareValidator realiza la validación mediante la evaluación de estas propiedades como expresión, tal y como se muestra a continuación:

( ControlToValidate ControlToCompare ) Si la expresión se evalúa como true, el resultado de la validación es válido.

El siguiente ejemplo muestra cómo utilizar el control CompareValidator.

<%@ Page clienttarget=downlevel %>

<html><head>

<script language="VB" runat="server">

Sub Button1_OnSubmit(sender As Object, e As EventArgs) If (Page.IsValid) Then lblOutput.Text = "Resultado: válido" Else lblOutput.Text = "Resultado: no válido" End If End Sub

Sub lstOperator_SelectedIndexChanged(sender As Object, e As EventArgs) comp1.Operator = lstOperator.SelectedIndex comp1.Validate End Sub

</script>

</head><body>

<h3><font face="Verdana">Ejemplo CompareValidator</font></h3> <p>Escribir un valor en el cuadro de texto, seleccionar un operador de comparación y, a continuación, hacer clic en "Validar" para probar.</p>

<form runat=server>

<table bgcolor="#eeeeee" cellpadding=10> <tr valign="top"> <td> <h5><font face="Verdana">Cadena 1:</font></h5> <asp:TextBox id="txtComp" runat="server"></asp:TextBox> </td> <td> <h5><font face="Verdana">Operador de comparación:</font></h5>

<asp:ListBox id="lstOperator" OnSelectedIndexChanged="lstOperator_SelectedIndexChanged" runat="server"> <asp:ListItem Selected Value="Equal" >Equal</asp:ListItem> <asp:ListItem Value="NotEqual" >NotEqual</asp:ListItem> <asp:ListItem Value="GreaterThan" >GreaterThan</asp:ListItem>

Page 50: Presentación del tutorial de ASP

<asp:ListItem Value="GreaterThanEqual" >GreaterThanEqual</asp:ListItem> <asp:ListItem Value="LessThan" >LessThan</asp:ListItem> <asp:ListItem Value="LessThanEqual" >LessThanEqual</asp:ListItem> </asp:ListBox> </td> <td> <h5><font face="Verdana">Cadena 2:</font></h5> <asp:TextBox id="txtCompTo" runat="server"></asp:TextBox><p> <asp:Button runat=server Text="Validar" ID="Button1" onclick="Button1_OnSubmit" /> </td> </tr> </table>

<asp:CompareValidator id="comp1" ControlToValidate="txtComp" ControlToCompare = "txtCompTo" Type="String" runat="server"/>

<br>

<asp:Label ID="lblOutput" Font-Name="verdana" Font-Size="10pt" runat="server"/>

</form>

</body></html>

Trabajar con RangeValidator

El control de servidor RangeValidator prueba si un valor de entrada produce un error en un intervalo dado. CompareValidator utiliza tres propiedades clave para realizar la validación. ControlToValidate contiene el valor que hay que validar. MinimumValue y MaximumValue definen los valores máximo y mínimo del intervalo válido.

En el siguiente ejemplo se muestra cómo utilizar el control RangeValidator.

<%@ Page clienttarget=downlevel %>

<html><head>

<script language="VB" runat="server">

Sub Button1_Click(sender As Object, e As EventArgs) rangeValInteger.Validate() If (rangeValInteger.IsValid) Then lblOutput1.Text = "Resultado: válido" Else lblOutput1.Text = "Resultado: no válido" End If

rangeValDate.Validate() If (rangeValDate.IsValid) Then lblOutput2.Text = "Resultado: válido" Else lblOutput2.Text = "Resultado: no válido" End If

rangeValString.Validate() If (rangeValString.IsValid) Then lblOutput3.Text = "Resultado: válido" Else lblOutput3.Text = "Resultado: no válido" End If

Page 51: Presentación del tutorial de ASP

If (Page.IsValid) Then lblOutput.Text = "Resultado: página válida" Else lblOutput.Text = "Resultado: página no válida" End If End Sub

</script>

</head><body>

<h3><font face="Verdana">Ejemplo RangeValidator</font></h3> <p>

<form runat="server">

<table bgcolor="#eeeeee" cellpadding=10> <tr valign="top"> <td> <h5><font face="Verdana">Valor para comprobar:</font></h5> <asp:TextBox id="txtComp1" runat="server"/> </td> <td> <h5><font face="Verdana">Tipo de datos: entero mín.(1), máx(10)</font></h5> </td> <td> <asp:Label id="lblOutput1" Font-Name="verdana" Font-Size="10pt" runat="server" /> </td> </tr> <tr valign="top"> <td> <h5><font face="Verdana">Valor para comprobar:</font></h5> <asp:TextBox id="txtComp2" runat="server"/> </td> <td> <h5><font face="Verdana">Tipo de datos: fecha mín.(2000/1/1), máx.(2001/1/1)</font></h5> </td> <td> <asp:Label id="lblOutput2" Font-Name="verdana" Font-Size="10pt" runat="server" /> </td> </tr> <tr valign="top"> <td> <h5><font face="Verdana">Valor para comprobar:</font></h5> <asp:TextBox id="txtComp3" runat="server"/> </td> <td> <h5><font face="Verdana">Tipo de datos: Mín.(Cerdo hormiguero), máx.(Cebra)</font></h5> </td> <td> <asp:Label id="lblOutput3" Font-Name="verdana" Font-Size="10pt" runat="server" /> </td> </tr> </table>

<asp:Button Text="Validar" ID="Button1" onclick="Button1_Click" runat="server" />

<asp:RangeValidator id="rangeValInteger" Type="Integer" ControlToValidate="txtComp1"

Page 52: Presentación del tutorial de ASP

MaximumValue="10" MinimumValue="1" runat="server"/>

<asp:RangeValidator id="rangeValDate" Type="Date" ControlToValidate="txtComp2" MaximumValue="2001/1/1" MinimumValue="2000/1/1" runat="server"/>

<asp:RangeValidator id="rangeValString" Type="String" ControlToValidate="txtComp3" MaximumValue="Zebra" MinimumValue="Aardvark" runat="server"/> <br>

<asp:Label id="lblOutput" Font-Name="verdana" Font-Size="10pt" runat="server" />

</form>

</body></html>

Trabajar con expresiones regulares

El control de servidor RegularExpressionValidator confirma que la entrada coincide con un patrón definido por una expresión regular. Este tipo de validación permite comprobar secuencias de caracteres previsibles, como las de los números de la seguridad social, las direcciones de correo electrónico, los números de teléfono y los códigos postales, entre otras.

RegularExpressionValidator utiliza dos propiedades clave para realizar la validación. ControlToValidate contiene el valor que hay que validar. ValidationExpression contiene la expresión regular con la que debe coincidir.

En el siguiente ejemplo se muestra cómo utilizar el control RegularExpressionValidator.

<html><head>

<script language="VB" runat="server">

Sub ValidateBtn_Click(sender As Object, e As EventArgs) If (Page.IsValid) Then lblOutput.Text = "Página válida" Else lblOutput.Text = "Página no válida :-(" End If End Sub

</script>

</head><body>

<h3><font face="Verdana">Ejemplo simple RegularExpressionValidator</font></h3><p>

<form runat="server">

Page 53: Presentación del tutorial de ASP

<table bgcolor="#eeeeee" cellpadding=10> <tr valign="top"> <td colspan=3> <asp:Label ID="lblOutput" Text="Escribir código postal con 5 dígitos" Font-Name="Verdana" Font-Size="10pt" runat="server"/> </td> </tr>

<tr> <td colspan=3> <font face=Verdana size=2><b>Información personal</b></font> </td> </tr> <tr> <td align=right> <font face=Verdana size=2>Código postal:</font> </td> <td> <ASP:TextBox id=TextBox1 runat=server /> </td> <td> <asp:RegularExpressionValidator id="RegularExpressionValidator1" runat="server" ControlToValidate="TextBox1" ValidationExpression="^\d{5}$" Display="Static" Font-Name="verdana" Font-Size="10pt"> El código postal debe tener 5 dígitos numéricos </asp:RegularExpressionValidator> </td> </tr> <tr> <td></td> <td> <ASP:Button text="Validar" OnClick="ValidateBtn_Click" runat=server /> </td> <td></td> </tr> </table>

</form>

</body></html>

<%@ Page clienttarget="downlevel" %>

<html><head>

<script language="VB" runat="server">

Sub ValidateBtn_Click(sender As Object, e As EventArgs) If (Page.IsValid) Then lblOutput.Text = "Página válida" Else lblOutput.Text = "Página no válida :-(" End If End Sub

Page 54: Presentación del tutorial de ASP

</script>

</head><body>

<h3><font face="Verdana">Más ejemplos de expresiones regulares</font></h3><p>

<form runat="server">

<table cellpadding=10> <tr valign="top"> <td colspan=3> <asp:Label ID="lblOutput" Text="Escribir valores para cada campo" Font-Name="Verdana" Font-Size="10pt" runat="server" /> </td> </tr>

<tr> <td colspan=3> <font face=Verdana size=2><b>Información personal</b></font> </td> </tr> <tr> <td align=right> <font face=Verdana size=2>Correo electrónico:</font> </td> <td> <ASP:TextBox id=TextBox1 runat=server /> </td> <td> <asp:RequiredFieldValidator id="RequiredFieldValidator1" runat="server" ControlToValidate="TextBox1" Display="Dynamic" Font-Name="Verdana" Font-Size="10pt" > * </asp:RequiredFieldValidator>

<asp:RegularExpressionValidator id="RegularExpressionValidator1" runat="server" ControlToValidate="TextBox1" ValidationExpression="^[\w-]+@[\w-]+\.(com|net|org|edu|mil)$" Display="Static" Font-Name="verdana" Font-Size="10pt"> Escribir una dirección de correo electrónico válida </asp:RegularExpressionValidator> </td> </tr> <tr> <td align=right> <font face=Verdana size=2>Teléfono:</font> </td> <td> <ASP:TextBox id=TextBox2 runat=server /> </td> <td> <asp:RequiredFieldValidator id="RequiredFieldValidator2" runat="server" ControlToValidate="TextBox2" Display="Dynamic" Font-Name="Verdana" Font-Size="10pt"> * </asp:RequiredFieldValidator>

Page 55: Presentación del tutorial de ASP

<asp:RegularExpressionValidator id="RegularExpressionValidator2" ControlToValidate="TextBox2" ValidationExpression="(^x\s*[0-9]{5}$)|(^(\([1-9][0-9]{2}\)\s)?[1-9][0-9]{2}-[0-9]{4}(\sx\s*[0-9]{5})?$)" Display="Static" Font-Name="verdana" Font-Size="10pt" runat=server> Debe tener el siguiente formato: (XXX) XXX-XXXX </asp:RegularExpressionValidator> </td> </tr> <tr> <td align=right> <font face=Verdana size=2>Código postal:</font> </td> <td> <ASP:TextBox id=TextBox3 runat=server /> </td> <td> <asp:RequiredFieldValidator id="RequiredFieldValidator3" runat="server" ControlToValidate="TextBox3" Display="Dynamic" Font-Name="Verdana" Font-Size="10pt"> * </asp:RequiredFieldValidator>

<asp:RegularExpressionValidator id="RegularExpressionValidator3" ControlToValidate="TextBox3" ValidationExpression="^\d{5}$" Display="Static" Width="100%" Font-Name="verdana" Font-Size="10pt" runat=server> El código postal debe tener 5 dígitos numéricos </asp:RegularExpressionValidator> </td> </tr> <tr> <td></td> <td> <ASP:Button text="Validar" OnClick="ValidateBtn_Click" runat=server /> </td> <td></td> </tr> </table>

</form>

</body></html>

Realizar validaciones personalizadas

El control de servidor CustomValidator llama a una función definida por el usuario para realizar validaciones que los validadores estándar no pueden controlar. La función personalizada puede ejecutarse en el servidor o en la secuencia de comandos del cliente, como JScript o VBScript. Para una validación personalizada en el cliente, el nombre de la función personalizada debe identificarse en la propiedad ClientValidationFunction. La función personalizada debe presentar la siguiente estructura:

function myvalidator(source, arguments)Debe observarse que source es el objeto CustomValidator del cliente, mientras que arguments es un objeto con dos propiedades, Value e IsValid. La propiedad Value es el valor que se validará y la propiedad IsValid es el booleano utilizado para establecer el resultado devuelto de la validación.

Page 56: Presentación del tutorial de ASP

Para obtener la validación personalizada del servidor, coloque la validación personalizada en el delegado OnServerValidate del validador.

En el siguiente ejemplo se muestra cómo utilizar el control CustomValidator.

<html><head>

<script language="VB" runat="server">

Sub ValidateBtn_OnClick(sender As Object, e As EventArgs) If (Page.IsValid) Then lblOutput.Text = "Página válida" Else lblOutput.Text = "Página no válida :-(" End If End Sub

Sub ServerValidate (sender As Object, value As ServerValidateEventArgs) Try Dim num As Int32 = Int32.Parse(value.Value) If num Mod 2 = 0 Then value.IsValid = True Exit Sub End If Catch exc As Exception End Try

value.IsValid = False End Sub

</script>

</head><body>

<h3><font face="Verdana">Ejemplo CustomValidator</font></h3><p>

<form runat="server">

<asp:Label id=lblOutput runat="server" Text="Escribir número par:" Font-Name="Verdana" Font-Size="10pt" /><br>

<p>

<asp:TextBox id=Text1 runat="server" />

<asp:RequiredFieldValidator id="RequiredFieldValidator1" runat="server" ControlToValidate="Text1" ErrorMessage="Escribir un número" Display="Dynamic" Font-Name="verdana" Font-Size="10pt"> </asp:RequiredFieldValidator>

<asp:CustomValidator id="CustomValidator1" runat="server" ControlToValidate="Text1"

ClientValidationFunction="ClientValidate" OnServerValidate="ServerValidate" Display="Static" Font-Name="verdana" Font-Size="10pt"> No es un número par </asp:CustomValidator>

Page 57: Presentación del tutorial de ASP

<p>

<asp:Button text="Validar" onclick="ValidateBtn_OnClick" runat="server" />

<script language="javascript">

function ClientValidate(source, arguments) { // even number? if (arguments.Value%2 == 0) arguments.IsValid = true; else arguments.IsValid = false; }

</script>

</form>

</body></html>

Combinarlo todo

En este ejemplo se muestra un formulario de registro típico con las variaciones de controles de validación comentadas en este tema.

<%@ Page Language="VB" %>

<html>

<body>

<h3><font face="Verdana">Iniciar sesión del ejemplo del formulario de validación</font></h3>

<form method=post runat=server> <hr width=600 size=1 noshade>

<center> <asp:ValidationSummary ID="valSum" runat="server" HeaderText="Debe escribir un valor válido en los siguientes campos:" DisplayMode="SingleParagraph" Font-Name="verdana" Font-Size="12" /> <p>

<!-- sign-in --> <table border=0 width=600> <tr><td colspan=3> <table border=0 cellpadding=0 cellspacing=0 width="100%"> <tr><td> <font face=geneva,arial size=-1><b>Información de inicio de sesión</b></font> </td></tr> </table> </td></tr> <tr> <td align=right>

Page 58: Presentación del tutorial de ASP

<font face=Arial size=2>Dirección de correo electrónico:</font> </td> <td> <asp:TextBox id=email width=200px maxlength=60 runat=server /> </td> <td> <asp:RequiredFieldValidator id="emailReqVal" ControlToValidate="email" ErrorMessage="Correo electrónico. " Display="Dynamic" Font-Name="Verdana" Font-Size="12" runat=server> * </asp:RequiredFieldValidator> <asp:RegularExpressionValidator id="emailRegexVal" ControlToValidate="email" ErrorMessage="Correo electrónico. " Display="Static" ValidationExpression="^[\w-]+@[\w-]+\.(com|net|org|edu|mil)$" Font-Name="Arial" Font-Size="11" runat=server> La dirección de correo electrónico no es válida, debe tener el siguiente formato: correo_electró[email protected]. </asp:RegularExpressionValidator> </td> </tr>

<tr> <td align=right> <font face=Arial size=2>Contraseña:</font> </td> <td> <asp:TextBox id=passwd TextMode="Password" maxlength=20 runat=server/> </td> <td> <asp:RequiredFieldValidator id="passwdReqVal" ControlToValidate="passwd" ErrorMessage="Contraseña. " Display="Dynamic" Font-Name="Verdana" Font-Size="12" runat=server> * </asp:RequiredFieldValidator> <asp:RegularExpressionValidator id="passwdRegexBal" ControlToValidate="passwd" ErrorMessage="Contraseña. " ValidationExpression=".*[!@#$%^&*+;:].*" Display="Static" Font-Name="Arial" Font-Size="11" Width="100%" runat=server> La contraseña debe incluir uno de los siguientes caracteres (!@#$%^&amp;*+;:) </asp:RegularExpressionValidator> </td> </tr> <tr> <td align=right> <font face=Arial size=2>Volver a escribir la contraseña:</font> </td> <td> <asp:TextBox id=passwd2 TextMode="Password" maxlength=20 runat=server/> </td> <td> <asp:RequiredFieldValidator id="passwd2ReqVal"

Page 59: Presentación del tutorial de ASP

ControlToValidate="passwd2" ErrorMessage="Vuelva a escribir la contraseña. " Display="Dynamic" Font-Name="Verdana" Font-Size="12" runat=server> * </asp:RequiredFieldValidator> <asp:CompareValidator id="CompareValidator1" ControlToValidate="passwd2" ControlToCompare="passwd" ErrorMessage="Vuelva a escribir la contraseña. " Display="Static" Font-Name="Arial" Font-Size="11" runat=server> Los campos de contraseña no coinciden </asp:CompareValidator> </td> </tr> <tr><td colspan=3>&nbsp;</td></tr>

<!-- personalization information --> <tr><td colspan=3> <table border=0 cellpadding=0 cellspacing=0 width="100%"> <tr><td><font face=geneva,arial size=-1> <b>Información personal</b></font> </td></tr> </table> </td></tr> <tr> <td align=right> <font face=Arial size=2>Nombre:</font> </td> <td> <asp:TextBox id=fn maxlength=20 width=200px runat=server /> </td> <td> </td> </tr> <tr> <td align=right> <font face=Arial size=2>Apellido:</font> </td> <td> <asp:TextBox id=ln maxlength=40 width=200px runat=server /> </td> <td> </td> </tr> <tr> <td align=right> <font face=Arial size=2>Dirección:</font> </td> <td> <asp:TextBox id=address width=200px runat=server /> </td> <td> </td> </tr> <tr> <td align=right> <font face=Arial size=2>Estado:</font> </td> <td>

Page 60: Presentación del tutorial de ASP

<asp:TextBox id=state width=30px maxlength=2 runat=server />&nbsp; <font face=Arial size=2>Código postal:</font>&nbsp; <ASP:TextBox id=zip width=60px maxlength=5 runat=server /> </td> <td> <asp:RegularExpressionValidator id="RegularExpressionValidator1" ControlToValidate="zip" ErrorMessage="Código postal. " ValidationExpression="^\d{5}$" Display="Static" Font-Name="Arial" Font-Size="11" runat=server> El código postal debe tener 5 dígitos numéricos </asp:RegularExpressionValidator> </td> </tr> <tr> <td align=right> <font face=Arial size=2>Teléfono:</font> </td> <td> <asp:TextBox id="phone" maxlength=20 runat="server" /> </td> <td> <asp:RequiredFieldValidator id="phoneReqVal" ControlToValidate="phone" ErrorMessage="Teléfono. " Display="Dynamic" Font-Name="Verdana" Font-Size="12" runat=server> * </asp:RequiredFieldValidator> <asp:RegularExpressionValidator id="phoneRegexVal" ControlToValidate="phone" ErrorMessage="Teléfono. " ValidationExpression="(^x\s*[0-9]{5}$)|(^(\([1-9][0-9]{2}\)\s)?[1-9][0-9]{2}-[0-9]{4}(\sx\s*[0-9]{5})?$)" Display="Static" Font-Name="Arial" Font-Size="11" runat=server> Debe tener el siguiente formato: (XXX) XXX-XXXX </asp:RegularExpressionValidator> </td> </tr> <tr><td colspan=3>&nbsp;</td></tr>

<!-- Credit Card Info --> <tr> <td colspan=3> <font face=Arial size=2><b>Información de la tarjeta de crédito</b></font> </td> </tr> <tr> <td align=right> <font face=Arial size=2>Tipo de tarjeta:</font> </td> <td> <ASP:RadioButtonList id=ccType Font-Name="Arial" RepeatLayout="flow" runat=server> <asp:ListItem>MasterCard</asp:ListItem> <asp:ListItem>Visa</asp:ListItem> </ASP:RadioButtonList> </td> <td> <asp:RequiredFieldValidator id="ccTypeReqVal"

Page 61: Presentación del tutorial de ASP

ControlToValidate="ccType" ErrorMessage="Tipo de tarjeta. " Display="Static" InitialValue="" Font-Name="Verdana" Font-Size="12" runat=server> * </asp:RequiredFieldValidator> </td> </tr> <tr> <td align=right> <font face=Arial size=2>Número de tarjeta:</font> </td> <td> <ASP:TextBox id="ccNum" runat=server /> </td> <td> <asp:RequiredFieldValidator id="ccNumReqVal" ControlToValidate="ccNum" ErrorMessage="Número de tarjeta. " Display="Dynamic" Font-Name="Verdana" Font-Size="12" runat=server> * </asp:RequiredFieldValidator> <asp:CustomValidator id="ccNumCustVal" ControlToValidate="ccNum" ErrorMessage="Número de tarjeta. " clientvalidationfunction="ccClientValidate" Display="Static" Font-Name="Arial" Font-Size="11" runat=server> El número de la tarjeta de crédito no es válido, debe contener 16 dígitos. </asp:CustomValidator> </td> </tr> <tr> <td align=right> <font face=Arial size=2>Fecha de caducidad:</font> </td> <td> <ASP:DropDownList id=expDate runat=server> <asp:ListItem></asp:ListItem> <asp:ListItem >06/00</asp:ListItem> <asp:ListItem >07/00</asp:ListItem> <asp:ListItem >08/00</asp:ListItem> <asp:ListItem >09/00</asp:ListItem> <asp:ListItem >10/00</asp:ListItem> <asp:ListItem >11/00</asp:ListItem> <asp:ListItem >01/01</asp:ListItem> <asp:ListItem >02/01</asp:ListItem> <asp:ListItem >03/01</asp:ListItem> <asp:ListItem >04/01</asp:ListItem> <asp:ListItem >05/01</asp:ListItem> <asp:ListItem >06/01</asp:ListItem> <asp:ListItem >07/01</asp:ListItem> <asp:ListItem >08/01</asp:ListItem> <asp:ListItem >09/01</asp:ListItem> <asp:ListItem >10/01</asp:ListItem> <asp:ListItem >11/01</asp:ListItem> <asp:ListItem >12/01</asp:ListItem> </ASP:DropDownList>

Page 62: Presentación del tutorial de ASP

</td> <td> <asp:RequiredFieldValidator id="expDateReqVal" ControlToValidate="expDate" ErrorMessage="Fecha de caducidad. " Display="Static" InitialValue="" Font-Name="Verdana" Font-Size="12" runat=server> * </asp:RequiredFieldValidator> </td> </tr> </table>

<p> <input runat="server" type=submit value="Inicio de sesión"> <p>

<hr width=600 size=1 noshade>

<script language="javascript">

function ccClientValidate(source, arguments) { var cc = arguments.Value; var ccSansSpace; var i, digits, total;

// SAMPLE ONLY. Not a real world actual credit card algo. // Based on ANSI X4.13, the LUHN formula (also known as the modulus 10 -- or mod 10 -- algorithm ) // is used to generate and/or validate and verify the accuracy of some credit-card numbers.

// Get the number, parse out any non digits, should have 16 left ccSansSpace = cc.replace(/\D/g, ""); if(ccSansSpace.length != 16) { arguments.IsValid = false; return; // invalid ccn }

// Convert to array of digits digits = new Array(16); for(i=0; i<16; i++) digits[i] = Number(ccSansSpace.charAt(i));

// Double & sum digits of every other number for(i=0; i<16; i+=2) { digits[i] *= 2; if(digits[i] > 9) digits[i] -= 9; }

// Sum the numbers total = 0; for(i=0; i<16; i++) total += digits[i];

// Results if( total % 10 == 0 ) { arguments.IsValid = true; return; // valid ccn } else { arguments.IsValid = false; return; // invalid ccn

Page 63: Presentación del tutorial de ASP

} }

</script></form></center>

</body></html>

Resumen de la sección

1. Los controles de validación se pueden utilizar para validar entradas en cualquier página Web. 2. Se puede utilizar más de un control en un campo de entrada dado. 3. La validación en el cliente se puede utilizar junto con la validación en el servidor para mejorar las prestaciones del formulario. 4. El control CustomValidator permite que el usuario defina criterios de validación personalizados.

Controles de usuario de formularios Web

Introducción a los controles de usuario

Además de los controles de servidor integrados que proporciona ASP.NET, se pueden definir fácilmente controles propios mediante las mismas técnicas de programación que ya conoce el usuario para escribir páginas de formularios Web. De hecho, con sólo unas pocas modificaciones, se puede volver a utilizar casi cualquier página de formularios Web en otra página como control de servidor (debe tenerse en cuenta que un control de usuario es de tipo System.Web.UI.UserControl, que se hereda directamente desde System.Web.UI.Control). Una página de formularios Web utilizada como un control de servidor recibe el nombre de control de usuario para abreviar. Como convención, se utiliza la extensión .ascx para indicar dichos controles. De esta forma, se evita que el archivo del control de usuario pueda ejecutarse como una página de formularios Web independiente (más adelante se verá brevemente que existen unas pocas diferencias, aunque importantes, entre un control de usuario y una página de formularios Web). Los controles de usuario se incluyen en una página de formularios mediante una directiva Register:

<%@ Register TagPrefix="Acme" TagName="Message" Src="pagelet1.ascx" %>

TagPrefix determina un único espacio de nombres para el control de usuario (de forma que los controles de múltiples usuarios con el mismo nombre pueden diferenciarse entre sí). TagName es el único nombre del control de usuario (se puede elegir cualquier nombre). El atributo Src es la ruta de acceso virtual al control de usuario, por ejemplo, "MyPagelet.ascx" o "/MyApp/Include/MyPagelet.ascx". Después de registrar el control de usuario, se puede colocar la etiqueta de control de usuario en la página de formularios Web del mismo modo que se haría para un control de servidor ordinario (incluido el atributo runat="server"):

<Acme:Message runat="server"/>

En el siguiente ejemplo se muestra cómo se importa un control de usuario a otra página de formularios Web. Debe observarse que, en este caso, el control de usuario es sólo un archivo estático simple.

<%@ Register TagPrefix="Acme" TagName="Message" Src="pagelet1.ascx" %>

<html>

<body style="font: 10pt verdana">

<h3>Control de usuario simple</h3>

<Acme:Message runat="server"/>

</body></html>

Exponer propiedades de controles de usuario

Page 64: Presentación del tutorial de ASP

Cuando se trata a una página de formularios Web como a un control, los métodos y campos públicos de dicho formulario Web se ascienden a propiedades públicas (es decir, a atributos de etiqueta) y los métodos del control también. En el siguiente ejemplo, se muestra una extensión del ejemplo de control de usuario anterior que agrega dos campos String públicos. Debe observarse que estos campos pueden establecerse en la página contenedora tanto de forma declarativa, como mediante programación.

<%@ Register TagPrefix="Acme" TagName="Message" Src="pagelet2.ascx" %>

<html>

<script language="VB" runat="server">

Sub SubmitBtn_Click(Sender As Object, E As EventArgs) MyMessage.MessageText = "El texto del mensaje ha cambiado" MyMessage.Color = "red" End Sub

</script>

<body style="font: 10pt verdana">

<h3>Control de usuario simple con propiedades</h3>

<form runat="server">

<Acme:Message id="MyMessage" MessageText="Mensaje personalizado" Color="blue" runat="server"/>

<p>

<asp:button text="Cambiar propiedades" OnClick="SubmitBtn_Click" runat=server/>

</form>

</body></html>

Además de ascender los campos públicos a propiedades de control, se puede utilizar la sintaxis de propiedades. La sintaxis de la propiedad tiene la ventaja de poder ejecutar código cuando se establecen o se recuperan las propiedades. En el siguiente ejemplo se muestra un control de usuario Address que ajusta las propiedades de texto de los controles TextBox que contiene. La ventaja de hacerlo así es que el control hereda libremente la administración de estado automática del control TextBox.

Debe observarse que hay dos controles de usuario Address en la página de formularios Web contenedora que establece la propiedad Caption en "Billing Address" y "Shipping Address", respectivamente. El poder real de los controles de usuario se encuentra en este tipo de reutilización.

<%@ Register TagPrefix="Acme" TagName="Address" Src="pagelet3.ascx" %>

<html>

<script language="VB" runat="server">

Sub SubmitBtn_Click(Sender As Object, E As EventArgs) MyLabel.Text &= "<b>Dirección de envío</b> " _ & ShipAddr.Address & ", " _ & ShipAddr.City & ", " _ & ShipAddr.StateName & ", " _ & ShipAddr.Zip & "<br>"

Page 65: Presentación del tutorial de ASP

MyLabel.Text &= "<b>Dirección de facturación:</b> " _ & BillAddr.Address & ", " _ & BillAddr.City & ", " _ & BillAddr.StateName & ", " _ & BillAddr.Zip & "<br>" End Sub

</script>

<body style="font: 10pt verdana">

<h3>Control de usuario simple con propiedades</h3>

<form runat="server">

<Acme:Address id="ShipAddr" Caption="Dirección de envío" Address="One Microsoft Way" City="Redmond" StateName="WA" Zip="98052" runat="server"/>

<p>

<Acme:Address id="BillAddr" Caption="Dirección de facturación" runat="server"/>

<p>

<asp:button Text="Enviar formulario" OnClick="SubmitBtn_Click" runat=server/>

</form>

<asp:Label id="MyLabel" runat="server"/>

</body></html>

Otro control de usuario útil consiste en un control Login para recoger nombres de usuario y contraseñas.

<%@ Register TagPrefix="Acme" TagName="Login" Src="pagelet4.ascx" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

If (Page.IsPostBack) MyLabel.Text &= "Id. de usuario " & MyLogin.UserId & "<br>" MyLabel.Text &= "Contraseña " & MyLogin.Password & "<br>" End If End Sub

</script>

<body style="font: 10pt verdana">

<h3>Control de usuario de inicio de sesión</h3>

<form runat="server">

<Acme:Login id="MyLogin" UserId="John Doe" Password="Secret" BackColor="beige" runat="server"/>

</form>

Page 66: Presentación del tutorial de ASP

<asp:Label id="MyLabel" runat="server"/>

</body></html>

En este ejemplo, los controles de validación de formulario se agregan al control de usuario Login.

<%@ Register TagPrefix="Acme" TagName="Login" Src="pagelet5.ascx" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

If (Page.IsPostBack) Page.Validate() If (Page.IsValid) MyLabel.Text &= "Id. de usuario " & MyLogin.UserId & "<br>" MyLabel.Text &= "Contraseña " & MyLogin.Password & "<br>" End If End If End Sub

</script>

<body style="font: 10pt verdana">

<h3>Control de usuario de inicio de sesión</h3>

<form runat="server">

<Acme:Login id="MyLogin" BackColor="beige" runat="server"/>

</form>

<asp:Label id="MyLabel" runat="server"/>

</body></html>

Encapsular eventos en un control de usuario

Los controles de usuario participan en todo el periodo de duración de la ejecución de la solicitud, en gran medida de la misma forma que los controles de servidor. Esto significa que un control de usuario puede controlar los propios eventos mediante la encapsulación de algo de lógica de página de la página de formularios Web contenedora. En el siguiente ejemplo se muestra un control de usuario que enumera el producto que controla internamente las devoluciones propias. Debe observarse que el mismo control de usuario no dispone de un control <form runat="server"> de ajuste. Debido a que sólo un control de formulario puede estar presente en una página (ASP.NET no permite formularios de servidor anidados), se deja que sea la página de formularios Web contenedora la que lo defina.

<%@ Register TagPrefix="Acme" TagName="BookList" Src="pagelet6.ascx" %>

<html>

<body style="font: 10pt verdana">

<h3>Control de usuario con un evento</h3>

Page 67: Presentación del tutorial de ASP

<form runat="server">

<Acme:BookList runat="server"/>

</form>

</body></html>

Crear controles de usuario mediante programación

Al igual que se pueden crear mediante programación controles de servidor ordinarios, también se puede con los controles de usuario. El

método LoadControl de la página se utiliza para cargar el control de usuario pasando la ruta de acceso virtual al archivo de código fuente del control de usuario:

Dim c1 As Control = LoadControl("pagelet7.ascx")CType(c1, (Pagelet7VB)).Category = "business"Page.Controls.Add(c1)

El tipo de control de usuario viene determinado por un atributo ClassName de la directiva Control. Por ejemplo, a un control de usuario guardado con el nombre de archivo "pagelet7.ascx" se le asigna el tipo inflexible "Pagelet7CS" de la siguiente manera:

<%@ Control ClassName="Pagelet7CS" %>

Puesto que el método LoadControl devuelve un tipo de System.Web.UI.Control, debe convertirse al tipo inflexible adecuado para establecer propiedades individuales del control. Finalmente, el control de usuario se agrega a la colección ControlCollection de la página base.

<%@ Register TagPrefix="Acme" TagName="BookList" Src="pagelet7.ascx" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

Page.Controls.Add(New HtmlGenericControl("hr"))

Dim c1 As Control = LoadControl("pagelet7.ascx") CType(c1, Pagelet7VB).Category = "business" Page.Controls.Add(c1)

Page.Controls.Add(New HtmlGenericControl("hr"))

Dim c2 As Control = LoadControl("pagelet7.ascx") CType(c2, Pagelet7VB).Category = "trad_cook" Page.Controls.Add(c2)

Page.Controls.Add(New HtmlGenericControl("hr"))

Dim c3 As Control = LoadControl("pagelet7.ascx") CType(c3, Pagelet7VB).Category = "mod_cook" Page.Controls.Add(c3) End Sub

</script>

<body style="font: 10pt verdana">

Page 68: Presentación del tutorial de ASP

<h3>Crear controles de usuario mediante programación</h3>

</body></html>

Importante: El tipo inflexible de un control de usuario está disponible para la página de formularios Web contenedora sólo si se incluye una directiva Register para el control de usuario (incluso si no hay realmente ninguna etiqueta de control de usuario declarada).

Resumen de la sección

1. Los controles de usuario permiten a los programadores definir fácilmente controles personalizados mediante las mismas técnicas de programación que para escribir páginas de formularios Web.

2. Como convención, se utiliza una extensión .ascx de nombre de archivo para indicar dichos controles. De esta forma, se evita que un archivo del control de usuario pueda ejecutarse como una página de formularios Web independiente.

3. Los controles de usuario se incluyen en otra página de formularios Web mediante una directiva Register que especifica TagPrefix, TagName y Src location.

4. Después de registrar el control de usuario, se puede colocar una etiqueta de control de usuario en una página de formularios Web del mismo modo que se haría para un control de servidor ordinario (incluido el atributo runat="server").

5. Los campos, propiedades y métodos públicos de un control de usuario se ascienden a propiedades públicas (atributos de etiqueta) y métodos del control de la página de formularios Web contenedora.

6. Los controles de usuario participan en todo el período de duración de la ejecución de cada solicitud y puede controlar los propios eventos mediante la encapsulación de la lógica de página de la página de formularios Web contenedora.

7. Los controles de usuario deberían contener cualquier control de formulario pero deberían confiar en la página de formularios Web contenedora para incluir una en caso necesario.

8. Los controles de usuario pueden crearse mediante programación al utilizar el método LoadControl de la clase System.Web.UI.Page. El tipo del control de usuario viene determinado por el motor de tiempo de ejecución de ASP.NET, según la convención filename_extension.

9. El tipo inflexible de un control de usuario está disponible para la página de formularios Web contenedora sólo si se incluye una directiva Register para el control de usuario (incluso si no hay realmente ninguna etiqueta de control de usuario declarada).

Controles de servidor de enlace de datos

Información general y sintaxis del enlace de datos

ASP.NET introduce una nueva sintaxis declarativa de enlace de datos. Esta sintaxis tan extremadamente flexible permite al programador enlazar no sólo con orígenes de datos, sino también con propiedades simples, colecciones, expresiones e incluso con resultados de eventos devueltos de llamadas a métodos. En la siguiente tabla se muestran algunos ejemplos de la nueva sintaxis.

Propiedad simple Customer: <%# custID %>

Colección Orders: <asp:ListBox id="List1" datasource='<%# myArray %>' runat="server">

Expresión Contact: <%# ( customer.First Name + " " + customer.LastName ) %>

Resultado del método

Outstanding Balance: <%# GetBalance(custID) %>

Aunque esta sintaxis se parece al método abreviado de ASP para Response.Write (<%= %>), su comportamiento es bastante diferente. Mientras que el método abreviado Response.Write de ASP se evaluó al procesar la página, la sintaxis de enlace de datos de ASP.NET sólo se evalúa cuando se invoca al método DataBind.

DataBind es un método de Page y de todos los controles de servidor. Cuando se llama a DataBind en un control primario, desciende en cascada a todos los controles secundarios de dicho control. Así pues, por ejemplo DataList1.DataBind() invoca al método DataBind en cada control de las plantillas de DataList. Al llamar a DataBind en Page (Page.DataBind() o simplemente DataBind()) se provoca que todas las

expresiones de enlace de datos de la página se evalúen. DataBind suele llamarse desde el evento Page_Load, tal y como se muestra en el siguiente ejemplo.

Protected Sub Page_Load(Src As Object, E As EventArgs) DataBind()End Sub

Se puede utilizar una expresión de enlace casi en cualquier parte de la sección declarativa de una página .aspx, suponiendo que evalúe el tipo de datos esperado en tiempo de ejecución. Los anteriores ejemplos de propiedad simple, expresión y método muestran texto al usuario

Page 69: Presentación del tutorial de ASP

cuando se evalúa. En estos casos, la expresión de enlace de datos debe evaluar a un valor de tipo String. En el ejemplo de la colección, la expresión de enlace de datos evalúa a un valor de tipo válido para la propiedad DataSource de ListBox. Puede resultar necesario convertir el tipo de valor en la expresión de enlace para producir el resultado deseado. Por ejemplo, si count es de tipo integer:

Number of Records: <%# count.ToString() %>

Enlazar con propiedades simples

La sintaxis de enlace de datos de ASP.NET admite enlazar con variables públicas, propiedades de Page y propiedades de otros controles de la página.

En el siguiente ejemplo se muestra cómo enlazar con una variable pública y una propiedad simple de la página. Debe observarse que estos valores se inicializan antes de llamar a DataBind().

<html><head>

<script language="VB" runat="server">

Sub Page_Load(sender As Object, e As EventArgs) Page.DataBind End Sub ReadOnly Property custID() As String Get Return "ALFKI" End Get End Property ReadOnly Property orderCount() As Integer Get Return 11 End Get End Property

</script>

</head><body>

<h3><font face="Verdana">Enlace de datos de una propiedad de la página</font></h3>

<form runat=server> Cliente: <b><%# custID %></b><br> Órdenes de apertura: <b><%# orderCount %></b>

</form>

</body></html>

En el siguiente ejemplo se muestra cómo enlazar con una propiedad de otro control.

<html><head>

<script language="VB" runat="server">

Page 70: Presentación del tutorial de ASP

Sub SubmitBtn_Click(sender As Object, e As EventArgs) ' Rather than explictly pull out the variable from the "StateList" ' and then manipulate a label control, just call "Page.DataBind". ' This will evaluate any <%# %> expressions within the page Page.DataBind End Sub

</script>

</head><body>

<h3><font face="Verdana">Enlace de datos de una propiedad de otro control de servidor</font></h3>

<form runat=server>

<asp:DropDownList id="StateList" runat="server"> <asp:ListItem>CA</asp:ListItem> <asp:ListItem>IN</asp:ListItem> <asp:ListItem>KS</asp:ListItem> <asp:ListItem>MD</asp:ListItem> <asp:ListItem>MI</asp:ListItem> <asp:ListItem>OR</asp:ListItem> <asp:ListItem>TN</asp:ListItem> <asp:ListItem>UT</asp:ListItem> </asp:DropDownList> <asp:button Text="Enviar" OnClick="SubmitBtn_Click" runat=server/> <p> Estado seleccionado: <asp:label text='<%# StateList.SelectedItem.Text %>' runat=server/> </form>

</body></html>

Enlazar con colecciones y listas

Los controles de servidor de lista, como DataGrid, ListBox y HTMLSelect, utilizan una colección como origen de datos. En los siguientes ejemplos se muestra cómo enlazar con los tipos de colección de Common Language Runtime habituales. Estos controles sólo se pueden enlazar a colecciones que admiten las interfaces IEnumerable, ICollection o IListSource. Lo más habitual será enlazar con ArrayList, Hashtable, DataView y DataReader.

En el siguiente ejemplo se muestra cómo enlazar con ArrayList.

<html><head>

<script language="VB" runat="server">

Sub Page_Load(sender As Object, e As EventArgs) If Not IsPostBack Then

Dim values as ArrayList= new ArrayList()

values.Add ("IN")

Page 71: Presentación del tutorial de ASP

values.Add ("KS") values.Add ("MD") values.Add ("MI") values.Add ("OR") values.Add ("TN")

DropDown1.DataSource = values DropDown1.DataBind End If End Sub

Sub SubmitBtn_Click(sender As Object, e As EventArgs) Label1.Text = "Su elección: " + DropDown1.SelectedItem.Text End Sub

</script>

</head><body>

<h3><font face="Verdana">Enlace de datos de DropDownList</font></h3>

<form runat=server> <asp:DropDownList id="DropDown1" runat="server" />

<asp:button Text="Enviar" OnClick="SubmitBtn_Click" runat=server/>

<p> <asp:Label id=Label1 font-name="Verdana" font-size="10pt" runat="server" />

</form>

</body></html>

En el siguiente ejemplo se muestra cómo enlazar con DataView. Debe observarse que la clase DataView se define en el espacio de nombres System.Data.

<%@ Import namespace="System.Data" %>

<html><head>

<script language="VB" runat="server">

Sub Page_Load(sender As Object, e As EventArgs) If Not IsPostBack Then Dim dt As DataTable Dim dr As DataRow Dim i As Integer

'create a DataTable dt = New DataTable dt.Columns.Add(New DataColumn("IntegerValue", GetType(Integer))) dt.Columns.Add(New DataColumn("StringValue", GetType(String))) dt.Columns.Add(New DataColumn("DateTimeValue", GetType(DateTime))) dt.Columns.Add(New DataColumn("BooleanValue", GetType(Boolean)))

Page 72: Presentación del tutorial de ASP

'Make some rows and put some sample data in

For i = 1 To 9 dr = dt.NewRow() dr(0) = i dr(1) = "Elemento " + i.ToString() dr(2) = DateTime.Now.ToShortTimeString If (i Mod 2 <> 0) Then dr(3) = True Else dr(3) = False End If 'add the row to the datatable dt.Rows.Add(dr) Next

dataGrid1.DataSource = new DataView(dt) dataGrid1.DataBind

End If End Sub

</script>

</head><body>

<h3><font face="Verdana">Enlace de datos de DataView</font></h3>

<form runat=server>

<asp:DataGrid id="dataGrid1" runat="server" BorderColor="black" BorderWidth="1" GridLines="Both" CellPadding="3" CellSpacing="0" HeaderStyle-BackColor="#aaaadd" />

</form>

</body></html>

En el siguiente ejemplo se muestra cómo enlazar con Hashtable.

<html><head>

<script language="VB" runat="server">

Sub Page_Load(sender As Object, e As EventArgs) If Not IsPostBack Then

Dim h As Hashtable = new Hashtable() h.Add ("key1", "valor1") h.Add ("key2", "valor2") h.Add ("key3", "valor3")

Page 73: Presentación del tutorial de ASP

MyDataList.DataSource = h MyDataList.DataBind End If End Sub

</script>

</head><body>

<h3><font face="Verdana">Enlace de datos de una tabla Hash</font></h3>

<form runat=server>

<asp:DataList id="MyDataList" runat="server" BorderColor="black" BorderWidth="1" GridLines="Both" CellPadding="4" CellSpacing="0" >

<ItemTemplate> <%# Container.DataItem.Key %> : <%# Container.DataItem.Value %> </ItemTemplate>

</asp:DataList>

</form>

</body></html>

Enlazar expresiones o métodos

A menudo se desea manipular los datos antes de enlazar con la página o con un control. En el siguiente ejemplo se muestra cómo enlazar con una expresión y con el valor devuelto de un método.

<html><head>

<script language="VB" runat="server">

Sub Page_Load(sender As Object, e As EventArgs) If Not IsPostBack Then Dim values as ArrayList= new ArrayList()

values.Add (0) values.Add (1) values.Add (2) values.Add (3) values.Add (4) values.Add (5) values.Add (6)

DataList1.DataSource = values DataList1.DataBind End If End Sub

Page 74: Presentación del tutorial de ASP

Function EvenOrOdd(number As Integer) As String If (number Mod 2 <> 0) Then Return "Impar" Else Return "Par" End If End Function

</script>

</head><body>

<h3><font face="Verdana">Enlace de datos de métodos y expresiones</font></h3>

<form runat=server>

<asp:DataList id="DataList1" runat="server" BorderColor="black" BorderWidth="1" GridLines="Both" CellPadding="3" CellSpacing="0" >

<ItemTemplate> Valor del número: <%# Container.DataItem %> Par o impar: <%# EvenOrOdd(Container.DataItem) %> </ItemTemplate>

</asp:datalist>

</form>

</body></html>

DataBinder.Eval

El marco de trabajo ASP.NET proporciona un método estático que evalúa expresiones de enlace de datos enlazadas en tiempo de ejecución y, de forma opcional, aplica un formato de cadena al resultado. DataBinder.Eval resulta práctico en cuanto a que elimina gran parte de la conversión de tipos explícita que debe realizar el programador para convertir valores en el tipo de datos deseado. Resulta especialmente útil al enlazar datos con controles en una lista con plantillas, ya que con frecuencia la fila de datos y el campo de datos deben estar convertidos.

Considérese el siguiente ejemplo, en el que un entero se visualiza como una cadena de moneda. Con la sintaxis de enlace de datos estándar de ASP.NET, se debe realizar primero la conversión del tipo de la fila de datos para recuperar el campo de datos, IntegerValue. A

continuación, se pasa como argumento al método String.Format.

<%# String.Format("{0:c}", (CType(Container.DataItem, DataRowView)("IntegerValue"))) %>

Esta sintaxis puede resultar compleja y difícil de recordar. En contraste, DataBinder.Eval es simplemente un método con tres argumentos: el contenedor que da nombre al elemento de datos, el nombre del campo de datos y una cadena de formato. En una lista con plantillas, como DataList, DataGrid o Repeater, el contenedor que da nombre es siempre Container.DataItem. Page es otro contenedor de nombres que se

puede utilizar con DataBinder.Eval.

<%# DataBinder.Eval(Container.DataItem, "IntegerValue", "{0:c}") %>

Page 75: Presentación del tutorial de ASP

El argumento de cadena de formato es opcional. Si se omite, DataBinder.Eval devuelve un valor de tipo object, tal y como se muestra en el siguiente ejemplo.

<%# CType(DataBinder.Eval(Container.DataItem, "BoolValue"), Boolean) %>

Es importante tener en cuenta que DataBinder.Eval puede conllevar una notable reducción de rendimiento sobre la sintaxis de enlace de datos estándar ya que utiliza la reflexión en tiempo de ejecución. DataBinder.Eval debe utilizarse de forma juiciosa, sobre todo cuando no se necesita el formato de cadena.

<%@ Import namespace="System.Data" %>

<html><head>

<script language="VB" runat="server">

Sub Page_Load(sender As Object, e As EventArgs)

If Not IsPostBack Then Dim dt As DataTable Dim dr As DataRow Dim i As Integer

'create a DataTable dt = New DataTable dt.Columns.Add(New DataColumn("IntegerValue", GetType(Integer))) dt.Columns.Add(New DataColumn("StringValue", GetType(String))) dt.Columns.Add(New DataColumn("DateTimeValue", GetType(DateTime))) dt.Columns.Add(New DataColumn("BoolValue", GetType(Boolean)))

'Make some rows and put some sample data in For i = 0 To 8 dr = dt.NewRow() dr(0) = i dr(1) = "Elemento " + i.ToString() dr(2) = DateTime.Now.ToShortTimeString If (i Mod 2 <> 0) Then dr(3) = True Else dr(3) = False End If 'add the row to the datatable dt.Rows.Add(dr) Next

dataList1.DataSource = new DataView(dt) dataList1.DataBind End If End Sub

</script>

</head><body>

<h3><font face="Verdana">Enlace de datos utilizando DataBinder.Eval</font></h3>

<form runat=server>

Page 76: Presentación del tutorial de ASP

<asp:DataList id="dataList1" runat="server" RepeatColumns="3" Width="80%" BorderColor="black" BorderWidth="1" GridLines="Both" CellPadding="4" CellSpacing="0" >

<ItemTemplate>

Fecha de pedido: <%# DataBinder.Eval(Container.DataItem, "DateTimeValue", "{0:d}") %>

<p>

Cantidad: <%# DataBinder.Eval(Container.DataItem, "IntegerValue", "{0:N2}") %>

<p>

Elemento: <%# DataBinder.Eval(Container.DataItem, "StringValue") %>

Fecha de pedido: <asp:CheckBox id=chk1 Checked='<%# DataBinder.Eval(Container.DataItem, "BoolValue") %>' runat=server/>

<p>

</ItemTemplate>

</asp:Datalist>

</form>

</body></html>

Resumen de la sección

1. La sintaxis declarativa del enlace de datos de ASP.NET utiliza la notación <%# %>. 2. Se puede enlazar con orígenes de datos, propiedades de la página u otro control, colecciones, expresiones y resultados

devueltos desde llamadas a métodos. 3. Los controles de lista sólo se pueden enlazar con colecciones que admiten las interfaces IEnumerable, ICollection o

IListSource, como ArrayList, Hashtable, DataView y DataReader. 4. DataBinder.Eval es un método estático para enlazar en tiempo de ejecución. La sintaxis correspondiente puede ser más simple

que la sintaxis de enlace de datos estándar, pero el rendimiento es más lento.

Acceso a datos en el servidor

Introducción a los datos del servidor

El acceso a datos es la esencia de cualquier aplicación del mundo real y ASP.NET proporciona un nutrido conjunto de controles que se integran bien con las API de acceso administrado a datos que proporciona Common Language Runtime. En esta sección se describen varias iteraciones de un ejemplo que utiliza el control DataGrid de ASP.NET para enlazar con los resultados de consultas SQL y archivos de datos XML. También se supone cierta familiaridad con los fundamentos de bases de datos y el lenguaje de consulta SQL.

El acceso a datos en el servidor es exclusivo en cuanto a la ausencia básica de información de estado de las páginas Web, lo que presenta algunas dificultades al intentar realizar transacciones como insertar o actualizar registros a partir de un conjunto de datos recuperados desde una base de datos. Como se verá en esta misma sección, el control DataGrid puede ayudar a administrar estas dificultades, lo que permite concentrarse más en la lógica de la aplicación y menos en los detalles de la administración del estado y el control de eventos.

Page 77: Presentación del tutorial de ASP

Conexiones, comandos y conjuntos de datos

Common Language Runtime proporciona un conjunto completo de interfaces API de acceso a datos administrados para el desarrollo de aplicaciones con un uso intensivo de datos. Estas API ayudan a extraer los datos y a presentarlos de forma coherente sin importar el origen real (SQL Server, OLEDB, XML, entre otros). Básicamente, hay tres objetos con los que más se trabaja: conexiones, comandos y conjuntos de datos.

Una conexión representa una conexión física a algún almacén de datos, como SQL Server o un archivo XML. Un comando representa una directiva para recuperar (select) desde el almacén de datos o manipular dicho almacén (insert,

update, delete). Un conjunto de datos representa los datos reales con los que trabaja una aplicación. Hay que tener en cuenta que los conjuntos

de datos se encuentran siempre desconectados de la conexión de origen y el modelo de datos correspondiente, y que pueden modificarse de forma independiente. Sin embargo, los cambios que se realizan en un conjunto de datos pueden reconciliarse fácilmente con el modelo de datos originario.

Para ver una descripción más detallada de la solución de acceso a datos administrada en Common Language Runtime, lea la sección Información general de ADO.NET de este tutorial.

Obtener acceso a datos basados en SQL

Una aplicación suele necesitar realizar una o más consultas a la base de datos de SQL en lo que respecta a seleccionar, insertar, actualizar o eliminar. En la siguiente tabla se muestra un ejemplo de cada una de estas consultas.

Consulta Ejemplo

Selección simple SELECT * from Employees WHERE FirstName = 'Bradley';

Selección combinada SELECT * from Employees E, Managers M WHERE E.FirstName = M.FirstName;

Insertar INSERT into Employees VALUES ('123-45-6789','Bradley','Millington','Program Manager');

Actualizar UPDATE Employees SET Title = 'Development Lead' WHERE FirstName = 'Bradley';

Eliminar DELETE from Employees WHERE Productivity < 10;

Con el fin de proporcionar a la página acceso a las clases que se necesitarán para realizar un acceso a datos de SQL, hay que importar los espacios de nombres System.Data y System.Data.SqlClient a la página en cuestión.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

Para realizar una consulta de selección en una base de datos de SQL, es necesario crear SqlConnection para la base de datos pasando la cadena de conexión y, a continuación, hay que construir un objeto SqlDataAdapter con la instrucción de consulta. Para llenar un objeto

DataSet con los resultados de la consulta, hay que llamar al método Fill del comando.

Dim myConnection As New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes")Dim myCommand As New SqlDataAdapter("select * from Authors", myConnection)

Dim ds As New DataSet()myCommand.Fill(ds, "Authors")

Tal y como ya se ha comentado en esta misma sección, la ventaja de utilizar un conjunto de datos consiste en que proporciona una vista desconectada de la base de datos. Se puede operar con un conjunto de datos de la aplicación y, posteriormente, cotejar los cambios con la base de datos real. En el caso de aplicaciones cuya ejecución es de larga duración, éste suele ser el mejor enfoque. En el caso de aplicaciones Web, se suelen realizar operaciones breves con cada solicitud (normalmente, sólo para visualizar los datos). A menudo, no se necesita mantener un objeto DataSet en una serie de varias solicitudes. Para situaciones como éstas, se puede utilizar SqlDataReader.

SqlDataReader proporciona un puntero de tipo sólo hacia delante de sólo lectura sobre los datos recuperados desde una base de datos de SQL. Para utilizar SqlDataReader, se declara un SqlCommand en vez de un SqlDataAdapter. SqlCommand expone un método ExecuteReader que devuelve SqlDataReader. También hay que observar que se debe abrir y cerrar explícitamente SqlConnection cuando

Page 78: Presentación del tutorial de ASP

se utiliza SqlCommand. Después de una llamada a ExecuteReader, SqlDataReader puede enlazarse a un control del servidor ASP.NET, tal y como se verá en la siguiente sección.

Dim myConnection As SqlConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes")Dim myCommand As SqlCommand = New SqlCommand("select * from Authors", myConnection)

myConnection.Open()

Dim dr As SqlDataReader = myCommand.ExecuteReader()

...

myConnection.Close()

Al ejecutar comandos que no necesitan devolución de datos, como los de insertar, actualizar y eliminar, también se utiliza SqlCommand. El comando se emite llamando a un método ExecuteNonQuery, que devuelve el número de filas afectadas. Hay que tener en cuenta que la

conexión debe abrirse de forma explícita cuando se utiliza el comando SqlCommand. SqlDataAdapter controla automáticamente la apertura de la conexión para el usuario.

Dim myConnection As New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes")Dim myCommand As New SqlCommand( _ "UPDATE Authors SET phone='(800) 555-5555' WHERE au_id = '123-45-6789'", _ myConnection)

myCommand.Connection.Open()myCommand.ExecuteNonQuery()myCommand.Connection.Close()

Importante: siempre hay que acordarse de cerrar la conexión en el modelo de datos antes de que se termine de ejecutar la página. Si el usuario no cierra la conexión, se puede agotar el límite de conexión de forma inadvertida mientras se espera a que el recolector de elementos no utilizados controle las instancias de páginas.

Enlazar datos SQL a DataGrid

En el siguiente ejemplo se muestra una consulta de selección simple enlazada a un control DataGrid. DataGrid procesa una tabla que contiene los datos de SQL.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

Dim DS As DataSet Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") MyCommand = New SqlDataAdapter("select * from Authors", MyConnection)

DS = new DataSet() MyCommand.Fill(ds, "Authors")

Page 79: Presentación del tutorial de ASP

MyDataGrid.DataSource=ds.Tables("Authors").DefaultView MyDataGrid.DataBind() End Sub

</script>

<body>

<h3><font face="Verdana">Selección simple del control DataGrid</font></h3>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="700" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" EnableViewState="false" />

</body></html>

Al igual que el control DropDownList que se muestra en la sección Enlace de datos, el control DataGrid admite una propiedad DataSource que toma IEnumerable o ICollection, así como DataSet. Se puede utilizar DataSet asignando la propiedad DefaultView de una tabla incluida en DataSet al nombre de la tabla que se desea utilizar dentro de DataSet. La propiedad DefaultView representa el estado actual de una tabla dentro de DataSet, incluido cualquier cambio que haya realizado el código de la aplicación (como eliminar filas o cambiar valores). Después de

establecer la propiedad DataSource, se llama a DataBind() para llenar el control.

MyDataGrid.DataSource=ds.Tables("Authors").DefaultViewMyDataGrid.DataBind()

Se recurre a una sintaxis alternativa para especificar tanto DataSource como DataMember. En este caso, ASP.NET obtiene automáticamente

DefaultView.

MyDataGrid.DataSource=dsMyDataGrid.DataMember="Authors"MyDataGrid.DataBind()

También se puede enlazar directamente con SqlDataReader. En este caso sólo se muestran datos, por lo que la naturaleza sólo hacia delante de SqlDataReader se adapta perfectamente a este escenario y el usuario se beneficia del aumento de rendimiento que proporciona SqlDataReader.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

Dim MyConnection As SqlConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") Dim MyCommand As SqlCommand = New SqlCommand("select * from Authors", MyConnection)

Page 80: Presentación del tutorial de ASP

MyConnection.Open()

Dim dr As SqlDataReader = MyCommand.ExecuteReader()

MyDataGrid.DataSource = dr MyDataGrid.DataBind()

MyConnection.Close() End Sub

</script>

<body>

<h3><font face="Verdana">Selección simple del control DataGrid</font></h3>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="700" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" EnableViewState="false" />

</body></html>

Nota: en el resto de esta sección, sólo se muestra el modelo DataSet de acceso a datos; no obstante, cualquiera de estos ejemplos puede volver a escribirse para beneficiarse también de SQLDataReader.

Realizar una selección parametrizada

También se puede realizar una selección parametrizada mediante el objeto SqlDataAdapter. En el siguiente ejemplo se muestra cómo se pueden modificar los datos seleccionados mediante el valor expuesto desde un control HtmlControl seleccionado.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Sub GetAuthors_Click(Sender As Object, E As EventArgs)

Dim DS As DataSet Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

Dim SelectCommand As String = "select * from Authors where state = @State"

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") MyCommand = New SqlDataAdapter(SelectCommand, MyConnection)

MyCommand.SelectCommand.Parameters.Add(New SqlParameter("@State", SqlDbType.NVarChar, 2)) MyCommand.SelectCommand.Parameters("@State").Value = MySelect.Value

Page 81: Presentación del tutorial de ASP

DS = new DataSet() MyCommand.Fill(DS, "Authors")

MyDataGrid.DataSource=DS.Tables("Authors").DefaultView MyDataGrid.DataBind() End Sub

</script>

<body style="font: 10pt verdana">

<form runat="server">

<h3><font face="Verdana">Selección con parámetros de un control DataGrid</font></h3>

Seleccionar un estado:

<select id="MySelect" runat="server"> <option>CA</option> <option>IN</option> <option>KS</option> <option>MD</option> <option>MI</option> <option>OR</option> <option>TN</option> <option>UT</option> </select>

<input type="submit" OnServerClick="GetAuthors_Click" Value="Obtener autores" runat="server"/><p>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="700" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" EnableViewState="false" />

</form>

</body></html>

SqlDataAdapter mantiene una colección Parameters que puede utilizarse para reemplazar identificadores variables (indicado por un símbolo "@" delante del nombre) con valores. Se agrega un nuevo objeto SqlParameter a la colección que especifica el nombre, el tipo y el tamaño del

parámetro y, a continuación, se establece la propiedad Value en el valor de la selección.

myCommand.SelectCommand.Parameters.Add(New SqlParameter("@State", SqlDbType.NVarChar, 2))myCommand.SelectCommand.Parameters("@State").Value = MySelect.Value

Importante: obsérvese que la propiedad EnableViewState de DataGrid se ha establecido en false. Si se rellenan los datos en cada solicitud, no supone ningún beneficio hacer que DataGrid almacene información de estado para que se envíe en un viaje de ida y vuelta con devoluciones de formularios. Dado que DataGrid almacena todos los datos cuando se mantiene el estado, es importante desactivarlo en aquellos momentos en los que sea adecuado para mejorar el rendimiento de las páginas.

Page 82: Presentación del tutorial de ASP

En DataGrid2.aspx, los valores del cuadro de selección se llenan estáticamente, pero esto no funcionará bien si dichos valores cambian en la base de datos. Dado que el HtmlControl seleccionado también admite una propiedad IEnumerable DataSource, se puede utilizar una consulta de selección para llenar dinámicamente el cuadro de selección, lo que garantiza que la base de datos y la interfaz de usuario están siempre sincronizadas. En el siguiente ejemplo se muestra este proceso.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Dim MyConnection As SqlConnection

Sub Page_Load(Sender As Object, E As EventArgs)

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") If Not (IsPostBack)

Dim DS As DataSet Dim MyCommand As SqlDataAdapter

MyCommand = New SqlDataAdapter("select distinct State from Authors", MyConnection) DS = new DataSet() MyCommand.Fill(DS, "States")

MySelect.DataSource= DS.Tables("States").DefaultView MySelect.DataBind() End If End Sub

Sub GetAuthors_Click(Sender As Object, E As EventArgs)

Dim SelectCmd As String = "select * from Authors where state = @State"

Dim DS As DataSet Dim MyCommand As SqlDataAdapter

MyCommand = New SqlDataAdapter(SelectCmd, MyConnection)

MyCommand.SelectCommand.Parameters.Add(New SqlParameter("@State", SqlDbType.NVarChar, 2)) MyCommand.SelectCommand.Parameters("@State").Value = MySelect.Value

DS = new DataSet() MyCommand.Fill(DS, "Authors")

MyDataGrid.DataSource= DS.Tables("Authors").DefaultView MyDataGrid.DataBind() End Sub

</script>

<body style="font: 10pt verdana">

<form runat="server">

<h3><font face="Verdana">Selección dinámica con parámetros de un control DataGrid</font></h3>

Seleccionar un estado:

<select id="MySelect" DataTextField="State" runat="server"/>

Page 83: Presentación del tutorial de ASP

<input type="submit" OnServerClick="GetAuthors_Click" Value="Obtener autores" runat="server"/><p>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="700" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" EnableViewState="false" />

</form>

</body></html>

Insertar datos en una base de datos de SQL

Para insertar una fila en una base de datos, se puede agregar a la página un formulario de entrada sencillo y ejecutar un comando de inserción en el controlador de eventos de envío de formularios. Al igual que en los dos ejemplos anteriores, se utiliza la colección Parameters del objeto de comando para llenar los valores del comando. Hay que tener en cuenta que también hay que realizar una comprobación para asegurarse de que los valores requeridos no son nulos antes de intentar insertarlos en la base de datos. Esto evita una infracción accidental de las restricciones de campo de la base de datos. El comando de inserción también se ejecuta dentro de un bloque try/catch, por si se da el caso de que ya exista la clave primaria de la fila insertada.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Dim MyConnection As SqlConnection

Sub Page_Load(Sender As Object, E As EventArgs)

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes")

If Not (IsPostBack) BindGrid() End If End Sub

Sub AddAuthor_Click(Sender As Object, E As EventArgs)

Dim DS As DataSet Dim MyCommand As SqlCommand

If au_id.Value = "" Or au_fname.Value = "" Or au_lname.Value = "" Or phone.Value = ""

Message.InnerHtml = "ERROR: los campos id. de autor, nombre o teléfono no admiten valores nulos" Message.Style("color") = "red" BindGrid() End If

Dim InsertCmd As String = "insert into Authors (au_id, au_lname, au_fname, phone, address, city, state, zip, contract) values (@Id, @LName, @FName, @Phone, @Address, @City, @State, @Zip, @Contract)"

Page 84: Presentación del tutorial de ASP

MyCommand = New SqlCommand(InsertCmd, MyConnection)

MyCommand.Parameters.Add(New SqlParameter("@Id", SqlDbType.NVarChar, 11)) MyCommand.Parameters("@Id").Value = au_id.Value

MyCommand.Parameters.Add(New SqlParameter("@LName", SqlDbType.NVarChar, 40)) MyCommand.Parameters("@LName").Value = au_lname.Value

MyCommand.Parameters.Add(New SqlParameter("@FName", SqlDbType.NVarChar, 20)) MyCommand.Parameters("@FName").Value = au_fname.Value

MyCommand.Parameters.Add(New SqlParameter("@Phone", SqlDbType.NChar, 12)) MyCommand.Parameters("@Phone").Value = phone.Value

MyCommand.Parameters.Add(New SqlParameter("@Address", SqlDbType.NVarChar, 40)) MyCommand.Parameters("@Address").Value = address.Value

MyCommand.Parameters.Add(New SqlParameter("@City", SqlDbType.NVarChar, 20)) MyCommand.Parameters("@City").Value = city.Value

MyCommand.Parameters.Add(New SqlParameter("@State", SqlDbType.NChar, 2)) MyCommand.Parameters("@State").Value = stateabbr.Value

MyCommand.Parameters.Add(New SqlParameter("@Zip", SqlDbType.NChar, 5)) MyCommand.Parameters("@Zip").Value = zip.Value

MyCommand.Parameters.Add(New SqlParameter("@Contract", SqlDbType.NVarChar,1)) MyCommand.Parameters("@Contract").Value = contract.Value

MyCommand.Connection.Open()

Try MyCommand.ExecuteNonQuery() Message.InnerHtml = "<b>Registro agregado</b><br>" & InsertCmd.ToString()

Catch Exp As SQLException If Exp.Number = 2627 Message.InnerHtml = "ERROR: ya existe un registro con la misma clave principal" Else Message.InnerHtml = "ERROR: no se pudo agregar el registro, compruebe que los campos están rellenos correctamente" End If Message.Style("color") = "red"

End Try

MyCommand.Connection.Close()

BindGrid() End Sub

Sub BindGrid()

Dim MyCommand As SqlDataAdapter = new SqlDataAdapter("select * from Authors", MyConnection)

Dim DS As DataSet = new DataSet() MyCommand.Fill(DS, "Authors")

MyDataGrid.DataSource=DS.Tables("Authors").DefaultView MyDataGrid.DataBind() End Sub

Page 85: Presentación del tutorial de ASP

</script>

<body style="font: 10pt verdana">

<form runat="server">

<h3><font face="Verdana">Insertar una fila de datos</font></h3>

<table width="95%"> <tr> <td valign="top">

<ASP:DataGrid id="MyDataGrid" runat="server" Width="700" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" EnableViewState="false" />

</td> <td valign="top">

<table style="font: 8pt verdana"> <tr> <td colspan="2" bgcolor="#aaaadd" style="font:10pt verdana">Agregar un nuevo autor:</td> </tr> <tr> <td nowrap>Id. del autor: </td> <td><input type="text" id="au_id" value="000-00-0000" runat="server"></td> </tr> <tr> <td nowrap>Apellido: </td> <td><input type="text" id="au_lname" value="Doe" runat="server"></td> </tr> <tr nowrap> <td>Nombre: </td> <td><input type="text" id="au_fname" value="John" runat="server"></td> </tr> <tr> <td>Teléfono: </td> <td><input type="text" id="phone" value="808 555-5555" runat="server"></td> </tr> <tr> <td>Dirección: </td> <td><input type="text" id="address" value="One Microsoft Way" runat="server"></td> </tr> <tr> <td>Ciudad: </td> <td><input type="text" id="city" value="Redmond" runat="server"></td> </tr> <tr> <td>Estado: </td> <td> <select id="stateabbr" runat="server"> <option>CA</option> <option>IN</option> <option>KS</option>

Page 86: Presentación del tutorial de ASP

<option>MD</option> <option>MI</option> <option>OR</option> <option>TN</option> <option>UT</option> </select> </td> </tr> <tr> <td nowrap>Código postal: </td> <td><input type="text" id="zip" value="98005" runat="server"></td> </tr> <tr> <td>Contrato: </td> <td> <select id="contract" runat="server"> <option value="0">False</option> <option value="1">True</option> </select> </td> </tr> <tr> <td></td> <td style="padding-top:15"> <input type="submit" OnServerClick="AddAuthor_Click" value="Agregar autor" runat="server"> </td> </tr> <tr> <td colspan="2" style="padding-top:15" align="center"> <span id="Message" EnableViewState="false" style="font: arial 11pt;" runat="server"/> </td> </tr> </table>

</td> </tr> </table>

</form>

</body></html>

En vez de comprobar explícitamente los valores de entrada, se podría haber utilizado perfectamente los controles de validación proporcionados con ASP.NET. En el siguiente ejemplo se muestra cómo hacerlo. Obsérvese que mediante RegEx Validator se proporciona el beneficio adicional de comprobar el formato de los campos del identificador de autor, del código postal y del número de teléfono.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Dim MyConnection As SqlConnection

Sub Page_Load(Sender As Object, E As EventArgs)

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes")

If Not (IsPostBack)

Page 87: Presentación del tutorial de ASP

BindGrid() End If End Sub

Sub AddAuthor_Click(Sender As Object, E As EventArgs) Message.InnerHtml = ""

If (Page.IsValid)

Dim DS As DataSet Dim MyCommand As SqlCommand

Dim InsertCmd As String = "insert into Authors (au_id, au_lname, au_fname, phone, address, city, state, zip, contract) values (@Id, @LName, @FName, @Phone, @Address, @City, @State, @Zip, @Contract)"

MyCommand = New SqlCommand(InsertCmd, MyConnection)

MyCommand.Parameters.Add(New SqlParameter("@Id", SqlDbType.NVarChar, 11)) MyCommand.Parameters("@Id").Value = au_id.Value

MyCommand.Parameters.Add(New SqlParameter("@LName", SqlDbType.NVarChar, 40)) MyCommand.Parameters("@LName").Value = au_lname.Value

MyCommand.Parameters.Add(New SqlParameter("@FName", SqlDbType.NVarChar, 20)) MyCommand.Parameters("@FName").Value = au_fname.Value

MyCommand.Parameters.Add(New SqlParameter("@Phone", SqlDbType.NChar, 12)) MyCommand.Parameters("@Phone").Value = phone.Value

MyCommand.Parameters.Add(New SqlParameter("@Address", SqlDbType.NVarChar, 40)) MyCommand.Parameters("@Address").Value = address.Value

MyCommand.Parameters.Add(New SqlParameter("@City", SqlDbType.NVarChar, 20)) MyCommand.Parameters("@City").Value = city.Value

MyCommand.Parameters.Add(New SqlParameter("@State", SqlDbType.NChar, 2)) MyCommand.Parameters("@State").Value = stateabbr.Value

MyCommand.Parameters.Add(New SqlParameter("@Zip", SqlDbType.NChar, 5)) MyCommand.Parameters("@Zip").Value = zip.Value

MyCommand.Parameters.Add(New SqlParameter("@Contract", SqlDbType.NVarChar,1)) MyCommand.Parameters("@Contract").Value = contract.Value

MyCommand.Connection.Open()

Try MyCommand.ExecuteNonQuery() Message.InnerHtml = "<b>Registro agregado</b><br>" & InsertCmd.ToString() Catch Exp As SQLException If Exp.Number = 2627 Message.InnerHtml = "ERROR: ya existe un registro con la misma clave principal" Else Message.InnerHtml = "ERROR: no se pudo agregar el registro, compruebe que los campos están rellenos correctamente" End If Message.Style("color") = "red" End Try

MyCommand.Connection.Close()

End If

Page 88: Presentación del tutorial de ASP

BindGrid() End Sub

Sub BindGrid()

Dim DS As DataSet Dim MyCommand As SqlDataAdapter MyCommand = new SqlDataAdapter("select * from Authors", MyConnection)

DS = new DataSet() MyCommand.Fill(DS, "Authors")

MyDataGrid.DataSource=DS.Tables("Authors").DefaultView MyDataGrid.DataBind() End Sub

</script>

<body style="font: 10pt verdana">

<form runat="server">

<h3><font face="Verdana">Insertar una fila de datos con validación</font></h3>

<table width="95%"> <tr> <td valign="top">

<ASP:DataGrid id="MyDataGrid" runat="server" Width="700" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" EnableViewState="false" />

</td> <td valign="top">

<table style="font: 8pt verdana"> <tr> <td colspan="2" bgcolor="#aaaadd" style="font:10pt verdana">Agregar un nuevo autor:</td> </tr> <tr> <td nowrap>Id. del autor: </td> <td> <input type="text" id="au_id" value="000-00-0000" runat="server"> <asp:RequiredFieldValidator id="au_idReqVal" ControlToValidate="au_id" Display="Static" Font-Name="Verdana" Font-Size="12" runat=server> &nbsp;* </asp:RequiredFieldValidator> </td> </tr> <tr>

Page 89: Presentación del tutorial de ASP

<td nowrap>Apellido: </td> <td> <input type="text" id="au_lname" value="Doe" runat="server"> <asp:RequiredFieldValidator id="au_lnameReqVal" ControlToValidate="au_lname" Display="Static" Font-Name="Verdana" Font-Size="12" runat=server> &nbsp;* </asp:RequiredFieldValidator> </td> </tr> <tr> <td nowrap>Nombre: </td> <td> <input type="text" id="au_fname" value="John" runat="server"> <asp:RequiredFieldValidator id="au_fnameReqVal" ControlToValidate="au_fname" Display="Static" Font-Name="Verdana" Font-Size="12" runat=server> &nbsp;* </asp:RequiredFieldValidator> </td> </tr> <tr> <td>Teléfono: </td> <td><nobr> <input type="text" id="phone" value="808 555-5555" runat="server"> <asp:RequiredFieldValidator id="phoneReqVal" ControlToValidate="phone" Display="Static" Font-Name="Verdana" Font-Size="12" runat=server> &nbsp;* </asp:RequiredFieldValidator> </td> </tr> <tr> <td>Dirección: </td> <td><input type="text" id="address" value="One Microsoft Way" runat="server"></td> </tr> <tr> <td>Ciudad: </td> <td><input type="text" id="city" value="Redmond" runat="server"></td> </tr> <tr> <td>Estado: </td> <td> <select id="stateabbr" runat="server"> <option>CA</option> <option>IN</option> <option>KS</option> <option>MD</option> <option>MI</option> <option>OR</option> <option>TN</option> <option>UT</option> </select> </td> </tr> <tr> <td nowrap>Código postal: </td>

Page 90: Presentación del tutorial de ASP

<td><input type="text" id="zip" value="98005" runat="server"></td> </tr> <tr> <td>Contrato: </td> <td> <select id="contract" runat="server"> <option value="0">False</option> <option value="1">True</option> </select> </td> </tr> <tr> <td></td> <td style="padding-top:15"> <input type="submit" OnServerClick="AddAuthor_Click" value="Agregar autor" runat="server"> </td> </tr> <tr> <td colspan="2" style="padding-top:15" align="center"> <span id="Message" EnableViewState="false" style="font: arial 11pt;" runat="server"/> <asp:RegularExpressionValidator id="RegularExpressionValidator1" ASPClass="RegularExpressionValidator" ControlToValidate="zip" ValidationExpression="[0-9]{5}" Display="Dynamic" Font-Name="Arial" Font-Size="11" runat=server> * El código postal debe tener 5 dígitos numéricos <br> </asp:RegularExpressionValidator> <asp:RegularExpressionValidator id="phoneRegexVal" ControlToValidate="phone" ValidationExpression="[0-9]{3} [0-9]{3}-[0-9]{4}" Display="Dynamic" Font-Name="Arial" Font-Size="11" runat=server> * El teléfono debe tener el siguiente formato: XXX XXX-XXXX <br> </asp:RegularExpressionValidator> <asp:RegularExpressionValidator id="au_idRegexVal" ControlToValidate="au_id" ValidationExpression="[0-9]{3}-[0-9]{2}-[0-9]{4}" Display="Dynamic" Font-Name="Arial" Font-Size="11" runat=server> * El campo Id. del autor debe estar compuesto por dígitos: XXX-XX-XXXX <br> </asp:RegularExpressionValidator> </td> </tr> </table> </td> </tr> </table>

</form>

</body></html>

Actualizar datos en una base de datos de SQL

Actualizar una base de datos puede resultar a menudo complicado en aplicaciones Web. El control DataGrid proporciona soporte integrado para este escenario que simplifica las actualizaciones. Para permitir que se editen filas, DataGrid admite una propiedad EditItemIndex de tipo entero que indica qué fila de la cuadrícula se podrá editar. Cuando se establece esta propiedad, DataGrid procesa la fila en el índice como cuadros de entrada de texto en vez de simples etiquetas. Un valor de -1 (el predeterminado) indica que no se puede editar ninguna fila. La

Page 91: Presentación del tutorial de ASP

página puede incluir el control DataGrid en un formulario del servidor y obtener acceso a los datos editados a través del modelo de objeto de DataGrid.

Para saber qué fila se puede editar, se necesita una forma de aceptar alguna entrada del usuario relacionada con la fila que se desearía editar. DataGrid puede incluir una EditCommandColumn que procesa vínculos para desencadenar tres eventos especiales: EditCommand, UpdateCommand, y CancelCommand. EditCommandColumn se agrega de forma declarativa a la colección Columns de DataGrid, tal y como se muestra en el siguiente ejemplo.

<ASP:DataGrid id="MyDataGrid" runat="server" ... OnEditCommand="MyDataGrid_Edit" OnCancelCommand="MyDataGrid_Cancel" OnUpdateCommand="MyDataGrid_Update" DataKeyField="au_id">

<Columns> <asp:EditCommandColumn EditText="Edit" CancelText="Cancel" UpdateText="Update" /> </Columns>

</ASP:DataGrid>En la misma etiqueta DataGrid, se conectan controladores de eventos con cada uno de los comandos desencadenados desde EditCommandColumn. El argumento DataGridCommandEventArgs de estos controladores proporciona acceso directo al índice seleccionado por el cliente que se utiliza para establecer el EditItemIndex de DataGrid. Hay que tener en cuenta que se necesita volver a

enlazar el control DataGrid para que se produzca el cambio, tal y como se muestra en el siguiente ejemplo. Public Sub MyDataGrid_Edit(sender As Object, E As DataGridCommandEventArgs) MyDataGrid.EditItemIndex = E.Item.ItemIndex BindGrid()End Sub

Cuando se está editando una fila de DataGrid, EditCommandColumn procesa los vínculos Update y Cancel. Si el cliente selecciona Cancel, EditItemIndex simplemente se vuelve a establecer en -1. No obstante, si el cliente selecciona Update, es necesario ejecutar el comando de actualización en la base de datos. Para realizar una consulta de actualización se necesita conocer la clave principal de la base de datos para la fila que se desea actualizar. Para admitirlo, DataGrid expone una propiedad DataKeyField que puede establecerse en el nombre del campo de la clave principal. En el controlador de eventos conectado a UpdateCommand, se puede recuperar el nombre de la clave desde la

colección DataKeys de DataGrid. Para indizar en esta colección, se utiliza el ItemIndex del evento, tal y como se muestra en el siguiente ejemplo.

myCommand.Parameters("@Id").Value = MyDataGrid.DataKeys(CType(E.Item.ItemIndex, Integer))

Al final del controlador de eventos de actualización, EditItemIndex se vuelve a establecer en -1. En el siguiente ejemplo se muestra cómo actúa este código.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Dim MyConnection As SqlConnection

Sub Page_Load(Sender As Object, E As EventArgs)

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes")

If Not (IsPostBack) BindGrid() End If End Sub

Page 92: Presentación del tutorial de ASP

Sub MyDataGrid_Edit(Sender As Object, E As DataGridCommandEventArgs)

MyDataGrid.EditItemIndex = CInt(E.Item.ItemIndex) BindGrid() End Sub

Sub MyDataGrid_Cancel(Sender As Object, E As DataGridCommandEventArgs)

MyDataGrid.EditItemIndex = -1 BindGrid() End Sub

Sub MyDataGrid_Update(Sender As Object, E As DataGridCommandEventArgs)

Dim DS As DataSet Dim MyCommand As SqlCommand

Dim UpdateCmd As String = "UPDATE Authors SET au_id = @Id, au_lname = @LName, au_fname = @FName, phone = " _ & " @Phone, address = @Address, city = @City, state = @State, zip = @Zip, contract = @Contract where au_id = @Id"

MyCommand = New SqlCommand(UpdateCmd, MyConnection)

MyCommand.Parameters.Add(New SqlParameter("@Id", SqlDbType.NVarChar, 11)) MyCommand.Parameters.Add(New SqlParameter("@LName", SqlDbType.NVarChar, 40)) MyCommand.Parameters.Add(New SqlParameter("@FName", SqlDbType.NVarChar, 20)) MyCommand.Parameters.Add(New SqlParameter("@Phone", SqlDbType.NChar, 12)) MyCommand.Parameters.Add(New SqlParameter("@Address", SqlDbType.NVarChar, 40)) MyCommand.Parameters.Add(New SqlParameter("@City", SqlDbType.NVarChar, 20)) MyCommand.Parameters.Add(New SqlParameter("@State", SqlDbType.NChar, 2)) MyCommand.Parameters.Add(New SqlParameter("@Zip", SqlDbType.NChar, 5)) MyCommand.Parameters.Add(New SqlParameter("@Contract", SqlDbType.NVarChar,1))

MyCommand.Parameters("@Id").Value = MyDataGrid.DataKeys(CInt(E.Item.ItemIndex))

Dim Cols As String() = {"@Id","@LName","@FName","@Phone","@Address","@City","@State","@Zip","@Contract"}

Dim NumCols As Integer = E.Item.Cells.Count

Dim I As Integer For I=2 To NumCols-2 'skip first, second and last column

Dim CurrentTextBox As TextBox CurrentTextBox = E.Item.Cells(I).Controls(0) Dim ColValue As String = CurrentTextBox.Text

' Check for null values in required fields If I<6 And ColValue = ""

Message.InnerHtml = "ERROR: los campos id. de autor, nombre o teléfono no admiten valores nulos" Message.Style("color") = "red" Return End If

MyCommand.Parameters(Cols(I-1)).Value = ColValue Next

' Append last row, converting true/false values to 0/1 Dim ContractTextBox As TextBox ContractTextBox = E.Item.Cells(NumCols-1).Controls(0)

Page 93: Presentación del tutorial de ASP

If ContractTextBox.Text = "true" MyCommand.Parameters("@Contract").Value = "1" Else MyCommand.Parameters("@Contract").Value = "0" End If

MyCommand.Connection.Open()

Try MyCommand.ExecuteNonQuery() Message.InnerHtml = "<b>Registro actualizado</b><br>" & UpdateCmd.ToString() MyDataGrid.EditItemIndex = -1 Catch Exp As SQLException If Exp.Number = 2627 Message.InnerHtml = "ERROR: ya existe un registro con la misma clave principal" Else Message.InnerHtml = "ERROR: no se pudo actualizar el registro, compruebe que los campos están rellenos correctamente" End If Message.Style("color") = "red" End Try

MyCommand.Connection.Close()

BindGrid() End Sub

Sub BindGrid()

Dim DS As DataSet Dim MyCommand As SqlDataAdapter MyCommand = new SqlDataAdapter("select * from Authors", MyConnection)

DS = new DataSet() MyCommand.Fill(DS, "Authors")

MyDataGrid.DataSource=DS.Tables("Authors").DefaultView MyDataGrid.DataBind() End Sub

</script>

<body style="font: 10pt verdana">

<form runat="server">

<h3><font face="Verdana">Actualizar fila de datos</font></h3>

<span id="Message" EnableViewState="false" style="font: arial 11pt;" runat="server"/><p>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="800" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" OnEditCommand="MyDataGrid_Edit" OnCancelCommand="MyDataGrid_Cancel" OnUpdateCommand="MyDataGrid_Update"

Page 94: Presentación del tutorial de ASP

DataKeyField="au_id" >

<Columns> <asp:EditCommandColumn EditText="Editar" CancelText="Cancelar" UpdateText="Actualizar" ItemStyle-Wrap="false"/> </Columns>

</ASP:DataGrid>

</form>

</body></html>

Existe un problema con el ejemplo anterior que consiste en que el campo clave principal (au_id) también se procesa como un cuadro de entrada de texto cuando se puede editar una fila. No se desea que el cliente cambie este valor, ya que se necesita que determine qué fila va a actualizar en la base de datos. Afortunadamente, se puede deshabilitar esta columna para que no se procese como cuadro de texto especificando exactamente la apariencia de cada columna en la fila que se puede editar. Esto se hace definiendo cada fila en la colección Columns de DataGrid mediante el control BoundColumn para asignar campos de datos a cada columna. Mediante esta técnica se dispone de control total sobre el orden de las columnas, así como de las propiedades ReadOnly. Para la columna au_id, se establece la propiedad ReadOnly en true. Cuando una fila se encuentra en modo de edición, se seguirá procesando la columna como etiqueta. En el siguiente ejemplo se muestra esta técnica.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Dim MyConnection As SqlConnection

Sub Page_Load(Sender As Object, E As EventArgs)

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes")

If Not (IsPostBack) BindGrid() End If End Sub

Sub MyDataGrid_Edit(Sender As Object, E As DataGridCommandEventArgs)

MyDataGrid.EditItemIndex = CInt(E.Item.ItemIndex) BindGrid() End Sub

Sub MyDataGrid_Cancel(Sender As Object, E As DataGridCommandEventArgs)

MyDataGrid.EditItemIndex = -1 BindGrid() End Sub

Sub MyDataGrid_Update(Sender As Object, E As DataGridCommandEventArgs)

Dim DS As DataSet Dim MyCommand As SqlCommand

Page 95: Presentación del tutorial de ASP

Dim UpdateCmd As String = "UPDATE Authors SET au_id = @Id, au_lname = @LName, au_fname = @FName, phone = " _ & " @Phone, address = @Address, city = @City, state = @State, zip = @Zip, contract = @Contract where au_id = @Id"

MyCommand = New SqlCommand(UpdateCmd, MyConnection)

MyCommand.Parameters.Add(New SqlParameter("@Id", SqlDbType.NVarChar, 11)) MyCommand.Parameters.Add(New SqlParameter("@LName", SqlDbType.NVarChar, 40)) MyCommand.Parameters.Add(New SqlParameter("@FName", SqlDbType.NVarChar, 20)) MyCommand.Parameters.Add(New SqlParameter("@Phone", SqlDbType.NChar, 12)) MyCommand.Parameters.Add(New SqlParameter("@Address", SqlDbType.NVarChar, 40)) MyCommand.Parameters.Add(New SqlParameter("@City", SqlDbType.NVarChar, 20)) MyCommand.Parameters.Add(New SqlParameter("@State", SqlDbType.NChar, 2)) MyCommand.Parameters.Add(New SqlParameter("@Zip", SqlDbType.NChar, 5)) MyCommand.Parameters.Add(New SqlParameter("@Contract", SqlDbType.NVarChar,1))

MyCommand.Parameters("@Id").Value = MyDataGrid.DataKeys(CInt(E.Item.ItemIndex))

Dim Cols As String() = {"@Id","@LName","@FName","@Phone","@Address","@City","@State","@Zip","@Contract"}

Dim NumCols As Integer = E.Item.Cells.Count

Dim I As Integer For I=2 To NumCols-2 'skip first, second and last column

Dim CurrentTextBox As TextBox CurrentTextBox = E.Item.Cells(I).Controls(0) Dim ColValue As String = CurrentTextBox.Text

' Check for null values in required fields If I<6 And ColValue = ""

Message.InnerHtml = "ERROR: los campos id. de autor, nombre o teléfono no admiten valores nulos" Message.Style("color") = "red" Return End If

MyCommand.Parameters(Cols(I-1)).Value = ColValue Next

' Append last row, converting true/false values to 0/1 Dim ContractTextBox As TextBox ContractTextBox = E.Item.Cells(NumCols-1).Controls(0) If ContractTextBox.Text = "true" MyCommand.Parameters("@Contract").Value = "1" Else MyCommand.Parameters("@Contract").Value = "0" End If

MyCommand.Connection.Open()

Try MyCommand.ExecuteNonQuery() Message.InnerHtml = "<b>Registro actualizado</b><br>" & UpdateCmd.ToString() MyDataGrid.EditItemIndex = -1 Catch Exp As SQLException If Exp.Number = 2627 Message.InnerHtml = "ERROR: ya existe un registro con la misma clave principal" Else Message.InnerHtml = "ERROR: no se pudo actualizar el registro, compruebe que los campos están rellenos correctamente"

Page 96: Presentación del tutorial de ASP

End If Message.Style("color") = "red" End Try

MyCommand.Connection.Close()

BindGrid() End Sub

Sub BindGrid()

Dim DS As DataSet Dim MyCommand As SqlDataAdapter MyCommand = new SqlDataAdapter("select * from Authors", MyConnection)

DS = new DataSet() MyCommand.Fill(DS, "Authors")

MyDataGrid.DataSource=DS.Tables("Authors").DefaultView MyDataGrid.DataBind() End Sub

</script>

<body style="font: 10pt verdana">

<form runat="server">

<h3><font face="Verdana">Actualizar fila de datos con una columna de sólo lectura</font></h3>

<span id="Message" EnableViewState="false" style="font: arial 11pt;" runat="server"/><p>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="800" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" OnEditCommand="MyDataGrid_Edit" OnCancelCommand="MyDataGrid_Cancel" OnUpdateCommand="MyDataGrid_Update" DataKeyField="au_id" AutoGenerateColumns="false" >

<Columns> <asp:EditCommandColumn EditText="Editar" CancelText="Cancelar" UpdateText="Actualizar" ItemStyle-Wrap="false"/> <asp:BoundColumn HeaderText="au_id" SortExpression="au_id" ReadOnly="True" DataField="au_id" ItemStyle-Wrap="false"/> <asp:BoundColumn HeaderText="au_lname" SortExpression="au_lname" DataField="au_lname"/> <asp:BoundColumn HeaderText="au_fname" SortExpression="au_fname" DataField="au_fname"/> <asp:BoundColumn HeaderText="phone" SortExpression="phone" DataField="phone"/> <asp:BoundColumn HeaderText="address" SortExpression="address" DataField="address"/> <asp:BoundColumn HeaderText="city" SortExpression="city" DataField="city"/> <asp:BoundColumn HeaderText="state" SortExpression="state" DataField="state"/> <asp:BoundColumn HeaderText="zip" SortExpression="zip" DataField="zip"/> <asp:BoundColumn HeaderText="contract" SortExpression="contract" DataField="contract"/> </Columns>

Page 97: Presentación del tutorial de ASP

</ASP:DataGrid>

</form>

</body></html>

Los controles BoundColumn no son los únicos que se pueden establecer en la colección Columns de DataGrid. También se puede especificar TemplateColumn, que proporciona un control total sobre el contenido de la columna. La plantilla sólo tiene contenido arbitrario; se puede procesar cualquier cosa que se desee, incluyendo controles de servidor, dentro de las columnas de DataGrid. En el siguiente ejemplo se muestra cómo utilizar el control TemplateColumn para procesar la columna "State" como una lista desplegable y la columna "Contract" como una casilla de verificación HtmlControl. La sintaxis de enlace de datos de ASP.NET se utiliza para extraer el valor del campo de datos de la plantilla. Debe observarse que hay algo de lógica complicada para hacer que la lista desplegable y la casilla de verificación reflejen el estado de los datos dentro de la fila.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Dim MyConnection As SqlConnection Public StateIndex As Hashtable

Sub Page_Load(Src As Object, E As EventArgs)

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes")

If Not (IsPostBack) BindGrid() End If

StateIndex = New Hashtable() StateIndex("CA") = 0 StateIndex("IN") = 1 StateIndex("KS") = 2 StateIndex("MD") = 3 StateIndex("MI") = 4 StateIndex("OR") = 5 StateIndex("TN") = 6 StateIndex("UT") = 7 End Sub

Public Function GetStateIndex(StateName As String) As Integer

If StateIndex(stateName) <> Nothing Return CInt(StateIndex(stateName)) Else Return 0 End If End Function

Sub MyDataGrid_Edit(Sender As Object, E As DataGridCommandEventArgs)

MyDataGrid.EditItemIndex = CInt(E.Item.ItemIndex) BindGrid() End Sub

Sub MyDataGrid_Cancel(Sender As Object, E As DataGridCommandEventArgs)

Page 98: Presentación del tutorial de ASP

MyDataGrid.EditItemIndex = -1 BindGrid() End Sub

Sub MyDataGrid_Update(Sender As Object, E As DataGridCommandEventArgs)

Dim DS As DataSet Dim MyCommand As SqlCommand

Dim UpdateCmd As String = "UPDATE Authors SET au_id = @Id, au_lname = @LName, au_fname = @FName, phone = " _ & " @Phone, address = @Address, city = @City, state = @State, zip = @Zip, contract = @Contract where au_id = @Id"

MyCommand = New SqlCommand(UpdateCmd, MyConnection)

MyCommand.Parameters.Add(New SqlParameter("@Id", SqlDbType.NVarChar, 11)) MyCommand.Parameters.Add(New SqlParameter("@LName", SqlDbType.NVarChar, 40)) MyCommand.Parameters.Add(New SqlParameter("@FName", SqlDbType.NVarChar, 20)) MyCommand.Parameters.Add(New SqlParameter("@Phone", SqlDbType.NChar, 12)) MyCommand.Parameters.Add(New SqlParameter("@Address", SqlDbType.NVarChar, 40)) MyCommand.Parameters.Add(New SqlParameter("@City", SqlDbType.NVarChar, 20)) MyCommand.Parameters.Add(New SqlParameter("@State", SqlDbType.NChar, 2)) MyCommand.Parameters.Add(New SqlParameter("@Zip", SqlDbType.NChar, 5)) MyCommand.Parameters.Add(New SqlParameter("@Contract", SqlDbType.NVarChar,1))

MyCommand.Parameters("@Id").Value = MyDataGrid.DataKeys(CInt(E.Item.ItemIndex))

Dim Cols As String() = {"LName","FName","Phone","Address","City","Zip"}

Dim I As Integer For I = 0 To 5

Dim CurrentTextBox As TextBox CurrentTextBox = E.Item.FindControl("edit_" & Cols(I)) Dim ColValue As String = CurrentTextBox.Text

' Check for null values in required fields If I<3 And ColValue = ""

Message.InnerHtml = "ERROR: los campos Nombre o Teléfono no admiten valores nulos" Message.Style("color") = "red" Return End If

MyCommand.Parameters("@" & Cols(I)).Value = ColValue Next

Dim StateDropDownList As DropDownList StateDropDownList = E.Item.FindControl("edit_State") MyCommand.Parameters("@State").Value = StateDropDownList.SelectedItem.ToString()

Dim ContractCheckBox As CheckBox ContractCheckBox = E.Item.FindControl("edit_Contract")

If ContractCheckBox.Checked = true MyCommand.Parameters("@Contract").Value = "1" Else MyCommand.Parameters("@Contract").Value = "0" End If

MyCommand.Connection.Open()

Page 99: Presentación del tutorial de ASP

Try MyCommand.ExecuteNonQuery() Message.InnerHtml = "<b>Registro actualizado</b><br>" & UpdateCmd MyDataGrid.EditItemIndex = -1 Catch Exp As SqlException If Exp.Number = 2627 Message.InnerHtml = "ERROR: ya existe un registro con la misma clave principal" Else Message.InnerHtml = "ERROR: no se pudo actualizar el registro, compruebe que los campos están rellenos correctamente" End If Message.Style("color") = "red" End Try

MyCommand.Connection.Close()

BindGrid() End Sub

Sub BindGrid()

Dim DS As DataSet Dim MyCommand As SqlDataAdapter MyCommand = new SqlDataAdapter("select * from Authors", MyConnection)

DS = new DataSet() MyCommand.Fill(DS, "Authors")

MyDataGrid.DataSource=DS.Tables("Authors").DefaultView MyDataGrid.DataBind() End Sub

</script>

<body style="font: 10pt verdana">

<form runat="server">

<h3><font face="Verdana">Actualizar fila de datos con una columna con plantillas</font></h3>

<span id="Message" EnableViewState="false" style="font: arial 11pt;" runat="server"/><p>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="800" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" OnEditCommand="MyDataGrid_Edit" OnCancelCommand="MyDataGrid_Cancel" OnUpdateCommand="MyDataGrid_Update" DataKeyField="au_id" AutoGenerateColumns="false" >

<Columns> <asp:EditCommandColumn EditText="Editar" CancelText="Cancelar" UpdateText="Actualizar" ItemStyle-Wrap="false"/>

Page 100: Presentación del tutorial de ASP

<asp:BoundColumn HeaderText="au_id" SortExpression="au_id" ReadOnly="True" DataField="au_id" ItemStyle-Wrap="false"/> <asp:TemplateColumn HeaderText="au_lname" SortExpression="au_lname"> <ItemTemplate> <asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "au_lname") %>'/> </ItemTemplate> <EditItemTemplate> <asp:TextBox runat="server" id="edit_LName" Text='<%# DataBinder.Eval(Container.DataItem, "au_lname") %>'/> </EditItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn HeaderText="au_fname" SortExpression="au_fname"> <ItemTemplate> <asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "au_fname") %>'/> </ItemTemplate> <EditItemTemplate> <asp:TextBox runat="server" id="edit_FName" Text='<%# DataBinder.Eval(Container.DataItem, "au_fname") %>'/> </EditItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn HeaderText="phone" SortExpression="phone"> <ItemTemplate> <asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "phone") %>'/> </ItemTemplate> <EditItemTemplate> <asp:TextBox runat="server" id="edit_Phone" Text='<%# DataBinder.Eval(Container.DataItem, "phone") %>'/> </EditItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn HeaderText="address" SortExpression="address"> <ItemTemplate> <asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "address") %>'/> </ItemTemplate> <EditItemTemplate> <asp:TextBox runat="server" id="edit_Address" Text='<%# DataBinder.Eval(Container.DataItem, "address") %>'/> </EditItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn HeaderText="city" SortExpression="city"> <ItemTemplate> <asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "city") %>'/> </ItemTemplate> <EditItemTemplate> <asp:TextBox runat="server" id="edit_City" Text='<%# DataBinder.Eval(Container.DataItem, "city") %>'/> </EditItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn HeaderText="state" SortExpression="state"> <ItemTemplate> <asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "state") %>'/> </ItemTemplate> <EditItemTemplate> <asp:DropDownList runat="server" SelectedIndex='<%# GetStateIndex(Container.DataItem("state")) %>' id="edit_State"> <asp:ListItem>CA</asp:ListItem> <asp:ListItem>IN</asp:ListItem> <asp:ListItem>KS</asp:ListItem> <asp:ListItem>MD</asp:ListItem> <asp:ListItem>MI</asp:ListItem> <asp:ListItem>OR</asp:ListItem> <asp:ListItem>TN</asp:ListItem> <asp:ListItem>UT</asp:ListItem> </asp:DropDownList>

Page 101: Presentación del tutorial de ASP

</EditItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn HeaderText="zip" SortExpression="zip"> <ItemTemplate> <asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "zip") %>'/> </ItemTemplate> <EditItemTemplate> <asp:TextBox runat="server" id="edit_Zip" Text='<%# DataBinder.Eval(Container.DataItem, "zip") %>'/> </EditItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn HeaderText="contract" SortExpression="contract"> <ItemTemplate> <asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "contract", "{0}") %>'/> </ItemTemplate> <EditItemTemplate> <asp:CheckBox runat="server" id="edit_Contract" Checked='<%# DataBinder.Eval(Container.DataItem, "contract") %>'/> </EditItemTemplate> </asp:TemplateColumn> </Columns>

</ASP:DataGrid>

</form>

</body></html>

Al igual que se puede colocar una lista desplegable o una casilla de verificación HtmlControl en TemplateColumn, también pueden colocarse otros controles. En el siguiente ejemplo se agregan controles Validator a las columnas para comprobar la entrada del cliente antes de intentar realizar la actualización.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<head>

<script language="VB" runat="server">

Dim MyConnection As SqlConnection Public StateIndex As Hashtable

Sub Page_Load(Src As Object, E As EventArgs)

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes")

If Not (IsPostBack) BindGrid() End If

StateIndex = New Hashtable() StateIndex("CA") = 0 StateIndex("IN") = 1 StateIndex("KS") = 2 StateIndex("MD") = 3 StateIndex("MI") = 4 StateIndex("OR") = 5 StateIndex("TN") = 6

Page 102: Presentación del tutorial de ASP

StateIndex("UT") = 7 End Sub

Sub MyDataGrid_Edit(Sender As Object, E As DataGridCommandEventArgs)

MyDataGrid.EditItemIndex = CInt(E.Item.ItemIndex) BindGrid() End Sub

Sub MyDataGrid_Cancel(Sender As Object, E As DataGridCommandEventArgs)

MyDataGrid.EditItemIndex = -1 BindGrid() End Sub

Sub MyDataGrid_Update(Sender As Object, E As DataGridCommandEventArgs)

If (Page.IsValid) Dim DS As DataSet Dim MyCommand As SqlCommand

Dim UpdateCmd As String = "UPDATE Authors SET au_id = @Id, au_lname = @LName, au_fname = @FName, phone = " _ & " @Phone, address = @Address, city = @City, state = @State, zip = @Zip, contract = @Contract where au_id = @Id"

MyCommand = New SqlCommand(UpdateCmd, MyConnection)

MyCommand.Parameters.Add(New SqlParameter("@Id", SqlDbType.NVarChar, 11)) MyCommand.Parameters.Add(New SqlParameter("@LName", SqlDbType.NVarChar, 40)) MyCommand.Parameters.Add(New SqlParameter("@FName", SqlDbType.NVarChar, 20)) MyCommand.Parameters.Add(New SqlParameter("@Phone", SqlDbType.NChar, 12)) MyCommand.Parameters.Add(New SqlParameter("@Address", SqlDbType.NVarChar, 40)) MyCommand.Parameters.Add(New SqlParameter("@City", SqlDbType.NVarChar, 20)) MyCommand.Parameters.Add(New SqlParameter("@State", SqlDbType.NChar, 2)) MyCommand.Parameters.Add(New SqlParameter("@Zip", SqlDbType.NChar, 5)) MyCommand.Parameters.Add(New SqlParameter("@Contract", SqlDbType.NVarChar,1))

MyCommand.Parameters("@Id").Value = MyDataGrid.DataKeys(CInt(E.Item.ItemIndex))

Dim Cols As String() = {"LName","FName","Phone","Address","City","Zip"}

Dim I As Integer For I = 0 To 5 Dim CurrentTextBox As TextBox CurrentTextBox = E.Item.FindControl("edit_" & Cols(I)) Dim ColValue As String = CurrentTextBox.Text MyCommand.Parameters("@" & Cols(I)).Value = ColValue Next

Dim StateDropDownList As DropDownList StateDropDownList = E.Item.FindControl("edit_State") MyCommand.Parameters("@State").Value = StateDropDownList.SelectedItem.ToString()

Dim ContractCheckBox As CheckBox ContractCheckBox = E.Item.FindControl("edit_Contract")

If ContractCheckBox.Checked = true MyCommand.Parameters("@Contract").Value = "1" Else MyCommand.Parameters("@Contract").Value = "0" End If

Page 103: Presentación del tutorial de ASP

MyCommand.Connection.Open()

Try MyCommand.ExecuteNonQuery() Message.InnerHtml = "<b>Registro actualizado</b><br>" & UpdateCmd MyDataGrid.EditItemIndex = -1 Catch Exp As SqlException If Exp.Number = 2627 Message.InnerHtml = "ERROR: ya existe un registro con la misma clave principal" Else Message.InnerHtml = "ERROR: no se pudo actualizar el registro, compruebe que los campos están rellenos correctamente" End If Message.Style("color") = "red" End Try

MyCommand.Connection.Close()

BindGrid() Else Message.InnerHtml = "ERROR: compruebe las condiciones de error de cada campo." Message.Style("color") = "red" End If End Sub

Sub BindGrid()

Dim DS As DataSet Dim MyCommand As SqlDataAdapter MyCommand = new SqlDataAdapter("select * from Authors", MyConnection)

DS = new DataSet() MyCommand.Fill(DS, "Authors")

MyDataGrid.DataSource=DS.Tables("Authors").DefaultView MyDataGrid.DataBind() End Sub

</script></head>

<body style="font: 10pt verdana">

<form runat="server">

<h3><font face="Verdana">Actualizar fila de datos con validación</font></h3>

<span id="Message" EnableViewState="false" style="font: arial 11pt;" runat="server"/><p>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="800" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" OnEditCommand="MyDataGrid_Edit" OnCancelCommand="MyDataGrid_Cancel" OnUpdateCommand="MyDataGrid_Update" DataKeyField="au_id"

Page 104: Presentación del tutorial de ASP

AutoGenerateColumns="false" >

<Columns> <asp:EditCommandColumn EditText="Editar" CancelText="Cancelar" UpdateText="Actualizar" ItemStyle-Wrap="false"/> <asp:BoundColumn HeaderText="au_id" SortExpression="au_id" ReadOnly="True" DataField="au_id" ItemStyle-Wrap="false"/> <asp:TemplateColumn HeaderText="au_lname" SortExpression="au_lname"> <ItemTemplate> <asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "au_lname") %>'/> </ItemTemplate> <EditItemTemplate> <nobr> <asp:TextBox runat="server" id="edit_LName" Text='<%# DataBinder.Eval(Container.DataItem, "au_lname") %>'/> <asp:RequiredFieldValidator id="au_lnameReqVal" ControlToValidate="edit_LName" Display="Dynamic" Font-Name="Verdana" Font-Size="12" runat=server> &nbsp;* </asp:RequiredFieldValidator> </EditItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn HeaderText="au_fname" SortExpression="au_fname"> <ItemTemplate> <asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "au_fname") %>'/> </ItemTemplate> <EditItemTemplate> <nobr> <asp:TextBox runat="server" id="edit_FName" Text='<%# DataBinder.Eval(Container.DataItem, "au_fname") %>'/> <asp:RequiredFieldValidator id="au_fnameReqVal" ControlToValidate="edit_FName" Display="Dynamic" Font-Name="Verdana" Font-Size="12" runat=server> &nbsp;* </asp:RequiredFieldValidator> </EditItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn HeaderText="phone" SortExpression="phone"> <ItemTemplate> <asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "phone") %>'/> </ItemTemplate> <EditItemTemplate> <nobr> <asp:TextBox runat="server" id="edit_Phone" Text='<%# DataBinder.Eval(Container.DataItem, "phone") %>'/> <asp:RequiredFieldValidator id="phoneReqVal" ControlToValidate="edit_Phone" Display="Dynamic" Font-Name="Verdana" Font-Size="12" runat=server> &nbsp;* </asp:RequiredFieldValidator> <asp:RegularExpressionValidator id="phoneRegexVal" ControlToValidate="edit_Phone" ValidationExpression="[0-9]{3} [0-9]{3}-[0-9]{4}" Display="Dynamic" Font-Name="Arial" Font-Size="11" runat=server>

Page 105: Presentación del tutorial de ASP

* El teléfono debe tener el siguiente formato: XXX XXX-XXXX <br> </asp:RegularExpressionValidator> </EditItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn HeaderText="address" SortExpression="address"> <ItemTemplate> <asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "address") %>'/> </ItemTemplate> <EditItemTemplate> <asp:TextBox runat="server" id="edit_Address" Text='<%# DataBinder.Eval(Container.DataItem, "address") %>'/> </EditItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn HeaderText="city" SortExpression="city"> <ItemTemplate> <asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "city") %>'/> </ItemTemplate> <EditItemTemplate> <asp:TextBox runat="server" id="edit_City" Text='<%# DataBinder.Eval(Container.DataItem, "city") %>'/> </EditItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn HeaderText="state" SortExpression="state"> <ItemTemplate> <asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "state") %>'/> </ItemTemplate> <EditItemTemplate> <asp:DropDownList runat="server" SelectedIndex='<%# StateIndex(Container.DataItem("state")) %>' id="edit_State"> <asp:ListItem>CA</asp:ListItem> <asp:ListItem>IN</asp:ListItem> <asp:ListItem>KS</asp:ListItem> <asp:ListItem>MD</asp:ListItem> <asp:ListItem>MI</asp:ListItem> <asp:ListItem>OR</asp:ListItem> <asp:ListItem>TN</asp:ListItem> <asp:ListItem>UT</asp:ListItem> </asp:DropDownList> </EditItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn HeaderText="zip" SortExpression="zip"> <ItemTemplate> <asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "zip") %>'/> </ItemTemplate> <EditItemTemplate> <asp:TextBox runat="server" id="edit_Zip" Text='<%# DataBinder.Eval(Container.DataItem, "zip") %>'/> <asp:RegularExpressionValidator id="RegularExpressionValidator1" ASPClass="RegularExpressionValidator" ControlToValidate="edit_Zip" ValidationExpression="[0-9]{5}" Display="Dynamic" Font-Name="Arial" Font-Size="11" runat=server> * El código postal debe tener 5 dígitos numéricos <br> </asp:RegularExpressionValidator> </EditItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn HeaderText="contract" SortExpression="contract"> <ItemTemplate> <asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "contract", "{0}") %>'/> </ItemTemplate> <EditItemTemplate>

Page 106: Presentación del tutorial de ASP

<asp:CheckBox runat="server" id="edit_Contract" Checked='<%# DataBinder.Eval(Container.DataItem, "contract") %>'/> </EditItemTemplate> </asp:TemplateColumn> </Columns>

</ASP:DataGrid>

</form>

</body></html>

Eliminar datos en una base de datos de SQL

Realizar eliminaciones en una base de datos es muy similar a una actualización o a un comando de inserción, pero se necesita una forma de determinar la fila concreta de la cuadrícula que se va a eliminar. Otro control que puede agregarse a la colección Columns de DataGrid es el control ButtonColumn, que simplemente procesa un control de botón. ButtonColumn admite una propiedad CommandName que puede establecerse en Delete. En DataGrid, se conecta un controlador de eventos a DeleteCommand, donde se realiza la operación de eliminación. De nuevo, se utiliza la colección DataKeys para determinar la fila seleccionada por el cliente. En el siguiente ejemplo se muestra este proceso.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Dim MyConnection As SqlConnection

Sub Page_Load(Sender As Object, E As EventArgs)

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes")

If Not (IsPostBack) BindGrid() End If End Sub

Sub MyDataGrid_Delete(Sender As Object, E As DataGridCommandEventArgs)

Dim MyCommand As SqlCommand Dim DeleteCmd As String = "DELETE from Employee where emp_id = @Id"

MyCommand = New SqlCommand(DeleteCmd, MyConnection) MyCommand.Parameters.Add(New SqlParameter("@Id", SqlDbType.NVarChar, 11)) MyCommand.Parameters("@Id").Value = MyDataGrid.DataKeys(CInt(E.Item.ItemIndex))

MyCommand.Connection.Open()

Try MyCommand.ExecuteNonQuery() Message.InnerHtml = "<b>Registro eliminado</b><br>" & DeleteCmd Catch Exc As SQLException Message.InnerHtml = "ERROR: no se pudo eliminar el registro" Message.Style("color") = "red" End Try

MyCommand.Connection.Close()

Page 107: Presentación del tutorial de ASP

BindGrid() End Sub

Sub BindGrid()

Dim DS As DataSet Dim MyCommand As SqlDataAdapter MyCommand = New SqlDataAdapter("select * from Employee", MyConnection)

DS = new DataSet() MyCommand.Fill(DS, "Employee")

MyDataGrid.DataSource=DS.Tables("Employee").DefaultView MyDataGrid.DataBind() End Sub

</script>

<body style="font: 10pt verdana">

<form runat="server">

<h3><font face="Verdana">Eliminar la fila de datos</font></h3>

<span id="Message" EnableViewState="false" style="font: arial 11pt;" runat="server"/><p>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="800" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" DataKeyField="emp_id" OnDeleteCommand="MyDataGrid_Delete" >

<Columns> <asp:ButtonColumn Text="Delete Employee" CommandName="Delete"/> </Columns>

</ASP:DataGrid>

</form>

</body></html>

Ordenar datos de una base de datos de SQL

Un requisito común de cualquier cuadrícula radica en la capacidad de ordenar los datos que contiene. Mientras que el control DataGrid no ordena explícitamente los datos, proporciona una forma de llamar a un controlador de eventos cuando el usuario hace clic en un encabezado de columna, que se puede utilizar para ordenar los datos. Cuando la propiedad AllowSorting de DataGrid se establece en true, procesa hipervínculos para los encabezados de columna que desencadenan un comando Sort de nuevo en la cuadrícula. La propiedad OnSortCommand de DataGrid se establece en el controlador al que se desea llamar cuando el usuario hace clic en un vínculo de columna. El nombre de la columna se pasa como una propiedad SortExpression en el argumento DataGridSortCommandEventArgs, que se puede

utilizar para establecer la propiedad Sort del enlace DataView en la cuadrícula. En el siguiente ejemplo se muestra este proceso.

Page 108: Presentación del tutorial de ASP

<script> Protected Sub MyDataGrid_Sort(Src As Object, E As DataGridSortCommandEventArgs) ... DataView Source = ds.Tables("Authors").DefaultView Source.Sort = E.SortExpression MyDataGrid.DataBind() End Sub</script>

<form runat="server"> <ASP:DataGrid id="MyDataGrid" OnSortCommand="MyDataGrid_Sort" AllowSorting="true" runat="server" /></form>

En el siguiente ejemplo se muestra cómo actúa este código.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Dim MyConnection As SqlConnection

Sub Page_Load(Sender As Object, E As EventArgs)

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes")

If Not (IsPostBack) BindGrid("au_id") End If End Sub

Sub MyDataGrid_Sort(Sender As Object, E As DataGridSortCommandEventArgs)

BindGrid(E.SortExpression) End Sub

Sub BindGrid(SortField As String)

Dim DS As DataSet Dim MyCommand As SqlDataAdapter MyCommand = New SqlDataAdapter("select * from Authors", MyConnection)

DS = new DataSet() MyCommand.Fill(DS, "Authors")

Dim Source As DataView = DS.Tables("Authors").DefaultView Source.Sort = SortField

MyDataGrid.DataSource = Source MyDataGrid.DataBind() End Sub

</script>

<body>

<h3><font face="Verdana">Ordenar datos en un control DataGrid</font></h3>

Page 109: Presentación del tutorial de ASP

<form runat="server">

<ASP:DataGrid id="MyDataGrid" runat="server" OnSortCommand="MyDataGrid_Sort" Width="700" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" AllowSorting="true" />

</form>

</body></html>

Al utilizar controles BoundColumn, se puede establecer explícitamente la propiedad SortExpression para cada columna, tal y como se muestra en el siguiente ejemplo.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Dim MyConnection As SqlConnection

Sub Page_Load(Sender As Object, E As EventArgs)

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes")

If Not (IsPostBack) BindGrid("au_id") End If End Sub

Sub MyDataGrid_Sort(Src As Object, E As DataGridSortCommandEventArgs)

BindGrid(E.SortExpression) End Sub

Sub BindGrid(SortField As String)

Dim DS As DataSet Dim MyCommand As SqlDataAdapter MyCommand = New SqlDataAdapter("select * from Authors", MyConnection)

DS = new DataSet() MyCommand.Fill(DS, "Authors")

Dim Source As DataView = DS.Tables("Authors").DefaultView Source.Sort = SortField

MyDataGrid.DataSource = Source MyDataGrid.DataBind() End Sub

Page 110: Presentación del tutorial de ASP

</script>

<body>

<h3><font face="Verdana">Ordenar datos en el control DataGrid con columnas enlazadas</font></h3>

<form runat="server">

<ASP:DataGrid id="MyDataGrid" runat="server" OnSortCommand="MyDataGrid_Sort" Width="700" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" AllowSorting="true" AutoGenerateColumns="false" >

<Columns> <asp:BoundColumn HeaderText="au_id" SortExpression="au_id" DataField="au_id" ItemStyle-Wrap="false"/> <asp:BoundColumn HeaderText="au_lname" SortExpression="au_lname" DataField="au_lname"/> <asp:BoundColumn HeaderText="au_fname" SortExpression="au_fname" DataField="au_fname"/> <asp:BoundColumn HeaderText="teléfono" SortExpression="phone" DataField="phone"/> <asp:BoundColumn HeaderText="dirección" SortExpression="address" DataField="address"/> <asp:BoundColumn HeaderText="ciudad" SortExpression="city" DataField="city"/> <asp:BoundColumn HeaderText="estado" SortExpression="state" DataField="state"/> <asp:BoundColumn HeaderText="código postal" SortExpression="zip" DataField="zip"/> <asp:BoundColumn HeaderText="contrato" SortExpression="contract" DataField="contract"/> </Columns>

</ASP:DataGrid>

</form>

</body></html>

Trabajar con relaciones de detalles maestros

A menudo el modelo de datos contiene relaciones que no se pueden representar sólo mediante una cuadrícula sencilla. Una interfaz Web muy común es aquella en la que una fila de datos, que se puede seleccionar, desplaza al cliente hacia una página "details" que muestra información detallada acerca de la fila seleccionada. Para conseguirlo mediante DataGrid, se puede agregar una columna HyperLinkColumn a la colección Columns, que especifica la página de detalles a la que se desplazará el cliente al hacer clic en el vínculo. La sintaxis de cadena de formato se utiliza para sustituir un valor del campo en este vínculo que se pasa como un argumento querystring. En el siguiente ejemplo se muestra este proceso.

<ASP:DataGrid id="MyDataGrid" runat="server">

<Columns> <asp:HyperLinkColumn DataNavigateUrlField="au_id" DataNavigateUrlFormatString="datagrid13_details.aspx?id={0}" Text="Get Details" /> </Columns>

Page 111: Presentación del tutorial de ASP

</ASP:DataGrid>

En la página de detalles, se recupera el argumento querystring y se realiza una selección combinada para obtener detalles a partir de la base de datos. En el siguiente ejemplo se muestra este escenario.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

Dim DS As DataSet Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") MyCommand = New SqlDataAdapter("select * from Authors", MyConnection)

DS = new DataSet() MyCommand.Fill(ds, "Authors")

MyDataGrid.DataSource=ds.Tables("Authors").DefaultView MyDataGrid.DataBind() End Sub

</script>

<body style="font: 10pt verdana">

<form runat="server">

<h3><font face="Verdana">Trabajar con relaciones principal-detalle</font></h3>

<span id="Message" EnableViewState="false" style="font: arial 11pt;" runat="server"/><p>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="800" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" DataKeyField="au_id" >

<Columns> <asp:HyperLinkColumn DataNavigateUrlField="au_id" DataNavigateUrlFormatString="datagrid13_details.aspx?id={0}" Text="Obtener detalles" /> </Columns>

</ASP:DataGrid>

Page 112: Presentación del tutorial de ASP

</form>

</body></html>

Escribir y utilizar procedimientos almacenados

En general, realizar consultas ad hoc se produce a costa del rendimiento. La utilización de procedimientos almacenados puede reducir el coste de realizar importantes operaciones en la base de datos de una aplicación. Resulta fácil crear un procedimiento almacenado e incluso puede hacerse mediante una instrucción de SQL. En el siguiente ejemplo de código se crea un procedimiento almacenado que simplemente devuelve una tabla.

CREATE Procedure GetAuthors AS SELECT * FROM Authors returnGO

También se pueden crear procedimientos almacenados que aceptan parámetros. Por ejemplo:

CREATE Procedure LoadPersonalizationSettings (@UserId varchar(50)) AS SELECT * FROM Personalization WHERE UserID=@UserId returnGO

El uso de un procedimiento almacenado de una página ASP.NET es tan sólo una extensión de lo que se ha aprendido hasta ahora acerca del objeto SqlCommand. CommandText sólo es el nombre del procedimiento almacenado en vez del texto de consulta ad hoc. Para indicar a

SqlCommand que CommandText es un procedimiento almacenado, se establece la propiedad CommandType.

myCommand.SelectCommand.CommandType = CommandType.StoredProcedure

En el siguiente ejemplo se muestra una llamada a un procedimiento almacenado para llenar DataSet.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

Dim DS As DataSet Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection("server=(local)\NetSDK;database=northwind;Trusted_Connection=yes") MyCommand = New SqlDataAdapter("Ten Most Expensive Products", MyConnection)

MyCommand.SelectCommand.CommandType = CommandType.StoredProcedure

DS = new DataSet() MyCommand.Fill(DS, "Products")

MyDataGrid.DataSource=DS.Tables("Products").DefaultView MyDataGrid.DataBind() End Sub

</script>

Page 113: Presentación del tutorial de ASP

<body>

<h3><font face="Verdana">Selección de procedimiento almacenado simple de un control DataGrid</font></h3>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="360" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" EnableViewState="false" />

</body></html>

Los parámetros en procedimientos almacenados se pasan tal cual a consultas ad hoc, tal y como se muestra en los siguientes ejemplos.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Sub GetEmployees_Click(Sender As Object, E As EventArgs)

Dim DS As DataSet Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection("server=(local)\NetSDK;database=northwind;Trusted_Connection=yes") MyCommand = New SqlDataAdapter("SalesByCategory", MyConnection)

MyCommand.SelectCommand.CommandType = CommandType.StoredProcedure

MyCommand.SelectCommand.Parameters.Add(New SqlParameter("@CategoryName", SqlDbType.NVarChar, 15)) MyCommand.SelectCommand.Parameters("@CategoryName").Value = SelectCategory.Value

MyCommand.SelectCommand.Parameters.Add(New SqlParameter("@OrdYear", SqlDbType.NVarChar, 4)) MyCommand.SelectCommand.Parameters("@OrdYear").Value = SelectYear.Value

DS = new DataSet() MyCommand.Fill(DS, "Sales")

MyDataGrid.DataSource=DS.Tables("Sales").DefaultView MyDataGrid.DataBind() End Sub

</script>

<body style="font: 10pt verdana">

<form runat="server">

Page 114: Presentación del tutorial de ASP

<h3><font face="Verdana">Selección de un procedimiento con parámetros almacenado de un control DataGrid</font></h3>

Seleccionar una categoría:

<select id="SelectCategory" runat="server"> <option>Beverages</option> <option>Condiments</option> <option>Confections</option> <option>Dairy Products</option> <option>Grains/Cereals</option> <option>Meat/Poultry</option> <option>Produce</option> <option>Seafood</option> </select>

&nbsp;

Seleccionar un año:

<select id="SelectYear" runat="server"> <option>1996</option> <option>1997</option> <option>1998</option> </select>

&nbsp;

<input type="submit" OnServerClick="GetEmployees_Click" Value="Obtener ventas" runat="server"/><p>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="650" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" EnableViewState="false" />

</form>

</body></html>

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Sub GetSales_Click(Sender As Object, E As EventArgs)

Dim DS As DataSet Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection("server=(local)\NetSDK;database=northwind;Trusted_Connection=yes")

Page 115: Presentación del tutorial de ASP

MyCommand = New SqlDataAdapter("Employee Sales By Country", MyConnection)

MyCommand.SelectCommand.CommandType = CommandType.StoredProcedure

MyCommand.SelectCommand.Parameters.Add(New SqlParameter("@Beginning_Date", SqlDbType.DateTime)) MyCommand.SelectCommand.Parameters("@Beginning_Date").Value = BeginDate.SelectedDate

MyCommand.SelectCommand.Parameters.Add(New SqlParameter("@Ending_Date", SqlDbType.DateTime)) MyCommand.SelectCommand.Parameters("@Ending_Date").Value = EndDate.SelectedDate

DS = new DataSet() MyCommand.Fill(DS, "Sales")

MyDataGrid.DataSource=DS.Tables("Sales").DefaultView MyDataGrid.DataBind() End Sub

</script>

<body>

<form runat="server">

<h3><font face="Verdana">Selección de un procedimiento con parámetros almacenado de un control DataGrid</font></h3>

<table width="700"> <tr> <td valign="top" > <b>Fecha de inicio </b> <ASP:Calendar id="BeginDate" BorderWidth="2" BorderColor="lightblue" Font-Size="8pt" TitleStyle-Font-Size="8pt" TitleStyle-BackColor="#cceecc" DayHeaderStyle-BackColor="#ddffdd" DayHeaderStyle-Font-Size="10pt" WeekendDayStyle-BackColor="#ffffcc" SelectedDate="7/1/1996" VisibleDate="7/1/1996" SelectedDayStyle-BackColor="lightblue" runat="server"/> </td> <td valign="top" > <b>Fecha final</b> <ASP:Calendar id="EndDate" BorderWidth="2" BorderColor="lightblue" Font-Size="8pt" TitleStyle-Font-Size="8pt" TitleStyle-BackColor="#cceecc" DayHeaderStyle-BackColor="#ddffdd" DayHeaderStyle-Font-Size="10pt" WeekendDayStyle-BackColor="#ffffcc" SelectedDate="7/25/1996" VisibleDate="7/25/1996" SelectedDayStyle-BackColor="lightblue" runat="server"/>

</td> <td valign="top" style="padding-top:20">

Page 116: Presentación del tutorial de ASP

<input type="submit" OnServerClick="GetSales_Click" Value="Obtener ventas por empleado" runat="server"/><p> </td> </tr> <tr> <td colspan="3" style="padding-top:20"> <ASP:DataGrid id="MyDataGrid" runat="server" Width="500" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" EnableViewState="false" /> </td> </tr> </table>

</form>

</body></html>

Obtener acceso a datos basados en XML

Al principio de esta sección, se mencionó que DataSet estaba diseñado para datos abstractos de tal forma que es independiente del origen de datos real. Esto puede verse cambiando el enfoque de los ejemplos de SQL a XML. DataSet admite un método ReadXml que toma un objeto FileStream como parámetro. El archivo que se lee en este caso debe contener un esquema y los datos que se desean leer. DataSet espera que los datos estén en el formulario tal y como se muestra en el siguiente ejemplo.

<DocumentElement> <TableName> <ColumnName1>column value</ColumnName1> <ColumnName2>column value</ColumnName2> <ColumnName3>column value</ColumnName3> <ColumnName4>column value</ColumnName4> </TableName> <TableName> <ColumnName1>column value</ColumnName1> <ColumnName2>column value</ColumnName2> <ColumnName3>column value</ColumnName3> <ColumnName4>column value</ColumnName4> </TableName></DocumentElement>

Cada sección TableName se corresponde con una sola fila de la tabla. En el siguiente ejemplo se muestra cómo leer esquemas y datos a partir de un archivo XML mediante el método ReadXml de DataSet. Debe observarse que tras la lectura de los datos en DataSet, éstos no se pueden distinguir de los datos SQL; DataGrid los enlaza del mismo modo, tal y como se muestra en el siguiente ejemplo.

<%@ Import Namespace="System.IO" %><%@ Import Namespace="System.Data" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

Page 117: Presentación del tutorial de ASP

Dim DS As New DataSet Dim FS As FileStream Dim Reader As StreamReader

FS = New FileStream(Server.MapPath("schemadata.xml"),FileMode.Open,FileAccess.Read) Reader = New StreamReader(FS) DS.ReadXml(Reader) FS.Close()

Dim Source As DataView Source = new DataView(ds.Tables(0))

MyLiteral.Text = Source.Table.TableName MyDataGrid.DataSource = Source MyDataGrid.DataBind() End Sub

</script>

<body>

<h3><font face="Verdana">Datos XML para la tabla: <asp:Literal id="MyLiteral" runat="server" /></font></h3>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="900" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" EnableViewState="false" />

</body></html>

También es posible leer los datos y el esquema por separado mediante los métodos ReadXmlData y ReadXmlSchema de DataSet, tal y como se muestra en el siguiente ejemplo.

<%@ Import Namespace="System.IO" %><%@ Import Namespace="System.Data" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs) Dim DS As New DataSet Dim FS As FileStream Dim Schema, Reader As StreamReader

FS = New FileStream(Server.MapPath("schema.xml"),FileMode.Open,FileAccess.Read) Schema = new StreamReader(FS) DS.ReadXmlSchema(Schema) FS.Close()

FS = New FileStream(Server.MapPath("data.xml"),FileMode.Open,FileAccess.Read)

Page 118: Presentación del tutorial de ASP

Reader = New StreamReader(FS) DS.ReadXml(Reader) FS.Close()

Dim Source As DataView Source = new DataView(ds.Tables(0))

MyLiteral.Text = Source.Table.TableName MyDataGrid.DataSource = Source MyDataGrid.DataBind() End Sub

</script>

<body>

<h3><font face="Verdana">Datos XML para la tabla: <asp:Literal id="MyLiteral" runat="server" /></font></h3>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="900" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" EnableViewState="false" />

</body></html>

Al igual que DataSet admite métodos de lectura de datos XML, también admite la escritura de los datos. En el siguiente ejemplo se implementa una herramienta para seleccionar datos a partir de SQL y para escribir el resultado como datos XML o texto de esquema.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html><head>

<script language="VB" runat="server">

Public Source As DataView Public DS As DataSet Public GetSchemaChecked, GetDataChecked As Boolean

Sub Submit_Click(Sender As Object, E As EventArgs)

If (Page.IsPostBack)

Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection(ConnectString.Value) MyCommand = New SqlDataAdapter(MyText.Value, MyConnection)

Page 119: Presentación del tutorial de ASP

DS = New DataSet() MyCommand.Fill(DS, "Tabla")

Source = New DataView(DS.Tables(0))

GetSchemaChecked = GetSchema.Checked GetDataChecked = GetData.Checked

MyDataGrid.DataSource=Source MyDataGrid.DataBind() End If End Sub

</script>

</head><body bgcolor="ffffcc">

<h3><font face="Verdana">Generador SQL a XML</font></h3>

<form runat="server">

<table border=0 cellpadding=5 style="font:10pt verdana"> <tr> <td colspan="2"> <b>Cadena de conexión:</b><br> <input id="ConnectString" type="text" value="server=(local)\NetSDK;database=pubs;Trusted_Connection=yes" size="85" runat="server"> </td> </tr> <tr> <td colspan="2"> <b>Consulta:</b><br> <input id="myText" type="text" value="select * from Authors" size="85" runat="server"> </td> </tr> <tr> <td> <input type="radio" id="GetSchema" name="Mode" runat="server"/>Obtener esquema XML<br> <input type="radio" id="GetData" name="Mode" runat="server"/>Obtener datos XML<br> <input type="radio" id="GetBoth" name="Mode" checked runat="server"/>Obtener ambos </td> <td valign="top"> <input type="submit" runat="server" OnServerClick="Submit_Click"> </td> </tr> <tr> <td colspan="2">

<% If Page.IsPostBack %> <b>Resultado:</b><br> <textarea cols=80 rows=25> <% If GetSchemaChecked DS.WriteXmlSchema(Response.Output) Else If GetDataChecked DS.WriteXml(Response.Output, XmlWriteMode.IgnoreSchema) Else DS.WriteXml(Response.Output, XmlWriteMode.WriteSchema) End If %> </textarea> <% End If %>

Page 120: Presentación del tutorial de ASP

</td> </tr> <tr> <td colspan="2">

<% If (Page.IsPostBack) %> <b>Datos:</b><br> <% End If %>

<ASP:DataGrid id="MyDataGrid" BackColor="#EDBE7B" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#DC6035" EnableViewState="false" runat="server" />

</td> <tr> </table>

</form>

</body></html>

Resumen de la sección

1. Las API de acceso administrado a datos de Common Language Runtime extraen los datos y los presentan de forma coherente sin importar el origen real (SQL Server, OLEDB, XML, entre otros).

2. Con el fin de proporcionar a la página acceso a las clases que se necesitarán para realizar un acceso a datos de SQL, hay que importar los espacios de nombres System.Data y System.Data.SqlClient a la página en cuestión.

3. Llenar un conjunto de datos a partir de una consulta de SQL implica crear un objeto SqlConnection, mediante la asociación de un objeto SqlDataAdapter con la conexión que contiene la instrucción de consulta, y llenar el conjunto de datos a partir del comando.

4. El control DataGrid admite una propiedad DataSource que toma un tipo IEnumerable (o ICollection). Esto se puede establecer en el resultado de una consulta de SQL asignando la propiedad DefaultView de DataSet que es del tipo DataView.

5. SqlDataAdapter mantiene una colección Parameters que puede utilizarse para reemplazar identificadores variables (indicado por un símbolo "@" delante del nombre) con valores.

6. Al ejecutar comandos que no necesitan devolución de datos, como los de insertar, actualizar y eliminar, se utiliza SqlCommand en vez de SqlDataAdapter. El comando se emite llamando a un método ExecuteNonQuery, que devuelve el número de filas afectadas.

7. SqlConnection debe abrirse de forma explícita cuando se utiliza el comando SqlCommand (SqlDataAdapter controla automáticamente la apertura de la conexión para el usuario). Siempre hay que acordarse de cerrar SqlConnection en el modelo de datos antes de que se termine de ejecutar la página. Si el usuario no cierra la conexión, se puede agotar el límite de conexión de forma inadvertida mientras se espera a que las instancias de páginas se liberen en el recolector de elementos no utilizados.

8. Para permitir que se editen filas, DataGrid admite una propiedad EditItemIndex de tipo entero que indica qué fila de la cuadrícula se podrá editar. Cuando se establece esta propiedad, DataGrid procesa la fila en el índice como cuadros de entrada de texto en vez de simples etiquetas.

9. DataGrid expone una propiedad DataKeyField que puede establecerse en el nombre del campo de la clave principal. En el controlador de eventos conectado a UpdateCommand, se puede recuperar el nombre de la clave desde la colección DataKeys de DataGrid.

10. Mediante los controles BoundColumn de DataGrid se dispone de control total sobre el orden de las columnas, así como de las propiedades ReadOnly.

11. Mediante los controles TemplateColumn de DataGrid se dispone de control total sobre el contenido de la columna. 12. El control ButtonColumn puede utilizarse simplemente para procesar un control de botón en cada fila de la columna, que puede

estar asociado a un evento. 13. Se puede agregar una columna HyperLinkColumn a la colección Columns de DataGrid, que admite desplazarse a otra página

al hacer clic en el vínculo. 14. Cuando la propiedad AllowSorting de DataGrid se establece en true, procesa hipervínculos para los encabezados de columna

que desencadenan un comando Sort de nuevo en la cuadrícula. La propiedad OnSortCommand de DataGrid se establece en el controlador al que se desea llamar cuando el usuario hace clic en un vínculo de columna.

Page 121: Presentación del tutorial de ASP

15. DataSet admite los métodos ReadXml, ReadXmlData y ReadXmlSchema que toman a FileStream como parámetro; esto puede utilizarse para llenar un DataSet a partir de un archivo XML.

16. La utilización de procedimientos almacenados puede reducir el coste de realizar importantes operaciones en la base de datos de una aplicación.

Acceso a datos y personalización

Introducción a controles con plantilla

Mientras que el control de servidor DataGrid que se mostró en la sección anterior es apto para muchos escenarios de aplicaciones Web en los que la representación de datos en forma de cuadrícula resulta adecuada, en numerosas ocasiones se necesita que la presentación de datos sea más rica. ASP.NET ofrece dos controles (DataList y Repeater) que proporcionan mayor flexibilidad sobre el procesamiento de datos en forma de lista. Estos controles están basados en plantillas, por lo que no tienen ningún procesamiento predeterminado propio. La forma de procesar datos viene completamente predeterminada por la implementación de las plantillas del control que describen cómo presentar los elementos de datos.

Al igual que el control DataGrid, DataList y Repeater admiten una propiedad DataSource que puede establecerse en cualquier tipo de ICollection, IEnumerable o IListSource. Los datos de DataSource se enlazan con el control mediante el método DataBind correspondiente. Una vez enlazados los datos, el formato de cada elemento de datos lo describe una plantilla.

La propiedad ItemTemplate controla el procesamiento de cada elemento de la colección DataSource. Dentro de una plantilla ItemTemplate, se puede definir cualquier código de presentación arbitrario (HTML o de otra forma). Mediante la sintaxis de enlace de datos de ASP.NET, se pueden insertar valores a partir del enlace de datos con el control DataList o Repeater, tal y como se muestra en el siguiente ejemplo.

<ASP:Repeater id="MyRepeater" runat="server">

<ItemTemplate> Hello <%# DataBinder.Eval(Container.DataItem, "name") %> ! </ItemTemplate>

</ASP:Repeater>

Container representa el primer control de la jerarquía inmediata que admite la interfaz de marcador System.Web.UI.INamingContainer. En este caso, Container se resuelve en un objeto de tipo System.Web.UI.WebControls.RepeaterItem con una propiedad DataItem. Como Repeater itera por la colección DataSource, DataItem contiene el elemento actual de la colección. Por ejemplo, si el origen de datos se establece en ArrayList de objetos Employee, DataItem es de tipo Employees. Cuando se enlaza con DataView, DataItem es de tipo DataRowView.

En el siguiente ejemplo se muestra un control Repeater enlazado con DataView (devuelto desde una consulta de SQL). HeaderTemplate y FooterTemplate también se han definido y procesado al principio y al final de la lista respectivamente.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

Dim DS As DataSet Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") MyCommand = New SqlDataAdapter("select * from Titles", MyConnection)

DS = New DataSet() MyCommand.Fill(ds, "Titles")

MyRepeater.DataSource = ds.Tables("Titles").DefaultView MyRepeater.DataBind() End Sub

Page 122: Presentación del tutorial de ASP

</script>

<body topmargin="0" leftmargin="0" marginwidth="0" marginheight="0">

<!-- #include virtual="/quickstart/aspplus/samples/webforms/customize/header.inc" -->

<ASP:Repeater id="MyRepeater" runat="server">

<HeaderTemplate>

<table width="100%" style="font: 8pt verdana"> <tr style="background-color:DFA894"> <th> Título </th> <th> Id. del título </th> <th> Tipo </th> <th> Id. del editor </th> <th> Precio </th> </tr>

</HeaderTemplate>

<ItemTemplate>

<tr style="background-color:FFECD8"> <td> <%# DataBinder.Eval(Container.DataItem, "title") %> </td> <td> <%# DataBinder.Eval(Container.DataItem, "title_id") %> </td> <td> <%# DataBinder.Eval(Container.DataItem, "type") %> </td> <td> <%# DataBinder.Eval(Container.DataItem, "pub_id") %> </td> <td> <%# DataBinder.Eval(Container.DataItem, "price", "$ {0}") %> </td> </tr>

</ItemTemplate>

<FooterTemplate>

</table>

</FooterTemplate>

</ASP:Repeater>

<!-- #include virtual="/quickstart/aspplus/samples/webforms/customize/footer.inc" -->

Page 123: Presentación del tutorial de ASP

</body></html>

El control Repeater sólo itera por los datos enlazados, procesando la plantilla ItemTemplate una vez por elemento de la colección DataSource. No procesa nada aparte de los elementos incluidos en las plantillas. Mientras que Repeater es un iterador de propósito general, DataList proporciona algunas funciones adicionales para controlar el diseño de la lista. A diferencia de Repeater, DataList procesa elementos adicionales, como filas y celdas de tablas, y se extiende con atributos de estilo, aparte de la definición de plantilla para habilitar el formato enriquecido. Por ejemplo, DataList admite las propiedades RepeatColumns y RepeatDirection que especifican si deberían procesarse los datos en múltiples columnas, así como la dirección (vertical u horizontal) en la que se deben procesar los elementos de datos. DataList también admite atributos de estilo, tal y como se muestra en el siguiente ejemplo.

<ASP:DataList runat="server" DataSource="<%#MyData%>" RepeatColumns="2" RepeatDirection="Horizontal" ItemStyle-Font-Size="10pt" ItemStyle-Font-Name="Verdana"> ...</ASP:DataList>

Nota: El resto de la sección se centra en las múltiples funciones del control DataList. Para obtener más información acerca del control Repeater, vea el tema Repeater de la sección Referencia de controles de formularios Web de este tutorial.

En el siguiente ejemplo se muestra el uso del control DataList. Debe observarse que se ha modificado la apariencia de los elementos de datos desde el ejemplo anterior mediante el simple cambio de los contenidos de la propiedad ItemTemplate del control. Las propiedades RepeatDirection y RepeatColumns determinan cómo se distribuyen las plantillas ItemTemplate.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

Dim DS As DataSet Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") MyCommand = New SqlDataAdapter("select * from Titles", MyConnection)

DS = New DataSet() MyCommand.Fill(ds, "Titles")

MyDataList.DataSource = ds.Tables("Titles").DefaultView MyDataList.DataBind() End Sub

</script>

<body topmargin="0" leftmargin="0" marginwidth="0" marginheight="0">

<!-- #include virtual="/quickstart/aspplus/samples/webforms/customize/header.inc" -->

<ASP:DataList id="MyDataList" RepeatColumns="2" RepeatDirection="Horizontal" runat="server">

<ItemTemplate>

Page 124: Presentación del tutorial de ASP

<div style="padding:15,15,15,15;font-size:10pt;font-family:Verdana">

<div style="font:12pt verdana;color:darkred"> <i><b><%# DataBinder.Eval(Container.DataItem, "title") %></i></b> </div>

<br>

<b>Id. de título: </b><%# DataBinder.Eval(Container.DataItem, "title_id") %><br> <b>Categoría: </b><%# DataBinder.Eval(Container.DataItem, "type") %><br> <b>Id. del editor: </b><%# DataBinder.Eval(Container.DataItem, "pub_id") %><br> <b>Precio: </b><%# DataBinder.Eval(Container.DataItem, "price", "$ {0}") %><p>

</div>

</ItemTemplate>

</ASP:DataList>

<!-- #include virtual="/quickstart/aspplus/samples/webforms/customize/footer.inc" -->

</body></html>

En el siguiente ejemplo se demuestra aún más la infinita flexibilidad de las plantillas cambiando de nuevo la plantilla ItemTemplate. En esta ocasión, uno de los valores de DataItem se ha sustituido por el atributo "src" de una etiqueta <img>. También se ha utilizado el parámetro String de format de DataBinder.Eval para sustituir el valor de DataItem en la cadena de consulta de una dirección URL.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

Dim DS As DataSet Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") MyCommand = New SqlDataAdapter("select * from Titles", MyConnection)

DS = New DataSet() MyCommand.Fill(ds, "Titles")

MyDataList.DataSource = ds.Tables("Titles").DefaultView MyDataList.DataBind() End Sub

</script>

<body topmargin="0" leftmargin="0" marginwidth="0" marginheight="0">

<!-- #include virtual="/quickstart/aspplus/samples/webforms/customize/header.inc" -->

<ASP:DataList id="MyDataList" RepeatColumns="2" runat="server">

<ItemTemplate>

Page 125: Presentación del tutorial de ASP

<table cellpadding=10 style="font: 10pt verdana"> <tr> <td width=1 bgcolor="BD8672"/>

<td valign="top"> <img align="top" src='<%# DataBinder.Eval(Container.DataItem, "title_id", "/quickstart/aspplus/images/title-{0}.gif") %>' > </td>

<td valign="top">

<b>Título: </b><%# DataBinder.Eval(Container.DataItem, "title") %><br> <b>Categoría: </b><%# DataBinder.Eval(Container.DataItem, "type") %><br> <b>Id. del editor: </b><%# DataBinder.Eval(Container.DataItem, "pub_id") %><br> <b>Precio: </b><%# DataBinder.Eval(Container.DataItem, "price", "$ {0}") %>

<p>

<a href='<%# DataBinder.Eval(Container.DataItem, "title_id", "purchase.aspx?titleid={0}") %>' > <img border="0" src="/quickstart/aspplus/images/purchase_book.gif" > </a>

</td> </tr> </table>

</ItemTemplate>

</ASP:DataList>

<!-- #include virtual="/quickstart/aspplus/samples/webforms/customize/footer.inc" -->

</body></html>

Controlar devoluciones desde una plantilla

Como en DataGrid, se puede desencadenar un comando desde el interior de una plantilla de DataList que se pasa a un controlador de eventos conectado al mismo control DataList. Por ejemplo, un control LinkButton dentro de ItemTemplate puede desencadenar un comando Select. Al establecer la propiedad OnSelectedIndexChanged de DataList, se puede llamar a un controlador de eventos como respuesta a este comando. En el siguiente ejemplo se muestra este proceso.

<ASP:DataList id="MyDataList" OnSelectedIndexChanged="MyDataList_Select" runat="server">

<ItemTemplate>

<asp:linkbutton CommandName="Select" runat="server"> <%# DataBinder.Eval(Container.DataItem, "title") %> </asp:linkbutton>

</ItemTemplate>

</ASP:DataList>

En el siguiente ejemplo se muestra cómo actúa este código. En el controlador de eventos MyDataList_Select, se llenan otros controles de servidor con los detalles acerca del elemento seleccionado concreto.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

Page 126: Presentación del tutorial de ASP

<html>

<script language="VB" runat="server">

Dim MyConnection As SqlConnection

Sub Page_Load(Sender As Object, E As EventArgs)

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes")

If Not (Page.IsPostBack)

Dim DS As New DataSet Dim MyCommand As New SqlDataAdapter("select * from Titles where type = 'business'", MyConnection)

MyCommand.Fill(ds, "Titles")

MyDataList.DataSource = ds.Tables("Titles").DefaultView MyDataList.DataBind()

End If End Sub

Sub MyDataList_Select(Sender As Object, E As EventArgs)

Dim Title As String = MyDataList.DataKeys(MyDataList.SelectedItem.ItemIndex) Dim MyCommand As New SqlDataAdapter("select * from Titles where title_id = '" & Title & "'" , MyConnection)

Dim DS As New DataSet MyCommand.Fill(DS, "TitleDetails")

Dim RowView As DataRowView = DS.Tables("TitleDetails").DefaultView(0)

DetailsImage.Src = "/quickstart/aspplus/images/title-" & RowView("title_id") & ".gif" DetailsPubId.InnerHtml = "<b>Id. del editor: </b>" & RowView("pub_id").ToString() & "<br>" DetailsTitleId.InnerHtml = "<b>Id. de título: </b>" & RowView("title_id").ToString() & "<br>" DetailsType.InnerHtml = "<b>Categoría: </b>" & RowView("type").ToString() + "<br>" DetailsPrice.InnerHtml = "<b>Precio: </b> $ " & RowView("price").ToString() + "<p>" PurchaseLink.InnerHtml = "<img border='0' src='/quickstart/aspplus/images/purchase_book.gif' >" PurchaseLink.HRef="purchase.aspx?titleid=" & RowView("title_id").ToString() DetailsTitle.InnerHtml = RowView("title").ToString()

DetailsImage.Visible = true

End Sub

</script>

<body topmargin="0" leftmargin="0" marginwidth="0" marginheight="0">

<form runat="server">

<!-- #include virtual="/quickstart/aspplus/samples/webforms/customize/header.inc" -->

<table width="100%"> <tr> <td width="50%">

<ASP:DataList id="MyDataList" OnSelectedIndexChanged="MyDataList_Select" DataKeyField="title_id" runat="server">

Page 127: Presentación del tutorial de ASP

<ItemTemplate>

<table cellpadding=10 style="font: 10pt verdana"> <tr> <td valign="top"> <img align="top" width="25" border=1 src='<%# DataBinder.Eval(Container.DataItem, "title_id", "/quickstart/aspplus/images/title-{0}.gif") %>' runat="server"/> </td> <td valign="top"> <b>Título: </b> <asp:linkbutton Text='<%# DataBinder.Eval(Container.DataItem, "title") %>' CommandName="Select" style="color:darkred" runat="server"/> <br> <b>Precio: </b><%# DataBinder.Eval(Container.DataItem, "price", "$ {0}") %><br> </td> </tr> </table>

</ItemTemplate>

</ASP:DataList>

</td>

<td valign="top" style="padding-top:15" width="50%"> <table cellpadding="5" width="100%" style="font: 10pt verdana"> <tr> <td> <img id="DetailsImage" visible="false" runat="server"> </td> <td valign="top" width="400"> <div style="font: 12pt verdana;color:darkred"> <i><b><span id="DetailsTitle" runat="server"/></i></b><br> </div> <span id="DetailsTitleId" runat="server"/> <span id="DetailsPubId" runat="server"/> <span id="DetailsType" runat="server"/> <span id="DetailsPrice" runat="server"/> <a id="PurchaseLink" runat="server"/> </td> </tr> </table> </td>

</tr> </table>

<!-- #include virtual="/quickstart/aspplus/samples/webforms/customize/footer.inc" -->

</form>

</body></html>

Debe observarse que mientras DataList reconoce unos pocos comandos especiales, como Select y Edit/Update/Cancel, la cadena de comando desencadenada en el interior de la plantilla puede ser cualquier cadena arbitraria. Para todos los comandos, se desencadena

OnItemCommand de DataList. Este evento puede conectarse a un controlador de la misma forma que en el ejemplo anterior, tal y como se muestra en el siguiente ejemplo.

<script runat="server">

Page 128: Presentación del tutorial de ASP

Protected Sub MyDataList_ItemCommand(Sender As Object, E As DataListCommandEventArgs) Dim Command As String = E.CommandName

Select Case Command Case "Discuss" ShowDiscussions(E.Item.DataItem) Case "Ratings" ShowRatings(E.Item.DataItem) End Select End Sub

</script>

<ASP:DataList id="MyDataList" OnItemCommand="MyDataList_ItemCommand" runat="server">

<ItemTemplate>

<asp:linkbutton CommandName="Ratings" runat="server"> View Ratings </asp:linkbutton> | <asp:linkbutton CommandName="Discuss" runat="server"> View Discussions </asp:linkbutton>

</ItemTemplate>

</ASP:DataList>

Debe observarse que, debido a que más de un comando puede desencadenar este controlador de eventos, se debe emplear una instrucción switch para determinar el comando concreto que se desencadenó. En el siguiente ejemplo se muestra cómo actúa este código.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Dim MyConnection As SqlConnection

Sub Page_Load(Sender As Object, E As EventArgs)

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes")

If Not (Page.IsPostBack)

Dim DS As New DataSet Dim MyCommand As New SqlDataAdapter("select * from Titles where type = 'business'", MyConnection)

MyCommand.Fill(ds, "Titles")

MyDataList.DataSource = ds.Tables("Titles").DefaultView MyDataList.DataBind()

End If End Sub

Sub MyDataList_Select(Sender As Object, E As EventArgs)

Page 129: Presentación del tutorial de ASP

Dim Title As String = MyDataList.DataKeys(MyDataList.SelectedItem.ItemIndex) Dim MyCommand As New SqlDataAdapter("select * from Titles where title_id = '" & Title & "'" , MyConnection)

Dim DS As New DataSet MyCommand.Fill(DS, "TitleDetails")

Dim RowView As DataRowView = DS.Tables("TitleDetails").DefaultView(0)

DetailsImage.Src = "/quickstart/aspplus/images/title-" & RowView("title_id") & ".gif" DetailsPubId.Text = "<b>Id. del editor: </b>" & RowView("pub_id").ToString() & "<br>" DetailsTitleId.Text = "<b>Id. de título: </b>" & RowView("title_id").ToString() & "<br>" DetailsType.Text = "<b>Categoría: </b>" & RowView("type").ToString() + "<br>" DetailsPrice.Text = "<b>Precio: </b> $ " & RowView("price").ToString() + "<p>" PurchaseLink.Text = "<img border='0' src='/quickstart/aspplus/images/purchase_book.gif' >" PurchaseLink.NavigateUrl = "purchase.aspx?titleid=" & RowView("title_id").ToString() DetailsTitle.Text = RowView("title").ToString()

DetailsImage.Visible = true End Sub

Sub MyDataList_ItemCommand(Sender As Object, E As DataListCommandEventArgs)

Dim Title As String = MyDataList.DataKeys(E.Item.ItemIndex) Dim Command As String = E.CommandName

Select (Command)

Case "Discuss" : ShowDiscussions(Title)

Case "Ratings" : ShowRatings(Title) End Select End Sub

Sub ShowRatings(Title As String)

Message.InnerHtml = "<h5>Clasificación para """ & Title & """</h5>" Message.InnerHtml &= "Imprimir clasificación aquí..." End Sub

Sub ShowDiscussions(Title As String)

Message.InnerHtml = "<h5>Discusiones sobre """ & Title & """</h5>" Message.InnerHtml &= "Imprimir discusiones aquí..." End Sub

</script>

<body topmargin="0" leftmargin="0" marginwidth="0" marginheight="0">

<form runat="server">

<!-- #include virtual="/quickstart/aspplus/samples/webforms/customize/header.inc" -->

<table width="100%"> <tr> <td width="50%">

<ASP:DataList id="MyDataList" OnSelectedIndexChanged="MyDataList_Select" OnItemCommand="MyDataList_ItemCommand" DataKeyField="title_id" runat="server">

<ItemTemplate>

Page 130: Presentación del tutorial de ASP

<table cellpadding=10 style="font: 10pt verdana"> <tr> <td valign="top"> <img align="top" width="25" border=1 src='<%# DataBinder.Eval(Container.DataItem, "title_id", "/quickstart/aspplus/images/title-{0}.gif") %>' runat="server"/> </td> <td valign="top"> <b>Título: </b> <asp:linkbutton Text='<%# DataBinder.Eval(Container.DataItem, "title") %>' CommandName="Select" style="color:darkred" runat="server"/> <br> <b>Precio: </b><%# DataBinder.Eval(Container.DataItem, "price", "$ {0}") %> <br> <asp:linkbutton Text="Discusiones" CommandName="Discuss" style="color:darkred;font:8pt tahoma" runat="server"/> | <asp:linkbutton Text="Clasificación" CommandName="Ratings" style="color:darkred;font:8pt tahoma" runat="server"/> </td> </tr> </table>

</ItemTemplate>

</ASP:DataList>

</td>

<td valign="top" style="padding-top:15" width="50%"> <table cellpadding="5" width="100%" style="font: 10pt verdana"> <tr> <td> <img id="DetailsImage" visible="false" runat="server"> </td> <td valign="top" width="400"> <div style="font: 12pt verdana;color:darkred"> <i><b><asp:Label id="DetailsTitle" runat="server"/></i></b><br> </div> <asp:Label id="DetailsTitleId" runat="server"/> <asp:Label id="DetailsPubId" runat="server"/> <asp:Label id="DetailsType" runat="server"/> <asp:Label id="DetailsPrice" runat="server"/> <asp:HyperLink id="PurchaseLink" runat="server"/> </td> </tr> </table> </td>

</tr> </table>

<!-- #include virtual="/quickstart/aspplus/samples/webforms/customize/footer.inc" -->

<div id="Message" style="font: 10pt verdana;padding:0,15,15,15" runat="server"/>

</form>

</body></html>

Page 131: Presentación del tutorial de ASP

Utilizar plantillas de selección y de edición

Además de controlar el comando Select mediante un controlador de eventos en ámbito de página, DataList puede responder al evento internamente. Si SelectedItemTemplate se define para DataList, DataList procesa esta plantilla para el elemento que desencadenó el comando Select. En el siguiente ejemplo se utiliza SelectedItemTemplate para poner en negrita el título del libro seleccionado.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Dim MyConnection As SqlConnection

Sub Page_Load(Sender As Object, E As EventArgs)

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes")

Dim DS As New DataSet Dim MyCommand As New SqlDataAdapter("select * from Titles where type = 'business'", MyConnection)

MyCommand.Fill(DS, "Titles")

MyDataList.DataSource = DS.Tables("Titles").DefaultView

If Not (Page.IsPostBack) MyDataList.DataBind() End If End Sub

Sub MyDataList_Select(Sender As Object, E As EventArgs)

Dim Title As String = MyDataList.DataKeys(MyDataList.SelectedItem.ItemIndex) Dim MyCommand As New SqlDataAdapter("select * from Titles where title_id = '" & Title & "'" , MyConnection)

Dim DS As New DataSet MyCommand.Fill(DS, "TitleDetails")

Dim RowView As DataRowView = DS.Tables("TitleDetails").DefaultView(0)

DetailsImage.Src = "/quickstart/aspplus/images/title-" & RowView("title_id") & ".gif" DetailsPubId.Text = "<b>Id. del editor: </b>" & RowView("pub_id").ToString() & "<br>" DetailsTitleId.Text = "<b>Id. de título: </b>" & RowView("title_id").ToString() & "<br>" DetailsType.Text = "<b>Categoría: </b>" & RowView("type").ToString() + "<br>" DetailsPrice.Text = "<b>Precio: </b> $ " & RowView("price").ToString() + "<p>" PurchaseLink.Text = "<img border='0' src='/quickstart/aspplus/images/purchase_book.gif' >" PurchaseLink.NavigateUrl = "purchase.aspx?titleid=" & RowView("title_id").ToString() DetailsTitle.Text = RowView("title").ToString()

DetailsImage.Visible = true

MyDataList.DataBind() End Sub

Sub MyDataList_ItemCommand(Sender As Object, E As DataListCommandEventArgs)

Dim Title As String = MyDataList.DataKeys(E.Item.ItemIndex) Dim MyLinkButton As LinkButton = E.CommandSource

Select (MyLinkButton.Text)

Page 132: Presentación del tutorial de ASP

Case "Discussions" : ShowDiscussions(Title)

Case "Ratings" : ShowRatings(Title) End Select End Sub

Sub ShowRatings(Title As String)

Message.InnerHtml = "<h5>Clasificación para """ & Title & """</h5>" Message.InnerHtml &= "Imprimir clasificación aquí..." End Sub

Sub ShowDiscussions(Title As String)

Message.InnerHtml = "<h5>Discusiones sobre """ & Title & """</h5>" Message.InnerHtml &= "Imprimir discusiones aquí..." End Sub

</script>

<body topmargin="0" leftmargin="0" marginwidth="0" marginheight="0">

<form runat="server">

<!-- #include virtual="/quickstart/aspplus/samples/webforms/customize/header.inc" -->

<table width="100%"> <tr> <td width="50%">

<ASP:DataList id="MyDataList" OnSelectedIndexChanged="MyDataList_Select" OnItemCommand="MyDataList_ItemCommand" DataKeyField="title_id" runat="server">

<ItemTemplate>

<table cellpadding=10 style="font: 10pt verdana"> <tr> <td valign="top"> <img align="top" width="25" border=1 src='<%# DataBinder.Eval(Container.DataItem, "title_id", "/quickstart/aspplus/images/title-{0}.gif") %>' runat="server"/> </td> <td valign="top"> <b>Título: </b> <asp:linkbutton Text='<%# DataBinder.Eval(Container.DataItem, "title") %>' CommandName="Select" style="color:darkred" runat="server"/> <br> <b>Precio: </b><%# DataBinder.Eval(Container.DataItem, "price", "$ {0}") %> <br> <asp:linkbutton Text="Discusiones" CommandName="Discuss" style="color:darkred;font:8pt tahoma" runat="server"/> | <asp:linkbutton Text="Clasificación" CommandName="Ratings" style="color:darkred;font:8pt tahoma" runat="server"/> </td> </tr> </table>

</ItemTemplate>

<EditItemTemplate>

Page 133: Presentación del tutorial de ASP

<table cellpadding=10 style="font: 10pt verdana" > <tr> <td valign="top"> <img src='<%# DataBinder.Eval(Container.DataItem, "title_id", "/quickstart/aspplus/images/title-{0}.gif") %>' align="top" width="25" border=1 runat="server"/> </td> <td valign="top"> <b>Title: </b> <asp:linkbutton Font-Bold="true" Text='<%# DataBinder.Eval(Container.DataItem, "title") %>' CommandName="Select" style="color:darkred" runat="server"/> <br> <b>Price: </b><%# DataBinder.Eval(Container.DataItem, "price", "$ {0}") %> <br> <asp:linkbutton Text="Discussions" Command="Discuss" style="color:darkred;font:8pt tahoma" runat="server"/> | <asp:linkbutton Text="Ratings" Command="Ratings" style="color:darkred;font:8pt tahoma" runat="server"/> </td> </tr> </table>

</EditItemTemplate>

</ASP:DataList>

</td>

<td valign="top" style="padding-top:15" width="50%"> <table cellpadding="5" width="100%" style="font: 10pt verdana"> <tr> <td> <img id="DetailsImage" visible="false" runat="server"> </td> <td valign="top" width="400"> <div style="font: 12pt verdana;color:darkred"> <i><b><asp:Label id="DetailsTitle" runat="server"/></i></b><br> </div> <asp:Label id="DetailsTitleId" runat="server"/> <asp:Label id="DetailsPubId" runat="server"/> <asp:Label id="DetailsType" runat="server"/> <asp:Label id="DetailsPrice" runat="server"/> <asp:HyperLink id="PurchaseLink" runat="server"/> </td> </tr> </table> </td>

</tr> </table>

<!-- #include virtual="/quickstart/aspplus/samples/webforms/customize/footer.inc" -->

<div id="Message" style="font: 10pt verdana;padding:0,15,15,15" runat="server"/>

</form>

</body></html>

DataList también admite EditItemTemplate para procesar un elemento cuyo índice es igual a la propiedad EditItemIndex de DataList. Para obtener más detalles sobre cómo editar y actualizar obras, vea el tema Actualizar datos de la sección Acceso a datos de este tutorial.

Page 134: Presentación del tutorial de ASP

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Sub PopulateList()

Dim DS As DataSet Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") MyCommand = New SqlDataAdapter("select * from Titles where type = 'business'", MyConnection)

DS = New DataSet() MyCommand.Fill(ds, "Titles")

MyDataList.DataSource = ds.Tables("Titles").DefaultView MyDataList.DataBind() End Sub

Sub Page_Load(Sender As Object, E As EventArgs)

If Not (Page.IsPostBack) PopulateList() End If End Sub

Sub MyDataList_Edit(Sender As Object, E As DataListCommandEventArgs)

MyDataList.EditItemIndex = CInt(e.Item.ItemIndex) PopulateList() End Sub

Sub MyDataList_Update(Sender As Object, E As DataListCommandEventArgs)

' database update left out for simplicity's sake...

Dim EditText As HtmlInputText EditText = E.Item.FindControl("edit_price")

Message.InnerHtml = "Precio actualizado: " & EditText.Value MyDataList.EditItemIndex = -1 PopulateList() End Sub

Sub MyDataList_Cancel(Sender As Object, E As DataListCommandEventArgs)

MyDataList.EditItemIndex = -1 PopulateList() End Sub

</script>

<body topmargin="0" leftmargin="0" marginwidth="0" marginheight="0">

<form runat="server">

<!-- #include virtual="/quickstart/aspplus/samples/webforms/customize/header.inc" -->

Page 135: Presentación del tutorial de ASP

<ASP:DataList id="MyDataList" RepeatColumns="2" OnEditCommand="MyDataList_Edit" OnUpdateCommand="MyDataList_Update" OnCancelCommand="MyDataList_Cancel" runat="server">

<ItemTemplate>

<table cellpadding=10 style="font: 10pt verdana"> <tr> <td width=1 bgcolor="BD8672"/> <td valign="top"> <img align="top" src='<%# DataBinder.Eval(Container.DataItem, "title_id", "/quickstart/aspplus/images/title-{0}.gif") %>' > </td> <td valign="top"> <b>Título: </b><%# DataBinder.Eval(Container.DataItem, "title") %><br> <b>Categoría: </b><%# DataBinder.Eval(Container.DataItem, "type") %><br> <b>Id. del editor: </b><%# DataBinder.Eval(Container.DataItem, "pub_id") %><br> <b>Precio: </b><%# DataBinder.Eval(Container.DataItem, "price", "$ {0}") %> <p> <asp:linkbutton CommandName="Edit" runat="server"> <img border="0" src="/quickstart/aspplus/images/edit_book.gif" > </asp:linkbutton> </td> </tr> </table>

</ItemTemplate>

<EditItemTemplate>

<table cellpadding=10 style="font: 10pt verdana"> <tr> <td width=1 bgcolor="BD8672"/> <td valign="top"> <img align="top" src='<%# DataBinder.Eval(Container.DataItem, "title_id", "/quickstart/aspplus/images/title-{0}.gif") %>' > </td> <td valign="top"> <b>Título: </b><%# DataBinder.Eval(Container.DataItem, "title") %><br> <b>Categoría: </b><%# DataBinder.Eval(Container.DataItem, "type") %><br> <b>Id. del editor: </b><%# DataBinder.Eval(Container.DataItem, "pub_id") %><br> <b>Precio: </b><input id="edit_price" type="text" value='<%# DataBinder.Eval(Container.DataItem, "price", "$ {0}") %>' runat="server"/> <p> <asp:linkbutton CommandName="Update" runat="server"><img border="0" src="/quickstart/aspplus/images/update_book.gif" ></asp:linkbutton> <asp:linkbutton CommandName="Cancel" runat="server"><img border="0" src="/quickstart/aspplus/images/cancel_book.gif" ></asp:linkbutton> </td> </tr> </table>

</EditItemTemplate>

</ASP:DataList>

<!-- #include virtual="/quickstart/aspplus/samples/webforms/customize/footer.inc" -->

</form>

<div style="font: 10pt verdana;padding:0,15,15,15" id="Message" runat="server"/>

</body>

Page 136: Presentación del tutorial de ASP

</html>

Buscar un control dentro de una plantilla

En ocasiones, resulta necesario ubicar un control contenido dentro de una plantilla. Si se le da un identificador a un control en una plantilla, dicho control puede recuperarse desde el contenedor (el primer control de la jerarquía primaria que admite INamingContainer). En este caso, el contenedor es el control DataListItem. Debe observarse que aunque haya varios controles con el mismo identificador (en virtud de la repetición de DataList), cada uno está contenido de forma lógica en el espacio de nombres del control contenedor DataListItem.

Se puede atravesar la colección Items de DataList para recuperar DataListItem para un índice dado y, a continuación, llamar al método

FindControl de DataListItem (heredado de la clase base Control) para recuperar un control con un identificador particular.

<script runat="server">

Public Sub Page_Load(sender As Object, E As EventArgs)) ' set datasource and call databind here

For I=0 To MyDataList.Items.Count-1 Dim IsChecked As String = MyDataList.Items(i).FindControl("Save").Checked.ToString() If IsChecked = "True" Then ... End If Next End Sub</script>

<ASP:DataList id="MyDataList" runat="server">

<ItemTemplate> <asp:CheckBox id="Save" runat="server"/> <b>Save to Favorites</b> </ItemTemplate>

</ASP:DataList>

En el siguiente ejemplo se muestra cómo actúa este código.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Src As Object, E As EventArgs)

If Not (Page.IsPostBack)

Dim DS As DataSet Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") MyCommand = New SqlDataAdapter("select * from Titles where type = 'business'", MyConnection)

DS = New DataSet() MyCommand.Fill(DS, "Titles")

Page 137: Presentación del tutorial de ASP

MyDataList.DataSource = DS.Tables("Titles").DefaultView MyDataList.DataBind() End If End Sub

Sub Submit_Click(Src As Object, E As EventArgs) Dim I As Long

For I=0 To MyDataList.Items.Count -1

Dim CurrentCheckBox As CheckBox CurrentCheckBox = MyDataList.Items(I).FindControl("Save") Message.InnerHtml &= "Elemento(" & i & "): " & CurrentCheckBox.Checked.ToString() & "<br>" Next End Sub

</script>

<body topmargin="0" leftmargin="0" marginwidth="0" marginheight="0">

<form runat="server">

<!-- #include virtual="/quickstart/aspplus/samples/webforms/customize/header.inc" -->

<ASP:DataList id="MyDataList" RepeatColumns="2" runat="server">

<ItemTemplate>

<table cellpadding=10 style="font: 10pt verdana"> <tr> <td width=1 bgcolor="BD8672"/> <td valign="top"> <img align="top" src='<%# DataBinder.Eval(Container.DataItem, "title_id", "/quickstart/aspplus/images/title-{0}.gif") %>' > </td> <td valign="top"> <b>Título: </b><%# DataBinder.Eval(Container.DataItem, "title") %><br> <b>Categoría: </b><%# DataBinder.Eval(Container.DataItem, "type") %><br> <b>Id. del editor: </b><%# DataBinder.Eval(Container.DataItem, "pub_id") %><br> <b>Precio: </b><%# DataBinder.Eval(Container.DataItem, "price", "$ {0}") %> <p> <asp:CheckBox id="save" runat="server"/> <b>Guardar en Favoritos</b> </td> </tr> </table>

</ItemTemplate>

</ASP:DataList>

<p>

<div style="padding:0,15,0,15"> <input type="submit" Value="Actualizar favoritos" OnServerClick="Submit_Click" runat="server"/> </div>

<p>

<!-- #include virtual="/quickstart/aspplus/samples/webforms/customize/footer.inc" -->

</form>

<div style="font: 10pt verdana" EnableViewState="false" id="Message" runat="server"/>

Page 138: Presentación del tutorial de ASP

</body></html>

Resumen de la sección

1. Los controles DataList y Repeater proporcionan a los programadores un control ajustado sobre el procesamiento de listas enlazadas a datos.

2. El procesamiento de datos enlazados se controla mediante una plantilla, como HeaderTemplate, FooterTemplate o ItemTemplate.

3. El control Repeater es un iterador de propósito general y no inserta nada en el procesamiento que no está contenido en una plantilla.

4. El control DataList ofrece más control sobre el diseño y el estilo de elementos y extrae su propio código de rendimiento para aplicar formato.

5. DataList admite los eventos Select, Edit/Update/Cancel e Item Command que pueden controlarse en el ámbito de página conectando los controladores de eventos a los eventos Command de DataList.

6. DataList admite SelectedItemTemplate y EditItemTemplate para controlar el procesamiento de un elemento seleccionado que se puede editar.

7. Los controles se pueden recuperar mediante programación desde una plantilla con el método Control.FindControl. Debería llamarse en un DataListItem recuperado a partir de la colección Items de DataList.

Trabajar con objetos empresariales

Encapsular lógica de programación en componentes empresariales es una parte esencial de cualquier aplicación del mundo real, ya sea en tecnología Web o de otro tipo. En ASP.NET, los objetos empresariales son bloques para construir aplicaciones Web de varios niveles, tales como aquellas con una capa para acceso de datos o reglas comunes para aplicaciones. Esta sección muestra cómo escribir algunos componentes sencillos e incluirlos en las páginas de formularios Web de la aplicación.

El directorio /Bin de la aplicación

Un problema que se plantea con el uso del modelo COM para componentes de aplicaciones Web es que esos componentes se deben registrar (normalmente con la herramienta regsvr32) antes de poder utilizarlos desde una aplicación ASP tradicional. La administración remota de estos tipos de aplicaciones no suele ser posible, ya que la herramienta de registro debe ejecutarse localmente en el servidor. Una dificultad adicional es que estos componentes permanecen bloqueados en disco una vez cargados por la aplicación, de modo que para poder sustituirlos o eliminarlos se debe detener completamente el servidor Web.

ASP.NET intenta resolver estos problemas haciendo que los componentes se puedan colocar en un directorio conocido, de manera que se encuentren automáticamente durante la ejecución. Este directorio conocido se denomina /bin, y se encuentra situado inmediatamente bajo el directorio raíz de la aplicación (un directorio virtual definido por Internet Information Services (IIS)). La ventaja es que no se requiere realizar ningún registro para que la aplicación ASP.NET Framework pueda utilizar los componentes (éstos se pueden distribuir simplemente copiándolos en el directorio /bin, o bien realizando una transferencia de archivos mediante FTP).

Además de ofrecer un método sin necesidad de registro para distribuir componentes compilados, ASP.NET no requiere que estos componentes permanezcan bloqueados en el disco durante la ejecución. Internamente, ASP.NET duplica los ensamblados presentes en /bin y carga estas copias "sombra" en lugar de los originales. Los componentes originales se pueden reemplazar aunque el servidor Web esté aún en funcionamiento. Los cambios del directorio /bin se detectan automáticamente durante la ejecución. Cuando se detecta un cambio, ASP.NET permite que las solicitudes actualmente en ejecución se completen, y dirige todas las nuevas solicitudes entrantes a la aplicación que utiliza el nuevo componente o componentes.

Importar objetos empresariales

En su nivel más básico, un componente empresarial es simplemente una clase para la que se puede crear una instancia desde la página de formularios Web que la importa. En el siguiente ejemplo, se define una clase sencilla HelloWorld. La clase dispone de un constructor público

(el cual se ejecuta cuando se crea por primera vez una instancia de la clase), una propiedad única de tipo String denominada FirstName y un método SayHello que imprime un saludo con el valor de la propiedad FirstName.

Imports SystemImports System.Text

Namespace HelloWorld Public Class HelloObj Private _name As String

Public Sub New MyBase.New()

Page 139: Presentación del tutorial de ASP

_name = Nothing End Sub

Public Property FirstName As String Get Return(_name) End get Set _name = value End Set End Property

Public Function SayHello() As String Dim sb As New StringBuilder("Hello ") If (_name <> Nothing) Then sb.Append(_name) Else sb.Append("World") End If sb.Append("!") Return(sb.ToString()) End Function End ClassEnd Namespace

Para compilar esta clase, se ejecuta el compilador de C# (Csc.exe) desde la línea de comandos. La opción /t indica al compilador que genere una biblioteca (DLL), mientras que la opción /out indica dónde colocar el ensamblado resultante. En este caso, el directorio /bin de la aplicación se encuentra directamente bajo la raíz "aspplus" de este tutorial, y se supone que este comando se está ejecutando desde el directorio del ejemplo, es decir, ...\QuickStart\AspPlus\Samples\WebForms\Busobjs.

csc /t:library /out:..\..\..\..\bin\HelloObj.dll HelloObj.cs

Para Visual Basic, el comando de compilación equivalente es:

vbc /t:library /out:..\..\..\..\bin\HelloObjVB.dll HelloObj.vb

Para JScript, el comando de compilación equivalente es:

jsc /out:..\..\..\..\bin\HelloObjJS.dll HelloObj.js

El componente está disponible ahora para cualquier página de formularios Web de la aplicación que necesite utilizarlo. El siguiente ejemplo HelloObj.aspx ilustra esta funcionalidad.

<%@ Import Namespace="HelloWorld" %>

<html>

<style>

div { font: 8pt verdana; background-color:cccccc; border-color:black; border-width:1; border-style:solid; padding:10,10,10,10; }

</style>

Page 140: Presentación del tutorial de ASP

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

Dim Comp As HelloObjVB Comp = New HelloObjVB()

Message.InnerHtml &= Comp.SayHello() + "<p>"

Comp.FirstName = "¡Microsoft .NET Framework" Message.InnerHtml &= Comp.SayHello() + "<p>"

Comp.FirstName = "¡ASP.NET" Message.InnerHtml &= Comp.SayHello() + "<p>"

Comp.FirstName = "¡Mundo!" Message.InnerHtml &= Comp.SayHello() End Sub

</script>

<body style="font: 10pt verdana">

<h3>Componente administrado simple</h3>

<h5>Resultado del objeto: </h5>

<div id="Message" runat="server"/>

</body></html>

Observe la directiva Import, en la parte superior de la página, que especifica el espacio de nombres que se va a incluir. Una vez que el espacio de nombres se ha incluido mediante esta directiva, la clase se puede utilizar desde dentro de la página de formularios Web. Como el módulo de ejecución de ASP.NET carga previamente el ensamblado, sólo se requiere una simple importación del espacio de nombres para hacer que el componente esté disponible. El siguiente ejemplo de código ilustra el uso de la directiva Import.

<%@ Import Namespace="HelloWorld" %>

De forma predeterminada, ASP.NET carga todos los ensamblados desde el directorio /bin cuando se inicia la aplicación. Los ensamblados que se deben cargar se especifican por medio de la configuración del sistema. Para obtener más información, vea la sección Descripción general de la configuración. Mediante la configuración, también es posible importar ensamblados adicionales en una aplicación. Por ejemplo:

<configuration> <compilation> <assemblies> <!--The following assemblies are loaded explicitly from the global cache--> <add assembly="System.Data"/> <add assembly="System.Web.Services"/> <add assembly="System.Drawing"/> <!--This tells ASP.NET to load all assemblies from /bin--> <add assembly="*"/> </assemblies> </compilation></configuration>Nota: el ámbito de cada ensamblado cargado desde /bin se limita a la aplicación en la que se ejecuta. Esto significa que las aplicaciones del mismo nivel podrían utilizar diferentes ensamblados con los mismos nombres de clases o espacios de nombres, sin conflictos.

Página de formularios Web sencilla de dos niveles

Page 141: Presentación del tutorial de ASP

El uso clásico para un componente externo consiste en realizar acceso a datos. De esta forma, se simplifica el código de la página, se mejora la legibilidad y se separa la lógica de la interfaz de usuario de la funcionalidad del sistema. El siguiente ejemplo muestra una página de formularios Web sencilla de dos niveles que utiliza un componente de acceso a datos para recuperar información de productos.

<%@ Import Namespace="DataLayer" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

If Not (Page.IsPostBack) Dim Data As DataObjVB Data = New DataObjVB("server=(local)\NetSDK;database=grocertogo;Trusted_Connection=yes") Categories.DataSource = Data.GetCategories() Categories.DataBind() End If End Sub

Sub Submit_Click(Sender As Object, E As EventArgs)

Dim Data As DataObjVB Data = New DataObjVB("server=(local)\NetSDK;database=grocertogo;Trusted_Connection=yes") Products.DataSource = Data.GetProductsForCategory(Categories.SelectedItem.Value) Products.DataBind() End Sub

</script>

<body style="font: 10pt verdana" bgcolor="ffffcc">

<form runat="server">

<h3>Web Forms simples de dos niveles</h3>

Seleccionar una categoría:

<ASP:DropDownList id="Categories" DataValueField="CategoryName" runat="server"/>

<input type="Submit" OnServerClick="Submit_Click" Value="Obtener productos" runat="server"/><p>

<ASP:DataList id="Products" ShowHeader=false ShowFooter=false RepeatColumns="2" RepeatDirection="horizontal" BorderWidth=0 runat="server">

<ItemTemplate> <table> <tr> <td width="150" style="text-align:center; font-size:8pt; vertical-align:top; height:200"> <ASP:ImageButton borderwidth=6 bordercolor="#ffffcc" command="Select" ImageUrl='<%# DataBinder.Eval(Container.DataItem, "ImagePath") %>' runat="server"/> <p> <%# DataBinder.Eval(Container.DataItem, "ProductName") %> <br> <%# DataBinder.Eval(Container.DataItem, "UnitPrice", "{0:C}").ToString() %> </td> </tr> </table> </ItemTemplate>

</ASP:DataList>

Page 142: Presentación del tutorial de ASP

</form>

</body></html>

El componente de acceso a datos recibe un único parámetro en su constructor que especifica la cadena de conexión con la base de datos de productos. La página de formularios Web llama al método GetCategories del componente para rellenar una lista desplegable y, también, llama al método GetProductsForCategory del componente para mostrar los productos correspondientes a la categoría seleccionada por el usuario.

Página de formularios Web sencilla de tres niveles

Un modelo de aplicación de tres niveles extiende el caso de dos niveles de modo que incluya reglas empresariales entre la interfaz de usuarios y la lógica de acceso a datos. Este modelo permite a los programadores de interfaces de usuario trabajar con un nivel superior de abstracción en vez de manipular los datos directamente por medio de las API de componentes de acceso a datos a bajo nivel. Normalmente, el componente empresarial central impone reglas empresariales y garantiza que las relaciones y las restricciones de clave principal de la base de datos se cumplan. El siguiente ejemplo utiliza el componente central para calcular un descuento según un identificador de proveedor de dos dígitos especificado por el cliente.

<%@ Page ClientTarget="DownLevel"%><%@ Import Namespace="BusinessLayer" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

If Not (IsPostBack)

Dim Bus As BusObjVB Bus = New BusObjVB() Categories.DataSource = Bus.GetCategories() Categories.DataBind() End If End Sub

Sub Submit_Click(Sender As Object, E As EventArgs)

If (Page.IsValid)

Dim Id As Integer = 0 If Not (CustomerId.Text = "") Id = CInt(CustomerId.Text) End If

Dim Bus As BusObjVB Bus = new BusObjVB() Products.DataSource = Bus.GetProductsForCategory(Categories.SelectedItem.Value,Id) End If

Products.DataBind() End Sub

</script>

<body style="font: 10pt verdana" bgcolor="ffffcc">

<form runat="server">

<h3>Web Forms simple de tres niveles</h3>

Page 143: Presentación del tutorial de ASP

Seleccionar una categoría:

<ASP:DropDownList id="Categories" DataValueField="CategoryName" runat="server"/> &nbsp;

Id. de cliente preferido:

<ASP:TextBox id="CustomerId" Width="35" runat="server"/> &nbsp;

<input type="Submit" OnServerClick="Submit_Click" Value="Obtener productos" runat="server"/>

&nbsp;&nbsp;

<asp:RegularExpressionValidator id="RegExValidator" ControlToValidate="CustomerId" ValidationExpression="[0-9]{2}" Display="Dynamic" Font-Name="verdana" Font-Size="10" runat=server> El id. de usuario debe tener dos dígitos numéricos </asp:RegularExpressionValidator>

<p>

<ASP:DataList id="Products" ShowHeader=false ShowFooter=false RepeatColumns="2" RepeatDirection="horizontal" BorderWidth=0 runat="server">

<ItemTemplate> <table> <tr> <td width="150" style="text-align:center; font-size:8pt; vertical-align:top; height:200"> <ASP:ImageButton borderwidth=6 bordercolor="#ffffcc" command="Select" ImageUrl='<%# DataBinder.Eval(Container.DataItem, "ImagePath") %>' runat="server"/> <p> <%# DataBinder.Eval(Container.DataItem, "ProductName") %> <br> <%# DataBinder.Eval(Container.DataItem, "UnitPrice", "{0:C}").ToString() %> </td> </tr> </table> </ItemTemplate>

</ASP:DataList>

</form>

</body></html>

Resumen de la sección

1. El módulo de ejecución de ASP.NET busca los objetos empresariales (ensamblados locales) en un directorio /bin conocido, directamente bajo la raíz de la aplicación. El directorio /bin ofrece las siguientes ventajas:

o No se requiere registro. No se requiere registro para poner un ensamblado a disposición de las páginas de la aplicación. Ya está disponible en virtud de su ubicación en el directorio /bin. El código compilado se puede distribuir simplemente copiándolo o transfiriéndolo mediante FTP a esa ubicación.

o No es necesario reiniciar el servidor. Cuando se cambia cualquier parte de una aplicación ASP.NET Framework (por ejemplo, al reemplazar una DLL en /bin), las nuevas solicitudes inician inmediatamente la ejecución con el archivo o archivos modificados. Las solicitudes actualmente en ejecución pueden terminarse antes de que la antigua aplicación deje de utilizarse. No es necesario reiniciar el servidor Web cuando cambie la aplicación, ni siquiera al reemplazar el código compilado.

o No se producen conflictos de espacios de nombres. El ámbito de cada ensamblado cargado desde /bin se limita a la aplicación en la que se ejecuta. Esto significa que las aplicaciones del mismo nivel podrían utilizar diferentes ensamblados con los mismos nombres de clases o espacios de nombres, sin conflictos.

2. Las clases de un ensamblado se ponen a disposición de una página de la aplicación mediante una directiva Import dentro del archivo .aspx.

Page 144: Presentación del tutorial de ASP

3. Las aplicaciones en dos niveles simplifican el código de una página, mejoran la legibilidad y separan la lógica de la interfaz de usuario de la funcionalidad del sistema.

4. Las aplicaciones de tres niveles extienden el modelo de dos niveles para permitir a los programadores de interfaces de usuario trabajar con un mayor nivel de abstracción. Normalmente, el componente empresarial central impone reglas empresariales y garantiza que las relaciones y las restricciones de clave principal de la base de datos se cumplan.

Crear controles personalizados

En esta sección del tutorial se explica a los programadores avanzados la forma de programar controles de servidor ASP.NET que funcionen en el marco de trabajo de páginas ASP.NET. Al programar sus propios controles de servidor ASP.NET personalizados, es posible encapsular una interfaz de usuario personalizada y otras características de funcionalidad en controles que se pueden reutilizar en páginas ASP.NET. El tutorial proporciona una introducción sobre la creación de controles personalizados mediante ejemplos interactivos. Para obtener más información acerca de la creación de controles, vea Programar controles de servidor ASP.NET en la documentación del SDK de Microsoft .NET Framework.

Nota: los controles descritos en esta sección podrían no funcionar correctamente en un diseñador de formularios, como Microsoft Visual Studio .NET, aunque funcionen correctamente en las páginas ASP.NET en tiempo de ejecución. Para trabajar en un diseñador, un control debe aplicar atributos de tiempo de diseño que no se describen aquí. Para obtener información detallada acerca de los atributos de tiempo de diseño que se deben aplicar, vea Atributos de tiempo de diseño para componentes en la documentación del SDK.

Programar un control personalizado simple

Es fácil iniciar la creación de sus propios controles de servidor ASP.NET. Para crear un control personalizado simple, todo lo que tiene que hacer es definir una clase que se derive de System.Web.UI.Control System.Web.UI.Control Render. El método Render utiliza un argumento de tipo System.Web.UI.HtmlTextWriter. El código HTML que el control envía al cliente se pasa como un argumento de tipo cadena al método Write de HtmlTextWriter.

En el ejemplo siguiente se muestra un control simple que procesa una cadena de mensaje.

<%@ Register TagPrefix="SimpleControlSamples" Namespace="SimpleControlSamples" Assembly="SimpleControlSamplesVB" %>

<html>

<body>

<form method="POST" action="Simple.aspx" runat=server>

<SimpleControlSamples:SimpleVB id="MyControl" runat=server/>

</form>

</body>

</html>

Definir propiedades sencillas

Las propiedades son como campos "inteligentes" con métodos de descriptor de acceso. Hay que exponer propiedades en lugar de campos públicos de los controles, ya que las propiedades permiten ocultar datos, crear versiones y son compatibles con los diseñadores visuales. Las propiedades tienen métodos get/set de descriptores de acceso que establecen y obtienen valores de propiedades, y que permiten ejecutar lógica de programa adicional si es necesario.

En el siguiente ejemplo se muestra la forma de agregar propiedades sencillas que corresponden a tipos de datos primitivos, como números enteros, booleanos o cadenas. En el ejemplo se definen tres propiedades: Message es de tipo cadena, MessageSize es de

tipo enumeración e Iterations es de tipo entero. Hay que tener en cuenta la sintaxis de página para establecer propiedades simples o de tipo enumeración.

Page 145: Presentación del tutorial de ASP

<%@ Register TagPrefix="SimpleControlSamples" Namespace="SimpleControlSamples" Assembly="SimpleControlSamplesVB" %>

<html>

<body>

<form method="POST" runat="server">

<SimpleControlSamples:SimplePropertyVB Message="Hola" MessageSize="Large" Iterations=3 runat=server/>

</form>

</body>

</html>

Definir propiedades de clase

Si una clase A tiene una propiedad cuyo tipo es la clase B, las propiedades de B (si existen) serán las subpropiedades de A. En el ejemplo siguiente se define un control SimpleSubProperty personalizado que tiene una propiedad de tipo Format. Format es una clase que

tiene dos propiedades primitivas: Color y Size, que a su vez serán subpropiedades de SimpleSubProperty.

<%@ Register TagPrefix="SimpleControlSamples" Namespace="SimpleControlSamples" Assembly="SimpleControlSamplesVB" %>

<html>

<body>

<form method="POST" runat="server">

<SimpleControlSamples:SimpleSubPropertyVB Message="Hola" Format-Color="red" Format-Size="3" runat=server/>

</form>

</body>

</html>

Hay que tener en cuenta que ASP.NET tiene una sintaxis especial para establecer el valor de las subpropiedades. En el siguiente ejemplo de código se muestra la manera de establecer de forma declarativa los valores de las subpropiedades Format.Color y Format.Size en

SimpleSubProperty. La sintaxis "-" denota una subpropiedad.

<SimpleControlSamples:SimpleSubProperty Message="Hello There" Format-Color="red" Format-Size="3" runat=server/>

Recuperar el contenido interno

Cada control tiene una propiedad Controls que hereda de System.Web.UI.Control. Se trata de una propiedad de colección que denota los controles secundarios (si existen) de un control. Si un control no está marcado con ParseChildrenAttribute o está marcado con ParseChildrenAttribute(ChildrenAsProperties = false), el marco de trabajo de páginas ASP.NET aplica la siguiente lógica de análisis cuando se usa el control de forma declarativa en una página. Si el analizador encuentra controles anidados dentro de las etiquetas del control,

Page 146: Presentación del tutorial de ASP

crea instancias de los controles y los agrega a la propiedad Controls del control. El texto literal entre etiquetas se agrega como un objeto LiteralControl. Los demás elementos anidados generarán un error de analizador.

En el ejemplo siguiente se muestra un control personalizado, SimpleInnerContent, que procesa texto agregado entre etiquetas comprobando si se ha agregado un objeto LiteralControl a su colección Controls. Si así fuera, obtiene el valor de la propiedad Text del objeto LiteralControl y lo anexa a la cadena de salida.

<%@ Register TagPrefix="SimpleControlSamples" Namespace="SimpleControlSamples" Assembly="SimpleControlSamplesVB" %>

<html>

<body>

<form method="POST" runat="server">

<SimpleControlSamples:SimpleInnerContentVB id="MyControl" runat=server> Mi mensaje está dentro de la etiqueta de control </SimpleControlSamples:SimpleInnerContentVB>

</form>

</body>

</html>

Nota: si el control personalizado se deriva de WebControl, no tendrá la lógica de análisis descrita en el ejemplo, ya que WebControl estará marcado con ParseChildrenAttribute(ChildrenAsProperties = true), que resulta de una lógica de análisis diferente. Para obtener más información acerca de ParseChildrenAttribute, vea la documentación del SDK.

Crear un control compuesto

Es posible crear controles nuevos combinando controles existentes mediante una mezcla de clases. Los controles compuestos son equivalentes a controles de usuario creados con la sintaxis de páginas ASP.NET. La diferencia principal entre los controles de usuario y los controles compuestos es que los controles de usuario se almacenan como archivos de texto .ascx, mientras que los controles compuestos se compilan y se almacenan en ensamblados.

Los pasos clave para desarrollar un control compuesto son:

Reemplazar el método CreateChildControls protegido que se hereda de Control para crear instancias de controles secundarios y agregarlas a la colección Controls.

Si se van a crear en la página instancias nuevas del control compuesto de forma repetida, debe implementarse la interfaz de sistema System.Web.UI.INamingContainer. Se trata de una interfaz de etiquetado que no tiene métodos. Cuando se implementa mediante un control, el marco de trabajo para páginas ASP.NET crea un nuevo ámbito de asignación de nombres bajo ese control. Esto garantiza que los controles secundarios tendrán identificadores únicos en el árbol jerárquico de controles.

No es necesario reemplazar el método Render, ya que los controles secundarios proporcionan lógica de procesamiento. Es posible exponer propiedades que sinteticen propiedades de los controles secundarios.

En el siguiente ejemplo se define un control compuesto, Composition1, que combina un objeto System.Web.UI.LiteralControl y un objeto

System.Web.UI WebControls.TextBox. El control Composition1 expone una propiedad personalizada, Value, de tipo entero, que se asigna a la propiedad Text de TextBox.

<%@ Register TagPrefix="CompositionSampleControls" Namespace="CompositionSampleControls" Assembly="CompositionSampleControlsVB" %>

<html>

Page 147: Presentación del tutorial de ASP

<script language="VB" runat=server>

Private Sub AddBtn_Click(Sender As Object, E As EventArgs) MyControl.Value = MyControl.Value + 1 End Sub

Private Sub SubtractBtn_Click(Sender As Object, E As EventArgs) MyControl.Value = MyControl.Value - 1 End Sub

</script>

<body>

<form method="POST" action="Composition1.aspx" runat=server>

<CompositionSampleControls:Composition1VB id="MyControl" runat=server/>

<br>

<asp:button text="Sumar" OnClick="AddBtn_Click" runat=server/> | <asp:button text="Restar" OnClick="SubtractBtn_Click" runat=server/>

</form>

</body>

</html>

Controlar eventos en un control compuesto

Un control compuesto puede controlar eventos desencadenados por sus controles secundarios. Esto se logra proporcionando métodos de control de eventos y asociando delegados a eventos desencadenados por los controles secundarios.

En el siguiente ejemplo se muestra un control compuesto, Composition2, que agrega dos controles de botón (denominados Add y

Subtract) al control compuesto del ejemplo anterior y proporciona métodos de control de eventos para los eventos Click de los botones.

Estos métodos incrementan y disminuyen el valor de la propiedad Value de Composition2. El método CreateChildControls de

Composition2 crea instancias de los controladores de eventos (delegados) que hacen referencia a estos métodos y asocia los delegados a los eventos Click de las instancias de Button. El resultado final es un control que realiza su propio control de eventos: cuando se hace clic en el botón Add, el valor del cuadro de texto aumenta y cuando se hace clic en el botón Subtract, el valor del cuadro de texto disminuye.

<%@ Register TagPrefix="CompositionSampleControls" Namespace="CompositionSampleControls" Assembly="CompositionSampleControlsVB" %>

<html>

<body>

<form method="POST" action="Composition2.aspx" runat=server>

<CompositionSampleControls:Composition2VB id="MyControl" runat=server/>

</form>

</body>

</html>

Page 148: Presentación del tutorial de ASP

Provocar eventos personalizados desde un control compuesto

Un control compuesto puede definir eventos personalizados que se provocan en respuesta a eventos provocados por sus controles secundarios.

En el ejemplo siguiente se muestra un control compuesto, Composition3, que provoca un evento personalizado, Change, en respuesta al evento TextChanged del control secundario TextBox.

Esto se realiza de la manera siguiente:

El evento Change personalizado se define mediante el modelo de evento estándar. Este patrón incluye la definición de un

método OnChange protegido que provoca el evento Change.

Public Event Change(Sender as Object, E as EventArgs)Protected Sub OnChange(e As EventArgs) Change(Me, e)End Sub

Se define un método de control de eventos para el evento TextChanged de TextBox. Este método desencadena el evento

Change llamando al método OnChange.

Private Sub TextBox_Change(sender As Object, e As EventArgs) OnChange(EventArgs.Empty)End Sub

El método CreateChildControls crea una instancia de un controlador de eventos que hace referencia al método anterior y

asocia el controlador de eventos al evento TextChanged de la instancia de TextBox.

Protected Overrides Sub CreateChildControls() ... Dim box As New TextBox() AddHandler Box.TextChanged, AddressOf TextBox_Change ...End Sub

El evento Change se puede controlar en una página que aloje al control, como se muestra en el siguiente ejemplo. En este ejemplo, la

página proporciona un método de control de eventos para el evento Change que establece el valor de la propiedad Value en cero si el número escrito por el usuario es negativo.

<%@ Register TagPrefix="CompositionSampleControls" Namespace="CompositionSampleControls" Assembly="CompositionSampleControlsVB" %>

<html>

<script language="VB" runat=server>

Private Sub Composition2_Change(Sender As Object, E As EventArgs)

If MyControl.Value < 0 MyControl.Value = 0 End If End Sub

</script>

<body>

<form method="POST" action="Composition3.aspx" runat=server>

Page 149: Presentación del tutorial de ASP

<CompositionSampleControls:Composition3VB id="MyControl" OnChange="Composition2_Change" runat=server/>

</form>

</body>

</html>

Mantener el estado

Todo control para formularios Web tiene una propiedad State (heredada de Control) que le permite participar en la administración de State. El tipo de State es Sytem.Web.UI.StateBag, una estructura de datos equivalente a una tabla hash. Un control puede guardar datos en State en forma de claves con sus valores correspondientes. El marco de trabajo de páginas ASP.NET almacena el valor de State en una variable de cadena; el valor realiza un viaje de ida y vuelta al cliente como una variable oculta. Tras la devolución automática al servidor, el marco de trabajo de páginas analiza la cadena de entrada de la variable oculta y llena la propiedad State de cada control de la jerarquía de controles de una página. Un control puede restaurar su estado (establecer los valores de las propiedades y los campos anteriores a la devolución automática al servidor) mediante la propiedad State. Los programadores de controles deben saber que hay una sobrecarga de rendimiento por viajes de ida y vuelta al cliente, y deben ser cuidadosos con lo que almacenan en State.

En el siguiente fragmento de código se muestra una propiedad que se guarda en State.

Public Property Text As String Get Return CType(State("Text"), String)) End Get Set State("Text") = Value End SetEnd Property

En el siguiente ejemplo se muestra un control personalizado, Label con dos propiedades, Text y FontSize, que se guardan en State. La página ASP.NET que utiliza Label contiene botones que tienen controladores de eventos para aumentar el tamaño de fuente del texto del control Label al hacer clic en un botón. Por tanto, el tamaño de fuente aumentará cada vez que se haga clic en un botón. Esto sólo es posible mediante la administración de estados: Label tiene que saber cuál era el tamaño de la fuente antes de la devolución automática al servidor con el fin de procesar la próxima fuente grande después de la devolución automática al servidor.

<%@ Register TagPrefix="ViewStateControlSamples" Namespace="ViewStateControlSamples" Assembly="ViewStateControlSamplesVB" %>

<html>

<script language="VB" runat=server>

Private Sub Btn1_Click(Sender As Object, E As EventArgs) MyLabel1.Text = "Se hizo clic en el botón 1" MyLabel1.FontSize = MyLabel1.FontSize + 1 End Sub

Private Sub Btn2_Click(Sender As Object, E As EventArgs) MyLabel2.Text = "Se hizo clic en el botón 2" MyLabel2.FontSize = MyLabel2.FontSize + 1 End Sub

Page 150: Presentación del tutorial de ASP

</script>

<body>

<form method="POST" action="Label.aspx" runat=server>

Mensaje1: <ViewStateControlSamples:LabelVB id="MyLabel1" Text="Label1" FontSize=1 runat=server/> <br> Mensaje2: <ViewStateControlSamples:LabelVB id="MyLabel2" Text="Label2" FontSize=1 runat=server/>

<br><br>

<asp:button Text="Botón1" OnClick="Btn1_Click" runat=server/> | <asp:button Text="Botón2" OnClick="Btn2_Click" runat=server/>

</form>

</body>

</html>

En el siguiente ejemplo se muestra un control personalizado, Label con dos propiedades, Text y FontSize, que se guardan en State. La página ASP.NET que utiliza Label contiene botones que tienen controladores de eventos para aumentar el tamaño de fuente del texto del control Label al hacer clic en un botón. Por tanto, el tamaño de fuente aumentará cada vez que se haga clic en un botón. Esto sólo es posible mediante la administración de estados: Label tiene que saber cuál era el tamaño de la fuente antes de la devolución automática al servidor con el fin de procesar la próxima fuente grande después de la devolución automática al servidor.

<%@ Register TagPrefix="NonCompositionSampleControls" Namespace="NonCompositionSampleControls" Assembly="NonCompositionSampleControlsVB" %>

<html>

<script language="VB" runat=server>

Private Sub AddBtn_Click(Sender As Object, E As EventArgs) MyControl.Value = MyControl.Value + 1 End Sub

Private Sub SubtractBtn_Click(Sender As Object, E As EventArgs) MyControl.Value = MyControl.Value - 1 End Sub

</script>

<body>

<form method="POST" action="NonComposition1.aspx" runat=server>

<NonCompositionSampleControls:NonComposition1VB id="MyControl" runat=server/>

<br>

<asp:button text="Sumar" OnClick="AddBtn_Click" runat=server/> | <asp:button text="Restar" OnClick="SubtractBtn_Click" runat=server/>

</form>

</body>

Page 151: Presentación del tutorial de ASP

</html>

Desarrollar un control personalizado (no compuesto) para controlar datos de devolución automática al servidor

Al principio del tutorial, se creó un control personalizado sencillo. En el siguiente ejemplo se muestra un control personalizado que hace algo más complejo: muestra un cuadro de entrada de datos y lee los datos escritos por el usuario. Los controles que examinan datos de devolución automática al servidor (entrada) deben implementar la interfaz System.Web.UI.IPostBackDataHandler. Esto indica al marco de trabajo de páginas ASP.NET que un control debe participar en el control de la devolución automática de datos al servidor. El marco de trabajo de páginas

transfiere los datos de entrada al método LoadPostData de esta interfaz en forma de claves con sus valores correspondientes. En la implementación de este método, el control puede examinar los datos de entrada y actualizar sus propiedades de la forma indicada a continuación.

Private _value As Integer = 0Public Function LoadPostData(postDataKey As String, values As NameValueCollection) As Boolean _value = Int32.Parse(values(Me.UniqueID)) Return(False)End Function

<%@ Register TagPrefix="NonCompositionSampleControls" Namespace="NonCompositionSampleControls" Assembly="NonCompositionSampleControlsVB" %>

<html>

<body>

<form method="POST" action="NonComposition2.aspx" runat=server>

<NonCompositionSampleControls:NonComposition2VB id="MyControl" runat=server/>

</form>

</body>

</html>

En el siguiente ejemplo se define un control personalizado, NonComposition1, que implementa IPostBackDataHandler y tiene una

propiedad, Value. El control muestra un cuadro HTML de entrada de datos cuyo atributo de texto es la representación en forma de cadena de

Value. Esta propiedad se establece examinando datos de entrada de devolución automática al servidor. La página que utiliza

NonComposition1 también tiene dos botones con controladores de eventos con el fin de incrementar y disminuir el valor de la propiedad

Value de NonComposition1.

<%@ Register TagPrefix="TemplateControlSamples" Namespace="TemplateControlSamplesVB" Assembly="TemplateControlSamplesVB" %> <script runat=server language=VB> Sub Page_Load() DataBind() End Sub </script>

<html>

<body>

<form method="POST" runat="server">

Versión sin plantillas:

Page 152: Presentación del tutorial de ASP

<TemplateControlSamples:Template1VB Message="Hello World!" runat=server/>

<hr>

Versión con plantillas:

<TemplateControlSamples:Template1VB Message="Hello World!" runat=server>

<MessageTemplate>

<b><i><u> <%# Container.Message%> </u></i></b>

</MessageTemplate>

</TemplateControlSamples:Template1VB>

</form>

</body>

</html>

Generar código JavaScript de cliente para la devolución automática personalizada al servidor

Si un control desea capturar eventos de devolución automática al servidor (envíos de formularios desde un cliente), debe implementarse la interfaz System.Web.UI.IPostBackEventHandler. Esto indica al marco de trabajo de páginas ASP.NET que un control requiere notificación de un evento de devolución automática de datos al servidor. El control utiliza el método RaisePostBackEvent para controlar el evento y provocar otros eventos. Además, el marco de trabajo de páginas ASP.NET tiene una arquitectura de eventos personalizada que permite a un control generar código JavaScript de cliente que inicia la devolución automática personalizada al servidor. Normalmente, la devolución automática al servidor la inician pocos elementos, como un botón Enviar o un botón Imagen. Sin embargo, al emitir código JavaScript de cliente, un control también puede iniciar la devolución automática al servidor desde otros elementos HTML.

En el siguiente ejemplo se define un control personalizado, NonComposition2, a partir del ejemplo anterior, NonComposition1. Además de la interfaz proporcionada por NonComposition1, muestra dos controles HtmlButton que generan código JavaScript de cliente para provocar la devolución automática al servidor cuando se haga clic en ellos. Los atributos de nombre de estos botones son Add y Subtract. El marco de trabajo de páginas pasa el atributo de nombre como un argumento de cadena a RaisePostBackEvent. NonComposition2 implementa

RaisePostBackEvent con el fin de incrementar el valor de la propiedad Value si se hace clic en Add y para disminuir su valor si se hace clic en Subtract, como se indica a continuación.

Public Sub RaisePostBackEvent(eventArgument As String) If eventArgument = "Add" Then Me.Value = Me.Value + 1 Else Me.Value = Me.Value - 1 End IfEnd Sub

La interfaz de usuario que se presenta al cliente es idéntica a la del ejemplo anterior; sin embargo, se presenta la interfaz de usuario completa mediante un control personalizado que también controla los eventos de devolución automática al servidor. El programador de la página puede agregar simplemente NonComposition2 a la página, sin proporcionar lógica de control de eventos. En el ejemplo siguiente se presenta este código en acción.

<%@ Register TagPrefix="TemplateControlSamples" Namespace="TemplateControlSamplesVB" Assembly="TemplateControlSamplesVB" %>

<html>

Page 153: Presentación del tutorial de ASP

<script language="VB" runat=server>

Sub Page_Load(Sender As Object, E As EventArgs)

If Not (IsPostBack)

Dim Values As New ArrayList

Values.Add("1") Values.Add("2") Values.Add("3") Values.Add("4")

MyList.DataSource = Values MyList.DataBind() End If End Sub

Private Sub Btn1_Click(Sender As Object, E As EventArgs)

' Do nothing for now. Just demonstrate that values ' where preserved.... End Sub

</script>

<body>

<form method="POST" action="Repeater1.aspx" runat=server>

<TemplateControlSamples:Repeater1VB id="MyList" runat=server>

<ItemTemplate>

Valor: <asp:textbox id="MyValue" Text="<%#Container.DataItem%>" runat=server/>

<hr align=left width=200>

</ItemTemplate>

</TemplateControlSamples:Repeater1VB>

<asp:button Text="Actualizar" OnClick="Btn1_Click" runat=server/>

</form>

</body>

</html>

Desarrollar un control a partir de plantillas

El marco de trabajo de páginas ASP.NET permite a los programadores de controles crear controles que separen la interfaz de usuario de la lógica del control mediante el uso de plantillas. Los programadores de páginas pueden personalizar la presentación del control proporcionando la interfaz de usuario en forma de parámetros entre etiquetas de plantilla.

Los controles creados a partir de plantillas tienen una o más propiedades de tipo System.Web.UI.ITemplate, como se indica en el siguiente ejemplo.

Public Property <TemplateContainer(GetType(Template1VB))> MessageTemplate As ITemplate

Page 154: Presentación del tutorial de ASP

El atributo (mostrado arriba entre corchetes) especifica el tipo del control contenedor (control principal).

La interfaz ITemplate tiene un método, InstantiateIn, que crea una instancia de control dinámicamente. Se invoca en la propiedad ITemplate

del método CreateChildControls, como se muestra en el siguiente ejemplo.

Protected Overrides Sub CreateChildControls() If MessageTemplate <> Null Then MessageTemplate.InstantiateIn(Me) End if ...End Sub

En el siguiente ejemplo se muestra un control sencillo creado a partir de plantillas y una página ASP.NET que lo utiliza.

<%@ Register TagPrefix="TemplateControlSamples" Namespace="TemplateControlSamplesVB" Assembly="TemplateControlSamplesVB" %>

<html>

<script language="VB" runat=server>

Public Sub Page_Load(Sender As Object, E As EventArgs)

If Not (IsPostBack)

Dim Values As New ArrayList

Values.Add("1") Values.Add("2") Values.Add("3") Values.Add("4")

MyList.DataSource = Values MyList.DataBind()

End If

End Sub

Private Sub Btn1_Click(Sender As Object, E As EventArgs) Dim X As Long

For X = 0 To MyList.Items.Count - 1

Dim MyValue As TextBox = MyList.Items(X).FindControl("MyValue") MyValue.Text = (Int32.Parse(MyValue.Text) + 1).ToString() Next End Sub

</script>

<body>

<form method="POST" action="Repeater2.aspx" runat=server>

<TemplateControlSamples:Repeater2VB id="MyList" runat=server>

Page 155: Presentación del tutorial de ASP

<ItemTemplate>

Valor: <asp:textbox id="MyValue" Text="<%#Container.DataItem%>" runat=server/>

<hr align=left width=200>

</ItemTemplate>

</TemplateControlSamples:Repeater2VB>

<asp:button Text="Actualizar" OnClick="Btn1_Click" runat=server/>

</form>

</body>

</html>

Desarrollar un control enlazado a datos a partir de plantillas

En el siguiente ejemplo se muestra un uso más complejo de plantillas para crear un control enlazado a datos. El control Repeater definido en este ejemplo es similar al control System.Web.UI.WebControls.Repeater.

<%@ Register TagPrefix="CustomParsingControlSamples" Namespace="CustomParsingControlSamples" Assembly="CustomParsingControlSamplesVB" %>

<html>

<body>

<form method="POST" runat=server>

<CustomParsingControlSamples:CustomParse1VB SelectedIndex="2" runat=server>

<CustomParsingControlSamples:ItemVB Message="Uno" runat=server/> <CustomParsingControlSamples:ItemVB Message="Dos" runat=server/> <CustomParsingControlSamples:ItemVB Message="Tres" runat=server/> <CustomParsingControlSamples:ItemVB Message="Cuatro" runat=server/>

</CustomParsingControlSamples:CustomParse1VB>

</form>

</body>

</html>

En el siguiente ejemplo se modifica el ejemplo anterior de forma que un usuario de páginas pueda recorrer la colección Items durante la devolución automática al servidor para obtener valores de la misma.

<%@ Register TagPrefix="CustomParsingControlSamples" Namespace="CustomParsingControlSamples" Assembly="CustomParsingControlSamplesVB" %>

<html>

Page 156: Presentación del tutorial de ASP

<body>

<form method="POST" runat=server>

<CustomParsingControlSamples:CustomParse2VB SelectedIndex="2" runat=server>

<customitem Message="Uno"/> <customitem Message="Dos"/> <customitem Message="Tres"/> <customitem Message="Cuatro"/>

</CustomParsingControlSamples:CustomParse2VB>

</form>

</body>

</html>

Reemplazar el análisis de controles

Como se ha podido ver en Recuperar el contenido interno, si un control A tiene controles anidados dentro de las etiquetas de control en una página, el analizador de la página agrega instancias de esos controles a la colección Controles A. Para ello hay que invocar al método AddSubParsedObject de A. Cada control hereda este método de Control; la implementación predeterminada sólo inserta un control secundario en el árbol de jerarquía de controles. Un control puede reemplazar la lógica de análisis predeterminada reemplazando el método AddSubParsedObject. Hay que tener en cuenta que este análisis está simplificado. En el siguiente ejemplo se proporcionan más detalles.

En el siguiente ejemplo se define un control personalizado, CustomParse1, que reemplaza la lógica de análisis predeterminada. Cuando se

analiza un control secundario de un tipo determinado, se agrega a una colección. La lógica de presentación de CustomParse1 se basa en

el número de elementos de la colección. También se define en el ejemplo un control personalizado sencillo, Item.

<%@ Register TagPrefix="CustomParsingControlSamples" Namespace="CustomParsingControlSamples" Assembly="CustomParsingControlSamplesVB" %>

<html>

<body>

<form method="POST" runat=server>

<CustomParsingControlSamples:CustomParse1VB SelectedIndex="2" runat=server>

<CustomParsingControlSamples:ItemVB Message="Uno" runat=server/> <CustomParsingControlSamples:ItemVB Message="Dos" runat=server/> <CustomParsingControlSamples:ItemVB Message="Tres" runat=server/> <CustomParsingControlSamples:ItemVB Message="Cuatro" runat=server/>

</CustomParsingControlSamples:CustomParse1VB>

</form>

</body>

</html>

Nota: si el control personalizado se deriva de WebControl, no tendrá la lógica de análisis descrita en el ejemplo, ya que WebControl estará marcado con ParseChildrenAttribute(ChildrenAsProperties = true), que resulta de una lógica de análisis diferente. Para obtener más información acerca de ParseChildrenAttribute, vea la documentación del SDK. En el tema Recuperar el contenido interno también se describe este tema de forma más detallada.

Page 157: Presentación del tutorial de ASP

Definir un generador de controles personalizados

El marco de trabajo de páginas ASP.NET utiliza clases denominadas generadores de controles para procesar las declaraciones escritas entre etiquetas de control en una página. Todo control de formularios Web está asociado a una clase de generador de controles predeterminada, System.Web.UI.ControlBuilder. El generador de controles predeterminado agrega un control secundario a la colección Controls para cada

control anidado que encuentre entre etiquetas de control. Además, agrega controles Literal para texto entre etiquetas de control anidado. Es posible reemplazar este comportamiento predeterminado asociando una clase de generador de controles personalizados al control. Esto se hace aplicando al control un atributo de generador de controles, como se muestra en el siguiente ejemplo.

Public Class <ControlBuilderAttribute(GetType(CustomParse2ControlBuilderVB))> _ CustomParse2VB : Inherits Control

El elemento entre corchetes anterior es un atributo de Common Language Runtime que asocia la clase CustomParse2ControlBuilder al

control CustomParse2. Es posible definir su propio generador de controles personalizado derivando de ControlBuilder y reemplazando sus métodos.

En el siguiente ejemplo se define un generador de controles personalizado que reemplaza el método GetChildControlType heredado de ControlBuilder. Este método devuelve el tipo de control que se va a agregar y se puede utilizar para decidir qué controles se van a agregar. En el ejemplo, el generador de controles sólo agregará un control secundario si el nombre de la etiqueta es "customitem". El código del control es muy similar al del ejemplo anterior, con la diferencia de que se agrega el atributo personalizado.

<%@ Register TagPrefix="CustomParsingControlSamples" Namespace="CustomParsingControlSamples" Assembly="CustomParsingControlSamplesVB" %>

<html>

<body>

<form method="POST" runat=server>

<CustomParsingControlSamples:CustomParse2VB SelectedIndex="2" runat=server>

<customitem Message="Uno"/> <customitem Message="Dos"/> <customitem Message="Tres"/> <customitem Message="Cuatro"/>

</CustomParsingControlSamples:CustomParse2VB>

</form>

</body>

</html>

Servicios Web de ASP.NET 

Escibir un servicio Web sencillo

Es posible escribir un servicio Web de XML sencillo en pocos minutos mediante el uso de cualquier editor de texto. El servicio que se creará en esta sección, MathService, presenta métodos para sumar, restar, dividir y multiplicar dos números. En la parte superior de la página, la directiva siguiente identifica el archivo como un servicio Web de XML además de especificar el lenguaje para el servicio (C#, en este caso). <%@ WebService Language="C#" Class="MathService" %>

En este mismo archivo, se define una clase que encapsula la funcionalidad del servicio. Esta clase debe ser pública y opcionalmente puede heredar de la clase base WebService. Cada método que se va a exponer del servicio presenta un indicador con un atributo [WebMethod]

Page 158: Presentación del tutorial de ASP

delante de él. Sin este atributo, no se expondrá el método del servicio. A veces, esto resulta útil para ocultar los detalles de implementación llamados por métodos Web Service públicos o en el caso en que la clase WebService se utilice también en aplicaciones locales (una

aplicación local puede utilizar cualquier clase pública, pero sólo las clases WebMethod se encuentran accesibles de forma remota como servicios Web de XML).

Imports SystemImports System.Web.Services

Public Class MathService : Inherits WebService

<WebMethod()> Public Function Add(a As Integer, b As Integer) As Integer Return(a + b) End Function

End Class

Los archivos de servicios Web de XML se guardan con la extensión de archivo .asmx. Al igual que los archivos .aspx, estos archivos se compilan de forma automática mediante el motor de tiempo de ejecución de ASP.NET cuando se realiza una solicitud al servicio (las solicitudes posteriores se atienden mediante un objeto de tipo precompilado almacenado en caché). En el caso de MathService, se ha definido la clase WebService en el propio archivo .asmx. Hay que tener en cuenta que si un explorador solicita un archivo .asmx, el motor de tiempo de ejecución de ASP.NET devuelve una página de ayuda del servicio Web de XML que describe el servicio Web en cuestión.

<%@ WebService Language="VB" Class="MathService" %>

Imports SystemImports System.Web.Services

Public Class MathService : Inherits WebService

<WebMethod()> Public Function Add(A As System.Single, B As System.Single) As System.Single

Return A + B End Function

<WebMethod()> Public Function Subtract(A As System.Single, B As System.Single) As System.Single

Return A - B End Function

<WebMethod()> Public Function Multiply(A As System.Single, B As System.Single) As System.Single

Return A * B End Function

<WebMethod()> Public Function Divide(A As System.Single, B As System.Single) As System.Single

If B = 0 Return -1 End If Return Convert.ToSingle(A / B) End Function

End Class

Servicios Web de XML precompiladosSi se tiene una clase precompilada que se desea exponer como un servicio Web de XML (y esta clase expone métodos marcados con el atributo [WebMethod]), es posible crear un archivo .asmx con sólo la siguiente línea. <%@ WebService Class="MyWebApplication.MyWebService" %>

Page 159: Presentación del tutorial de ASP

MyWebApplication.MyWebService define la clase WebService y está incluido en el subdirectorio \bin de la aplicación ASP.NET.

Consumir un servicio Web de XML desde una aplicación clientePara utilizar este servicio, hay que hacer uso de la herramienta de línea de comandos del lenguaje de descripción de servicios Web (Web Services Description Language), WSDL.exe, incluida en el kit de desarrollo de software (SDK) con el fin de crear una clase proxy que sea similar a la clase definida en el archivo .asmx. (Solamente contendrá los métodos WebMethod). A continuación, hay que compilar el código con esta clase proxy incluida.

WSDL.exe acepta diversas opciones de línea de comandos; sin embargo, para crear un proxy sólo se requiere una opción: el identificador URI al WSDL. En este ejemplo, se pasan algunas opciones adicionales que especifican el lenguaje preferido, el espacio de nombres y la ubicación de salida para el proxy. También es posible compilar con un archivo WSDL guardado anteriormente en lugar del identificador URI al propio servicio:

wsdl.exe /l:CS /n:MathService /out:MathService.cs MathService.wsdl

Una vez que existe la clase proxy, es posible crear objetos basados en ella. Cada llamada de método realizada con el objeto pasa seguidamente al identificador URI del servicio Web de XML (normalmente como una solicitud SOAP).

<%@ Import Namespace="MathServiceVB" %>

<html>

<script language="VB" runat="server">

Dim Op1 As Single = 0 Dim Op2 As Single = 0

Public Sub Submit_Click(Sender As Object, E As EventArgs)

Try

Op1 = Single.Parse(Operand1.Text) Op2 = Single.Parse(Operand2.Text)

Catch Exp As Exception ' Ignored End Try

Dim Service As MathServiceVB.MathService = New MathServiceVB.MathService()

Select (CType(sender,Control).ID)

Case "Add" : Result.Text = "<b>Resultado</b> = " & Service.Add(Op1, Op2).ToString() Case "Subtract" : Result.Text = "<b>Resultado</b> = " & Service.Subtract(Op1, Op2).ToString() Case "Multiply" : Result.Text = "<b>Resultado</b> = " & Service.Multiply(Op1, Op2).ToString() Case "Divide" : Result.Text = "<b>Resultado</b> = " & Service.Divide(Op1, Op2).ToString() End Select End Sub

</script>

<body style="font: 10pt verdana">

<h4>Utilizar un servicio matemático simple </h4>

<form runat="server">

Page 160: Presentación del tutorial de ASP

<div style="padding:15,15,15,15;background-color:beige;width:330;border-color:black;border-width:1;border-style:solid">

Operando 1: <br><asp:TextBox id="Operand1" Text="15" runat="server"/><br> Operando 2: <br><asp:TextBox id="Operand2" Text="5" runat="server"/><p>

<input type="submit" id="Add" value="Agregar" OnServerClick="Submit_Click" runat="server"> <input type="submit" id="Subtract" value="Restar" OnServerClick="Submit_Click" runat="server"> <input type="submit" id="Multiply" value="Multiplicar" OnServerClick="Submit_Click" runat="server"> <input type="submit" id="Divide" value="Dividir" OnServerClick="Submit_Click" runat="server">

<p>

<asp:Label id="Result" runat="server"/>

</div>

</form>

</body></html>

Cálculo de referencias de servicios Web de XML

En esta sección se muestra que se pueden pasar varios tipos de datos a métodos de servicios Web y devolverlos desde los mismos. Dado que la implementación de servicios Web de XML se genera en la parte superior de la arquitectura de serialización de XML, admite un importante número de tipos de datos. En la siguiente tabla se enumeran los tipos de datos admitidos para métodos de servicios Web cuando se utiliza el protocolo SOAP (por ejemplo, al utilizar el proxy generado por la herramienta del Lenguaje de descripción de servicios Web, WSDL.exe).

Tipo Descripción

Tipos primitivos Tipos primitivos estándar. En la lista completa de tipos primitivos admitidos se encuentran String, Char, Byte, Boolean, Int16, Int32, Int64, UInt16, UInt32, UInt64, Single, Double, Guid, Decimal, DateTime (como timeInstant de XML), DateTime (como fecha de XML), DateTime (como hora de XML) y XmlQualifiedName (como QName de XML).

Tipos Enum Tipos de enumeración, como por ejemplo, "public enum color { red=1, blue=2 }"

Matrices de primitivos, Enums Matrices de los tipos primitivos anteriores, como string[] e int[]

Clases y estructuras Tipos de clases y estructuras con campos o propiedades públicas. Se pueden serializar los campos y propiedades públicos.

Matrices de clases (estructuras) Matrices de los tipos anteriores.

DataSet Tipos de DataSet de ADO.NET (vea la siguiente sección para obtener un ejemplo). También pueden aparecer DataSet como campos en estructuras y clases.

Nota: Microsoft Visual Studio.NET y la utilidad del SDK XSD.EXE son compatibles para "establecer tipos inflexiblemente" en DataSet. Estas herramientas generan una clase que se hereda de DataSet para producir DataSet1 al agregar varios métodos, propiedades, etc. que son específicos para un esquema XML concreto. Si se pasa DataSet, los servicios Web de XML siempre transmiten el esquema junto con los datos (de forma que sabe qué tablas y columnas se están pasando) y los tipos correspondientes (por ejemplo, int, string). Si se pasa una subclase de DataSet (por ejemplo, DataSet1), los servicios Web de XML suponen que se agregan tablas o columnas al constructor, así como que esas tablas o columnas representan el esquema.

Matrices de DataSet Matrices del tipo anterior.

XmlNode XmlNode consiste en una representación en memoria de un fragmento de XML (como un modelo ligero de objeto de documento de XML). Por ejemplo, "<comment>This is<b>pretty</b> neat</comment>" podría almacenarse en un tipo XmlNode. Se pueden pasar tipos XmlNode como parámetros y se agregan al resto de XML que se está pasando al servicio Web de XML (los otros parámetros) de forma compatible con SOAP. Lo mismo sirve para los valores devueltos. Esto permite pasar o devolver XML cuya estructura cambia de llamada en llamada o donde no se pueden conocer todos los tipos que se están pasando. También pueden aparecer tipos XmlNode como campos en

Page 161: Presentación del tutorial de ASP

estructuras y clases.

Matrices de XmlNode Matrices del tipo anterior.

Valores devueltos:

Tanto si se llama a un servicio Web de XML mediante SOAP o HTTP GET/POST, todos los tipos anteriores son compatibles con los valores devueltos.

Parámetros:

Tanto los parámetros por valor, como por referencia (in/out) son compatibles si se utiliza el protocolo SOAP. Los parámetros por referencia pueden enviar el valor en dos direcciones: hasta el servidor y de vuelta al cliente. Cuando se analizan parámetros de entrada a un servicio Web de XML mediante HTTP GET/POST, sólo se admite un conjunto limitado de tipos de datos que deben ser parámetros por valor. A continuación se enumeran los tipos compatibles con los parámetros GET/POST de HTTP:

Tipo DescripciónTipos primitivos (limitados) La mayoría de los tipos primitivos estándar. La lista completa de tipos primitivos admitidos

comprende los tipos Int32, String, Int16, Int64, Boolean, Single, Double, Decimal, DateTime, UInt16, UInt32, UInt64 y Currency. Desde el punto de vista del cliente, todos estos tipos se transforman en cadenas.

Tipos Enum Tipos de enumeración, como por ejemplo, "public enum color { red=1, blue=2 }". Desde el punto de vista del cliente, los tipos enum se convierten en clases con una cadena const estática para cada valor.

Matrices de primitivos, Enums Matrices de los tipos primitivos anteriores, como string[] e int[]

En el siguiente ejemplo se muestra el uso de los tipos que se acaban de enumerar, mediante un proxy SOAP generado desde WSDL.exe. Debe tenerse en cuenta que, como hay más de una clase pública definida en el archivo .asmx file, se debe especificar cuál se tratará como clase WebService mediante el atributo "Class" de la directiva WebService:

<%@ WebService Language="C#" Class="DataTypes" %>

<%@ WebService Language="VB" Class="DataTypes" %>

Imports SystemImports System.Web.Services

Namespace DataTypesVB.Enumerations Public Enum Mode

EOn = 1 EOff = 2

End Enum

Public Class Order

Public OrderID As Integer Public Price As Double End Class

End Namespace

Public Class DataTypes : Inherits WebService

<WebMethod()> Public Function SayHello() As String

Return "Hola a todos" End Function

<WebMethod()> Public Function SayHelloName(Name As String) As String

Return "Hola" & Name

Page 162: Presentación del tutorial de ASP

End Function

<WebMethod()> Public Function GetIntArray() As Integer()

Dim I As Integer Dim A(4) As Integer For I = 0 to 4 A(I) = I*10 Next Return A End Function

<WebMethod()> Public Function GetMode() As DataTypesVB.Enumerations.Mode

Return DataTypesVB.Enumerations.Mode.EOff End Function

<WebMethod()> Public Function GetOrder() As DataTypesVB.Enumerations.Order

Dim MyOrder As New DataTypesVB.Enumerations.Order

MyOrder.Price=34.5 MyOrder.OrderID = 323232

Return MyOrder End Function

<WebMethod()> Public Function GetOrders() As DataTypesVB.Enumerations.Order()

Dim MyOrder(2) As DataTypesVB.Enumerations.Order

MyOrder(0) = New DataTypesVB.Enumerations.Order() MyOrder(0).Price=34.5 MyOrder(0).OrderID = 323232 MyOrder(1) = New DataTypesVB.Enumerations.Order() MyOrder(1).Price=99.4 MyOrder(1).OrderID = 645645

Return MyOrder End Function

End Class

El método SayHello muestra cómo devolver una cadena desde un servicio. El método SayHelloName devuelve una cadena y también toma una cadena como parámetro. El método GetIntArray muestra cómo devolver una matriz de enteros. El método GetMode devuelve un valor enum. El método GetOrder devuelve una clase (que, en este caso, es casi lo mismo que una estructura). El método GetOrders devuelve una matriz de objetos Order.

Mediante la herramienta de generación del proxy de la línea de comandos WSDL.exe, el cálculo de referencias de estos tipos de datos resulta transparente para la aplicación del cliente consumidor. A continuación, se incluye una aplicación del cliente de muestra para el anterior servicio Web de XML:

<%@ Import Namespace="DataTypesVB" %>

<html>

<style>

div { font: 8pt verdana;

Page 163: Presentación del tutorial de ASP

background-color:cccccc; border-color:black; border-width:1; border-style:solid; padding:10,10,10,10; }

</style>

<script language="VB" runat="server">

Public Sub Page_Load(Sender As Object, E As EventArgs)

Dim D As DataTypesVB.DataTypes = New DataTypesVB.DataTypes() Message1.InnerHtml = D.SayHello() Message1.InnerHtml = Message1.InnerHtml & D.SayHelloName("Bob")

Message3.InnerHtml = Message3.InnerHtml & D.GetMode()

Dim MyIntArray As Integer() = D.GetIntArray() Dim MyString As String = "Contenido de la matriz:<BR>" Dim I As Integer For I = 0 To MyIntArray.Length - 1

MyString = MyString & MyIntArray(I) & "<BR>" Next

Message2.InnerHtml = Message2.InnerHtml & MyString

Dim MyOrder As Order = D.GetOrder() Message4.InnerHtml = Message4.InnerHtml & "<BR>Id. de pedido: " & MyOrder.OrderID Message4.InnerHtml = Message4.InnerHtml & "<BR>Precio: " & MyOrder.Price

Dim MyOrders As Order() = D.GetOrders() For I = 0 To myOrders.Length - 1 If i > 0 Then Message5.InnerHtml &= "<BR>" Message5.InnerHtml &= "<BR>Nº de pedido: " & i Message5.InnerHtml &= "<BR>Id. de pedido: " & myOrders(0).OrderID Message5.InnerHtml &= "<BR>Precio: " & myOrders(0).Price Next End Sub

</script>

<body style="font: 10pt verdana"><H4>Utilizar tipos de datos con servicios Web</H4>

<h5>Métodos que devuelven tipos primitivos (cadena): </h5> <div id="Message1" runat="server"/>

<h5>Métodos que devuelven una matriz de tipos primitivos (enteros): </h5> <div id="Message2" runat="server"/>

<h5>Método que devuelve una enumeración: </h5> <div id="Message3" runat="server"/>

<h5>Método que devuelve una clase o estructura: </h5> <div id="Message4" runat="server"/>

<h5>Método que devuelve una matriz de clases o estructuras: </h5> <div id="Message5" runat="server"/>

</body>

Page 164: Presentación del tutorial de ASP

</html>

Utilizar datos en servicios Web de XML

En este ejemplo se muestra la forma de devolver conjuntos de datos (objetos DataSet), que representan una nueva y eficaz manera de representar datos desconectados basada en XML, desde un método de servicio Web. Esto constituye un uso muy eficaz de los servicios Web de XML, ya que los objetos DataSet pueden almacenar información y relaciones complejas en una estructura inteligente. Al exponer conjuntos de datos a través de un servicio, es posible limitar las conexiones de base de datos que se establecen con el servidor de datos.

El método GetTitleAuthors establece una conexión con una base de datos y emite dos instrucciones SQL: una que devuelve una lista de autores y otra que devuelve una lista de títulos de libros. Coloca los dos conjuntos de resultados en un único conjunto de datos denominado ds y después devuelve el objeto DataSet en cuestión.

El método PutTitleAuthors ilustra un método de servicio Web que utiliza un conjunto de datos como parámetro y devuelve un número entero que representa el número de filas recibidas en la tabla "Authors" del conjunto de datos. Aunque la implementación de este método es un poco simplista, también se podrían combinar de forma inteligente los datos transferidos con el servidor de la base de datos.

<%@ WebService Language="VB" Class="DataService" %>

Imports SystemImports System.DataImports System.Data.SqlClientImports System.Web.Services

Public Class DataService : Inherits WebService

<WebMethod()> Public Function GetTitleAuthors() As DataSet

Dim MyConnection As SqlConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") Dim MyCommand1 As SqlDataAdapter = New SqlDataAdapter("select * from Authors", MyConnection) Dim MyCommand2 As SqlDataAdapter = New SqlDataAdapter("select * from Titles", MyConnection)

Dim DS As New DataSet MyCommand1.Fill(DS, "Authors") MyCommand2.Fill(ds, "Titles")

Return DS End Function

<WebMethod()> Public Function PutTitleAuthors(DS As DataSet) As Integer Return DS.Tables(0).Rows.Count End Function

End Class

La aplicación cliente para este servicio Web de XML llama a GetTitleAuthors y enlaza la tabla Authors con un control DataGrid, de la manera ilustrada en los ejemplos anteriores. Para ilustrar el método PutTitleAuthors, el cliente quita tres filas de datos del conjunto de datos antes de llamar a este método y muestra el número de filas recibidas por el servicio.

<%@ Import Namespace="DataServiceVB" %><%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Protected Sub Page_Load(Src As Object, E As EventArgs)

Page 165: Presentación del tutorial de ASP

Dim D As DataServiceVB.DataService = New DataServiceVB.DataService() Dim MyData As DataSet = D.GetTitleAuthors()

Authors_DataGrid.DataSource=MyData.Tables("Authors").DefaultView Authors_DataGrid.DataBind()

Message.InnerHtml = "Número de filas: " & MyData.Tables("Authors").Rows.Count.ToString()

End Sub

Protected Sub Submit_DataSet(Src As Object, E As EventArgs)

Dim D As DataServiceVB.DataService = New DataServiceVB.DataService() Dim MyData As DataSet = D.GetTitleAuthors()

'Remove three rows from the Authors table MyData.Tables("Authors").Rows.RemoveAt(0) MyData.Tables("Authors").Rows.RemoveAt(1) MyData.Tables("Authors").Rows.RemoveAt(2)

Dim RowCount As Integer = D.PutTitleAuthors(MyData)

Authors_DataGrid.DataSource=MyData.Tables("Authors").DefaultView Authors_DataGrid.DataBind()

Message.InnerHtml = "Número modificado de filas: " & RowCount.ToString() End Sub

</script>

<body style="font: 10pt verdana">

<h4>Utilizar acceso a datos con servicios Web</h4>

<form runat="server">

<input type="submit" OnServerclick="Submit_DataSet" value="Quitar tres filas del conjunto de datos" runat="server"/> <p> <span id="Message" runat="server"/>

<p>

<ASP:DataGrid id="Authors_DataGrid" runat="server" Width="700" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" EnableViewState="false" />

</form>

</body></html>

Page 166: Presentación del tutorial de ASP

Utilizar objetos y elementos intrínsecos

En este ejemplo se ilustra la forma de tener acceso a los elementos intrínsecos de ASP.NET, como Session y Application. También se muestra la forma de desactivar el objeto Session mediante WebMethod.

El primer método del archivo .asmx de ejemplo, UpdateHitCounter, tiene acceso al objeto Session y agrega e incrementa en una unidad el valor de "HitCounter". Después devuelve este valor en forma de cadena. El segundo método, UpdateAppCounter, hace lo mismo, pero con el

objeto Application. Hay que tener en cuenta lo siguiente:

<WebMethod(EnableSession:=true)>

El estado de sesión de los servicios Web de XML está deshabilitado de forma predeterminada y hay que utilizar una propiedad de atributo especial para habilitar objetos Session. Sin embargo, este objeto no necesita objetos Session, ya que sólo utiliza el objeto Application.

<%@ WebService Language="VB" Class="SessionService1" %>

Imports SystemImports System.Web.Services

Public Class SessionService1 : Inherits WebService

<WebMethod(EnableSession:=true)> Public Function UpdateHitCounter() As String

If Session("HitCounter") Is Nothing Session("HitCounter") = 1 Else Session("HitCounter") = Cint(Session("HitCounter")) + 1 End If

Return "Ha tenido acceso a este servicio " & Session("HitCounter").ToString() & " veces." End Function

<WebMethod(EnableSession:=false)> Public Function UpdateAppCounter() As String

If Application("HitCounter") Is Nothing Application("HitCounter") = 1 Else Application("HitCounter") = CInt(Application("HitCounter")) + 1 End If

Return "Se obtuvo acceso al servicio " & Application("HitCounter").ToString() & " veces." End Function

End Class

Cuando se tiene acceso al cliente proxy, éste contiene una colección de cookies. Esta colección se utiliza para aceptar y devolver la cookie APSESSIONID que utiliza ASP.NET para hacer un seguimiento de las sesiones. Esto es lo que permite al cliente recibir distintas respuestas al método Hit de Session.

<%@ Import Namespace="System.Net" %><%@ Import Namespace="SessionService1VB" %>

<html>

<style>

div {

Page 167: Presentación del tutorial de ASP

font: 8pt verdana; background-color:cccccc; border-color:black; border-width:1; border-style:solid; padding:10,10,10,10; }

</style>

<script language="VB" runat="server">

Public Sub Page_Load(Sender As Object, E As EventArgs)

Dim S As SessionService1VB.SessionService1 = New SessionService1VB.SessionService1() S.CookieContainer = new CookieContainer() Message1.InnerHtml = S.UpdateHitCounter() & "<BR>" & S.UpdateHitCounter() & "<BR>" + S.UpdateHitCounter() Message2.InnerHtml = S.UpdateAppCounter() & "<BR>" & S.UpdateAppCounter() & "<BR>" + S.UpdateAppCounter() End Sub

</script>

<body style="font: 10pt verdana"><H4>Utilizar intrínsecos con servicios Web</H4>

<h5>Resultados de UpdateHitCounter: </h5> <div id="Message1" runat="server"/>

<h5>Resultados de UpdateAppCounter: </h5> <div id="Message2" runat="server"/>

</body></html>

Comportamiento WebService

Microsoft lanzó recientemente un comportamiento DHTML con habilitación SOAP nuevo para Microsoft Internet Explorer 5.0 y versiones posteriores. El nuevo comportamiento WebService permite a la secuencia de comandos en el cliente invocar métodos remotos expuestos por servicios Web .NET XML de Microsoft u otros servidores Web que admiten el protocolo SOAP (Simple Object Access Protocol). El comportamiento WebService se implementa con un archivo HTML Components (HTC) como un comportamiento asociado, por lo que puede utilizarse en Internet Explorer.

La finalidad del comportamiento WebService es proporcionar una manera sencilla de utilizar y aplicar SOAP, sin requerir un conocimiento profundo de su implementación. El comportamiento WebService admite el uso de una amplia variedad de tipos de datos, incluidos tipos de datos SOAP intrínsecos, matrices y datos XML (Lenguaje de marcado extensible, Extensible Markup Language). Este componente flexible permite a Internet Explorer recuperar información de servicios Web de XML y actualizar una página de forma dinámica mediante el uso de DHTML y secuencias de comandos, sin requerir el desplazamiento ni una actualización de página completa.

La siguiente generación de la infraestructura y las herramientas de programación .NET, incluido Visual Studio.NET, .NET Framework y servidores .NET Enterprise, está diseñada para la programación de aplicaciones según el modelo de servicios Web de XML. El comportamiento WebService es especialmente significativo ya que permite a Internet Explorer utilizar estos servicios Web de XML de próxima generación.

El sitio de Microsoft Developer Network (MSDN) proporciona la documentación siguiente.

Información general del comportamiento WebService http://msdn.microsoft.com/workshop/author/webservice/overview.aspUso del comportamiento WebService http://msdn.microsoft.com/workshop/author/webservice/using.aspComportamiento WebService http://msdn.microsoft.com/workshop/author/webservice/webservice.asp

Page 168: Presentación del tutorial de ASP

Hacer coincidir modelos de texto HTML

Este ejemplo muestra cómo crear un proxy cliente para cualquier URL que proporcione texto. En vez de crear el archivo .asmx, se puede crear un archivo WSDL que describa la página HTML (o XML o cualquier otro formato no binario) actual. El archivo WSDL puede utilizarse para generar un proxy cliente mediante la herramienta de línea de comandos WSDL.exe que utilizará RegEx para analizar la página HTML especificada y extraer los valores.

Esto se puede realizar agregando etiquetas <Match> a la sección Response del archivo WSDL. Estas etiquetas utilizan un atributo denominado pattern, que es la expresión regular que se corresponde con la parte de texto de la página que constituye el valor de la propiedad. (Nota: la propiedad de la clase proxy es de sólo lectura).

El código consumidor puede entonces crear el objeto, obtener acceso al objeto Matches devuelto por el nombre convertido en función que aparece en el archivo WSDL y conseguir acceso a cualquier parte del código HTML como una propiedad. No es necesario ningún conocimiento de WSDL, expresiones regulares, ni HTML para poder utilizar la clase proxy. Ésta se comporta como cualquier otra clase de .NET Framework.

<?xml version="1.0"?><definitions xmlns:s="http://www.w3.org/1999/XMLSchema" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:s0="http://tempuri.org/" targetNamespace="http://tempuri.org/" xmlns="http://schemas.xmlsoap.org/wsdl/"> <types> <s:schema targetNamespace="http://tempuri.org/" attributeFormDefault="qualified" elementFormDefault="qualified"> <s:element name="TestHeaders"> <s:complexType derivedBy="restriction"/> </s:element> <s:element name="TestHeadersResult"> <s:complexType derivedBy="restriction"> <s:all> <s:element name="result" type="s:string" nullable="true"/> </s:all> </s:complexType> </s:element> <s:element name="string" type="s:string" nullable="true"/> </s:schema> </types> <message name="TestHeadersHttpGetIn"/> <message name="TestHeadersHttpGetOut"> <part name="Body" element="s0:string"/> </message> <portType name="TestItHttpGet"> <operation name="TestHeaders"> <input message="s0:TestHeadersHttpGetIn"/> <output message="s0:TestHeadersHttpGetOut"/> </operation> </portType> <binding name="TestItHttpGet" type="s0:TestItHttpGet"> <http:binding verb="GET"/> <operation name="TestHeaders"> <http:operation location="/QuickStart/aspplus/samples/services/TextMatching/VB/MatchServer.html"/> <input> <http:urlEncoded/> </input> <output> <text xmlns="http://microsoft.com/wsdl/mime/textMatching/"> <match name='Title' pattern='TITLE&gt;(.*?)&lt;'/> <match name='H1' pattern='H1&gt;(.*?)&lt;'/> </text> </output>

Page 169: Presentación del tutorial de ASP

</operation> </binding> <service name="TestIt"> <port name="TestItHttpGet" binding="s0:TestItHttpGet"> <http:address location="http://localhost" /> </port> </service></definitions>

Información general de una aplicación

¿En qué consiste una aplicación ASP.NET?

ASP.NET define una aplicación como el conjunto de todos los archivos, páginas, controladores, módulos y código ejecutable que se pueden invocar o ejecutar dentro del ámbito de un determinado directorio virtual (y sus subdirectorios) en un servidor de aplicaciones Web. Por ejemplo, una aplicación de "pedido" podría publicarse dentro del directorio virtual "/pedido" de un servidor Web. Para IIS, el directorio virtual se puede configurar en el Administrador de servicios de Internet y contiene todos los subdirectorios, a menos que los propios subdirectorios sean directorios virtuales.

Cada aplicación ASP.NET Framework de un servidor Web se ejecuta dentro de un dominio único de aplicaciones ejecutables de .NET Framework, lo que garantiza el aislamiento de clases (no se producen conflictos de nombres o versiones), el uso seguro de recursos (se impide el acceso a determinados equipos o recursos de red) y el aislamiento de variables estáticas.

ASP.NET mantiene una agrupación de instancias HttpApplication durante el período de duración de una aplicación Web. ASP.NET asigna automáticamente una de estas instancias para procesar cada solicitud HTTP entrante recibida por la aplicación. La instancia HttpApplication asignada en particular es responsable del proceso de la solicitud a lo largo de todo su período de duración y sólo se puede volver a utilizar después de que la solicitud se haya completado. Esto significa que el código de usuario incluido en la instancia HttpApplication no necesita ser reentrante.

Crear una aplicación

Para crear una aplicación ASP.NET Framework, se puede utilizar un directorio virtual existente o crear uno nuevo. Por ejemplo, si Windows 2000 Server se instaló con IIS, probablemente existirá un directorio C:\\InetPub\\WWWRoot. IIS se puede configurar mediante el Administrador de servicios de Internet, que se encuentra en Inicio -> Programas -> Herramientas administrativas. Haga clic con el botón secundario del mouse (ratón) en un directorio existente y elija Nuevo (para crear un nuevo directorio virtual) o Propiedades (para convertir un directorio normal existente).

Cuando se coloca una simple página .aspx, como la siguiente, en el directorio virtual, y se obtiene acceso a ella con el explorador, se desencadena el proceso de creación de la aplicación ASP.NET.

<%@Page Language="VB"%><html><body><h1>hello world, <% Response.Write(DateTime.Now.ToString()) %></h1></body></html>

Ahora, ya se puede agregar el código apropiado para utilizar el objeto Application (por ejemplo, para almacenar objetos con ámbito de aplicación). Mediante la creación un archivo global.asax, también se pueden definir diversos controladores de eventos (por ejemplo, para el evento Application_Start).

Duración de una aplicación

Una aplicación ASP.NET Framework se crea la primera vez que se realiza una solicitud al servidor; antes de ello, no se ejecuta ningún código ASP.NET. Cuando se realiza la primera solicitud, se crea una agrupación de instancias HttpApplication y se provoca el evento Application_Start. Las instancias HttpApplication procesan esta solicitud y las siguientes hasta que la última instancia termina y se provoca el evento Application_End.

Observe que los métodos Init y Dispose de HttpApplication se invocan por cada instancia y, de este modo, pueden utilizarse varias veces entre Application_Start y Application_End. Sólo se comparten estos eventos entre todas las instancias de HttpApplication en una aplicación ASP.NET.

Page 170: Presentación del tutorial de ASP

Nota sobre subprocesos múltiples

Si se utilizan objetos con ámbito de aplicación, se debería tener en cuenta que ASP.NET procesa las solicitudes de forma concurrente y que

pueden ser varios los subprocesos que obtengan acceso al objeto Application. Por lo tanto, el siguiente código es peligroso y podría no producir el resultado esperado si diferentes clientes solicitan la página reiteradamente al mismo tiempo.

<%Application("counter") = CType(Application("counter") + 1, Int32)%>

Para conseguir que este código sea seguro durante la ejecución de subprocesos, hay que serializar el acceso al objeto Application mediante

los métodos Lock y UnLock. Sin embargo, al hacerlo se produce una efecto de merma considerable en el rendimiento:

<%Application.Lock()Application("counter") = CType(Application("counter") + 1, Int32)Application.UnLock()%>

Otra solución consiste en hacer que el objeto almacenado con un ámbito de aplicación sea seguro para la ejecución de subprocesos. Por ejemplo, observe que las clases de colección del espacio de nombres System.Collections no son seguras para la ejecución de subprocesos por razones de rendimiento.

Resumen de la sección

1. Las aplicaciones ASP.NET Framework se componen de todo aquello que se encuentra bajo un directorio virtual del servidor Web.

2. Una aplicación ASP.NET Framework se crea agregando archivos a un directorio virtual del servidor Web. 3. La duración de una aplicación ASP.NET Framework viene marcada por los eventos Application_Start y Application_End. 4. El acceso a los objetos con ámbito de aplicación debe ser seguro para el acceso a múltiples subprocesos.

Utilizar el archivo Global.asax

Archivo Global.asax

Además de escribir código para interfaces de usuario, los programadores también pueden agregar lógica del nivel de aplicación y código de control de eventos a sus aplicaciones Web. Este código no se encarga de generar interfaces de usuario y no se invoca normalmente en respuesta a solicitudes de páginas individuales. En vez de ello, se encarga de procesar eventos de la aplicación de nivel superior, tales como Application_Start, Application_End, Session_Start, Session_End, etc. Los programadores crean esta lógica mediante un archivo Global.asax ubicado en la raíz del árbol de directorios virtuales de una aplicación Web. ASP.NET analiza y compila automáticamente este archivo para producir una clase dinámica de .NET Framework, la cual extiende la clase base HttpApplication (la primera vez que se activa o se solicita cualquier recurso o URL dentro del espacio de nombres de la aplicación).

ASP.NET analiza y compila dinámicamente el archivo Global.asax para producir una clase de .NET Framework la primera vez que se activa o se solicita cualquier recurso o URL dentro del espacio de nombres de la aplicación. El archivo Global.asax está configurado para rechazar automáticamente cualquier solicitud de URL directa de modo que los usuarios externos no puedan descargar o ver el código interno.

Eventos cuyo ámbito es una sesión o una aplicación

Los programadores pueden definir controladores para eventos de la clase base HttpApplication creando métodos en el archivo Global.asax que se ajusten al modelo de nomenclatura "NombreDeEventoDeLaAplicación(FirmaDeArgumentosDelEvento)". Por ejemplo:

<script language="VB" runat="server">

Sub Application_Start(Sender As Object, E As EventArgs)

Page 171: Presentación del tutorial de ASP

' Application startup code goes hereEnd Sub</script>

Si el código de control de eventos necesita importar espacios de nombres adicionales, se puede utilizar la directiva @ import en una página .aspx, como se indica a continuación:

<%@ Import Namespace="System.Text" %>

El siguiente ejemplo ilustra el período de vida de Application, Session y Request.

<script language="VB" runat="server">

Sub Application_Start(Sender As Object, E As EventArgs) ' Do application startup code here End Sub

Sub Application_End(Sender As Object, E As EventArgs) ' Clean up application resources here End Sub

Sub Session_Start(Sender As Object, E As EventArgs) Response.Write("La sesión se está iniciando...<br>") End Sub

Sub Session_End(Sender As Object, E As EventArgs) ' Clean up session resources here End Sub

Sub Application_BeginRequest(Sender As Object, E As EventArgs) Response.Write("<h3><font face='Verdana'>Usar el archivo Global.asax</font></h3>") Response.Write("La solicitud se está iniciando...<br>") End Sub

Sub Application_EndRequest(Sender As Object, E As EventArgs) Response.Write("La solicitud está terminando...<br>") End Sub Sub Application_Error(Sender As Object, E As EventArgs) Context.ClearError() Response.Redirect("errorpage.htm") End Sub

</script>

La primera vez que se abre la página, se provoca el evento Start para la aplicación y la sesión:

Sub Application_Start(Sender As Object, E As EventArgs) ' Application startup code goes hereEnd Sub

Sub Session_Start(Sender As Object, E As EventArgs) Response.Write("Session is Starting...<br>") Session.Timeout = 1End Sub

Page 172: Presentación del tutorial de ASP

Los eventos BeginRequest y EndRequest se provocan en cada solicitud. Cuando la página se actualiza, sólo aparecen mensajes de BeginRequest, EndRequest y el método Page_Load. Observe que, al abandonar la sesión actual (hacer clic en el botón "Finalizar esta sesión"), se crea una nueva sesión y se provoca de nuevo el evento Session_Start.

Objetos cuyo ámbito es una sesión o una aplicación

Los objetos estáticos, las clases de .NET Framework y los componentes COM se pueden definir en el archivo Global.asax mediante la etiqueta object. El ámbito puede ser appinstance, session o application. El ámbito appinstance denota que el objeto es específico para una instancia de HttpApplication y no está compartido.

<object id="id" runat="server" class=".NET Framework class Name" scope="appinstance"/><object id="id" runat="server" progid="COM ProgID" scope="session"/><object id="id" runat="server" classid="COM ClassID" scope="application"/>

Resumen de la sección

1. En las aplicaciones ASP.NET Framework se pueden definir controladores de eventos cuyo ámbito sea toda la aplicación o toda la sesión, en el archivo Global.asax.

2. En las aplicaciones ASP.NET Framework se pueden definir objetos cuyo ámbito sea toda la aplicación o toda la sesión, en el archivo Global.asax.

Administrar el estado de las aplicaciones

Utilizar el estado de las aplicaciones

En este ejemplo se ilustra el uso del estado de una aplicación para leer un conjunto de datos en Application_Start.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.IO" %>

<script language="VB" runat="server">

Sub Application_Start(Sender As Object, E As EventArgs) Dim DS As New DataSet

Dim FS As FileStream

FS = New FileStream(Server.MapPath("schemadata.xml"),FileMode.Open,FileAccess.Read) Dim Reader As StreamReader Reader = New StreamReader(FS) DS.ReadXml(Reader) FS.Close()

Dim View As DataView View = New DataView(ds.Tables(0)) Application("Source") = View End Sub

</script>

Ya que distintos subprocesos pueden tener acceso a una aplicación y a todos los objetos que contiene dicha aplicación de forma simultánea, es mejor almacenar con ámbito de aplicación únicamente los datos que se modifican con poca frecuencia. Idealmente, los objetos se inicializan en el evento Application_Start y los accesos posteriores son de sólo lectura.

En el siguiente ejemplo se lee un archivo en Application_Start (definido en el archivo Global.asax) y el contenido se almacena en un objeto

DataView en el estado de la aplicación.

Sub Application_Start()

Page 173: Presentación del tutorial de ASP

Dim ds As New DataSet()

Dim fs As New FileStream(Server.MapPath("schemadata.xml"),FileMode.Open,FileAccess.Read) Dim reader As New StreamReader(fs) ds.ReadXml(reader) fs.Close()

Dim view As New DataView (ds.Tables(0)) Application("Source") = viewEnd Sub

En el método Page_Load se recupera el objeto DataView y después se utiliza para llenar un objeto DataGrid:

Sub Page_Load(sender As Object, e As EventArgs) Dim Source As New DataView = CType(Application("Source"), DataView) ... MyDataGrid.DataSource = Source ...End Sub

La ventaja de esta solución es que el precio de obtener los datos sólo lo paga la primera solicitud. Las siguientes solicitudes utilizarán el objeto DataView existente. Como los datos no se modifican nunca después de la inicialización, no es necesaria la serialización del acceso.

Utilizar el estado de las sesiones

En el ejemplo siguiente se ilustra el uso del estado de una sesión para almacenar preferencias de usuario volátiles.

<script language="VB" runat="server">

Sub Session_Start(Sender As Object, E As EventArgs) Session("BackColor") = "beige" Session("ForeColor") = "black" Session("LinkColor") = "blue" Session("FontSize") = "8pt" Session("FontName") = "verdana" End Sub

</script>

Es posible almacenar los datos con ámbito de sesión a fin de proporcionar datos individuales a un usuario durante una sesión. En el ejemplo

siguiente se inicializan los valores de preferencias de usuario en el evento Session_Start del archivo Global.asax.

Sub Session_Start() Session("BackColor") = "beige" ...End Sub

En la siguiente página de personalización, se modifican los valores de las preferencias de usuario en el controlador de eventos Submit_Click con la información aportada por el usuario.

Protected Sub Submit_Click(sender As Object, e As EventArgs) Session("BackColor") = BackColor.Value ...

Page 174: Presentación del tutorial de ASP

Response.Redirect(State("Referer").ToString())End Sub

Los valores individuales se obtienen mediante el método GetStyle:

Protected GetStyle(key As String) As String Return(Session(key).ToString())End Sub

El método GetStyle se utiliza para crear estilos específicos de una sesión:

<style> body { font: <%=GetStyle("FontSize")%> <%=GetStyle("FontName")%>; background-color: <%=GetStyle("BackColor")%>; } a { color: <%=GetStyle("LinkColor")%> }</style>

Para comprobar que los valores se almacenan realmente con ámbito de sesión, hay que abrir dos veces la página del ejemplo, cambiar un valor en la primera ventana del explorador y actualizarlo en la segunda. La segunda ventana reflejará los cambios, ya que las dos instancias de explorador comparten un objeto Session común.

Configurar el estado de una sesión: es posible configurar las características del estado de una sesión en la sección <sessionState> de un archivo web.config. Para doblar el tiempo de espera predeterminado de 20 minutos, se puede agregar el código siguiente al archivo web.config de una aplicación:

De forma predeterminada, ASP.NET almacenará el estado de la sesión en el mismo proceso que atiende la solicitud, de la misma manera que ASP. Si no hay cookies disponibles, se puede hacer un seguimiento de una sesión agregando un identificador de sesión a la dirección URL. Se puede habilitar de la manera siguiente:

De forma predeterminada, ASP.NET almacenará el estado de la sesión en el mismo proceso que atiende la solicitud, de la misma manera que ASP. Además, ASP.NET puede almacenar datos de sesión en un proceso externo, que podría residir en otro equipo. Para habilitar esta función:

Inicie el servicio de estado de ASP.NET mediante el complemento Servicios o ejecutando la instrucción "net start aspnet_state" desde la línea de comandos. De manera predeterminada, el servicio de estado escuchará en el puerto 42424. Para cambiar el puerto, modifique la clave del Registro para el servicio: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\aspnet_state\Parameters\Port

Establezca el atributo mode de la sección <sessionState> en "StateServer". Configure el atributo stateConnectionString con los valores del equipo en que se inició aspnet_state.

En el siguiente ejemplo se da por hecho que el servicio de estado se ejecuta en el mismo equipo que el servidor Web ("localhost") y utiliza el puerto predeterminado (42424): <sessionState mode="StateServer" stateConnectionString="tcpip=localhost:42424"/>Hay que tener en cuenta que si se prueba el ejemplo anterior con este valor, es posible restablecer el servidor Web (escriba iisreset en la línea de comandos) y así se conservará el valor de estado de la sesión.

Utilizar las cookies del cliente

Page 175: Presentación del tutorial de ASP

En el ejemplo siguiente se ilustra el uso del estado de las cookies del cliente para almacenar preferencias de usuario volátiles.

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

If Request.Cookies("preferences1") Is Nothing

Dim Cookie As HttpCookie Cookie = New HttpCookie("preferences1") Cookie.Values.Add("ForeColor","black") Cookie.Values.Add("BackColor","beige") Cookie.Values.Add("LinkColor","blue") Cookie.Values.Add("FontSize","8pt") Cookie.Values.Add("FontName","Verdana") Response.AppendCookie(Cookie) End If End Sub

Protected Function GetStyle(Key As String) As String

Dim Cookie As HttpCookie Cookie = Request.Cookies("preferences1")

If Not Cookie Is Nothing

Select (Key)

Case "ForeColor" : Return Cookie.Values("ForeColor")

Case "BackColor" : Return Cookie.Values("BackColor")

Case "LinkColor" : Return Cookie.Values("LinkColor")

Case "FontSize" : Return Cookie.Values("FontSize")

Case "FontName" : Return Cookie.Values("FontName") End Select End If

Return "" End Function

</script>

<style>

body { font: <%=GetStyle("FontSize")%> <%=GetStyle("FontName")%>; background-color: <%=GetStyle("BackColor")%>; }

a { color: <%=GetStyle("LinkColor")%> }

Page 176: Presentación del tutorial de ASP

</style>

<body style="color:<%=GetStyle("ForeColor")%>">

<h3><font face="Verdana">Almacenar datos volátiles con las cookies del cliente</font></h3>

<b><a href="customize.aspx">Personalizar la página</a></b><p>

Imagine algo de contenido aquí ...<br> Imagine algo de contenido aquí ...<br> Imagine algo de contenido aquí ...<br> Imagine algo de contenido aquí ...<br> Imagine algo de contenido aquí ...<br> Imagine algo de contenido aquí ...<br> Imagine algo de contenido aquí ...<br> Imagine algo de contenido aquí ...<br>

</body>

</html>

Almacenar cookies en el cliente es uno de los métodos que utiliza el estado de las sesiones de ASP.NET para asociar solicitudes a sesiones. También se pueden utilizar cookies directamente para conservar datos entre solicitudes, pero después se almacenan los datos en el cliente y se envían al servidor con cada solicitud. Los exploradores limitan el tamaño de las cookies; por tanto, sólo se garantiza la aceptación de un número máximo de 4096.

Cuando se almacenan los datos en el cliente, el método Page_Load del archivo cookies1.aspx comprueba si el cliente ha enviado una cookie. Si no se ha enviado ninguna, se crea una cookie nueva y después se inicializa y almacena en el cliente:

Protected Sub Page_Load(sender As Object, e As EventArgs) If Request.Cookies("preferences1") = Null Then Dim cookie As New HttpCookie("preferences1") cookie.Values.Add("ForeColor", "black") ... Response.AppendCookie(cookie) End IfEnd Sub

En la misma página, se utiliza de nuevo un método GetStyle con el fin de proporcionar los valores individuales almacenados en la cookie:

Protected Function GetStyle(key As String) As String Dim cookie As HttpCookie = Request.Cookies("preferences1") If cookie <> Null Then Select Case key Case "ForeColor" Return(cookie.Values("ForeColor")) Case ... End Select End If Return("")End Function

Compruebe que el ejemplo funciona correctamente; para ello, abra la página cookies1.aspx y modifique las preferencias. Abra la página en otra ventana; esta ventana debería reflejar las nuevas preferencias. Cierre todas las ventanas del explorador y abra de nuevo la página cookies1.aspx. Esto debería eliminar la cookie temporal y restaurar los valores de preferencias predeterminados.

Page 177: Presentación del tutorial de ASP

<html>

<script language="VB" runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

If Request.Cookies("preferences2") Is Nothing

Dim Cookie As HttpCookie Cookie = New HttpCookie("preferences2") Cookie.Values.Add("ForeColor","black") Cookie.Values.Add("BackColor","beige") Cookie.Values.Add("LinkColor","blue") Cookie.Values.Add("FontSize","8pt") Cookie.Values.Add("FontName","Verdana") Response.AppendCookie(Cookie) End If End Sub

Protected Function GetStyle(Key As String) As String

Dim Cookie As HttpCookie Cookie = Request.Cookies("preferences2")

If Not Cookie Is Nothing

Select (Key)

Case "ForeColor" : Return Cookie.Values("ForeColor")

Case "BackColor" : Return Cookie.Values("BackColor")

Case "LinkColor" : Return Cookie.Values("LinkColor")

Case "FontSize" : Return Cookie.Values("FontSize")

Case "FontName" : Return Cookie.Values("FontName") End Select End If

Return "" End Function

</script>

<style>

body { font: <%=GetStyle("FontSize")%> <%=GetStyle("FontName")%>; background-color: <%=GetStyle("BackColor")%>; }

a { color: <%=GetStyle("LinkColor")%> }

</style>

Page 178: Presentación del tutorial de ASP

<body style="color:<%=GetStyle("ForeColor")%>">

<h3><font face="Verdana">Almacenar datos permanentes con las cookies del cliente</font></h3>

<b><a href="customize.aspx">Personalizar la página</a></b><p>

Imagine algo de contenido aquí ...<br> Imagine algo de contenido aquí ...<br> Imagine algo de contenido aquí ...<br> Imagine algo de contenido aquí ...<br> Imagine algo de contenido aquí ...<br> Imagine algo de contenido aquí ...<br> Imagine algo de contenido aquí ...<br> Imagine algo de contenido aquí ...<br>

</body></html>

Para conservar una cookie entre sesiones es necesario establecer el valor de la propiedad Expires de la clase HttpCookie en una fecha futura. El siguiente fragmento de código de la página customization.aspx es idéntico al ejemplo anterior, con la excepción de la asignación a

Cookie.Expires:

Protected Sub Submit_Click(sender As Object, e As EventArgs) Dim cookie As New HttpCookie("preferences2") cookie.Values.Add("ForeColor",ForeColor.Value) ... cookie.Expires = DateTime.MaxValue ' Never Expires

Response.AppendCookie(cookie)

Response.Redirect(State("Referer").ToString())End Sub

Compruebe que el ejemplo funciona; para ello, modifique un valor, cierre todas las ventanas del explorador y abra de nuevo el archivo cookies2.aspx. La ventana debería mostrar aún el valor personalizado.

Utilizar ViewState

En este ejemplo se ilustra el uso de la propiedad ViewState para almacenar valores específicos de una solicitud.

<%@ Register TagPrefix="Acme" TagName="Address" Src="address.ascx" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Src As Object, E As EventArgs)

If Not (IsPostBack) ViewState("PanelIndex") = 0 End If End Sub

Sub Next_Click(Src As Object, E As EventArgs)

Dim PrevPanelId As String = "Panel" & ViewState("PanelIndex").ToString()

Page 179: Presentación del tutorial de ASP

ViewState("PanelIndex") = CInt(ViewState("PanelIndex")) + 1 Dim PanelId As String = "Panel" & ViewState("PanelIndex").ToString()

Dim P As Panel P = FindControl(PanelId) P.Visible=true

P = FindControl(PrevPanelId) P.Visible=false End Sub

Sub Prev_Click(Src As Object, E As EventArgs)

Dim PanelId As String = "Panel" & ViewState("PanelIndex").ToString() ViewState("PanelIndex") = CInt(ViewState("PanelIndex")) - 1 Dim PrevPanelId As String = "Panel" & ViewState("PanelIndex").ToString()

Dim P As Panel P = FindControl(PanelId) P.Visible=false

P = FindControl(PrevPanelId) P.Visible=true End Sub

Sub Finish_Click(Src As Object, E As EventArgs)

Dim P As Panel Dim PanelId As String = "Panel" & ViewState("PanelIndex").ToString() P = FindControl(PanelId) P.Visible=false

MyLabel.Text &= "<b>Gracias, hemos recibido la siguiente información: </b><p>" MyLabel.Text &= "Nombre: " & FirstName.Value & "<br>" MyLabel.Text &= "Apellido: " & LastName.Value & "<br>" MyLabel.Text &= "Dirección: " & Address.Address & "<br>" MyLabel.Text &= "Ciudad: " & Address.City & "<br>" MyLabel.Text &= "Estado: " & Address.StateName & "<br>" MyLabel.Text &= "Código postal: " & Address.Zip & "<br>" MyLabel.Text &= "Número de tarjeta: " & CardNum.Value & "<br>" MyLabel.Text &= "Tipo de tarjeta: " & CardType.SelectedItem.Value & "<br>" MyLabel.Text &= "Caduca: " & Expires.Value & "<br>" End Sub

</script>

<body style="font: 10pt verdana">

<h3><font face="Verdana">Utilizar PageState</font></h3>

<form runat="server">

<ASP:Panel id="Panel0" Visible="true" runat="server"> <table width="500" height="200" style="font:10pt verdana;background-color:cccccc;border-width:1;border-style:solid;border-color:black"> <tr> <td style="padding:10,10,10,10" valign="top"> <table height="100%" style="font:10pt verdana;"> <tr> <td colspan="2"><b>Completar los siguientes campos y, a continuación, seleccionar Siguiente para continuar:</b></td> </tr> <tr height="20"/>

Page 180: Presentación del tutorial de ASP

<tr> <td>Nombre:</td> <td><input id="FirstName" type="text" size="45" runat="server"></td> </tr> <tr> <td>Apellido:</td> <td><input id="LastName" type="text" size="45" runat="server"></td> </tr> <tr> <td colspan="2" align="right" height="100%" valign="bottom"> <input type="submit" Value=" Siguiente >> " OnServerClick="Next_Click" runat="server"> </td> </tr> </table> </td> </tr> </table> </ASP:Panel>

<ASP:Panel id="Panel1" Visible="false" runat="server"> <table width="500" height="200" style="font:10pt verdana;background-color:cccccc;border-width:1;border-style:solid;border-color:black"> <tr> <td style="padding:10,10,10,10" valign="top"> <table height="100%" style="font:10pt verdana;"> <tr> <td colspan="2"><b>Completar los siguientes campos y, a continuación, seleccionar Siguiente para continuar:</b></td> </tr> <tr height="20"/> <tr> <td colspan="2"> <Acme:Address id="Address" ShowCaption="false" runat="server"/> </td> </tr> <tr> <td colspan="2" align="right" valign="bottom" height="100%"> <input type="submit" Value=" << Atrás " OnServerClick="Prev_Click" runat="server"> <input type="submit" Value=" Siguiente >> " OnServerClick="Next_Click" runat="server"> </td> </tr> </table> </td> </tr> </table> </ASP:Panel>

<ASP:Panel id="Panel2" Visible="false" runat="server"> <table width="500" height="200" style="font:10pt verdana;background-color:cccccc;border-width:1;border-style:solid;border-color:black"> <tr> <td style="padding:10,10,10,10" valign="top"> <table height="100%" style="font:10pt verdana;"> <tr> <td colspan="2"><b>Completar los siguientes campos y, a continuación, seleccionar Siguiente para continuar:</b></td> </tr> <tr height="20"/> <tr> <td>Número de tarjeta: </td> <td><input id="CardNum" size="45" type="text" runat="server"/></td> </tr> <tr>

Page 181: Presentación del tutorial de ASP

<td>Tipo de tarjeta: </td> <td> <asp:DropDownList id="CardType" runat="server"> <asp:ListItem>Visa</asp:ListItem> <asp:ListItem>Mastercard</asp:ListItem> <asp:ListItem>Discover</asp:ListItem> </asp:DropDownList> </td> </tr> <tr> <td>Caduca: </td> <td><input id="Expires" type="text" runat="server"/></td> </tr> <tr> <td colspan="2" align="right" valign="bottom" height="100%"> <input type="submit" Value=" << Atrás " OnServerClick="Prev_Click" runat="server"> <input type="submit" Value=" Finalizar " OnServerClick="Finish_Click" runat="server"> </td> </tr> </table> </td> </tr> </table> </ASP:Panel> </form>

<asp:Label id="MyLabel" EnableViewState="false" runat="server"/>

</body></html>

ASP.NET proporciona la noción de estado de la presentación de cada control en el servidor. Un control puede guardar su estado interno entre solicitudes mediante la propiedad ViewState de una instancia de la clase StateBag. La clase StateBag proporciona una interfaz de tipo diccionario para almacenar objetos asociados a una clave de tipo cadena.

El archivo pagestate1.aspx muestra un panel visible y almacena el índice del panel en el estado de la presentación de la página con la clave

PanelIndex:

Protected Sub Next_Click(sender As Object, e As EventArgs) Dim PrevPanelId As String = "Panel" + ViewState("PanelIndex").ToString() ViewState("PanelIndex") = CType(ViewState("PanelIndex") + 1, Integer) Dim PanelId As String = "Panel" + ViewState("PanelIndex").ToString() ...End Sub

Hay que tener en cuenta que si se abre la página en varias ventanas del explorador, cada ventana del explorador mostrará inicialmente el panel del nombre. Todas las ventanas permiten el desplazamiento entre paneles de forma independiente.

Resumen de la sección

1. Utilice variables de estado de la aplicación para almacenar datos que se modifican con poca frecuencia pero que se utilizan a menudo.

2. Utilice variables de estado de la sesión para almacenar datos específicos de una sesión o de un usuario. Los datos se almacenan completamente en el servidor. Hay que utilizar estas variables para datos de poca duración, datos importantes o grandes cantidades de datos.

3. Almacene pequeñas cantidades de datos volátiles en una cookie no persistente. Los datos se almacenan en el cliente, se envían al servidor en cada solicitud y caducan cuando finalice la ejecución en el cliente.

4. Almacene pequeñas cantidades de datos no volátiles en una cookie persistente. Los datos se almacenarán en el cliente hasta que caduquen y se enviarán al servidor en cada solicitud.

5. Almacene en el estado de la presentación pequeñas cantidades de datos específicos de una solicitud. Los datos se envían desde el servidor al cliente y viceversa.

Page 182: Presentación del tutorial de ASP

Generadores y controladores HTTP

Información general

ASP.NET proporciona una API de bajo nivel para solicitudes y respuestas que permite a los programadores utilizar clases de .NET Framework para ocuparse de las solicitudes HTTP entrantes. Para ello, los programadores crean clases que admiten la interfaz System.Web.IHTTPHandler e implementan el método ProcessRequest(). Los controladores suelen ser útiles cuando los servicios suministrados por la abstracción del marco de trabajo de página de alto nivel no son necesarios para procesar la solicitud HTTP. Entre los usos habituales de los controladores, se incluyen los filtros y las aplicaciones al estilo CGI, especialmente aquéllas que devuelven datos binarios.

Cada solicitud HTTP recibida por ASP.NET se procesa en último término mediante una instancia específica de una clase que implementa IHTTPHandler. IHttpHandlerFactory proporciona la infraestructura que controla la resolución real de las solicitudes URL en instancias IHttpHandler. Además de las clases IHttpHandlerFactory predeterminadas suministradas por ASP.NET, los programadores pueden opcionalmente crear y registrar generadores que admitan escenarios complejos de activación y resolución de solicitudes.

Configurar generadores y controladores HTTP

Los generadores y controladores HTTP se declaran en la configuración de ASP.NET como parte de un archivo web.config. ASP.NET define una sección de configuración <httphandlers> en la que se pueden agregar y quitar controladores y generadores. Los valores de configuración para HttpHandlerFactory y HttpHandler son heredados por los subdirectorios.

Por ejemplo, ASP.NET asigna todas las solicitudes de archivos .aspx a la clase PageHandlerFactory del archivo global machine.config:

<httphandlers> ... <add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory,System.Web" /> ...</httphandlers>Crear un controlador HTTP personalizado

El siguiente ejemplo crea un controlador HttpHandler personalizado que controla todas las solicitudes que se realizan a "SimpleHandler.aspx".

Imports System.Web

Namespace Acme

Public Class SimpleHandlerVB : Implements IHttpHandler Public Sub ProcessRequest(Context As HttpContext) Implements IHttpHandler.ProcessRequest Context.Response.Write("Hola a todos") End Sub

Public ReadOnly Property IsReusable As Boolean Implements IHttpHandler.IsReusable Get Return true End Get End Property

End Class

End Namespace

Un controlador HTTP personalizado se puede crear implementando la interfaz IHttpHandler, la cual contiene sólo dos métodos. Mediante una llamada a IsReusable, un generador HTTP puede consultar a un controlador para determinar si se puede utilizar la misma instancia con el f in de atender a varias solicitudes. El método ProcessRequest toma una instancia HttpContext como parámetro y le proporciona acceso a los

intrínsecos Request y Response. En el siguiente ejemplo, los datos de solicitud no se tienen en cuenta y se envía una constante de tipo cadena como respuesta al cliente.

Page 183: Presentación del tutorial de ASP

Public Class SimpleHandler : Inherits IHttpHandler Public Sub ProcessRequest(context As HttpContext) context.Response.Write("Hello World!") End Sub

Public Function IsReusable() As Boolean Return(True) End FunctionEnd Class

Después de incluir el ensamblado del controlador compilado en el directorio \bin de la aplicación, la clase del controlador se puede especificar como objetivo de las solicitudes. En este caso, todas las solicitudes dirigidas a "SimpleHandler.aspx" se desvían a una instancia de la clase SimpleHandler, la cual reside en el espacio de nombres Acme.SimpleHandler.

Resumen de la sección

1. Los controladores y generadores HTTP constituyen el eje del marco de trabajo de las páginas ASP.NET. 2. Los generadores asignan cada solicitud a un controlador, que se encarga de procesarlas. 3. Los generadores y controladores se definen en el archivo web.config. Los subdirectorios heredan la configuración de los

generadores.. 4. Para crear un controlador personalizado, hay que implementar IHttpHandler y agregar la clase a la sección <httphandlers> del

archivo web.config del directorio.

Servicios de caché

Información general sobre el almacenamiento en caché

El almacenamiento en caché es una técnica ampliamente utilizada en informática para aumentar el rendimiento, que consiste en mantener datos de acceso frecuente o costoso en una memoria más rápida. En el contexto de una aplicación Web, el almacenamiento en caché se utiliza para retener páginas o datos durante las solicitudes HTTP y reutilizarlos sin tener que crearlos de nuevo.

ASP.NET dispone de tres clases de caché que se pueden utilizar en las aplicaciones Web:

Caché de resultados , que almacena en caché la respuesta dinámica generada por una solicitud. Caché de fragmentos , que almacena en caché partes de una respuesta generada por una solicitud. Caché de datos , que almacena en caché objetos arbitrarios mediante programación. Para ello, ASP.NET proporciona un motor

de caché muy completo que permite a los programadores retener fácilmente datos entre solicitudes.

La caché de resultados es útil cuando el contenido de toda una página se puede almacenar en memoria caché. En un sitio con muchos accesos, mantener en caché las páginas con accesos más frecuentes durante incluso un minuto cada vez puede producir incrementos significativos del rendimiento. Mientras una página permanece en la caché de resultados, las solicitudes subsiguientes de esa página se atienden desde la página de resultados, sin ejecutar el código que la creó.

A veces, no resulta práctico mantener en caché una página entera (quizá es mejor crear o personalizar partes de la página para cada solicitud). En este caso, suele ser preferible identificar aquellos objetos o datos cuya construcción sea más costosa como candidatos al almacenamiento en caché. Tras identificar a esos elementos, se pueden crear una sola vez y almacenarse en caché durante algún tiempo. Además, se puede utilizar la caché de fragmentos para mantener en caché partes del resultado de una página.

La elección del momento en el que se puede almacenar en caché un elemento puede constituir una decisión interesante. Para algunos elementos, los datos podrían actualizarse a intervalos regulares o ser válidos durante un cierto período de tiempo. En ese caso, los elementos en caché pueden estar sometidos a unas reglas de caducidad que determinen el tiempo que pueden estar almacenados en la caché. El código que obtiene acceso al elemento en caché simplemente comprueba la ausencia del elemento y vuelve a crearlo si es necesario.

La caché de ASP.NET admite dependencias de clave y de archivos, lo que permite a los programadores hacer que un elemento en caché dependa de un archivo externo o de otro elemento almacenado en caché. Esta técnica se puede utilizar para invalidar elementos cuando su origen de datos subyacente cambia.

Page 184: Presentación del tutorial de ASP

Almacenar en caché resultados de página

El almacenamiento en caché de resultados es una técnica eficaz que aumenta el rendimiento de procesos solicitud/respuesta almacenando en caché el contenido generado en páginas dinámicas. El almacenamiento de resultados en caché está habilitado de forma predeterminada, pero los resultados de una respuesta no se almacenarán en caché a menos que se realice una acción explícita para que se pueda almacenar en caché la respuesta.

Para permitir que una respuesta sea válida con el fin de almacenar resultados en caché, hay que tener una directiva de vencimiento o validación vigente y de visibilidad en caché pública. Esto se puede hacer mediante la API de bajo nivel OutputCache o la directiva de alto nivel @ OutputCache. Cuando se habilita el almacenamiento de resultados en caché se crea una entrada en la primera solicitud GET enviada a la página. Las solicitudes GET o HEAD siguientes se atenderán desde la entrada de la caché de resultados hasta que caduque la solicitud almacenada en caché.

El almacenamiento en caché de resultados también admite variaciones de pares nombre/valor GET o POST almacenados en caché.

También respeta las directivas de vencimiento y validación de las páginas. Si una página está almacenada en la caché de resultados, marcada con una directiva de vencimiento que indica que la página caduca a los 60 minutos del momento en que se almacena en caché, se quitará de la caché de resultados cuando hayan transcurrido 60 minutos. Si se recibe otra solicitud después de ese momento, se ejecuta el código de la página y se puede volver a almacenar en caché dicha página. Este tipo de directiva de vencimiento se denomina vencimiento absoluto: una página es válida hasta una hora determinada.

En el ejemplo siguiente se muestra una forma sencilla de almacenar las respuestas en la caché de resultados mediante la directiva @ OutputCache. El ejemplo sólo muestra la hora a la que se generó la respuesta. Para ver el funcionamiento del almacenamiento en caché de resultados, llame a la página y vea la hora a la que se generó la respuesta. A continuación, actualice la página y observe que la hora no cambió, lo que indica que la segunda respuesta se envía desde la caché de resultados.

<%@ OutputCache Duration="60" VaryByParam="none" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Src As Object, E As EventArgs) TimeMsg.Text = DateTime.Now.ToString("G") End Sub

</script>

<body>

<h3><font face="Verdana">Utilizar la caché de resultados</font></h3>

<p><i>Generado por última vez el:</i> <asp:label id="TimeMsg" runat="server"/>

</body>

</html>

La siguiente directiva activa el almacenamiento en caché de los resultados de la respuesta:

<%@ OutputCache Duration="60" VaryByParam="none"%>Esta directiva indica simplemente que la página debe almacenarse en caché durante 60 segundos y que la página no varía en función de ningún parámetro GET o POST. Las solicitudes recibidas mientras la página permanezca almacenada se satisfarán desde la caché. Cuando hayan transcurrido 60 segundos, se quita la página de la caché; la siguiente solicitud se controla explícitamente y vuelve a almacenar en caché la página.

Como es lógico, en el ejemplo anterior se guarda poco trabajo mediante el almacenamiento en caché de los resultados. En el siguiente ejemplo se muestra la misma técnica para el almacenamiento en caché de los resultados, pero en este caso se consulta una base de datos y se muestran los resultados en una cuadrícula.

<%@ OutputCache Duration="60" VaryByParam="none" %><%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

Page 185: Presentación del tutorial de ASP

<html>

<script language="VB" runat="server">

Sub Page_Load(Src As Object, E As EventArgs) Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter Dim ds As DataSet

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") MyCommand = New SqlDataAdapter("select * from Authors", MyConnection)

ds = New DataSet() MyCommand.Fill(ds, "Authors")

MyDataGrid.DataSource=new DataView(ds.Tables(0)) MyDataGrid.DataBind()

TimeMsg.Text = DateTime.Now.ToString("G") End Sub

</script>

<body>

<h3><font face="Verdana">Utilizar la caché de resultados</font></h3>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="700" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding="3" CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" />

<p>

<i>Generado por última vez el:</i> <asp:label id="TimeMsg" runat="server" />

</body></html>

En el último ejemplo, se modifica ligeramente la aplicación para permitir al usuario consultar información de autores de varios estados de forma selectiva. En este ejemplo se muestran solicitudes de almacenamiento en caché que se diferencian en los pares nombre/valor de la cadena de consulta mediante el atributo VaryByParam de la directiva @ OutputCache.

<%@ OutputCache Duration="60" VaryByParam="state" %>Para cada estado del conjunto de datos hay un vínculo que pasa el estado deseado como parte de la cadena de consulta. A continuación la aplicación crea la consulta de base de datos apropiada y muestra sólo los autores que pertenecen al estado seleccionado.

Tenga en cuenta que la primera vez que haga clic en el vínculo para un estado determinado se generará una nueva marca de la hora en la parte inferior de la página. Después, siempre que se vuelva a enviar una solicitud para ese estado en el plazo de un minuto se obtendrá la marca de la hora original, lo que indica que la solicitud se almacenó en caché.

<%@ OutputCache Duration="60" VaryByParam="state" %>

Page 186: Presentación del tutorial de ASP

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Src As Object, E As EventArgs) Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter Dim ds As DataSet Dim queryState As String Dim selectCmd As String

queryState = Request.QueryString("state") If queryState = Nothing selectCmd = "select * from Authors" Else selectCmd = "select * from Authors where state = '" + queryState + "'" End If

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") MyCommand = New SqlDataAdapter(selectCmd, MyConnection)

ds = New DataSet() MyCommand.Fill(ds, "Authors")

MyDataGrid.DataSource=new DataView(ds.Tables(0)) MyDataGrid.DataBind()

' capture the time of the current request ' subsequent requests while we're still cached ' will show the original time TimeMsg.Text = DateTime.Now.ToString("G") End Sub

</script>

<body> <h3><font face="Verdana">Utilizar la caché de resultados</font></h3>

<b>Autores por estado:</b>

<table cellspacing="0" cellpadding="3" rules="all" style="background-color:#AAAADD;border-color:black;border-color:black;width:700px;border-collapse:collapse;"> <tr> <td><a href="outputcache3.aspx?state=CA">CA</a></td> <td><a href="outputcache3.aspx?state=IN">IN</a></td> <td><a href="outputcache3.aspx?state=KS">KS</a></td> <td><a href="outputcache3.aspx?state=MD">MD</a></td> <td><a href="outputcache3.aspx?state=MI">MI</a></td> <td><a href="outputcache3.aspx?state=OR">OR</a></td> <td><a href="outputcache3.aspx?state=TN">TN</a></td> <td><a href="outputcache3.aspx?state=UT">UT</a></td> </tr> </table>

<p>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="700" BackColor="#ccccff" BorderColor="black"

Page 187: Presentación del tutorial de ASP

ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" />

<p>

<i>Generado por última vez el:</i> <asp:label id="TimeMsg" runat="server"/>

</body></html>

Aquellas aplicaciones que se desee que ejerzan más control sobre los encabezados HTTP relacionados con el almacenamiento en caché

pueden utilizar la funcionalidad proporcionada por la clase System.Web.HttpCachePolicy. En el siguiente ejemplo se muestra el código equivalente a las directivas de página utilizadas en los ejemplos anteriores.

Response.Cache.SetExpires(DateTime.Now.AddSeconds(60))Response.Cache.SetCacheability(HttpCacheability.Public)

 Para convertir esto en una directiva de vencimiento variable, en la que se restablece el tiempo de vencimiento cada vez que se solicita la

página, establezca el valor de la propiedad SlidingExpiration de la manera mostrada en el código siguiente.

Response.Cache.SetExpires(DateTime.Now.AddSeconds(60))Response.Cache.SetCacheability(HttpCacheability.Public)Response.Cache.SetSlidingExpiration(True)

Nota: cuando la fecha de vencimiento variable está habilitada (SetSlidingExpiration(true)), una solicitud enviada al servidor de origen siempre origina una respuesta. El vencimiento variable es útil en escenarios en los que hay cachés indirectas, que pueden satisfacer solicitudes de clientes (si el contenido aún no ha caducado) sin solicitar el contenido al servidor de origen.

Puede que algunas aplicaciones que se están adaptando desde la tecnología ASP ya establezcan directivas de almacenamiento en caché con las propiedades de ASP; por ejemplo:

Response.CacheControl = "Public"Response.Expires = 60

ASP.NET es compatible con estas propiedades, que surten el mismo efecto que los otros ejemplos mostrados.

Resumen de la sección

1. El almacenamiento en caché de resultados permite almacenar en caché el contenido generado por las páginas ASP.NET. 2. Las páginas no se colocan en la caché de resultados a menos que tengan una fecha de vencimiento o una directiva de

validación válidas, así como visibilidad pública de caché.

Caché de fragmentos de página

Además de insertar en la caché una página completa, ASP.NET proporciona un medio sencillo para utilizar la caché sólo con ciertas áreas del contenido de la página; esta característica se denomina caché de fragmentos. Las áreas de la página se definen mediante un control de usuario y se marcan para insertarlas en la caché mediante la directiva @ OutputCache presentada en la sección anterior. Esta directiva especifica el tiempo (en segundos) que el contenido resultante del control de usuario debería permanecer en la caché del servidor, así como cualquier condición opcional por la cual debería modificarse.

Por ejemplo, la siguiente directiva indica a ASP.NET que coloque en la caché el control de usuario durante 120 segundos y que modifique la caché mediante los parámetros de envío del formulario o cadenas de consulta "CategoryID" y "SelectedID".

Page 188: Presentación del tutorial de ASP

<%@ OutputCache Duration="120" VaryByParam="CategoryID;SelectedID"%>

El atributo VaryByParam permite a los creadores de controles de usuario indicar a ASP.NET que coloque en la caché o almacene múltiples instancias de un área en el servidor. Por ejemplo, las siguientes URL que apuntan a la página host de la caché del control de usuario anterior separan instancias del contenido del control de usuario.

http://localhost/mypage.aspx?categoryid=foo&selectedid=0http://localhost/mypage.aspx?categoryid=foo&selectedid=1

La lógica interna de un control de usuario puede entonces generar dinámicamente contenido distinto (el cual se coloca en la caché por separado) según los argumentos suministrados.

Además de utilizar el atributo VaryByParam, la caché de fragmentos también admite el atributo VaryByControl. Mientras que el atributo VaryByParam modifica los resultados en caché según pares nombre/valor enviados mediante POST o GET, el atributo VaryByControl modifica el fragmento en caché mediante controles internos del control de usuario. Por ejemplo:

<%@ OutputCache Duration="120" VaryByParam="none" VaryByControl="Category" %>

Observe que, de modo similar a las páginas colocadas en la caché de salida, es necesario incluir VaryByParam aunque no se utilice.

Si el control de usuario contiene un control de cuadro de selección desplegable denominado Categoría, el resultado del control de usuario variará según el valor seleccionado en ese control.

Al igual que es posible anidar controles de usuario recursivamente dentro de una página (es decir, un control de usuario declarado dentro de otro control del servidor), también es posible anidar controles de usuario en la caché de salida de forma recursiva. Esto proporciona un modelo de composición que permite a las áreas que utilizan la caché estar compuestas de otras áreas que también vayan a utilizarla.

El siguiente código de ejemplo muestra cómo hacer que dos secciones de menú de una página utilicen la caché mediante un control de usuario declarativo.

<%@ Register TagPrefix="Acme" TagName="Menu" Src="Menu.ascx" %>

<html> <body> <table> <tr> <td> <Acme:Menu Category="LeftMenu" runat=server/> </td> <td> <h1>Hi, the time is now: <%=Now%> </h1> </td> <td> <Acme:Menu Category="RightMenu" runat=server/> </td> <tr> </table> </body></html>

El siguiente código de ejemplo muestra la implementación del control de usuario "Acme:Menu" con utilización de la caché.

<%@ OutputCache Duration="120" VaryByParam="none" %>

<script language="VB" runat=server>

Public Category As String;

Sub Page_Load(sender As Object, e As EventArgs)

Page 189: Presentación del tutorial de ASP

Dim conn As AdoConnection = New AdoConnection("MyDSN")

MyMenu.DataSource = conn.Execute("select * from menu where category=" & Category) MyMenu.DataBind() End Sub

</script>

<asp:datagrid id="MyMenu" runat=server/>

Observe que, en este ejemplo, la respuesta de cada control de usuario utiliza la caché durante un período de 120 segundos. Toda la lógica necesaria para volver a crear cada control de usuario del menú, en el caso de que no se encuentre en la caché (ya sea por haber finalizado el período de 120 segundos o porque la memoria del servidor no sea suficiente), se encapsula limpiamente dentro del control de usuario.

El siguiente ejemplo muestra un caso sencillo de caché de fragmentos. En este ejemplo, el resultado de un control que recupera datos de una base de datos SQL Server utiliza la caché, a la vez que se conservan las propiedades dinámicas de la página principal. Se puede ver que la página es dinámica porque el tiempo se modifica con cada actualización, mientras que el control sólo se actualiza cada 60 segundos.

<%@ Register TagPrefix="Acme" TagName="DataControl" Src="datactrl.ascx" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Src As Object, E As EventArgs)

TimeMsg.Text = DateTime.Now.ToString("G") End Sub

</script>

<body>

<h3><font face="Verdana">Almacenamiento en caché de fragmentos</font></h3>

<Acme:DataControl runat="server"/>

<br>

<i>Página generada por última vez:</i> <asp:label id="TimeMsg" runat="server" />

</body></html>

Advertencias

Nota: los intentos de manipular mediante programación un control que utiliza la caché, desde su página contenedora, hacen que se produzca un error. Por ejemplo, los intentos de utilizar una expresión declarativa de enlace de datos en la etiqueta del control de usuario hacen que se generen errores de análisis, como se muestra en el siguiente código.

<!-- Las siguientes etiquetas generan errores de análisis. --><Acme:Menu Category='<%# Container.DataItem("Category")' runat="server"/>

La razón de ello es simple. En los casos en que se utiliza la caché para el contenido de un control de usuario, se crea una instancia del control sólo en la primera solicitud; de este modo, una vez en la caché, el control deja de estar disponible. En vez de ello, se debería encapsular toda la lógica necesaria para crear el contenido de un control de usuario dentro del propio control; esto se hace normalmente dentro del evento Page_Load o Page_PreRender del control de usuario.

Page 190: Presentación del tutorial de ASP

Se pueden declarar y utilizar otros parámetros de propiedades declarativas para personalizar el control. Por ejemplo, el control de usuario anterior se puede personalizar del siguiente modo:

<Acme:Menu Category="LeftMenu" runat=server/><Acme:Menu Category="RightMenu" runat=server/>

Estas declaraciones hacen que el compilador de páginas genere y ejecute el código apropiado en caso de que se cree el control como resultado de una falta de datos en la caché. Los programadores de controles de usuario pueden tener acceso a estas opciones de configuración de igual forma que en el caso de controles de usuario que no utilizan la caché.

Resumen de la sección

1. Además de insertar en la caché una página completa, ASP.NET proporciona un medio sencillo para utilizar la caché sólo con ciertas áreas del contenido de la página; esta característica se denomina caché de fragmentos.

2. Las áreas de la página se definen mediante un control de usuario y se marcan para que utilicen la caché, mediante la directiva @ OutputCache presentada en la sección anterior.

3. Al igual que es posible anidar controles de usuario recursivamente dentro de una página (es decir, un control de usuario declarado dentro de otro control del servidor), también es posible anidar controles de usuario en la caché de salida de forma recursiva.

4. Los intentos de manipular mediante programación un control que utiliza la caché, desde su página contenedora, hacen que se produzca un error. En vez de ello, se debería encapsular toda la lógica necesaria para crear el contenido de un control de usuario directamente dentro del propio control, normalmente dentro del evento Page_Load o Page_PreRender del control de usuario.

5. Se pueden declarar y utilizar otros parámetros de propiedades declarativas para personalizar el control.

Caché de datos de página

Introducción a la caché de datos

ASP.NET proporciona un motor de caché con múltiples características que pueden utilizar las páginas con el fin de almacenar y recuperar objetos arbitrarios mediante las solicitudes HTTP. La caché de ASP.NET es privada para cada aplicación y almacena los objetos en memoria. El período de vida de la caché equivale al de la aplicación; es decir, cuando la aplicación se reinicia, la caché se crea de nuevo.

La caché proporciona una interfaz de diccionario sencilla que permite a los programadores colocar objetos dentro y recuperarlos posteriormente con facilidad. En el caso más simple, colocar un elemento en la caché es como agregar un elemento a un diccionario:

Cache("mykey") = myValue

Recuperar los datos es igual de sencillo:

myValue = Cache("mykey")If myValue <> Null Then DisplayData(myValue)End If

Para aplicaciones que necesitan una funcionalidad más sofisticada, la caché de ASP.NET ofrece eliminación de datos innecesarios, caducidad y dependencias de clave y archivo.

La eliminación de datos es el proceso por el que la caché intenta quitar elementos no importantes o utilizados con poca frecuencia en caso de que la memoria sea escasa. Los programadores que deseen controlar la eliminación de datos pueden proporcionar sugerencias al eliminador al insertar los elementos en la caché, lo que indica el costo relativo de creación del elemento y la rapidez relativa con la que se debe obtener acceso al elemento de manera que resulte útil.

La caducidad permite a los programadores asignar períodos de vida a los elementos en caché; pudiendo los períodos ser explícitos (por ejemplo, caduca a las 6:00) o bien relativos al último uso de un elemento (por ejemplo, caduca 20 minutos después de haber utilizado el elemento por última vez). Cuando un elemento ha caducado, se elimina de la caché y, entonces, cualquier intento para recuperarlo hace que se devuelva el valor null, a menos que el elemento se inserte de nuevo en la caché.

Las dependencias de clave y archivo permiten basar la validez de un elemento de la caché en un archivo externo o en otro elemento de la caché. Si una dependencia cambia, el elemento ya no es válido y, entonces, se quita de la caché. Un ejemplo de cómo se podría utilizar esta funcionalidad lo constituye el siguiente escenario: una aplicación lee información financiera de un

Page 191: Presentación del tutorial de ASP

archivo XML que se actualiza periódicamente. La aplicación procesa los datos del archivo y crea un grafico de objetos que representa los datos en un formato más significativo. La aplicación almacena los datos en la caché e inserta una dependencia en el archivo desde el que se leyeron los datos. Cuando el archivo se actualiza, los datos se eliminan de la caché y la aplicación puede volver a leerlos e insertar la copia actualizada de los datos.

Utilizar la caché de datos

El siguiente ejemplo muestra una utilización sencilla de la caché. Ejecuta una consulta de base de datos, guarda el resultado en la caché y sigue utilizándolo durante el período de vida de la aplicación. Cuando ejecute el ejemplo, observe el mensaje que aparece en la parte inferior de la página. Cuando se realiza una solicitud por primera vez, el mensaje indica que los datos se recuperaron explícitamente del servidor de la base de datos. Tras actualizar la página, ésta advierte que se utilizó la copia almacenada en caché.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Src As Object, E As EventArgs)

Dim Source As DataView

' try to retrieve item from cache ' if it's not there, add it

Source = Cache("MyDataSet")

If Source Is Nothing

Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection("server=(local)\NetSDK;database=pubs;Trusted_Connection=yes") MyCommand = New SqlDataAdapter("select * from Authors", MyConnection)

Dim ds As New DataSet myCommand.Fill(ds, "Authors")

Source = New DataView(ds.Tables("Authors")) Cache("MyDataSet") = Source

CacheMsg.Text = "Conjunto de datos creado explícitamente" Else cacheMsg.Text = "Conjunto de datos recuperado de la caché" End If

MyDataGrid.DataSource=Source MyDataGrid.DataBind() End Sub

</script>

<body>

<form method="GET" runat="server">

<h3><font face="Verdana">Almacenar datos en caché</font></h3>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="700" BackColor="#ccccff"

Page 192: Presentación del tutorial de ASP

BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaad" />

<p>

<i><asp:label id="CacheMsg" runat="server"/></i>

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

El siguiente ejemplo muestra un elemento de la caché que depende de un archivo XML. Es similar al primer ejemplo, aunque en este caso los datos se recuperan de un origen de datos XML en vez de un servidor de base de datos. Cuando los datos se insertan en la caché, el archivo XML se agrega como una dependencia.

Cuando se agrega un nuevo registro mediante el formulario de la parte inferior de la página, el archivo XML se actualiza y, entonces, el elemento almacenado en la caché debe crearse de nuevo.

<%@ Import Namespace="System.IO" %><%@ Import Namespace="System.Data" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Src As Object, E As EventArgs) If Not IsPostBack LoadData() End If End Sub

Sub NewAuthorBtn_Click(Src As Object, E As EventArgs) If Not Page.IsValid AuthorMsg.Text = "Faltan algunos campos requeridos" Else Dim fs As FileStream Dim reader As StreamReader Dim ds As DataSet Dim newAuthor As DataRow Dim writer As TextWriter

' open the file and read the current authors ds = New DataSet fs = New FileStream(Server.MapPath("authors.xml"), FileMode.Open, FileAccess.Read, FileShare.ReadWrite) reader = New StreamReader(fs) ds.ReadXml(reader) fs.Close()

' append a row Try newAuthor = ds.Tables(0).NewRow() newAuthor("au_id") = AuthorId.Text newAuthor("au_lname") = LastName.Text

Page 193: Presentación del tutorial de ASP

newAuthor("au_fname") = FirstName.Text newAuthor("phone") = Phone.Text newAuthor("address") = Address.Text newAuthor("city") = City.Text newAuthor("state") = AddressState.Text newAuthor("zip") = PostalCode.Text newAuthor("contract") = Contract.Checked ds.Tables(0).Rows.Add(newAuthor) Catch Exc As Exception CacheMsg.Text = "No se pudo crear el autor con id. = (" & AuthorId.Text & ")<br>" & "El autor ya existe." Return End Try

' rewrite the data file fs = New FileStream(Server.MapPath("authors.xml"), FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite) writer = New StreamWriter(fs) writer = TextWriter.Synchronized(writer) ds.WriteXml(writer) writer.Close()

Cache.Remove("MyData") LoadData() End If End Sub

Sub RefreshBtn_Click(Src As Object, E As EventArgs) LoadData() End Sub

Sub LoadData Dim Source As DataView

Source = Cache("MyData") If Source Is Nothing Dim ds As DataSet Dim fs As FileStream Dim reader As StreamReader

' read the data from the XML source ds = New DataSet() fs = New FileStream(Server.MapPath("authors.xml"), FileMode.Open,FileAccess.Read) reader = New StreamReader(fs) ds.ReadXml(reader) fs.Close()

Source = New DataView(ds.Tables(0))

' cache it for future use Cache.Insert("MyData", Source, New CacheDependency(Server.MapPath("authors.xml")))

' we created the data explicitly, so advertise that fact CacheMsg.Text = "Conjunto de datos creado explícitamente" Else CacheMsg.Text = "Conjunto de datos recuperado de la caché" End If

MyDataGrid.DataSource = Source MyDataGrid.DataBind() End Sub

</script>

Page 194: Presentación del tutorial de ASP

<body>

<form runat="server">

<h3><font face="Verdana">Dependencias del archivo</font></h3>

<ASP:DataGrid id="MyDataGrid" runat="server" Width="900" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" />

<hr>

<h3><font face="Verdana">Agregar nuevo autor</font></h3>

<asp:Label ID="AuthorMsg" Text="Rellenar los campos requeridos a continuación para agregar un nuevo autor" ForeColor="red" Font-Name="Verdana" Font-Size="10" runat=server />

<p>

<table> <tr> <td>Id. del autor:</td> <td><ASP:TextBox id=AuthorId Text="111-11-1111" runat=server/></td> <td><ASP:RequiredFieldValidator ControlToValidate="AuthorId" Display="Static" ErrorMessage="*" runat=server/></td> </tr> <tr> <td>Apellido:</td> <td><ASP:TextBox id=LastName Text="Doe" runat=server/></td> <td><ASP:RequiredFieldValidator ControlToValidate="LastName" Display="Static" ErrorMessage="*" runat=server/></td> </tr> <tr> <td>Nombre:</td> <td><ASP:TextBox id=FirstName Text="John" runat=server/></td> <td><ASP:RequiredFieldValidator ControlToValidate="FirstName" Display="Static" ErrorMessage="*" runat=server/></td> </tr> <tr> <td>Teléfono:</td> <td><ASP:TextBox id=Phone Text="555 555-5050" runat=server/></td> <td><ASP:RequiredFieldValidator ControlToValidate="Phone" Display="Static" ErrorMessage="*" runat=server/></td> </tr> <tr> <td>Dirección:</td> <td><ASP:TextBox id=Address Text="One Microsoft Way" runat=server/></td> <td><ASP:RequiredFieldValidator ControlToValidate="Address" ErrorMessage="*" Display="Static" runat=server/></td> </tr> <tr> <td>Ciudad:</td> <td><ASP:TextBox id=City Text="Redmond" runat=server/></td> <td><ASP:RequiredFieldValidator ControlToValidate="City" ErrorMessage="*" Display="Static" runat=server/></td>

Page 195: Presentación del tutorial de ASP

</tr> <tr> <td>Estado:</td> <td><ASP:TextBox id=AddressState Text="WA" runat=server/></td> <td><ASP:RequiredFieldValidator ControlToValidate="AddressState" ErrorMessage="*" Display="Static" runat=server/></td> </tr> <tr> <td>Código postal:</td> <td><ASP:TextBox id=PostalCode Text="98052" runat=server/></td> <td><ASP:RequiredFieldValidator ControlToValidate="PostalCode" ErrorMessage="*" Display="Static" runat=server/></td> </tr> <tr> <td>Contrato:</td> <td><ASP:CheckBox id=Contract Checked runat="server"/></td> <td></td> </tr> </table>

<asp:button Text="Agregar nuevo autor" OnClick="NewAuthorBtn_Click" runat=server/> <asp:button Text="Actualizar lista" OnClick="RefreshBtn_Click" runat=server/>

<p>

<hr>

<p>

<i><asp:label id="CacheMsg" runat="server"/></i></p>

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

Observe que se agrega una dependencia de archivo utilizando Cache.Insert y suministrando un objeto CacheDependency que hace referencia al archivo XML.

Cache.Insert("MyData", Source, _ New CacheDependency(Server.MapPath("authors.xml")))

Un elemento de la caché puede depender de uno o varios archivos o claves. Como ya se mencionó anteriormente, una aplicación puede definir reglas de caducidad sobre un elemento de la caché. El siguiente código establece un tiempo de caducidad absoluto.

Cache.Insert("MyData", Source, null, _ DateTime.Now.AddHours(1), TimeSpan.Zero)

El parámetro relevante es la llamada a DateTime.Now.AddHours(1), que indica que el tiempo finaliza 1 hora después del momento en que se insertó. El argumento final, TimeSpan.Zero, indica que no existe ninguna regla de caducidad relativa para este elemento.

El siguiente código muestra cómo establecer una regla de caducidad relativa. Inserta un elemento que caduca 20 minutos después de su

último acceso. Observe el uso de DateTime.MaxValue, que indica que no existe ninguna regla de caducidad absoluta para este elemento.

Cache.Insert("MyData", Source, null, DateTime.MaxValue, _

Page 196: Presentación del tutorial de ASP

TimeSpan.FromMinutes(20))

Resumen de la sección

1. Es posible almacenar objetos arbitrarios en la caché de datos mediante programación. 2. La caché de ASP.NET admite caducidad y dependencias. 3. La caché se asigna a una aplicación y su período de vida coincide con el de la aplicación.

Configuración

Información general sobre la configuración

Un requisito fundamental de cualquier servidor de aplicaciones Web consiste en la existencia de un sistema de configuración rico y flexible que permita a los programadores asociar fácilmente valores de configuración con una aplicación instalable (sin tener que incluir los valores dentro del código), y a los administradores poder personalizar con facilidad estos valores después de la distribución. El sistema de configuración de ASP.NET se ha diseñado para satisfacer las necesidades de ambos colectivos, proporcionando una infraestructura de configuración jerárquica que permite definir datos de configuración extensibles y utilizarlos a lo largo de una aplicación, un sitio o un equipo. Ofrece las siguientes cualidades que le convierten en un producto especialmente apropiado para la creación y el mantenimiento de aplicaciones Web:

ASP.NET permite almacenar valores de configuración junto con contenido estático, páginas dinámicas y objetos empresariales dentro de una única jerarquía de directorios de la aplicación. Los usuarios o administradores sólo necesitan copiar un único árbol de directorios para instalar una aplicación ASP.NET Framework en un equipo.

Los datos de configuración se almacenan en archivos de texto sin formato donde los usuarios pueden leer y escribir perfectamente. Los administradores y los programadores pueden utilizar cualquier editor de textos estándar, analizador XML o lenguaje de secuencias de comandos para interpretar y actualizar los valores de configuración.

ASP.NET proporciona una infraestructura de configuración extensible que permite a programadores terceros almacenar sus propios valores de configuración, definir el formato de persistencia de esos valores, participar de forma inteligente en su procesamiento y controlar el modelo de objetos resultante a través del cual esos valores se exponen en última instancia.

El sistema detecta automáticamente los cambios en los archivos de configuración de ASP.NET y los aplica sin intervención del usuario (no es necesario que un administrador reinicie el servidor Web o el equipo para que surtan efecto).

Las secciones de configuración se pueden bloquear mediante la etiqueta <location> y el atributo allowOverride.

Para aprender más sobre el sistema de configuración de ASP.NET y sobre su funcionamiento, vea Formato de archivos de configuración y Recuperar configuración.

Formato del archivo de configuración

Los archivos de configuración de ASP.NET son archivos de texto basados en XML (cada uno con el nombre web.config) que pueden aparecer en cualquier directorio de un servidor de aplicaciones ASP.NET. Cada archivo web.config aplica valores de configuración al directorio en el que se encuentra ubicado y a todos sus subdirectorios virtuales. Los valores de los subdirectorios pueden reemplazar o modificar opcionalmente los valores especificados en sus directorios principales. El archivo de configuración raíz--WinNT\Microsoft.NET\Framework\<version>\machine.config--proporciona valores de configuración predeterminados para todo el equipo. ASP.NET configura IIS para impedir el acceso directo desde un explorador a los archivos web.config, y así garantizar que sus valores no se hacen públicos (los intentos de acceso hacen que ASP.NET devuelva el código 403: Acceso prohibido).

Durante la ejecución, ASP.NET utiliza estos archivos de configuración web.config para calcular jerárquicamente una colección exclusiva de valores para cada solicitud de destino URL entrante (estos valores se calculan sólo una vez y después se guardan en caché a lo largo de las solicitudes subsiguientes; ASP.NET vigila automáticamente si se producen cambios e invalida la caché si cualquiera de los archivos de configuración cambia).

Por ejemplo, los valores de configuración para la URL http://myserver/myapplication/mydir/page.aspx se calcularían aplicando los valores del archivo web.config en el siguiente orden:

Configuración base del equipo.C:\WinNT\Microsoft.NET\Framework\v.1.00\machine.config

Sobrescrita por la configuración del sitio (o la aplicación de la raíz).C:\inetpub\wwwroot\web.config

Sobrescrita por la configuración de la aplicación.D:\MyApplication\web.config

Sobrescrita por la configuración del subdirectorio.D:\MyApplication\MyDir\web.config

Page 197: Presentación del tutorial de ASP

Si existe un archivo web.config en el directorio raíz de un sitio, por ejemplo, "Inetpub\\wwwroot", sus valores de configuración se utilizarán para todas las aplicaciones de ese sitio. Observe que la presencia de un archivo web.config dentro de un determinado directorio o de la raíz de la aplicación es totalmente opcional. Si no existe un archivo web.config, todos los valores de configuración para el directorio se heredan automáticamente del directorio principal.

Secciones y controladores de secciones de configuraciónUn archivo web.config es un archivo de texto XML que puede contener elementos de documentos XML estándar, incluidos comentarios, texto, etiquetas bien formadas, párrafos cdata, etc. El archivo puede ser ANSI, UTF-8 o Unicode; el sistema detecta automáticamente la codificación. El elemento raíz de un archivo web.config es siempre una etiqueta <configuration>. Los valores de ASP.NET y del usuario final se encapsulan entonces dentro de la etiqueta, como se indica a continuación: <configuration> <!- Configuration settings would go here. --></configuration>La etiqueta <configuration> contiene normalmente tres tipos diferentes de elementos: 1) declaraciones de controladores de secciones de configuración, 2) grupos de secciones de configuración y 3) valores de secciones de configuración.

Controladores de secciones de configuración - La infraestructura de configuración de ASP.NET no realiza ninguna suposición acerca del formato de archivos o de los valores admitidos dentro de un archivo web.config. En vez de ello, delega el procesamiento de los datos de web.config en controladores de secciones de configuración, que son clases de .NET Framework que implementan la interfaz IConfigurationSectionHandler. Sólo es necesario realizar una declaración individual de IConfigurationSectionHandler normalmente en el archivo machine.config. Los archivos web.config de subdirectorios heredan automáticamente esta declaración. Los controladores de secciones de configuración se declaran dentro de un archivo web.config mediante directivas de etiquetas de sección anidadas dentro de una etiqueta <configSections> . Las etiquetas de sección pueden calificarse con más detalle mediante etiquetas de grupo que permiten organizarlas en grupos lógicos (véase más abajo). Cada etiqueta de sección identifica un nombre de etiqueta que denota una sección específica de datos de configuración y una clase IConfigurationSectionHandler asociada que se encarga de procesarla.

Grupos de secciones de configuración - La configuración de ASP.NET permite el agrupamiento jerárquico de secciones a efectos de organización. Una etiqueta <sectionGroup> puede aparecer dentro de una etiqueta <configSections> o dentro de otras etiquetas <sectionGroup>. Por ejemplo, todos los controladores de secciones de ASP.NET aparecen dentro del grupo de secciones <system.web>.

Secciones de configuración - Los valores de configuración de ASP.NET se representan dentro de secciones de etiquetas de configuración, también anidadas dentro de una etiqueta <configuration> (y etiquetas de grupos de secciones opcionales). Para cada sección de configuración, se debe definir un controlador de sección apropiado en la jerarquía de configuraciones. En el ejemplo siguiente, la etiqueta <httpModules> es la sección de configuración que define los datos de configuración de módulos HTTP. La clase System.Configuration.HttpModulesConfigurationHandler es la responsable de interpretar en tiempo de ejecución el contenido existente dentro de la etiqueta <httpModules>. Observe que tanto la sección como la definición del controlador de sección deben tener el mismo calificador de grupo de secciones (en este caso, <system.web>). Asimismo, tenga en cuenta que en los nombres de etiqueta se distingue entre mayúsculas y minúsculas y, por tanto, deben escribirse exactamente como aparecen. Otros atributos y valores para ASP.NET también realizan esa distinción, de modo que, si no se escriben correctamente, el módulo de ejecución de configuraciones no los examinará.

<configuration>

<configSections> <sectionGroup name="system.web"> <section name="httpModules" type="System.Web.Configuration.HttpModulesConfigurationHandler,System.Web" /> </sectionGroup> </configSections>

<system.web> <httpModules> <add name="CookielessSession" type="System.Web.SessionState.CookielessSessionModule,System.Web" /> <add name="OutputCache" type="System.Web.Caching.OutputCacheModule,System.Web" /> <add name="Session" type="System.Web.SessionState.SessionStateModule,System.Web" /> <add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule,System.Web" />

Page 198: Presentación del tutorial de ASP

<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule,System.Web" /> <add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule,System.Web" /> <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule,System.Web" /> <add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule,System.Web" /> </httpModules> </system.web>

</configuration>

Utilizar Location y PathDe forma predeterminada, todos los valores de configuración definidos dentro de la etiqueta <configuration> de nivel superior se aplican a la ubicación del directorio actual que contiene el archivo web.config y a todos sus subdirectorios. Opcionalmente, se pueden aplicar valores de configuración a subdirectorios específicos por debajo del archivo de configuración actual mediante la etiqueta <location> y un atributo restrictivo path apropiado. Si el archivo de configuración es el archivo principal machine.config, se pueden aplicar valores a aplicaciones o directorios virtuales específicos. Si el archivo de configuración es un archivo web.config, se pueden aplicar valores a un archivo, subdirectorio, directorio virtual o aplicación específicos. <configuration>

<location path="EnglishPages"> <system.web> <globalization requestEncoding="iso-8859-1" responseEncoding="iso-8859-1" /> </system.web> </location>

<location path="EnglishPages\OneJapanesePage.aspx"> <system.web> <globalization requestEncoding="Shift-JIS" responseEncoding="Shift-JIS" /> </system.web> </location>

</configuration>

Bloquear valores de configuraciónAdemás de especificar la información de la ruta de acceso mediante la etiqueta <location> también se pueden establecer medidas de seguridad para que los valores no sean reemplazados por otro archivo de configuración situado más abajo en la jerarquía de configuraciones. Para bloquear un grupo de valores, se puede especificar un atributo allowOverride con el valor false, en la etiqueta <location> que los engloba. El siguiente código bloquea valores de suplantación de identidad para dos aplicaciones diferentes. <configuration>

<location path="app1" allowOverride="false"> <system.web> <identity impersonate="false" userName="app1" password="app1pw" /> </system.web> </location>

<location path="app2" allowOverride="false">

Page 199: Presentación del tutorial de ASP

<system.web> <identity impersonate="false" userName="app2" password="app2pw" /> </system.web> </location>

</configuration>

Observe que si un usuario intenta reemplazar estos valores en otro archivo de configuración, el sistema de configuración indicará un error:

<configuration>

<system.web> <identity userName="developer" password="loginpw" /> </system.web>

</configuration>

Sección de configuración ASP.NET estándar

ASP.NET se suministra con una serie de controladores estándar de secciones de configuración para procesar los valores de configuración de los archivos web.config. La siguiente tabla proporciona una breve descripción de las secciones junto con referencias para obtener más información. Nombre de sección Descripción

<httpModules> Se encarga de configurar módulos HTTP dentro de una aplicación. Los módulos HTTP participan en el procesamiento de todas las solicitudes en una aplicación. Entre los usos más habituales, se incluyen la seguridad y el inicio de sesión.

<httpHandlers> Se encarga de asignar direcciones URL de entrada a clases IHttpHandler. Los subdirectorios no heredan estos valores. También es responsable de asignar direcciones URL de entrada a clases IHttpHandlerFactory. Los datos representados en secciones <httpHandlers> son heredados jerárquicamente por subdirectorios. Para obtener más información, vea la sección Factorías y controladores Http de este tutorial.

<sessionState> Se encarga de configurar el módulo HTTP de estado de sesión. Para obtener más información, vea la sección Administrar el estado de la aplicación de este tutorial.

<globalization> Se encarga de configurar los valores de globalización de una aplicación. Para obtener más información, vea la sección Localización de este tutorial.

<compilation> Es responsable de todos los valores de compilación utilizados por ASP.NET. Para obtener más información, vea las secciones Objetos empresariales y Depuración de este tutorial.

<trace> Se encarga de configurar el servicio de seguimiento de ASP.NET. Para obtener más información, vea la sección Seguimiento de este tutorial.

<processModel> Se encarga de configurar los valores del modelo de proceso de ASP.NET en sistemas de servidores Web de IIS.

<browserCaps> Es responsable del control de los valores del componente de funcionalidad de explorador. Para obtener más información, vea la sección Recuperar configuración de este tutorial.

Recuperar configuración

ASP.NET permite a los programadores obtener acceso a los valores de configuración desde una aplicación, ya sea exponiendo los valores de configuración directamente (como propiedades con un fuerte control de tipos) o mediante determinadas API de configuración generales. El siguiente ejemplo muestra una página que proporciona acceso a la sección de configuración <browserCaps> mediante la propiedad Browser de la clase System.Web.HttpRequest. Se trata de una tabla hash de atributos que refleja las capacidades del explorador que está actualmente obteniendo acceso a la página. Los datos reales de la sección <browserCaps> se incluyen en el archivo machine.config.

<%@ Page Language="VB" %>

<html>

<body style="font: 10pt verdana">

Page 200: Presentación del tutorial de ASP

<h3>Recuperar funciones del explorador</h3>

Boolean ActiveXControls = <%=Request.Browser.ActiveXControls.ToString()%><br> Boolean AOL = <%=Request.Browser.AOL.ToString()%><br> Boolean BackgroundSounds = <%=Request.Browser.BackgroundSounds.ToString()%><br> Boolean Beta = <%=Request.Browser.Beta.ToString()%><br> String Browser = <%=Request.Browser.Browser%><br> Boolean CDF = <%=Request.Browser.CDF.ToString()%><br> Boolean Cookies = <%=Request.Browser.Cookies.ToString()%><br> Boolean Crawler = <%=Request.Browser.Crawler.ToString()%><br> Boolean Frames = <%=Request.Browser.Frames.ToString()%><br> Boolean JavaApplets = <%=Request.Browser.JavaApplets.ToString()%><br> Boolean JavaScript = <%=Request.Browser.JavaScript.ToString()%><br> Int32 MajorVersion = <%=Request.Browser.MajorVersion.ToString()%><br> Double MinorVersion = <%=Request.Browser.MinorVersion.ToString()%><br> String Platform = <%=Request.Browser.Platform%><br> Boolean Tables = <%=Request.Browser.Tables.ToString()%><br> String Type = <%=Request.Browser.Type%><br> Boolean VBScript = <%=Request.Browser.VBScript.ToString()%><br> String Version = <%=Request.Browser.Version%><br> Boolean Win16 = <%=Request.Browser.Win16.ToString()%><br> Boolean Win32 = <%=Request.Browser.Win32.ToString()%><br>

</body></html>

Además de obtener acceso a los valores de configuración, los programadores también pueden utilizar la clase System.Configuration.ConfigurationSettings para recuperar los datos de cualquier sección de configuración. Observe que el objeto particular devuelto por ConfigurationSettings depende del controlador asociado a la sección de configuración (vea IConfigurationSectionHandler.Create). El siguiente código muestra cómo se puede obtener acceso a los datos de configuración expuestos para una sección <customconfig>. En este ejemplo, se supone que el controlador de la sección de configuración devuelve un objeto de tipo

CustomConfigSettings con la propiedad Enabled.

Dim config As CustomConfigSettings = CType(ConfigurationSettings("customconfig"), CustomConfigSettings)

If config.Enabled = True Then ' Do something here.End If

Utilizar valores de aplicacionesLos archivos de configuración son perfectamente apropiados para almacenar valores de aplicaciones personalizados, tales como cadenas de conexión con bases de datos, rutas de acceso a archivos o direcciones URL de servicios Web de XML remotos. Entre las secciones de configuración predeterminadas (definidas en el archivo machine.config), se incluye una sección <appSettings> que se puede utilizar para almacenar estos valores como pares nombre/valor. En el siguiente ejemplo, se muestra una sección de configuración <appSettings> que define cadenas de conexión con bases de datos para una aplicación. <configuration> <appSettings> <add key="pubs" value="server=(local)\NetSDK;database=pubs;Trusted_Connection=yes" /> <add key="northwind" value="server=(local)\NetSDK;database=northwind;Trusted_Connection=yes" /> </appSettings></configuration>El objeto ConfigurationSettings expone una propiedad AppSettings especial que se puede utilizar para recuperar esos valores: Dim dsn As String = ConfigurationSettings.AppSettings("pubs")

Page 201: Presentación del tutorial de ASP

El siguiente ejemplo ilustra esta técnica.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.SqlClient" %><%@ Import Namespace="System.Configuration" %>

<html>

<script language="VB" runat="server">

Sub Page_Load(Src As Object, E As EventArgs)

Dim dsn As String = ConfigurationSettings.AppSettings("pubs")

Dim MyConnection As SqlConnection Dim MyCommand As SqlDataAdapter

MyConnection = New SqlConnection(DSN) MyCommand = New SqlDataAdapter("select * from Authors", MyConnection)

Dim DS As New DataSet MyCommand.Fill(DS, "Authors")

MyDataGrid.DataSource= New DataView(DS.Tables(0)) MyDataGrid.DataBind() End Sub

</script>

<body>

<h3><font face="Verdana">Recuperar datos sobre la configuración</font></h3>

<ASP:DataGrid id="MyDataGrid" runat="server" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" />

</body></html>

Implementación

Distribuir aplicaciones ASP.NET

Diseño del sistema de archivos de las aplicaciones ASP.NET

Se puede utilizar ASP.NET para albergar múltiples aplicaciones Web, cada una identificada mediante un prefijo URL único dentro de un sitio

Web (un sitio Web se representa en un servidor Web como una combinación NombreDeHost/Puerto). Por ejemplo, un único servidor Web de Microsoft Internet Information Services (IIS) con dos direcciones IP (una con alias

Page 202: Presentación del tutorial de ASP

"www.msn.com" y otra "intranet") y tres sitios lógicos (http://intranet, http://www.msn.com, http://www.msn.com port 81) podría exponer las siguientes seis aplicaciones ASP.NET.

URL de la aplicación Descripción

http://intranet Aplicación "Root" del sitio de la intranet.

http://www.msn.com Aplicación "Root" en el sitio www.msn.com.

http://www.msn.com:81 Aplicación "Root" en el sitio www.msn.com, puerto 81.

http://intranet/training Aplicación "training" del sitio de la intranet.

http://intranet/hr Aplicación "HR" del sitio de la intranet.

http://intranet/hr/compensation/ Aplicación "Compensation" del sitio de la intranet.

Nota: la URL de la aplicación "compensation" mencionada en la tabla está arraigada dentro del espacio de nombres de la URL de la aplicación HR. No obstante, esta notación jerárquica de URL no implica que la aplicación "compensation" se encuentre incluida o anidada dentro de la aplicación HR.

Cada aplicación ASP.NET Framework expuesta en un espacio de nombres de URL está respaldada por un directorio del sistema de archivos ubicado en un recurso compartido local o remoto. Los directorios de aplicaciones no necesitan estar ubicados centralmente en una parte contigua del sistema de archivos; pueden estar esparcidos por todo un disco. Por ejemplo, las aplicaciones ASP.NET mencionadas anteriormente podrían estar ubicadas en los diferentes directorios que aparecen en la tabla siguiente.

URL de la aplicación Ruta de acceso física

http://intranet c:\inetpub\wwwroot

http://www.msn.com c:\inetpub\msnroot

http://www.msn.com:81 d:\msnroot81

http://intranet/training d:\serverapps\trainingapp

http://intranet/hr \\hrweb\sillystuff\reviews

http://intranet/hr/compensation/ c:\inetpub\wwwroot\compensation

Resolver referencias de clases en ensamblados

Los ensamblados constituyen la unidad de implementación de clases en Common Language Runtime. Los programadores que escriben clases de .NET Framework utilizando la versión 7.0 de Visual Studio .NET producen un nuevo ensamblado con cada proyecto de Visual Studio que compilan. Aunque es posible hacer que un ensamblado ocupe varios archivos ejecutables portables (PE) (varias DLL de módulos), Visual Studio .NET compilará, de forma predeterminada, todo el código del ensamblado en una única DLL (1 proyecto de Visual Studio .NET = 1 ensamblado de .NET Framework = 1 DLL física).

Para utilizar un ensamblado en un equipo, se debe implementar en una caché de ensamblado. La caché de ensamblado puede ser global para un equipo o local para una aplicación determinada. En la caché de ensamblado global del sistema, sólo debería colocarse el código que se va a compartir entre varias aplicaciones. El código específico para una aplicación particular, como la lógica de la mayoría de las aplicaciones Web, se debería implementar en la caché de ensamblado local de la aplicación. Una de las ventajas de distribuir un ensamblado en la caché de ensamblado local de una aplicación es que sólo el código interno de esa aplicación puede tener acceso a él. (Se trata de una característica muy apreciada para escenarios en los que intervienen proveedores de servicios de Internet). También facilita la creación conjunta de versiones de la misma aplicación, ya que las clases son privadas para cada instancia de versión de la aplicación.

Un ensamblado se puede distribuir en la caché de ensamblado local de una aplicación simplemente copiando (o utilizando XCOPY o FTP) los archivos apropiados en un directorio marcado como "ubicación para la caché de ensamblado" de esa determinada aplicación. No es necesario ejecutar ninguna herramienta de registro adicional una vez copiados los archivos apropiados y tampoco es necesario reiniciar el equipo. Esto elimina algunas de las dificultades actualmente asociadas a la distribución de componentes COM dentro de aplicaciones ASP (actualmente, un administrador debe iniciar sesión localmente en el servidor Web y ejecutar Regsvr32.exe).

De forma predeterminada, una aplicación ASP.NET Framework se configura automáticamente para utilizar el subdirectorio \\bin, ubicado inmediatamente bajo la raíz de la aplicación, como su caché de ensamblado local. El directorio \\bin también está configurado para denegar cualquier acceso de un explorador Web, de modo que un cliente remoto no pueda descargar el código y apropiarse del mismo. El siguiente ejemplo muestra una posible distribución de directorios para una aplicación de ASP.NET, donde el directorio \bin se encuentra justo debajo de la raíz de la aplicación.

C:\inetpub\wwwroot Web.cfg Default.aspx

Page 203: Presentación del tutorial de ASP

\bin <= Application assembly cache directory MyPages.dll MyBizLogic.dll

\order SubmitOrder.aspx OrderFailed.aspx

\img HappyFace.gifInicio de aplicaciones ASP.NET Framework y resolución de clases

Las aplicaciones ASP.NET Framework se construyen lentamente la primera vez que un cliente solicita un recurso URL de ellas. Cada aplicación ASP.NET Framework se lanza dentro de un dominio de aplicación exclusivo (AppDomain), que consiste en una nueva construcción de Common Language Runtime que permite a los hosts de procesos ofrecer código extensivo, seguridad y aislamiento de configuraciones durante la ejecución.

ASP.NET se encarga de crear manualmente un dominio de aplicaciones cuando se inicia una nueva aplicación. Como parte de este proceso, ASP.NET proporciona valores de configuración para que Common Language Runtime los utilice. Entre estos valores de configuración, se incluyen:

Las rutas de acceso a directorios que componen la caché de ensamblado local. (Nota: es la arquitectura de aislamiento de dominios para aplicaciones de .NET Framework la que permite a cada aplicación mantener su propia caché de ensamblado local).

Las restricciones de seguridad de la aplicación (donde puede tener acceso la aplicación en el sistema).

Como ASP.NET no tiene conocimiento, en tiempo de compilación, de las aplicaciones que se escriben por encima de él, no puede utilizar referencias estáticas para resolver y hacer referencia al código de las aplicaciones. En vez de ello, ASP.NET debe utilizar un enfoque de resolución dinámico de clases y ensamblados para realizar la transición del módulo de ejecución de ASP.NET al código de aplicación.

Los archivos de activación de página y configuración de ASP.NET permiten hacer referencia dinámicamente a una clase de .NET Framework compilada para un objetivo especificando una combinación de nombre de clase y ensamblado. El formato de cadena para esta unión sigue el modelo

classname, assemblyname. Llegados a este punto, Common Language Runtime puede utilizar esta referencia simple de cadena para resolver y cargar la clase apropiada.

Sustitución de código

Los ensamblados de .NET Framework normalmente se compilan y distribuyen en un formato PE basado en DLL de Windows. Cuando el cargador de Common Language Runtime resuelve una clase implementada con este tipo de ensamblado, llama a la rutina LoadLibrary de Windows en el archivo (la cual bloquea su acceso en disco) y, a continuación, asigna los datos de código en memoria apropiados para la ejecución. Una vez cargado, el archivo DLL permanece bloqueado en disco hasta que el dominio de aplicación que hace referencia a él se destruye o se recicla manualmente.

Aunque ASP.NET no puede impedir que Common Language Runtime bloquee una DLL de ensamblado en disco, sí puede garantizar que el módulo de ejecución no cargue nunca realmente las DLL físicas en la caché de ensamblado privada de una aplicación Web. En vez de ello, se realizan copias en servidores de copia de seguridad de las DLL de ensamblados inmediatamente antes de su uso. El módulo de ejecución bloquea y carga entonces estos ensamblados del servidor de copia de seguridad (no los archivos originales).

Como los archivos de ensamblado originales siempre permanecen desbloqueados, existe la libertad de eliminarlos, reemplazarlos o cambiar su nombre sin activar el servidor Web o tener que usar una utilidad de registro. FTP y los métodos similares funcionan sin problemas. ASP.NET mantiene una lista activa de todos los ensamblados cargados dentro del dominio de una aplicación particular y utiliza código de supervisión de cambio de archivos para detectar cualquier actualización en los archivos originales.

Resumen de la sección

1. Las aplicaciones ASP.NET Framework se identifican mediante una URL única y residen en el sistema de archivos del servidor Web.

2. ASP.NET puede utilizar ensamblados compartidos, los cuales residen en la caché global, y ensamblados específicos de la aplicación, que residen en el directorio \bin de la raíz virtual de la aplicación.

3. Las aplicaciones ASP.NET Framework se ejecutan en el contexto de dominios de aplicaciones (AppDomains), los cuales proporcionan aislamiento e imponen restricciones de seguridad.

4. Se puede hacer referencia a las clases dinámicamente especificando "nombre de clase, nombre de ensamblado". 5. ASP.NET utiliza copias de servidor de copia de seguridad de archivos de ensamblado para evitar bloqueos y supervisa los

archivos para que los cambios se detecten inmediatamente.

Page 204: Presentación del tutorial de ASP

Utilizar el modelo de procesos

Uno de los requisitos más importantes de las aplicaciones ASP.NET Framework es la fiabilidad. La arquitectura de las aplicaciones que se ejecutan dentro del proceso de servidor (en IIS, Inetinfo.exe) no ofrece una base sólida para crear aplicaciones fiables que puedan ejecutarse sin problemas durante períodos prolongados de tiempo. Se comparten demasiados recursos en el nivel de procesos y es muy fácil que un error interrumpa todo el proceso de servidor.

Para solucionar este problema, ASP.NET proporciona un modelo de ejecución fuera de proceso, que protege al proceso de servidor del código de usuario. También permite aplicar técnicas heurísticas a la duración del proceso para mejorar la disponibilidad de las aplicaciones Web. El uso de comunicaciones asincrónicas entre procesos permite proporcionar el mejor equilibrio entre rendimiento, escalabilidad y fiabilidad.

Configuración del modelo de procesos

La configuración del modelo de procesos se expone en el archivo de configuración raíz del equipo, Machine.config. La sección de configuración se denomina <processModel> y se muestra en el ejemplo siguiente. El modelo de procesos está habilitado de forma predeterminada (enable="true").

<processModel enable="true" timeout="infinite" idleTimeout="infinite" shutdownTimeout="0:00:05" requestLimit="infinite" requestQueueLimit="5000" memoryLimit="80" webGarden="false" cpuMask="0xffffffff" userName="" password="" logLevel="errors" clientConnectedCheck="0:00:05"/>

La mayoría de los valores de configuración controlan el inicio de un nuevo proceso de trabajo para atender solicitudes (reemplazando discretamente un proceso de trabajo antiguo). El modelo de procesos admite dos tipos de reciclaje: reactivo y proactivo.

Reciclaje reactivo de procesos

El reciclaje reactivo de procesos se produce cuando un proceso funciona incorrectamente o no puede atender solicitudes. El proceso suele mostrar síntomas que se pueden detectar, como bloqueos, infracciones de acceso, fugas de memoria, etc., para desencadenar su reciclaje. Es posible controlar las condiciones que desencadena el reinicio de un proceso mediante los valores de configuración descritos en la tabla siguiente.

Valor Descripción

requestQueueLimit Controla las condiciones de bloqueo. El valor DWORD se establece en el número máximo permitido de solicitudes en cola que, una vez superado, hace que se considere que el proceso de trabajo funciona incorrectamente. Cuando se supera este número, se inicia un proceso nuevo y se reasignan las solicitudes. El valor predeterminado es de 5.000 solicitudes.

memoryLimit Controla las condiciones de pérdida de memoria. El valor DWORD se establece en el porcentaje de memoria física que puede consumir el proceso de trabajo antes de que se considere que funciona incorrectamente. Cuando se supera este porcentaje, se inicia un proceso nuevo y se reasignan las solicitudes. El valor predeterminado es el 80%.

shutdownTimeout Especifica el intervalo de tiempo del que dispone el proceso de trabajo para cerrarse discretamente (valor de tipo cadena, con el formato hr:min:seg). Cuando se agote el tiempo de espera, la API de servicios de Internet de ASP.NET cerrará el proceso de trabajo. El valor predeterminado es "00:00:05".

Reciclaje proactivo de procesos

Page 205: Presentación del tutorial de ASP

El reciclaje proactivo de procesos reinicia periódicamente el proceso de trabajo, aunque éste funcione correctamente. Esto puede ser una forma útil de evitar la denegación de servicio a causa de condiciones que no puede detectar el modelo de procesos. Es posible reiniciar un proceso después de un número determinado de solicitudes o cuando haya transcurrido un tiempo de espera.

Valor Descripción

timeout Valor de tipo cadena, con el formato hr:min:seg, que configura el límite de tiempo que, una vez transcurrido, hace que se inicie un nuevo proceso de trabajo para reemplazar al actual. El valor predeterminado es infinite, una palabra clave que indica que no se debe reiniciar el proceso.

idleTimeout Valor de tipo cadena, con el formato hr:min:seg, que configura el tiempo de inactividad que, una vez transcurrido, hace que se cierre automáticamente el proceso de trabajo. El valor predeterminado es infinite, una palabra clave que indica que no se debe reiniciar el proceso.

requestLimit El valor DWORD se establece en el número de solicitudes que pueden producirse antes de que se inicie un nuevo proceso de trabajo para reemplazar al actual. El valor predeterminado es infinite, una palabra clave que indica que no se debe reiniciar el proceso.

Registrar eventos del modelo de procesos

El modelo de procesos puede escribir eventos en el registro de eventos de Windows cuando se realice el cambio de proceso. Esto se controla

mediante el atributo logLevel en la sección de configuración <processModel>.

Valor Descripción

logLevel Controla el registro de eventos de cambio de proceso en el registro de eventos. El valor puede ser: Todos: se registran todos los eventos de cambio de proceso. Ninguno: no se registra ningún evento.

Errores: sólo se registran los eventos inesperados.

Cuando se produce un evento de cambio, si el registro está habilitado para el evento, se escriben los siguientes eventos en el registro de eventos de la aplicación.

Razón del cierreTipo de registro de eventos

Descripción

Inesperado Error El proceso de trabajo de ASP.NET se cerró de forma inesperada .

requestQueueLimit Error Se reinició el proceso de trabajo de ASP.NET porque se superó el límite de la cola de solicitudes.

RequestLimit Información Se reinició el proceso de trabajo de ASP.NET porque se superó el límite de solicitudes.

Timeout Información Se reinició el proceso de trabajo de ASP.NET porque se superó el intervalo de tiempo de espera.

IdleTimeout Información Se cerró el proceso de trabajo de ASP.NET porque se superó el intervalo de tiempo de espera de inactividad.

MemoryLimitExceeded Error Se reinició el proceso de trabajo de ASP.NET porque se superó el límite de memoria del proceso.

Habilitar unidades Web

El modelo de procesos permite habilitar la escalabilidad en equipos con varios procesadores mediante la distribución del trabajo entre varios procesos, uno por cada CPU, con la afinidad de procesador establecida en la CPU correspondiente. Esto elimina la contención de bloqueos entre procesadores, por lo que es ideal para grandes sistemas de multiprocesamiento simétrico (SMP). Esta técnica se conoce como uso de unidades Web. Los valores de configuración necesarios para habilitar unidades Web se muestran en la tabla siguiente. Tenga en cuenta que esta configuración sólo surtirá efecto después de reiniciar el servidor. Es necesario reciclar IIS para que el cambio surta efecto.

Valor Descripción

webGarden Controla la afinidad CPU. True indica que es necesario asignar a los procesos la afinidad

Page 206: Presentación del tutorial de ASP

correspondiente a la CPU. El valor predeterminado es False.

cpuMask Controla el número de procesos y el funcionamiento de la unidad Web. Se inicia un proceso para cada CPU en la que el bit correspondiente de la máscara esté establecido en 1. Cuando se establece el valor de UseCPUAffinity en 0, el valor de cpuMask sólo controla el número de procesos de trabajo (número de bits establecidos en 1). El número máximo permitido de procesos de trabajo es el número de CPU. De forma predeterminada, todas las CPU están habilitadas y se iniciarán tantos procesos de trabajo como CPU haya en el equipo. El valor predeterminado es 0xffffffff.

El uso de unidades Web produce algunos efectos secundarios que hay que tener en cuenta:

Si la aplicación utiliza el estado de sesión, debe elegir un proveedor fuera de proceso (servicio de Windows NT o SQL). Para cada proceso se miden tanto el estado de la aplicación como las estadísticas de la aplicación y no para cada equipo. El almacenamiento en caché se realiza para cada proceso, no para cada equipo.

Resumen de la sección

1. ASP.NET proporciona un modelo de ejecución fuera de proceso, que protege al proceso de servidor del código de usuario. También permite aplicar técnicas heurísticas a la duración del proceso para mejorar la disponibilidad global de las aplicaciones Web.

2. La configuración de <processModel> se expone en el archivo raíz de configuración del equipo, Machine.config. El modelo de procesos está habilitado de forma predeterminada.

3. El modelo de procesos admite dos tipos de reciclaje: reactivo y proactivo. El reciclaje reactivo de procesos se produce cuando un proceso funciona incorrectamente o no puede atender solicitudes. El reciclaje proactivo de procesos reinicia periódicamente el proceso de trabajo, aunque éste funcione correctamente.

4. El modelo de procesos puede escribir eventos en el registro de eventos de Windows cuando se realice el cambio de proceso. Esto se controla mediante el atributo de registro en la sección de configuración <processModel>.

5. El modelo de procesos permite habilitar la escalabilidad en equipos con varios procesadores mediante la distribución del trabajo entre varios procesos, uno por cada CPU, con la afinidad de procesador establecida en la CPU correspondiente. Esta técnica se conoce como uso de unidades Web.

Controlar errores

Cuando se produce un error en una página, ASP.NET envía información sobre el error al cliente. Los errores se dividen en cuatro categorías:

Errores de configuración: se producen cuando la sintaxis o la estructura de un archivo Web.config de la jerarquía de configuración es incorrecta.

Errores del analizador: se producen cuando la sintaxis ASP.NET de una página no está bien formada. Errores de compilación: se producen cuando las instrucciones del lenguaje de destino de una página son incorrectas. Errores de tiempo de ejecución: se producen durante la ejecución de una página, incluso aunque los errores no se detectaran en

tiempo de compilación.

De forma predeterminada, la información mostrada para un error de tiempo de ejecución consiste en la pila de llamadas (las cadenas de llamadas a procedimientos que condujeron hasta la excepción). Si el modo de depuración se encuentra habilitado, ASP.NET muestra el número de línea del código fuente donde se originó el error en tiempo de ejecución. El modo de depuración es una herramienta muy valiosa para depurar la aplicación. Es posible habilitar el modo de depuración a nivel de página, mediante la siguiente directiva:

<%@ Page Debug="true" %>

También se puede habilitar el modo de depuración en el ámbito de la aplicación, mediante el archivo Web.config del directorio raíz de la aplicación, como se indica en el siguiente ejemplo.

Nota: activar el modo de depuración implica una penalización del rendimiento. Asegúrese de desactivarlo antes de implementar la aplicación final.

El siguiente ejemplo muestra el uso del modo de depuración para obtener los números de línea de una excepción en tiempo de ejecución.

<%@ Debug="true"%>

<html>

<script language="VB" runat="server">

Page 207: Presentación del tutorial de ASP

' Setting Debug to true causes lines numbers to be printed for runtime errors ' Enable this at the Page or Application level while developing ' Be sure to disable before deploying to a production site

Sub Error_500(sender As Object, e As EventArgs) Dim foo As String = Nothing Response.Write(foo.ToString()) End Sub

</script>

<body> <form runat="server"> <h4><font face="verdana">Produce un error...</font></h4> <asp:button text="500 Error del servidor" OnClick="Error_500" width="150" runat="server"/><p> </form> </body>

</html>

Nota: sólo los archivos asignados con la extensión aspnet_isapi.dll en IIS generan estos errores. ASP.NET no procesa los archivos no atendidos por medio de aspnet_isapi.dll, estos archivos generan errores de IIS. Vea la documentación de IIS para obtener información sobre cómo configurar errores personalizados de IIS.

La tabla siguiente describe los atributos de configuración y los valores para la etiqueta <customerrors>.

Atributo Descripción

Modo Indica si los errores personalizados están habilitados, deshabilitados o sólo se muestran a equipos remotos. Valores: On, Off, RemoteOnly (predeterminado).

DefaultRedirect Indica la URL predeterminada a la que se debe desviar a un explorador Web si se produce un error. Este atributo es opcional.

El atributo Mode determina si los errores se van a mostrar a clientes locales, remotos o a ambos. Los efectos de cada opción se describen en la siguiente tabla.

Modo Solicitud de host local Solicitud de host remoto

On Página de errores personalizada. Página de errores personalizada.

Off Página de errores de ASP.NET. Página de errores de ASP.NET.

RemoteOnly Página de errores de ASP.NET. Página de errores personalizada.

El siguiente ejemplo muestra cómo se utiliza la sección de configuración <customerrors>.

<html>

<script language="VB" runat="server">

Sub Error_500(sender As Object, e As EventArgs) Dim foo As String = Nothing Response.Write(foo.ToString()) End Sub

</script>

<body> <form runat="server"> <h4><font face="verdana">Produce un error...</font></h4> <asp:button text="500 Error del servidor" OnClick="Error_500" width="150" runat="server"/><p> </form> </body>

Page 208: Presentación del tutorial de ASP

</html>

Además de desviar a una página común cuando se produce cualquier error, también se pueden asignar páginas de error específicas a códigos de estado de error específicos. La sección de configuración <customerrors> admite una etiqueta <error> interna que asocia códigos de estado HTTP con páginas de error personalizadas. Por ejemplo:

<configuration> <system.web> <customErrors mode="RemoteOnly" defaultRedirect="/genericerror.htm"> <error statusCode="500" redirect="/error/callsupport.htm"/> <error statusCode="404" redirect="/error/notfound.aspx"/> <error statusCode="403" redirect="/error/noaccess.aspx"/> </customErrors> </system.web> </configuration>

La tabla siguiente describe los atributos y valores de la etiqueta <error>.

Atributo Descripción

StatusCode Código de estado HTTP de los errores para los cuales se debería utilizar la página de errores personalizada. Ejemplos: 403 Prohibido, 404 No encontrado o 500 Error interno del servidor.

Redirect URL a la que se debe desviar a un explorador cliente si se produce un error.

El ejemplo siguiente muestra cómo utilizar la etiqueta <error>. Observe que el ejemplo especifica una página .aspx para errores de "Archivo no encontrado" con el fin de que la URL de la página que falta y que se pasa por medio de QueryString se pueda imprimir.

<html>

<script language="VB" runat="server">

Sub Error_404(sender As Object, e As EventArgs) Response.Redirect("nowhere.aspx") End Sub

Sub Error_500(sender As Object, e As EventArgs) Dim foo As String = Nothing Response.Write(foo.ToString()) End Sub

</script>

<body> <form runat="server"> <h4><font face="verdana">Produce un error...</font></h4> <asp:button text="404 No encontrado" OnClick="Error_404" width="150" runat="server"/><p> <asp:button text="500 Error del servidor" OnClick="Error_500" width="150" runat="server"/><p> </form> </body>

</html>

Controlar errores mediante programación

Los errores también se pueden tratar en el código, ya sea en el ámbito de la página o de la aplicación. La clase base Page expone

un método Page_Error que se puede reemplazar en las páginas. Se invoca al método siempre que una excepción no capturada se lanza en tiempo de ejecución.

<script language="C#" runat="server">

Sub Page_Error(Source As Object, E As EventArgs)

Page 209: Presentación del tutorial de ASP

Dim message As String = "<font face=verdana color=red>" _ & "<h4>" & Request.Url.ToString() & "</h4>" _ & "<pre><font color='red'>" & Server.GetLastError().ToString() & "</pre>" _ & "</font>"

Response.Write(message)End Sub

</script>

El siguiente ejemplo muestra el método Page_Error.

<html>

<script language="VB" runat="server">

Sub Error_500(sender As Object, e As EventArgs) Dim foo As String = Nothing Response.Write(foo.ToString()) End Sub

Sub Page_Error(sender As Object, e As EventArgs) Dim message As String = "<font face=verdana color=red>" _ & "<h4>" & Request.Url.ToString() & "</h4>" _ & "<pre><font color='red'>" & Server.GetLastError().ToString() & "</pre>" _ & "</font>"

Response.Write(message) Server.ClearError() End Sub

</script>

<body> <form runat="server"> <h4><font face="verdana">Produce un error...</font></h4> <asp:button text="500 Error del servidor" OnClick="Error_500" width="150" runat="server"/><p> </form> </body>

</html>

En el método se puede hacer algo útil, como enviar un mensaje al administrador del sitio avisándole de que la página no se ejecutó correctamente. ASP.NET proporciona un conjunto de clases en el espacio de nombres System.Web.Mail precisamente para este propósito. Para importar este espacio de nombres, utilice una directiva @Import en la parte superior de la página, como se indica a continuación:

<%@ Import Namespace="System.Web.Mail" %>

Se pueden utilizar entonces los objetos MailMessage y SmtpMail para enviar mensajes de correo electrónico mediante programa.

Dim mail As New MailMessagemail.From = "[email protected]"mail.To = "[email protected]"mail.Subject = "Site Error"mail.Body = messagemail.BodyFormat = MailFormat.HtmlSmtpMail.Send(mail)

El siguiente ejemplo muestra cómo enviar un mensaje de correo en respuesta a un error de página.

Page 210: Presentación del tutorial de ASP

Nota: este ejemplo no envía correo realmente a menos que haya configurado el servicio de correo SMTP en la máquina. Para obtener más información acerca del servicio de correo SMTP, consulte la documentación IIS.

<%@ Import Namespace="System.Web.Mail" %>

<html>

<script language="VB" runat="server">

Sub Error_500(sender As Object, e As EventArgs) Dim foo As String = Nothing Response.Write(foo.ToString()) End Sub

Sub Page_Error(Sender As Object, E As EventArgs) Dim message As String = "<font face=verdana color=red>" _ & "<h4>" & Request.Url.ToString() & "</h4>" _ & "<pre><font color=red>" & Server.GetLastError().ToString() & "</pre>" _ & "</font>"

Response.Write(message) Response.Write("Error en el servidor, se notificó al administrador del sitio.")

Dim mail As New MailMessage mail.From = "[email protected]" mail.To = "[email protected]" mail.Subject = "Error del sitio" mail.Body = message mail.BodyFormat = MailFormat.Html SmtpMail.Send(mail)

Server.ClearError() End Sub

</script>

<body> <form runat="server"> <h4><font face="verdana">Produce un error...</font></h4> <asp:button text="500 Error del servidor" OnClick="Error_500" width="150" runat="server"/><p> </form> </body>

</html>

Además de tratar los errores en el ámbito de la página, se pueden tratar también en el ámbito de la aplicación. Para ello, utilice

el evento Application_Error de Global.asax. Este evento tiene lugar para cualquier excepción sin tratamiento que se produce dentro de la aplicación.

Sub Application_Error(sender As Object, e As EventArgs) '...Do something hereEnd Sub

Escribir en el Registro de eventos

El espacio de nombres System.Diagnostics proporciona clases para escribir en el Registro de eventos de Windows. Para utilizar este espacio de nombres en las páginas, primero hay que importar el espacio de nombres como se indica a continuación:

<%@ Import Namespace="System.Diagnostics"%>

Page 211: Presentación del tutorial de ASP

La clase EventLog encapsula el propio Registro. Proporciona métodos estáticos para detectar o crear registros y puede crearse una instancia para escribir entradas de registro desde el código. El siguiente ejemplo muestra esta funcionalidad dentro del método

Application_Error de Global.asax. Siempre que se produce una excepción sin tratamiento en la aplicación, se crea una entrada con el mensaje de error y el estado de la pila en el registro de la aplicación.

Sub Application_Error(sender As Object, e As EventArgs)

Dim Message As String = "\n\nURL:\n http://localhost/" & Request.Path _ & "\n\nMESSAGE:\n " & Server.GetLastError().Message _ & "\n\nSTACK TRACE:\n" & Server.GetLastError().StackTrace

' Create event log if it does not exist

Dim LogName As String = "Application" If (Not EventLog.SourceExists(LogName)) EventLog.CreateEventSource(LogName, LogName) End If

' Insert into event log Dim Log As New EventLog Log.Source = LogName Log.WriteEntry(Message, EventLogEntryType.Error)

End Sub

El código fuente completo para el ejemplo anterior aparece a continuación. Observe que este código está deshabilitado para que no se pueda ejecutar y evitar así entradas en el Registro de eventos de Windows. Si quiere ver cómo se ejecuta este código, cree una raíz virtual IIS que apunte al directorio que contiene este archivo.

<%@ Import Namespace="System.Diagnostics" %>

<script language="VB" runat="server">

Sub Application_Error(sender As Object, e As EventArgs)

Dim Message As String = "\n\nURL:\n http://localhost/" & Request.Path _ & "\n\nMESSAGE:\n " & Server.GetLastError().Message _ & "\n\nSTACK TRACE:\n" & Server.GetLastError().StackTrace

' Create Event Log if it does not exist

Dim LogName As String = "Application" If (Not EventLog.SourceExists(LogName)) EventLog.CreateEventSource(LogName, LogName) End If

' Insert into Event Log Dim Log As New EventLog Log.Source = LogName Log.WriteEntry(Message, EventLogEntryType.Error)

End Sub

</script>

Resumen de la sección1. Los errores se dividen en cuatro categorías: errores de configuración, de análisis, de compilación y de ejecución. 2. De forma predeterminada, la información mostrada para un error de tiempo de ejecución consiste en la pila de

llamadas (la cadena de llamadas a procedimientos que condujo hasta la excepción). Si el modo de depuración se encuentra habilitado, ASP.NET muestra el número de línea del código fuente donde se originó el error en tiempo de ejecución.

3. ASP.NET permite especificar si los errores se van a mostrar a clientes locales, remotos o a ambos. De forma predeterminada, los errores sólo se muestran a clientes locales (los que se encuentran en el mismo equipo que el

Page 212: Presentación del tutorial de ASP

servidor). También se puede especificar una página de errores personalizada a la que enviar a los clientes si se produce un error.

4. Además de desviar a una página común cuando se produce cualquier error, también se pueden asignar páginas de error específicas a códigos de estado de error específicos. La sección de configuración <customerrors> admite una etiqueta <error> interna que asocia códigos de estado HTTP con páginas de errores personalizadas.

5. Los errores también se pueden controlar en el código, ya sea en el ámbito de la página o de la aplicación. La clase base Page expone un método HandleError que se puede reemplazar en las páginas. Se invocará al método siempre que una excepción no capturada se lance en tiempo de ejecución.

6. El espacio de nombres System.Web.Mail expone clases para enviar correo electrónico mediante programa. Esto resulta útil para avisar a un administrador cuando se produce un error.

7. Además de controlar errores en el ámbito de la página, se puede utilizar el evento Application_Error de Global.asax para controlarlos en el ámbito de la aplicación. Este evento tiene lugar para cualquier excepción sin tratamiento que se produce dentro de la aplicación.

8. El espacio de nombres System.Diagnostics proporciona clases para escribir en el Registro de eventos de Windows.

Personalizar páginas de error

Según las circunstancias, los errores de una aplicación se pueden tratar de distintos modos. Por ejemplo, durante el desarrollo probablemente desee ver las páginas de error detalladas suministradas por ASP.NET para ayudar a identificar y resolver problemas. Sin embargo, una vez que la aplicación se está utilizando en un entorno de producción, no es aconsejable mostrar errores detallados a los usuarios. Puede utilizar ASP.NET para especificar si los errores se van a mostrar a clientes locales, remotos o a ambos. De forma predeterminada, los errores sólo se muestran a clientes locales (los que se encuentran en el mismo equipo que el servidor). También se puede especificar una página de errores personalizada a la que enviar a los clientes si se produce un error.

Los errores personalizados se habilitan en el archivo Web.config para una aplicación. Por ejemplo:

<configuration> <system.web> <customErrors defaultRedirect="genericerror.htm" mode="remoteonly" /> </system.web></configuration>

Esta configuración permite a los clientes locales ver las páginas de error predeterminadas de ASP.NET detalladas pero desvía a los clientes remotos hacia una página personalizada, genericerror.htm. ASP.NET pasa la ruta de acceso de la página en la que se

produjo el error a la página de errores como un argumento QueryString. Observe que, si la ejecución de la página de errores genera un error, se envía una página en blanco al cliente remoto.

<%@ Page Language="VB" Description="Error page"%>

<html><head><title>Error page</title></head>

<body><h1>Error page</h1>Error originated on: <%=Request.QueryString("ErrorPage") %></body></html>

Seguridad

Información general sobre seguridad

Una parte importante de muchas aplicaciones Web radica en la capacidad de identificar usuarios y controlar el acceso a los recursos. Se conoce como autenticación al acto de determinar la identidad de la entidad solicitante. Por lo general, el usuario deberá presentar sus

Page 213: Presentación del tutorial de ASP

credenciales, como el par nombre de usuario y contraseña, para ser autenticado. Cuando se encuentre disponible una identidad autenticada, deberá determinarse si esa identidad puede tener acceso a un recurso específico. Este proceso se conoce como autorización. ASP.NET e IIS colaboran para proporcionar servicios de autenticación y autorización a las aplicaciones.

Una característica importante de los objetos COM es la capacidad de controlar la identidad con la que se ejecuta el código de objeto COM. Se conoce como representación al hecho de que un objeto COM ejecute código con la identidad de la entidad solicitante. Opcionalmente, las aplicaciones ASP.NET Framework pueden representar solicitudes.

Es posible que algunas aplicaciones también personalicen dinámicamente el contenido en función de la identidad del solicitante o de un conjunto de funciones al que pertenece la identidad del solicitante. Las aplicaciones ASP.NET Framework pueden comprobar dinámicamente si la entidad solicitante actual participa en una función concreta. Por ejemplo, es posible que una aplicación intente comprobar si el usuario actual pertenece a la función del administrador a fin de generar condicionalmente contenido para los administradores

Autenticación y autorización

ASP.NET funciona, junto con IIS, para permitir la autenticación de tipo básica, implícita y Windows. ASP.NET es compatible con el servicio de autenticación de pasaporte de Microsoft, el cual proporciona servicios simples de firma y servicios de perfiles de usuario. ASP.NET también proporciona un robusto servicio para aplicaciones que necesitan utilizar autenticación basada en formularios. La autenticación basada en formularios utiliza "cookies" para autenticar a los usuarios, y permite a la aplicación realizar su propia verificación de credenciales.

Es importante darse cuenta de que los servicios de autenticación de ASP.NET dependen de los servicios de autenticación suministrados por IIS. Por ejemplo, para poder utilizar una autenticación básica en una aplicación IIS, se debe configurar el uso de la autenticación básica para la aplicación mediante el Administrador de servicios Internet.

ASP.NET proporciona dos tipos de servicios de autorización:

Comprobaciones de ACLs o permisos sobre un recurso para determinar si la cuenta de usuario autenticada puede obtener acceso a los recursos

Autorización de URL, la cual autoriza una identidad para partes del espacio Web

Para ilustrar la diferencia, considere un escenario en el que una aplicación está configurada para permitir acceso anónimo mediante la cuenta IUSR_MYMACHINE. Cuando una solicitud de una página ASP.NET (como "/default.aspx") recibe autorización, se realiza una comprobación de los ACL de ese archivo (por ejemplo, "c:\\inetpub\\wwwroot\\default.aspx") para ver si la cuenta IUSR_MYMACHINE tiene permiso para leer el archivo. Si lo tiene, entonces se autoriza el acceso. La autorización de archivo se realiza automáticamente.

Para autorización de URL, se comprueba el usuario anónimo con los datos de configuración obtenidos para la aplicación ASP.NET. Si se permite el acceso a la dirección URL solicitada, la solicitud se autoriza. En este caso, ASP.NET comprueba si el usuario anónimo dispone de acceso a /Default.aspx (es decir, la comprobación se realiza sobre la propia URL, no sobre el archivo en el que se resuelve la URL en última instancia).

Ésta podría parecer una diferencia sutil, pero permite a las aplicaciones utilizar esquemas de autenticación como los basados en formularios o la autenticación de Pasaporte, en los cuales los usuarios no se corresponden con un equipo o una cuenta de dominio. También permite la autorización de recursos virtuales, para los cuales no existe un archivo físico subyacente al recurso. Por ejemplo, una aplicación podría optar por asignar todas las solicitudes de archivos que terminan en .stk a un controlador que proporciona cotizaciones de valores según ciertas variables presentes en la cadena de consulta. En ese caso, no existe un archivo .stk con el que hacer las comprobaciones ACL; por lo tanto, se utiliza autorización de URL para controlar el acceso al recurso virtual.

La autorización de archivo se realiza siempre con la cuenta autenticada suministrada por IIS. Si se permite el acceso anónimo, esta cuenta es la cuenta anónima configurada. En cualquier otro caso, se utiliza una cuenta NT. Esto funciona exactamente de la misma forma que ASP.

Las listas ACL de archivos se configuran para un determinado archivo o directorio mediante la ficha Seguridad de la página de propiedades del Explorador. La autorización de URL se configura como parte de una aplicación ASP.NET Framework y se describe en detalle en Autorizar usuarios y funciones.

Para activar un servicio de autenticación de ASP.NET, se debe configurar el elemento <authentication> en el archivo de configuración de la aplicación. Este elemento puede tener uno de los valores de la tabla siguiente.

Valor Descripción

Ninguno No hay ningún servicio de autenticación de ASP.NET activo. Observe que los servicios de autenticación de IIS pueden estar aún presentes.

Windows Los servicios de autenticación de ASP.NET asocian un WindowsPrincipal (System.Security.Principal.WindowsPrincipal) a la solicitud actual para permitir la autorización de usuarios o grupos de NT.

Formularios Los servicios de autenticación de ASP.NET administran las "cookies" y desvían a los usuarios no autenticados a una página de inicio de sesión. Este método se suele utilizar en conjunción con la opción IIS que permite el acceso anónimo a una aplicación.

Passport Los servicios de autenticación de ASP.NET proporcionan un envoltorio apropiado para los servicios

Page 214: Presentación del tutorial de ASP

suministrados por el kit de desarrollo de software para pasaporte (Passport SDK), el cual debe instalarse en el equipo.

Por ejemplo, el siguiente archivo de configuración permite la autenticación basada en formularios (cookie) para una aplicación:

<configuration> <system.web> <authentication mode="Forms"/> </system.web></configuration>

Autenticación basada en Windows

Al utilizar la autenticación de Windows de ASP.NET, ASP.NET asocia un objeto WindowsPrincipal a la solicitud actual. La autorización de dirección URL utiliza este objeto. La aplicación también puede utilizarlo mediante programación para determinar si una identidad solicitante se encuentra en una función dada. If User.IsInRole("Administrators") Then DisplayPrivilegedContent()End If

La clase WindowsPrincipal determina las funciones por la pertenencia al grupo de NT. Las aplicaciones que desean determinar sus propias funciones pueden hacerlo mediante el control del evento WindowsAuthentication_OnAuthenticate en su archivo Global.asax y la asociación

de su propia clase que implementa System.Security.Principal.IPrincipal a la solicitud, como se muestra en el ejemplo siguiente:

' Create a class that implements IPrincipalPublic Class MyPrincipal : Inherits IPrincipal ' Implement application-defined role mappingsEnd Class

' In a Global.asax filePublic Sub WindowsAuthentication_OnAuthenticate(Source As Object, e As WindowsAuthenticationEventArgs) ' Attach a new application-defined class that implements IPrincipal to ' the request. ' Note that since IIS has already performed authentication, the provided ' identity is used. e.User = New MyPrincipal(e.Identity)End Sub

En el ejemplo siguiente se muestra cómo obtener acceso al nombre de un usuario autenticado, que está disponible como User.Identity.Name. Los programadores familiarizados con ASP deben observar que este valor también está aún disponible, al igual que ocurre con la variable de servidor AUTH_USER:

html>

<script language="VB" runat=server>

Sub Page_Load(Src As Object, E As EventArgs) AuthUser.Text = User.Identity.Name AuthType.Text = User.Identity.AuthenticationType End Sub

</script>

<body>

<h3><font face="Verdana">Utilizar autenticación de Windows</font></h3>

Page 215: Presentación del tutorial de ASP

<table Width="700" rules="all" bordercolor="Black" style="background-color:#ccccff;bordercolor:black;font-family:Verdana;font-size:8pt;border-collapse:collapse;"> <tr> <td>Usuario:</td> <td><asp:label id=AuthUser runat=server/> </tr> <tr> <td>Tipo de autenticación:</td> <td><asp:label id=AuthType runat=server/> </tr> </table>

</body>

</html>

Autenticación basada en formularios

La autenticación basada en formularios es un servicio de autenticación de ASP.NET que permite a las aplicaciones suministrar su propia interfaz de inicio de sesión y hacer su propia verificación de credenciales. ASP.NET permite autenticar usuarios y desviar a los usuarios no autenticados hacia la página de inicio de sesión, además de realizar todas las tareas de administración de "cookies". Este tipo de autenticación es una técnica habitual utilizada en muchos sitios Web.

Para que una aplicación pueda utilizar autenticación basada en formularios, se debe configurar <authentication> con la opción Forms y denegar el acceso a los usuarios anónimos:

Los administradores usan autenticación basada en formularios para configurar el nombre de la "cookie" a usar, el tipo de protección, la dirección URL para la página de inicio de sesión, el tiempo de validez de la "cookie" y la ruta de acceso que se debe utilizar para la "cookie" suministrada. La siguiente tabla muestra los atributos válidos para el elemento <Forms>, el cual es un subelemento del elemento <authentication> mostrado en la siguiente tabla:

Atributo Descripción

loginUrl URL de inicio de sesión a la que se desvían los usuarios no autenticados. Puede estar en el mismo equipo o en uno remoto. Si es un equipo remoto, ambos equipos deben utilizar el mismo valor para el atributo decryptionkey.

name Nombre de la "cookie" HTTP que se va a utilizar a efectos de autenticación. Observe que si varias aplicaciones desean utilizar servicios de autenticación basados en formularios en un único equipo, cada uno debería configurar un valor de "cookie" única. Para evitar originar dependencias en direcciones URL, ASP.NET utiliza "/" como valor de la ruta de acceso al configurar "cookies" de autenticación, de modo que éstas se vuelvan a enviar a todas las aplicaciones del sitio.

timeout Tiempo, en minutos enteros, tras el cual la "cookie" caduca. El valor predeterminado es 30. El atributo timeout indica la caducidad con un valor continuo de n minutos contados desde el momento en que se recibió la última solicitud. Para evitar efectos negativos relacionados con el rendimiento y advertencias de los exploradores Web que tienen activadas las advertencias de "cookies", la "cookie" se actualiza si ha transcurrido más de la mitad del tiempo. (Esto implica una pérdida de precisión en algunos casos).

path Ruta de acceso que se utiliza para la "cookie" suministrada. Para evitar problemas con la escritura de las rutas, se utiliza "/" como valor predeterminado, ya que los exploradores Web distinguen estrictamente entre mayúsculas y minúsculas cuando devuelven "cookies". Las aplicaciones en un entorno de un servidor compartido deberían utilizar esta directiva para mantener "cookies" privadas. (Otra posibilidad consiste en que especifiquen la ruta de acceso en tiempo de ejecución y se utilicen las API para emitir "cookies").

protection Método utilizado para proteger los datos de las "cookies". Los valores posibles son los siguientes: All: utiliza validación y cifrado de datos para proteger la "cookie". El algoritmo de validación

de datos configurado se basa en el elemento . Triple DES se utiliza para cifrado, si está disponible y si la clave es suficientemente larga (48 bytes). All es el valor predeterminado (y sugerido).

None: se utiliza con sitios que utilizan "cookies" sólo para personalización y que tienen requisitos de seguridad menores. Tanto el cifrado como la validación, se pueden deshabilitar. Aunque se debería tener precaución al utilizar "cookies" de este modo, esta opción proporciona el mejor rendimiento entre los métodos de personalización mediante .NET Framework.

Encryption: permite cifrar la "cookie" mediante TripleDES o DES, pero no se realiza validación de datos. Este tipo de "cookie" puede sufrir ataques en lo que se refiere a la selección de texto sin formato.

Validation: el contenido de la "cookie" no está cifrado, pero se valida para comprobar que no ha sido modificado durante la transmisión. Para crear la "cookie", la clave de validación se concatena en un búfer con los datos de la "cookie" y, a continuación, se genera un MAC

Page 216: Presentación del tutorial de ASP

que se agrega a la "cookie" saliente.

Tras haber configurado la aplicación, se debe proporcionar una página de inicio de sesión. El siguiente ejemplo muestra una página de inicio de sesión. Cuando se ejecuta el ejemplo, éste solicita la página Default.aspx. Las solicitudes no autenticadas se desvían a la página de inicio de sesión (Login.aspx), la cual presenta un formulario sencillo que pide una dirección de correo electrónico y una contraseña. (Utilice "[email protected]" y "password" como credenciales.)

Después de validar las credenciales, la aplicación realiza las siguientes llamadas:

FormsAuthentication.RedirectFromLoginPage(UserEmail.Value, PersistCookie.Checked)

De esta forma, se vuelve a dirigir al usuario hacia la URL original solicitada. Las aplicaciones que no desean realizar la redirección pueden llamar a FormsAuthentication.GetAuthCookie, para recuperar el valor de la "cookie", o bien a FormsAuthentication.SetAuthCookie, para asociar una "cookie" apropiadamente cifrada a la respuesta saliente. Estas técnicas pueden resultar útiles para aplicaciones que proporcionan una interfaz de usuario de inicio de sesión incrustada en la página contenedora o que desean tener más control sobre el destino al que se desvía a los usuarios. Las "cookies" de autenticación pueden ser temporales o permanentes ("persistentes"). Las "cookies" temporales tienen la misma duración que la sesión actual del explorador. Cuando se cierra el explorador, la "cookie" se pierde. El explorador se encarga de guardar las "cookies" permanentes, las cuales se vuelven a enviar en cada sesión de un explorador a menos que el usuario las elimine explícitamente.

<%@ Import Namespace="System.Web.Security " %>

<html>

<script language="VB" runat=server> Sub Page_Load(Src As Object, E As EventArgs) Welcome.Text = "Hola, " + User.Identity.Name End Sub

Sub Signout_Click(Src As Object, E As EventArgs) FormsAuthentication.SignOut() Response.Redirect("login.aspx") End Sub </script>

<body>

<h3><font face="Verdana">Utilizar autenticación con cookies</font></h3>

<form runat=server>

<h3><asp:label id="Welcome" runat=server/></h3>

<asp:button text="Cerrar sesión" OnClick="Signout_Click" runat=server/>

</form>

</body>

</html>

La "cookie" utilizada por la autenticación basada en formularios consiste en una versión lineal de la clase System.Web.Security.FormsAuthenticationTicket . La información incluye el nombre de usuario (pero no la contraseña), la versión de autenticación basada en formularios utilizada, la fecha en la que se emitió la "cookie" y un campo para datos opcionales específicos de la aplicación.

El código de la aplicación puede revocar o quitar las "cookies" de autenticación mediante el método FormsAuthentication.SignOut. Este método permite quitar la "cookie" de autenticación independientemente de si es temporal o permanente.

Asimismo, se pueden suministrar servicios de autenticación basados en formularios con una lista de credenciales válidas que utilizan la configuración, tal como se muestra en el siguiente ejemplo:

Page 217: Presentación del tutorial de ASP

La aplicación puede entonces realizar una llamada a FormsAuthentication.Authenticate, con el nombre de usuario y la contraseña, y ASP.NET se encargará de verificar las credenciales. Las credenciales se pueden almacenar en texto no cifrado o como código "hash" de tipo

SHA1 o MD5, según los siguientes valores del atributo passwordFormat:

Tipo Hash Descripción

Clear Las contraseñas se almacenan en texto no cifrado

SHA1 Las contraseñas se almacenan en compendios SHA1

MD5 Las contraseñas se almacenan en compendios MD5

Autorizar usuarios y funciones

ASP.NET se utiliza para controlar el acceso de los clientes a los recursos URL. Se puede configurar para el método HTTP que se utilice para realizar la solicitud (GET o POST) y, también, para permitir o denegar el acceso a grupos de usuarios o funciones. El siguiente ejemplo muestra la concesión de acceso a un usuario llamado John y a una función denominada Admins, denegándosela a los demás usuarios.

Los elementos que se permiten en las directivas de autorización pueden ser allow o deny. Cada elemento allow o deny debe contener un atributo users o un atributo roles. Se pueden especificar varios usuarios o funciones en un único elemento mediante una lista de valores separados por comas.

El método HTTP se puede indicar mediante el atributo Verb:

Este ejemplo permite a Mary y John utilizar el método POST en los recursos protegidos, mientras que sólo permite utilizar el método GET al resto de los usuarios.

Existen dos nombres de usuario especiales:

*: Todos los usuarios ?: Usuarios anónimos (no autenticados)

Estos nombres de usuario especiales se utilizan comúnmente en aplicaciones que usan autenticación basada en formularios para denegar el acceso a los usuarios no autenticados, tal como se muestra en el siguiente ejemplo:

La autorización de direcciones URL se evalúa jerárquicamente y las reglas que se utilizan para determinar el acceso son las siguientes:

Las reglas relevantes para la URL se obtienen de la jerarquía y con ellas se construye una lista combinada de reglas. Las reglas más recientes se colocan al principio de la lista. Esto significa que la configuración situada en el directorio actual se

encuentra al principio de la lista, seguida de la configuración del nivel superior inmediato, y así sucesivamente, hasta el archivo de nivel superior para el equipo.

Las reglas se comprueban hasta encontrar una que se cumpla. Si la regla es admisible, el acceso se concede. En caso contrario, el acceso se rechaza.

Esto significa que las aplicaciones no interesadas en heredar su configuración deberían configurar explícitamente todas las posibilidades relevantes.

El archivo Web.config predeterminado de nivel superior para un determinado equipo permite el acceso a todos los usuarios. A menos que una aplicación se configure de forma contraria (y suponiendo que un usuario reciba autenticación y pase la comprobación ACL para autorización de archivos), el acceso se concede.

Cuando se comprueban las funciones, la autorización de URL recorre en sentido descendente la lista de funciones configuradas y hace algo similar al siguiente pseudocódigo:

If User.IsInRole("ConfiguredRole") Then ApplyRule()End If

Page 218: Presentación del tutorial de ASP

Lo que esto significa para la aplicación es que se puede utilizar una clase personalizada que implemente System.Security.Principal.IPrincipal para suministrar su propia semántica de asignación de funciones, como se explicó en Autenticación basada en Windows.

El siguiente ejemplo utiliza servicios de autenticación basados en formularios. Deniega explícitamente el acceso a [email protected] y a los usuarios anónimos. Intente realizar un inicio de sesión en el ejemplo con Nombre de usuario="[email protected]" y Contraseña="password". El acceso se denegará y volverá a aparecer la página de inicio de sesión. Ahora, intente iniciar una sesión con Nombre de usuario="[email protected]" y Contraseña="password". Observará que esta vez el acceso sí se concede.

<%@ Import Namespace="System.Web.Security " %>

<html>

<script language="VB" runat=server>

Sub Page_Load(Src As Object, E As EventArgs) Welcome.Text = "Hola, " + User.Identity.Name End Sub

Sub Signout_Click(Src As Object, E As EventArgs) FormsAuthentication.SignOut() Response.Redirect("login.aspx") End Sub

</script>

<body>

<h3><font face="Verdana">Utilizar autenticación con cookies</font></h3>

<form runat=server>

<h3><asp:label id="Welcome" runat=server/></h3>

<asp:button text="Cerrar sesión" OnClick="Signout_Click" runat=server/>

</form>

</body>

</html>

Suplantación de cuentas de usuario

Como ya se mencionó en Información general sobre seguridad, la suplantación de identidad hace referencia a un proceso en el que un objeto COM se ejecuta con la identidad de la entidad en representación de la cual está realizando su trabajo. Para una aplicación Web esto implica que, si un servidor está realizando una suplantación de identidad, está trabajando con la identidad del cliente que hace la solicitud.

De forma predeterminada, ASP.NET no realiza suplantaciones por cada solicitud. En esto difiere de ASP, que sí realiza suplantaciones en cada solicitud. Si se desea, se puede configurar una aplicación de modo que realice suplantaciones de identidad en cada solicitud mediante la siguiente directiva Configuration:

Puesto que ASP.NET realiza una compilación dinámica, para habilitar la suplantación se requiere que todas las cuentas dispongan de acceso de lectura y escritura en el directorio Codegen de la aplicación (donde el módulo de ejecución de ASP.NET almacena los objetos compilados dinámicamente) así como en la caché de ensamblado global (%Windir%\assembly). Algunas aplicaciones exigen habilitar la suplantación para conseguir compatibilidad con ASP o utilizar servicios de autenticación de Windows.

Seguridad y servicios Web

En esta sección se describen métodos para establecer la seguridad de los servicios Web de XML. Si aún no ha leído la sección Seguridad de este tutorial, hágalo ahora antes de continuar con este tema.

Page 219: Presentación del tutorial de ASP

Autenticación y autorización de Windows

Se utiliza la misma técnica para establecer la seguridad de los servicios Web de XML mediante autenticación de Windows que para establecer la seguridad de las páginas .aspx (descrita en la sección Autenticación basada en Windows). Para solicitar autenticación, hay que habilitar Autenticación integrada de Windows para la aplicación y deshabilitar Acceso anónimo en la consola de administración de IIS. Para permitir o denegar a usuarios específicos el acceso al servicio, utilice el sistema de configuración de ASP.NET o establezca las ACL en el archivo del servicio, como se indica en el siguiente ejemplo:

<configuration>

<system.web> <authentication mode="Windows"/> </system.web>

<location path="secureservice.asmx">

<system.web> <authorization> <allow users="Administrator"/> <allow users="DOMAIN\Bradley"/> <deny roles="BUILTIN\Power Users"/> </authorization> </system.web>

</location>

</configuration>

Esto funciona bien cuando se sabe que el cliente del servicio Web de XML se ejecutará como un usuario de Windows específico. Un caso más interesante es el de un cliente que se ejecuta como un usuario que actúa en nombre de otro. Considere una página ASP.NET que tiene acceso a un servicio Web de XML seguro que no suplanta a los clientes que tienen acceso al mismo. En este caso, es necesario establecer mediante programación el nombre de usuario y la contraseña antes de conectarse al servicio Web. En el siguiente ejemplo se utiliza la autenticación

básica y se ilustra un Servicio Web sencillo:

<%@ WebService language="VB" Class="SecureService" %>

Imports System.Web.ServicesImports System

Class SecureService : Inherits WebService

<WebMethod()> Public Function SecureTest As String Return "Hello from the secure web service" EndEnd Class

Puede que desee solicitar autenticación básica para este servicio; para ello, debe establecer una configuración apropiada en IIS de la manera siguiente:

1. Abra la consola MMC de IIS. 2. Start->Run "inetmgr"

3. En el panel de la izquierda, expanda el árbol para buscar el directorio virtual. 4. En el panel de la derecha, haga clic con el botón secundario del mouse (ratón) en Secureservice.asmx y elija Propiedades. 5. Seleccione la ficha Seguridad del archivo. Bajo Control de acceso anónimo y autenticación, haga clic en Editar.

o Deshabilite el acceso anónimo.

o Deshabilite la autenticación integrada de Windows.

o Habilite la autenticación básica. 6. Haga clic en Aceptar para guardar esta configuración y salir de la consola MMC.

La clase base de proxy WebService proporciona dos propiedades, Username y Password, que se pueden utilizar para especificar las credenciales con las que conectarse al servicio Web remoto. Deben estar establecidas las credenciales válidas de Windows en el equipo o dominio del servicio Web.

Page 220: Presentación del tutorial de ASP

<%@ Import Namespace="SecureService" %>

<html><script language="VB" runat="server">

Public Sub Page_Load(sender As Object, e As EventArgs)

Dim s As New SecureService

s.Credentials = New System.Net.NetworkCredential("Administrator", "test123")

Message.Text = s.SecureTest() End Sub

</script>

<body> <h4><font face="verdana"> <asp:Label id="Message" runat="server"/> </font></h4></body>

</html>

La clase base WebService también proporciona una propiedad User de tipo System.Security.Principal.IPrincipal, que puede utilizarse para recuperar información acerca del usuario cliente. En este caso, también puede autorizar el acceso al servicio Web en la sección Autorización del sistema de configuración de ASP.NET.

Autenticación y autorización personalizadas con encabezados SOAP

La autenticación de Windows funciona correctamente para escenarios de la intranet, en los que se autentica con un usuario del propio dominio. Sin embargo, es probable que en Internet desee llevar a cabo una autenticación y una autorización personalizadas, tal vez con una base de datos SQL. En ese caso, debe pasar credenciales personalizadas (como el nombre de usuario y la contraseña ) al servicio y dejarle controlar la autenticación y la autorización.

Una forma aconsejable de pasar información adicional junto con una solicitud al servicio Web de XML es un encabezado SOAP. Para hacerlo,

hay que definir en el servicio una clase derivada de SOAPHeader y después declarar un campo público del servicio como ese tipo. Esto se expone en el contrato público del servicio y está a la disposición del cliente cuando se crea el proxy mediante WebServiceUtil.exe, como en el siguiente ejemplo:

Imports System.Web.ServicesImports System.Web.Services.Protocols

' AuthHeader class extends from SoapHeaderPublic Class AuthHeader : Inherits SoapHeader Public Username As String Public Password As StringEnd Class

Public Class HeaderService : Inherits WebService Public sHeader As AuthHeader ...End Class

Cada atributo WebMethod del servicio puede definir un conjunto de encabezados asociados mediante el atributo personalizado SoapHeader. De forma predeterminada se solicitará el encabezado, aunque también es posible definir encabezados opcionales. El atributo SoapHeader especifica el nombre de un campo o una propiedad públicos de la clase Client o Server (a la que se hace referencia como una propiedad Headers en este tema). WebServices establece el valor de una propiedad Headers antes de que se llame al método para obtener encabezados de entrada y obtiene el valor cuando vuelve para obtener encabezados de salida. Para obtener más información acerca de los resultados o encabezados opcionales vea la documentación del kit de desarrollo de software (SDK) de .NET Framework.

Page 221: Presentación del tutorial de ASP

<WebMethod(), SoapHeader("sHeader")> Public Function SecureMethod() As String

If (sHeader Is Nothing) Return "ERROR: Please supply credentials" Else Return "USER: " & sHeader.Username End IfEnd Function

A continuación, un cliente establece el encabezado directamente en la clase de proxy antes de hacer una llamada a un método que lo requiera, como se muestra en el ejemplo siguiente:

Dim h As New HeaderServiceDim myHeader As New AuthHeadermyHeader.Username = "JohnDoe"myHeader.Password = "password"h.AuthHeader = myHeaderDim result As String = h.SecureMethod()

Para ver el funcionamiento de este fragmento de código, ejecute el siguiente ejemplo:

<%@ WebService Language="VB" Class="HeaderService" %>

Imports SystemImports System.Web.ServicesImports System.Web.Services.Protocols

' AuthHeader class extends from SoapHeader Public Class AuthHeaderVB : Inherits SoapHeader Public Username As String Public Password As String End Class

Public Class HeaderService

Public sHeader As AuthHeaderVB

<WebMethod, SoapHeader("sHeader")> Public Function SecureMethod() As String

If (sHeader Is Nothing) Return "ERROR: escribir las credenciales" End If

Dim usr As String = sHeader.Username Dim pwd As String = sHeader.Password

If (AuthenticateUser(usr, pwd)) Return "CORRECTO: " & usr & "," & pwd

Else Return "ERROR: no se pudo autenticar" End If

End Function

Private Function AuthenticateUser(usr As String, pwd As String) As Boolean

If (Not (usr Is Nothing) And Not (pwd Is Nothing)) ' could query a database here for credentials... Return true End If Return false

Page 222: Presentación del tutorial de ASP

End Function

End Class

Resumen de la sección

1. Para establecer la seguridad de los servicios Web de XML en el servidor mediante autenticación de Windows se sigue exactamente el mismo modelo que el descrito para las páginas .aspx.

2. También se pueden establecer mediante programación las credenciales de Windows con las propiedades Username y Password de la clase de proxy WebService.

3. Por último, puede realizar una autenticación personalizada mediante la transmisión de información de credenciales como SOAPHeaders, junto con una solicitud SOAP al método que la requiera.

Localización

Información general sobre internacionalización

Compatibilidad de codificación

ASP.NET utiliza internamente Unicode. Además, utiliza la clase String de la biblioteca de clases .NET Framework y las funciones de utilidad relacionadas, que también usan internamente Unicode. A la hora de comunicarse con el exterior, ASP.NET se puede configurar de varias formas con el fin de utilizar una codificación definida, tanto para los archivos .aspx como para los datos de solicitud o de respuesta. Por ejemplo, es posible almacenar archivos .aspx con codificación Unicode y convertir el resultado HTML de una página en una página de código ANSI como ISO-8859-1.

Adaptación a diferentes idiomas

El acceso a las propiedades de un idioma se puede realizar por medio de la clase CultureInfo. Además, ASP.NET realiza un seguimiento de dos propiedades de una cultura predeterminada por cada subproceso y solicitud: CurrentCulture para la opción predeterminada de las funciones dependientes del idioma y CurrentUICulture para una búsqueda de datos de recursos específica del idioma.

El siguiente código permite mostrar los valores culturales del servidor Web. Observe que la clase CultureInfo está completamente calificada.

<%@Import Namespace="System.Globalization"%>...<%=CultureInfo.CurrentCulture.NativeName%><%=CultureInfo.CurrentUICulture.NativeName%>

El resultado es el siguiente:

English (United States) English (United States)

Para datos dependientes del idioma, tales como formatos de fecha y hora, o moneda, ASP.NET aprovecha las posibilidades de la biblioteca de clases de .NET Framework en Common Language Runtime. El código de las páginas ASP.NET puede utilizar rutinas de formato dependientes del idioma, como DateTime.Format. Por ejemplo, el siguiente código muestra en pantalla la fecha actual en un formato largo: en la primera línea, según el idioma del sistema, y en la segunda, según el idioma alemán ("de"):

<%=DateTime.Now.ToString("f")%><%=DateTime.Now.ToString("f", new System.Globalization.CultureInfo("de"))%>

El resultado es el siguiente:

Wednesday, December 14, 2005 9:39 AMMittwoch, 14. Dezember 2005 09:39

Opciones de configuración

Cuando se crean páginas ASP.NET o módulos de código en segundo plano, los programadores pueden utilizar la biblioteca de clases de .NET Framework con el fin de conseguir características necesarias para un entorno globalizado o para adaptar la aplicación a un entorno local. ASP.NET también proporciona opciones de configuración para facilitar el desarrollo y la administración de aplicaciones de ASP.NET.

Page 223: Presentación del tutorial de ASP

ASP.NET utiliza archivos para definir la configuración en cada directorio (configuración que normalmente también heredan los subdirectorios). Cada archivo puede contener una sección Globalization en la que se pueden especificar codificaciones y culturas predeterminadas. Los valores son válidos si son aceptados por las clases relacionadas, Encoding y CultureInfo. Encontrará más información acerca de las clases Encoding y CultureInfo en el kit de desarrollo de software (SDK) de .NET Framework.

Dentro de la sección Globalization, el valor de fileEncoding determina la forma en que ASP.NET codifica los archivos .aspx; los valores de requestEncoding y responseEncoding determinan la forma en que se codifican los datos de solicitud y respuesta, respectivamente.

Los atributos de la sección Globalization del archivo Web.config también se pueden especificar en la directiva Page (con la excepción de fileEncoding, ya que éste se aplica al propio archivo). Estos valores de configuración sólo son válidos para una página específica y reemplazan a los valores de configuración del archivo Web.config. La siguiente directiva de ejemplo especifica que la página debería utilizar los valores de configuración de la cultura francesa y la codificación UTF-8 para la respuesta:

<%@Page Culture="fr" UICulture="fr" ResponseEncoding="utf-8"%>

Nota: dentro de una página, los valores culturales se pueden cambiar mediante programación actuando sobre Thread.CurrentCulture y Thread.UICulture.

Resumen de la sección

1. ASP.NET admite una amplia variedad de codificaciones para archivos .aspx y datos de solicitud y de respuesta. 2. La clase CultureInfo se encarga del tratamiento de datos dependientes del idioma y de realizar el seguimiento de los valores

CurrentCulture y CurrentUICulture. 3. Se pueden configurar opciones de internacionalización para cada equipo, cada directorio y cada página.

Configurar el referente cultural y la codificación

Codificaciones

Internamente, ASP.NET maneja todos los datos de cadenas en Unicode. En el siguiente ejemplo, se utiliza el atributo ResponseEncoding para pedir a ASP.NET que envíe la página con codificación UTF-8. Observe que es posible elegir cualquier codificación arbitraria sin afectar al archivo .aspx. ASP.NET también define el atributo CharSet sobre el Tipo de contenido del encabezado HTTP según el valor de ResponseEncoding. Esto permite a los exploradores determinar la codificación sin tener que utilizar una metaetiqueta o deducir la codificación correcta a partir del contenido.

<%@Page Language="VB" ResponseEncoding="UTF-8"%>

<html> <head> <link rel="stylesheet" href="../i18n_styles.css"> </head>

<body>

<h3>I18N: Codificación</h3> <div class="details"> Este ejemplo utiliza diferentes codificaciones en la misma página. El origen de la página está almacenado con codificación UTF-8. De forma interna, ASP.NET controla la página como Unicode. Al utilizar el atributo ResponseEncoding, se solicitará que ASP.NET envíe esta página con codificación UTF-8. </div>

<hr>

<center>

<div class="details">

Page 224: Presentación del tutorial de ASP

Texto en inglés. </div>

<div class="details"> Dies ist ein deutscher Text. Er demonstriert die Möglichkeit von Umlauten. </div>

<div class="details"> 効果的にビジネスシーンの変化に適応できるかは、企業内部のデジタルによる情報プロセス、"デジタル・ナーバス・システム"がどれだけ健全であるかにかかっています。 </div>

<div class="details" dir="rtl">אשר, העיקריים הרישוי כללי את לכלול השתדלנו. הרישוי כללי כל את למצות כדי בה ואין השונים ברשיונות המופיעים הכללים

אלינו פנו לגביהם </div>

</center>

</body></html>

Nota: si algunos caracteres aparecen como rectángulos vacíos, será necesario instalar las opciones adicionales para permitir los idiomas Japonés y Hebreo. En una plataforma de Windows 2000, abra Opciones regionales en el Panel de control y agregue la opción de compatibilidad de idioma necesaria. El ejemplo anterior muestra cómo utilizar diferentes conjuntos de caracteres nacionales en la misma página. La página contiene texto inglés (ASCII), texto alemán con un carácter con diéresis, texto japonés y texto hebreo (utiliza dir="rtl"). El código fuente de la propia página se almacena con codificación UTF-8, neutral con respecto a la página de códigos, según se especifica en Web.config. La directiva Page especifica la opción ResponseEncoding (codificación de la respuesta) sobre la propia página: <%@Page ... ResponseEncoding="utf-8"%>Nota: ResponseEncoding en Web.config, también se especifica como UTF-8, de modo que repetirlo en la página resulta redundante. Sin embargo, si el archivo .aspx se traslada a un servidor que no utiliza UTF-8, el archivo seguiría especificando la codificación correcta.

Utilizar CultureInfo El código de las páginas ASP.NET puede utilizar la clase CultureInfo para suministrar valores propios de un determinado entorno regional. En el siguiente ejemplo, las propiedades de un determinado referente cultural, inicialmente la cultura del servidor, se definen del siguiente modo: culture = CultureInfo.CurrentCulture

Si se suministra el nombre de una nueva cultura, se utilizará ésta: culture = New CultureInfo(NewCulture.Value)

La cultura suministrada se establece como el nuevo valor predeterminado y se muestran algunas propiedades: <%Thread.CurrentThread.CurrentCulture = culture%>...Current Culture is <%= CultureInfo.CurrentCulture.Name %>(<%=Thread.CurrentThread.CurrentCulture.Name%>),<%= CultureInfo.CurrentCulture.EnglishName %>/<%=CultureInfo.CurrentCulture.NativeName%>,The localized date is: <%= DateTime.Now.ToString("D", CultureInfo.CurrentCulture) %>

<%@Page Language="VB" ResponseEncoding="utf-8" %>

Page 225: Presentación del tutorial de ASP

<%@Import Namespace="System.Threading"%><%@Import Namespace="System.Globalization"%>

<html> <head> <link rel="stylesheet" href="../i18n_styles.css">

<script language="VB" runat="server"> Dim cult as CultureInfo

Sub Page_Load(sender as Object,args as EventArgs) If(IsPostBack) Then Try cult = new CultureInfo(NewCulture.Value) Catch ' unknown culture cult = Nothing End Try Else cult = CultureInfo.CurrentCulture End If End Sub

</script>

</head>

<body>

<h3>I18N: Clase CultureInfo</h3> <p> Este ejemplo utiliza la clase CultureInfo. En el caso del hombre de negocios japonés en Suecia, esta clase reflejará el origen del usuario, es decir, Japón. </p>

<% If(Not (cult is Nothing)) Then Thread.CurrentThread.CurrentCulture = cult %> Referencia cultural actual: <%= CultureInfo.CurrentCulture.Name %> (<%=Thread.CurrentThread.CurrentCulture.Name%>), <%= CultureInfo.CurrentCulture.EnglishName %>/<%=CultureInfo.CurrentCulture.NativeName%>, Fecha localizada: <%= DateTime.Now.ToString("D", CultureInfo.CurrentCulture) %> <% Else %> <b>Referencia cultural "<%=NewCulture.Value%>" no compatible.</b> <% End If %>

<form runat="server"> Cambiar a <input id="NewCulture" type="text" runat="server"> </form>

<hr>

<center>Algunas referencias culturales de ejemplo: <table width="99%">

<tr>

Page 226: Presentación del tutorial de ASP

<td><b>Nombre</b></td> <td><b>Nombre inglés</b></td> <td><b>Nombre nativo</b></td> <td><b>LCID</b></td> </tr>

<% Dim cultures() As String = { "en-us", "de-de", "ja-jp", "fr-fr" } Dim obj As Object For Each obj In cultures cult = new CultureInfo(obj.ToString()) %> <tr> <td><%=cult.Name%></td> <td><%=cult.EnglishName%></td> <td><%=cult.NativeName%></td> <td><%=cult.LCID%></td> </tr> <% Next %>

</table> </center>

</body></html>

Utilizar RegionInfo El código de las páginas ASP.NET también puede utilizar la clase RegionInfo para suministrar valores de configuración regional. En el siguiente ejemplo, se muestran las propiedades de una región. Inicialmente, se muestra la región predeterminada del servidor. region = RegionInfo.CurrentRegion...Current region is <%= region.EnglishName %> (<%=region.DisplayName%>),currency is <%= region.CurrencySymbol %>.

En subsiguientes solicitudes, se muestra la región especificada:

region = New RegionInfo(NewRegion.Value)

<%@Page Language="VB" ResponseEncoding="UTF-8"%><%@Import Namespace="System.Threading"%><%@Import Namespace="System.Globalization"%>

<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <link rel="stylesheet" href="../i18n_styles.css">

<script runat="server" Language="VB"> Dim _region as RegionInfo = Nothing

Sub Page_Load(sender As Object, args As EventArgs) If(IsPostBack) Then Try _region = new RegionInfo(NewRegion.Value)

Page 227: Presentación del tutorial de ASP

Catch ' unknown region _region = Nothing End Try Else _region = RegionInfo.CurrentRegion End If End Sub

</script>

</head>

<body>

<h3>I18N: Configuración regional</h3> <p>Este ejemplo utiliza la clase RegionInfo. En el caso del hombre de negocios japonés en Suecia, esta clase mostraría la ubicación en Suecia. </p>

<% If(Not(_region is Nothing)) Then %> Región actual: <%= _region.EnglishName %> (<%=_region.DisplayName%>), moneda: <%= _region.CurrencySymbol %>. <% Else %> <b>Región "<%=NewRegion.Value%>" no compatible.</b> <% End If %>

<form runat="server"> Cambiar a <input id="NewRegion" type="text" runat="server"> </form>

<hr>

<center>Algunas regiones de ejemplo: <table width="99%">

<tr> <td><b>Nombre</b></td> <td><b>Nombre inglés</b></td> <td><b>Nombre para mostrar</b></td> <td><b>Moneda</b></td> <td><b>Sistema métrico</b></td> </tr>

<% Dim regions() As String = {"us", "de", "jp", "fr", "il"} Dim obj As Object For Each obj In regions _region = new RegionInfo(obj.ToString()) %> <tr> <td><%=_region.Name%></td> <td><%=_region.EnglishName%></td> <td><%=_region.DisplayName%></td> <td><%=_region.CurrencySymbol%></td> <td><%=_region.IsMetric%></td> </tr>

Page 228: Presentación del tutorial de ASP

<% Next %>

</table>

</center>

</body></html>

Resumen de la sección

ASP.NET puede utilizar páginas que se almacenan con codificación UTF-8, compatibles con diferentes caracteres nacionales. La clase CultureInfo se puede definir y utilizar, mediante programación, para adaptar páginas a diferentes entornos regionales. La clase RegionInfo se puede utilizar para suministrar valores de configuración regional en páginas ASP.NET.

DEPURACION

Depurador del SDK .NET Framework de Microsoft

Todos los programadores, por muy competentes que sean, pueden cometer errores en algún momento. Realizar un seguimiento de los problemas en el código puede resultar desesperante si no se dispone de la herramienta adecuada. Afortunadamente, la naturaleza compilada de ASP.NET hace que depurar aplicaciones Web no se diferencie de depurar otras aplicaciones administradas y, además, el kit de desarrollo de software (SDK) de .NET Framework incluye un pequeño depurador perfectamente adaptado para esta tarea.

En esta sección se describen los pasos necesarios para depurar aplicaciones ASP.NET Framework mediante el depurador suministrado en este SDK. El depurador admite depuración manual de procesos en un equipo de desarrollo local. La documentación del depurador incluida en este kit de desarrollo es el mejor recurso para obtener información sobre características específicas.

Habilitar el modo de depuración para aplicaciones ASP.NETYa que muchas partes de una aplicación ASP.NET Framework se compilan dinámicamente en tiempo de ejecución (los archivos .aspx y .asmx, por ejemplo), se debe configurar el módulo de ejecución de ASP.NET para que compile la aplicación con información simbólica antes de poder depurarla. Los símbolos (archivos .pdb) indican al depurador cómo encontrar los archivos de código fuente originales correspondientes a un archivo binario, y cómo asignar los puntos de interrupción del código a las líneas de los archivos de código fuente. Para configurar una aplicación de modo que se compile con símbolos, hay que incluir un atributo debug en la sección compilation del grupo system.web del archivo Web.config correspondiente al directorio raíz de la aplicación, como se indica a continuación: <configuration> <compilation debug="true"/></configuration>Importante: sólo se debería habilitar esta opción cuando se esté depurando una aplicación, ya que puede afectar significativamente al rendimiento de la aplicación.

Depurar aplicaciones ASP.NETCuando se haya habilitado la depuración de la aplicación, se debería realizar la solicitud de la página que se desea depurar. Esto hace que se cree el proceso en tiempo de ejecución de ASP.NET (Aspnet_wp.exe) y que la aplicación se cargue en memoria.

Para iniciar la depuración:

1. Ejecute el depurador de .NET Framework, DbgClr.exe. 2. Utilice el menú Archivo...Archivos varios...Abrir archivo para abrir el archivo de código fuente correspondiente a la página que

desea depurar. 3. En el menú Herramientas, elija Procesos de depuración. Aparecerá la pantalla de la figura en cuestión. 4. Active la casilla de verificación Mostrar procesos del sistema, si no está ya activada. 5. Busque el proceso Aspnet_wp.exe y haga doble clic en él para abrir el cuadro de diálogo Asociar al proceso. 6. Asegúrese de que la aplicación aparece en la lista de aplicaciones en ejecución y seleccione Aceptar para asociarla. 7. Cierre el cuadro de diálogo Programas.

Page 229: Presentación del tutorial de ASP

Importante: cuando se asocia el depurador al proceso Aspnet_wp.exe, todos los subprocesos de ese proceso se congelan. No se debería intentar, bajo ninguna circunstancia, depurar una aplicación que se está utilizando en producción, ya que las solicitudes de cliente no se podrán ejecutar normalmente hasta que el depurador se desconecte.

Establecer puntos de interrupciónPara establecer un punto de interrupción en la página, haga clic en el margen izquierdo de una línea que contenga una instrucción ejecutable o una firma de función o método. Aparecerá un punto rojo que indicará la posición del punto de interrupción. Coloque el puntero del mouse (ratón) sobre el punto de interrupción para asegurarse de que está bien asociado a la instancia correcta de la aplicación en el proceso Aspnet_wp.exe.

Vuelva a solicitar de nuevo la página desde su explorador. El depurador se detiene en el punto de interrupción y obtiene el foco de la ventana actual. Desde este punto, se puede realizar una ejecución paso a paso, establecer inspecciones de variables, ver valores locales, obtener información de la pila, del código desensamblado, etc. Puede ver los objetos intrínsecos de la página, como Request, Response y Session, si utiliza this (C#) o Me (VB) en la ventana de inspección.

Generar símbolos para componentes precompiladosPara poder depurar componentes precompilados, tales como objetos empresariales o archivos de código en segundo plano, deberá compilar previamente con información simbólica. Los símbolos para ensamblados se detectan normalmente mediante un algoritmo de búsqueda basado en la ruta de acceso. El algoritmo utilizado por la biblioteca PDB (Mspdb70.dll) para encontrar información simbólica es el siguiente:

1. Busca en la misma ruta de acceso que el ensamblado. Ésta es la ubicación normal para archivos .pdb. Para ensamblados locales, coloque los símbolos (archivos .pdb) en el directorio /bin de la aplicación con las DLL.

Page 230: Presentación del tutorial de ASP

2. Busca en la ruta de acceso especificada en el archivo PE (el encabezado de depuración NB10). 3. Busca en ubicaciones de archivos de símbolos de NT (variables de entorno _NT_SYMBOL_PATH y

_NT_ALT_SYMBOL_PATH).

Nota: si la información simbólica no se encuentra, el depurador pide al usuario que especifique una ubicación.

Resumen de la sección

1. El depurador descrito en esta sección admite depuración manual de procesos en un equipo de desarrollo local. 2. El proceso de depuración permite al módulo de ejecución de ASP.NET compilar dinámicamente con información simbólica. Esta

opción se puede habilitar si se especifica <compilation debug="true"/> en el archivo Web.config situado en el directorio raíz de la aplicación. Esta opción del depurador sólo se debe habilitar cuando se desee depurar una aplicación, ya que produce una degradación del rendimiento de la aplicación.

3. Para depurar una aplicación, realice una solicitud de página, asocie el depurador al proceso Aspnet_wp.exe, establezca puntos de interrupción y solicite de nuevo la página anterior.

4. Cuando se asocia al proceso Aspnet_wp.exe, todos los subprocesos de ese proceso se congelan. No se debería depurar, bajo ninguna circunstancia, una aplicación que se está utilizando en producción, ya que las solicitudes de los clientes no se podrán ejecutar normalmente hasta que el depurador se desconecte.

5. Para poder depurar componentes precompilados, tales como objetos empresariales o archivos de código en segundo plano, deberá compilar previamente con información simbólica.

RENDIMIENTO

Introducción sobre el rendimiento

Aquellas aplicaciones Web que presenten muchas características no serán de utilidad si no funcionan correctamente. Las exigencias de las aplicaciones Web son de tal naturaleza que, ahora más que nunca, se espera del código que haga más en menos tiempo. En esta sección se describen algunos principios clave del rendimiento de aplicaciones Web, sugerencias para escribir código que se ejecute correctamente y herramientas para medir el rendimiento.

ASP.NET proporciona varias mejoras de rendimiento integradas. Por ejemplo, las páginas sólo se compilan una vez y se almacenan en caché para posibles solicitudes posteriores. Como estas páginas compiladas se guardan en disco, seguirán siendo válidas incluso después de reiniciar completamente el servidor. ASP.NET también almacena en caché objetos internos, como variables de servidor, para acelerar el acceso del código del usuario. Además, ASP.NET aprovecha todas las mejoras de rendimiento de la biblioteca Common Language Runtime: compilación incremental (Just-in-time), una biblioteca Common Language Runtime optimizada para equipos de un solo procesador o multiprocesador, etc.

Sin embargo, todas estas mejoras no sirven de nada si el código está mal escrito. Por último, debe asegurarse de que la aplicación satisface las necesidades de los usuarios. En la próxima sección se describen algunas de las formas comunes de evitar cuellos de botella en lo que se refiere al rendimiento. Pero primero es necesario comprender las métricas siguientes:

Rendimiento: número de solicitudes que una aplicación Web puede atender por unidad de tiempo, generalmente medido en solicitudes/segundo. El rendimiento puede variar en función de la carga (número de subprocesos cliente) del servidor. Normalmente, se suele considerar que ésta es la métrica de rendimiento más importante que hay que optimizar.

Tiempo de respuesta: tiempo transcurrido entre la emisión de una solicitud y el primer byte devuelto al cliente desde el servidor. Éste suele ser el aspecto del rendimiento que mejor puede percibir el usuario cliente. Si una aplicación tarda mucho en responder, el usuario podría impacientarse y pasar a otro sitio. El tiempo de respuesta puede variar independientemente de la tasa de rendimiento (incluso inversamente).

Tiempo de ejecución: tiempo que tarda en procesarse una solicitud, generalmente entre el primer y el último byte devuelto al cliente desde el servidor. El tiempo de ejecución afecta directamente al cálculo del rendimiento.

Escalabilidad: medida de la capacidad de una aplicación de ofrecer mejor rendimiento a medida que se le asignen más recursos (memoria, procesos o equipos). Generalmente se trata de una medida de la tasa de cambio del rendimiento con respecto al número de procesadores.

La base de la programación de aplicaciones con buen rendimiento es lograr un equilibrio entre estas métricas. Ninguna medida individual puede caracterizar el comportamiento de la aplicación en distintas circunstancias; varias medidas realizadas a la vez pueden ofrecer una imagen aproximada del rendimiento de una aplicación.

Sugerencias de ajuste del rendimiento

Cualquier modelo de programación tiene problemas de rendimiento comunes y ASP.NET no es una excepción. En esta sección se describen algunas formas de evitar los cuellos de botella en lo que se refiere al rendimiento del código.

1. Deshabilite el estado de sesión cuando no lo utilice: No todas las páginas y aplicaciones requieren estado de la sesión para cada usuario. Si no es necesario, deshabilítelo completamente. Esto se consigue fácilmente mediante una directiva para páginas, como la siguiente:

2. <%@ Page EnableSessionState="false" %>

Page 231: Presentación del tutorial de ASP

Nota: si una página necesita acceso a variables de sesión pero no las crea ni las modifica, establezca el valor de la directiva en ReadOnly. También se puede deshabilitar el estado de sesión para métodos de servicio Web de XML. Vea Utilizar objetos y elementos intrínsecos en la sección de servicios Web de XML.

3. Elija cuidadosamente el proveedor de estado de sesión: ASP.NET proporciona tres formas diferentes de almacenar datos de sesión para la aplicación: estado de sesión en proceso, estado de sesión fuera de proceso como un servicio de Windows y estado de sesión fuera de proceso en una base de datos SQL. Cada una de las formas tiene ventajas, pero el estado de sesión en proceso es con mucho la mejor solución. Si sólo se almacenan pequeñas cantidades de datos volátiles en el estado de sesión, sería conveniente utilizar el proveedor en proceso. Las soluciones fuera de proceso son útiles principalmente en escenarios de unidad Web y batería Web, o en situaciones en las que no se pueden perder datos en caso de reinicio del servidor o del proceso.

4. Evite realizar demasiados viajes de ida y vuelta al servidor: el marco de trabajo de página de formularios Web es una de las mejores características de ASP.NET, porque permite reducir en gran medida la cantidad de código que se debe escribir para realizar una tarea. El acceso mediante programación a elementos de página con controles de servidor y el modelo de control de eventos de devolución automática son posiblemente las características que permiten ahorrar más tiempo. Sin embargo, hay formas apropiadas y formas no tan apropiadas de utilizar estas características, y es importante saber cuándo es apropiado utilizarlas.

Normalmente, una aplicación sólo necesita realizar un viaje de ida y vuelta al servidor para recuperar o almacenar datos. La mayoría de las manipulaciones de datos pueden realizarse en el cliente, entre viajes de ida y vuelta. Por ejemplo, la validación de entradas de formulario suele tener lugar en el cliente antes de que el usuario envíe datos. En general, si se tiene que devolver información al servidor, no se debe hacer un viaje de ida y vuelta al servidor.

Si está programando sus propios controles de servidor, considere la posibilidad de prepararlos con el fin de procesar el código del cliente para exploradores de nivel superior (compatibles con ECMAScript). Al utilizar controles "inteligentes" se puede reducir en gran medida el número de visitas innecesarias al servidor Web.

5. Utilice Page.IsPostback para evitar trabajo adicional en un viaje de ida y vuelta: si controla devoluciones automáticas de controles de servidor, a menudo será necesario ejecutar código diferente la primera vez que se solicite la página desde el código

que se utiliza para el viaje de ida y vuelta al desencadenarse un evento. Si se activa la propiedad Page.IsPostBack, se podrá ejecutar el código condicionalmente, en función de si hay una solicitud inicial para la página o una respuesta a un evento de control de servidor. Hacer esto puede parecer obvio, pero en la práctica es posible omitir esta opción sin que se vea modificado el comportamiento de la página. Por ejemplo:

<script language="VB" runat="server">

Public ds As DataSet ...

Sub Page_Load(sender As Object, e As EventArgs) ' ...set up a connection and command here... If Not (Page.IsPostBack) Dim query As String = "select * from Authors where FirstName like '%JUSTIN%'" myCommand.Fill(ds, "Authors") myDataGrid.DataBind() End If End Sub

Sub Button_Click(sender As Object, e As EventArgs) Dim query As String = "select * from Authors where FirstName like '%BRAD%'" myCommand.Fill(ds, "Authors") myDataGrid.DataBind() End Sub

</script>

<form runat="server"> <asp:datagrid datasource='<%# ds.Tables["Authors"].DefaultView %>' runat="server"/><br> <asp:button onclick="Button_Click" runat="server"/></form>

1. El evento Page_Load se ejecuta en cada solicitud, por lo que activamos Page.IsPostBack de forma que la primera consulta

no se ejecute cuando procesemos la devolución automática del evento Button_Click. Tenga en cuenta que la página tendría

Page 232: Presentación del tutorial de ASP

el mismo comportamiento si no se activara esta opción, ya que la llamada a DataBind del controlador de eventos volcaría el enlace desde la primera consulta. Recuerde que es fácil pasar por alto esta sencilla mejora de rendimiento al escribir páginas.

2. Utilice pocos controles de servidor y de forma apropiada: aunque son muy fáciles de usar, los controles de servidor podrían no ser siempre la mejor opción. En muchos casos se puede lograr el mismo objetivo mediante procesamiento simple o sustitución de enlace de datos. Por ejemplo:

<script language="VB" runat="server">

Public imagePath As String Sub Page_Load(sender As Object, e As EventArgs) '...retrieve data for imagePath here... DataBind() End Sub

</script>

<%--the span and img server controls are unecessary...--%>The path to the image is: <span innerhtml='<%# imagePath %>' runat="server"/><br><img src='<%# imagePath %>' runat="server"/>

<br><br>

<%-- use databinding to substitute literals instead...--%>The path to the image is: <%# imagePath %><br><img src='<%# imagePath %>' />

<br><br>

<%-- or a simple rendering expression...--%>The path to the image is: <%= imagePath %><br><img src='<%= imagePath %>' />

1. En este ejemplo no es necesario un control de servidor para reemplazar valores en el código HTML resultante que se devuelve al cliente. Hay muchos otros casos en los que esta técnica funciona correctamente, incluso en plantillas de control de servidor. Sin embargo, si desea manipular mediante programación las propiedades del control, controlar eventos desde el control o aprovechar su conservación de estado, lo más apropiado es utilizar un control de servidor. Debe analizar el uso de los controles de servidor y localizar el código que se puede optimizar.

2. Evite utilizar demasiado el estado de vista de un control de servidor: la administración automática de estados es una característica que permite a los controles de servidor volver a llenar sus valores en un viaje de ida y vuelta sin que sea necesario programar código. Sin embargo esta característica no es libre, ya que el estado de un control se pasa al servidor y se recibe del servidor en un campo de formulario oculto. Debe saber cuándo resulta útil ViewState y cuando no. Por ejemplo, si enlaza un control a datos en cada viaje de ida y vuelta (como en la cuadrícula de datos de la sugerencia Nº 4), no necesita que el control mantenga su estado de vista, ya que en cualquier caso se eliminarán los datos con los que se vuelva a llenar.

La opción ViewState está habilitada de forma predeterminada para todos los controles de servidor. Para deshabilitarla, establezca el valor de la propiedad EnableViewState del control en false, como se indica en el siguiente ejemplo:

<asp:datagrid EnableViewState="false" datasource="..." runat="server"/>

También puede desactivar ViewState para la página. Esto es útil cuando no se realizan devoluciones automáticas desde una página, como en el siguiente ejemplo:

<%@ Page EnableViewState="false" %>

Tenga en cuenta que también se admite este atributo mediante la directiva User Control. Para analizar la cantidad de estado de vista utilizado por los controles de servidor de la página, habilite el seguimiento y vea la columna Estado de vista en la tabla Jerarquía de controles. Para obtener información acerca de la característica Seguimiento y la forma de habilitarla, vea la característica Registro de seguimiento de aplicación.

3. Utilice el método Response.Write para concatenar cadenas: utilice el método HttpResponse.Write en las páginas o controles de usuario para concatenar cadenas. Este método ofrece servicios de búfer y concatenación muy eficaces. En el caso de que pretenda realizar una concatenación compleja, la técnica descrita en el ejemplo siguiente (utilizando varias llamadas a

Response.Write) resulta más rápido que concatenar una cadena con una sola llamada al método Response.Write.

Page 233: Presentación del tutorial de ASP

Response.Write("a")Response.Write(myString)Response.Write("b")Response.Write(myObj.ToString())Response.Write("c")Response.Write(myString2)Response.Write("d")

1. No utilice las excepciones del código: las excepciones son costosas y raramente se producen en el código. Nunca debe utilizar las excepciones como una forma de controlar el flujo normal de un programa. Si es posible detectar en el código una condición que puede causar una excepción, debe hacerlo en lugar de esperar a detectar la excepción antes de controlar la condición. Algunos escenarios comunes incluyen la comprobación de valores NULL, la asignación a una cadena que se analizará para devolver un valor numérico o la comprobación de valores específicos antes de aplicar operaciones matemáticas. Por ejemplo:

' Consider changing this:

Try result = 100 / num

Catch (e As Exception) result = 0End Try

// To this:

If Not (num = 0) result = 100 / numElse result = 0End If

1. Utilice enlace en tiempo de compilación en código de Visual Basic o JScript: una de las ventajas de Visual Basic, VBScript y JScript es su naturaleza de lenguaje sin tipos. Es posible crear variables simplemente utilizándolas sin necesidad de una declaración explícita de tipo. Al realizar una asignación de un tipo a otro, las conversiones también se realizan automáticamente. Esto puede ser una ventaja y una desventaja, ya que el enlace en tiempo de ejecución es una utilidad muy costosa en términos de rendimiento.

Ahora el lenguaje Visual Basic admite programación protegida por tipos mediante el uso de una directiva de compilador especial, Option Strict. Por compatibilidad con las versiones anteriores, ASP.NET no habilita Option Strict de forma predeterminada. Sin embargo, con el fin de obtener un rendimiento óptimo debe habilitar Option Strict para las páginas mediante un atributo Strict en la página o la directiva Control:

<%@ Page Language="VB" Strict="true" %>

<%

Dim BDim C As String

' This causes a compiler error:A = "Hello"

' This causes a compiler error:B = "World"

' This does not:C = "!!!!!!"

' But this does:C = 0

Page 234: Presentación del tutorial de ASP

%>

JScript también admite la programación sin tipos, aunque no ofrece ninguna directiva de compilador para forzar el enlace en tiempo de compilación. Una variable se enlaza en tiempo de ejecución si:

o Se declara explícitamente como un objeto.

o Es un campo de una clase sin ninguna declaración de tipo.

o Es un miembro privado de función o método privado sin declaración explícita de tipos, y no se puede inferir su tipo a partir de su uso.

La última distinción es complicada. El compilador de JScript se optimizará si puede averiguar cuál es el tipo a partir del uso de la variable. En el siguiente ejemplo, la variable A se enlaza en tiempo de compilación; en cambio, la variable B se enlaza en tiempo de ejecución:

var A;var B;

A = "Hello";B = "World";B = 0;

Para obtener el mejor rendimiento, declare las variables de JScript como variables con tipo. Por ejemplo, "var A : String".

2. Adapte los componentes COM que requieran muchas llamadas a código administrado: la plataforma .NET Framework proporciona una manera sencilla de interoperar con componentes COM tradicionales. La ventaja es que puede aprovechar la nueva plataforma manteniendo el código existente. Sin embargo, hay algunas circunstancias en las que el coste de rendimiento que supone mantener los componentes antiguos es mayor que el coste de migrar los componentes a código administrado. Cada situación es única y la mejor manera de decidir qué cambios son necesarios es medir el rendimiento del sitio. Sin embargo, en general el impacto en el rendimiento de la interoperabilidad COM es proporcional al número de llamadas a funciones realizadas o la cantidad de datos para los que se calculan referencias de código no administrado a código administrado. Un componente que requiere un volumen elevado de llamadas para interactuar se denomina "hablador" a causa del número de comunicaciones entre capas. Debe considerar la posibilidad de adaptar estos componentes a código totalmente administrado para aprovechar las mejoras de rendimiento proporcionadas por la plataforma .NET. Como alternativa, puede considerar la posibilidad de volver a diseñar el componente para que requiera menos llamadas o para calcular más referencias de datos simultáneamente.

3. Utilice procedimientos SQL almacenados para el acceso a datos: de todos los métodos de acceso a datos proporcionados por .NET Framework, el acceso a datos basado en SQL es la mejor opción para generar aplicaciones Web escalables con el mejor rendimiento. Al utilizar el proveedor de SQL administrado, se obtiene un aumento de rendimiento adicional mediante el uso de procedimientos almacenados compilados en lugar de consultas ad hoc. Si desea examinar un ejemplo de uso de procedimientos almacenados de SQL, vea la sección Acceso a datos en el servidor de este tutorial.

4. Utilice SqlDataReader para obtener un cursor de datos de desplazamiento hacia adelante rápido: un objeto SqlDataReader proporciona un cursor hacia adelante de sólo lectura para datos obtenidos de una base de datos de SQL. El rendimiento de SqlDataReader es mejor que el uso de un objeto DataSet, si puede utilizarlo en el escenario. Como SqlDataReader es compatible con la interfaz IEnumerable, también es posible enlazar controles de servidor. Si desea examinar un ejemplo de uso SqlDataReader, vea la sección Acceso a datos en el servidor de este tutorial.

5. Almacene en caché datos y resultados siempre que sea posible: el modelo de programación ASP.NET proporciona un mecanismo sencillo para almacenar en caché resultados o datos de páginas cuando no sea necesario calcularlos dinámicamente para cada solicitud. Puede diseñar las páginas con almacenamiento en caché con el fin de optimizar los puntos de la aplicación en los que espera que haya más tráfico. El uso apropiado del almacenamiento en caché es más importante que ninguna otra característica de la plataforma .NET Framework con el fin de mejorar el rendimiento del sitio, a veces en un orden de magnitud e incluso más. Para obtener más información sobre cómo utilizar el almacenamiento en caché, vea la sección Servicios de almacenamiento en caché de este tutorial.

6. Habilite el uso de unidades Web para equipos multiprocesador: el modelo de procesos de ASP.NET permite habilitar la escalabilidad en equipos con varios procesadores distribuyendo el trabajo entre varios procesos, uno para cada CPU, cada uno con la afinidad de procesador establecida en la CPU correspondiente. Esta técnica se denominada Uso de unidades Web y permite mejorar en gran medida el rendimiento de algunas aplicaciones. Para aprender a habilitar el uso de unidades Web, consulte la sección Utilizar el modelo de procesos.

7. No olvide deshabilitar el modo de depuración: la sección <compilación> de la configuración de ASP.NET controla si una aplicación se compila en modo de depuración o no. El modo de depuración degrada en gran medida el rendimiento. Recuerde siempre que debe deshabilitar este modo antes de implantar una aplicación de producción o medir el rendimiento. Para obtener más información acerca del modo de depuración, vea la sección titulada Depurador de SDK.

Medir el rendimiento

La medición del rendimiento de un servidor Web es una característica que sólo se puede mejorar mediante la experiencia y la experimentación continua. Hay muchas variables en juego, como el número de clientes, la velocidad de las conexiones de clientes, los recursos de servidor, el código de la aplicación, etc. Para ello resulta útil tener buenas herramientas y, afortunadamente, estas herramientas están disponibles.

Microsoft proporciona la herramienta Web Application Stress (WAS), que simula la conexión al sitio Web de varios clientes HTTP. Es posible controlar la carga de clientes, el número de conexiones, el formato de las cookies, los encabezados y otros parámetros de la interfaz gráfica de la herramienta. Después de una ejecución de prueba, WAS proporciona informes con métricas de rendimiento, como los datos de tiempo de respuesta, de rendimiento y de contadores de rendimiento relevantes para la aplicación. El objetivo es sencillo: maximizar el rendimiento y el

Page 235: Presentación del tutorial de ASP

uso de la CPU para altos niveles de carga. WAS está disponible en el Kit de recursos de Microsoft Internet Information Server y también se puede descargar por separado desde la dirección http://webtool.rte.microsoft.com.

ASP.NET también expone varios contadores de rendimiento que se pueden utilizar para hacer un seguimiento de las aplicaciones. A diferencia de la tecnología ASP tradicional, la mayoría de estos contadores de rendimiento se exponen para aplicaciones individuales, en lugar de exponerse globalmente para todo el equipo. Los contadores para aplicaciones están disponibles con el objeto de rendimiento de aplicaciones ASP.NET Framework; hay que seleccionar una instancia de aplicación concreta al seleccionar un contador para supervisar. Por supuesto, aún podrán verse los valores del contador para todas las aplicaciones que utilicen una instancia de aplicación especial "__Total__" en el monitor de sistema. ASP.NET también expone contadores que son sólo globales y que no están enlazados a ninguna instancia de aplicación específica. Estos contadores se encuentran con el objeto de rendimiento del sistema de ASP.NET. Para poder ver todos los contadores disponibles de ASP.NET (en sistemas Windows 2000):

1. Seleccione Inicio->Programas->Herramientas administrativas->Rendimiento. 2. Haga clic en el botón Ver informe del Monitor de sistema. 3. Haga clic en el botón Agregar. 4. Seleccione Aplicaciones ASP.NET y después seleccione el botón de opción Todos los contadores. Haga clic en Aceptar. 5. Seleccione ASP.NET y después seleccione el botón de opción Todos los contadores. Haga clic en Aceptar.

La característica de seguimiento de ASP.NET también resulta útil para identificar cuellos de botella de rendimiento en el código. Puede mostrar información temporal importante entre sucesivas instrucciones con resultados de seguimiento, así como información acerca de la jerarquía de control del servidor, la cantidad de estado de vista utilizado y el tamaño de procesamiento de los controles en la página. Para obtener más información acerca de la característica Seguimiento, vea la sección Realizar un seguimiento de este tutorial.

APLICACIONES DE EJEMPLO

Portal personalizado

Este ejemplo ilustra una aplicación de página principal de portal personalizado. La aplicación permite a los usuarios personalizar una página principal para mostrar varios módulos de su elección, como un directorio de sitios o una lista de vínculos favoritos. Cada módulo se implementa como un control de usuario que se agrega dinámicamente a la página principal si el usuario ha elegido incluirlo. Los datos de configuración relacionados con la personalización se mantienen en una base de datos SQL y se recuperan mediante un componente de módulo HTTP de personalización (que funciona de forma muy similar a los módulos HTTP de estado de la sesión y de estado de la aplicación). Cada página de la aplicación hereda de una clase base común Page que está por detrás del código y que utiliza el componente de personalización para exponer un diccionario especial denominado UserState. Este diccionario UserState proporciona a las páginas de la aplicación acceso a la configuración de personalización de usuario (como pares de cadenas clave/valor). Además de almacenar las selecciones del módulo del usuario, el diccionario UserState almacena otros parámetros de personalización, como los esquemas de colores. Los módulos individuales también pueden utilizar el diccionario UserState para almacenar sus propias configuraciones de personalización.

La aplicación de portal utiliza el valor de FormsAuthenticationModule para la autenticación de usuarios. Cuando un usuario solicita por primera vez visitar la página principal, se muestra la configuración de un usuario anónimo. Si el usuario intenta tener acceso a una parte del portal que está restringida a usuarios autenticados (como la página de personalización del módulo), el módulo FormsAuthenticationModule lo redirige a una página de inicio de sesión para que escriba sus credenciales. Los usuarios que no hayan iniciado nunca una sesión pueden utilizar un formulario de registro para crear una nueva cuenta de usuario con su contraseña. En visitas posteriores a la página principal del portal, los usuarios sólo tendrán que iniciar una sesión con estas credenciales de cuenta (que se comprobarán en una base de datos SQL).

Para empezar a explorar la aplicación de portal, siga los pasos descritos arriba para crear una cuenta de usuario. Cuando haya creado su cuenta, podrá explorar y personalizar todo el portal.

<%@ Page Language="VB" Inherits="DefaultPage" Src="Default.vb" Description="Main Portal Page" %><%@ Register TagPrefix="LoginModule" TagName="LoginModule" Src="modules/login/login.ascx" %>

<html><head>

<title>ASP.NET PORTAL SITE</title></head>

<body bgcolor="<%=UserState("BackColor")%>" style="margin:0,0,0,0">

<form runat=server>

<table border=0 width="100%" cellspacing=0 cellpadding=0 bgcolor="ffffff">

Page 236: Presentación del tutorial de ASP

<tr> <td align=left> <img src="/Quickstart/aspplus/samples/portal/VB/images\home_<%# UserState("ColorScheme") %>.gif"> </td>

<td align=right valign=top style="padding:5,15,5,5"> <font face=Arial size=-1> <a href="/Quickstart/aspplus/samples/portal/VB/default.aspx?default.aspx">Actualizar</a> - <a OnServerClick="SignOff_Click" runat=server>Cerrar sesión</a> </font> </td> </tr> <tr height="8"/> </table>

<table border=0 width="100%" bgcolor="ffffff" cellspacing=0 cellpadding=0 > <tr> <span id="PagePanelLinks" EnableViewState="false" runat=server/>

<td width="1%">&nbsp;</td> <td align=right bgcolor="<%=UserState("SubheadColor")%>" width="50%" style="padding:0,10,0,0"> <font size=-1 face=Arial> [<a id="anchorAdd" href="" OnServerClick="AddPage_Click" runat="server">Agregar página</a> <asp:Label id="spanAdd" Text="&nbsp;-&nbsp;" runat="server"/> <a id="anchorDelete" href="" runat="server">Eliminar página</a> <asp:Label id="spanDelete" Text="&nbsp;-&nbsp;" runat="server"/> <a id="anchorOptions" href="" runat="server">Cambiar colores</a>] </font> </td>

</tr> <tr> <td width="100%" colspan=11> <table border=0 cellspacing=0 width="100%"> <tr> <td bgcolor="<%=UserState("HeadColor")%>"> <table border=0 cellspacing=0 cellpadding=0> <tr><td height=3></td></tr> </table> </td> </tr> </table> </td> </tr> </table>

<table border=0 width="100%" cellspacing=0 cellpadding=0 style="padding:0,0,0,0"> <tr> <td width="1%" valign="top">

<table border=0 width="100%" cellspacing=10 cellpadding=0 style="padding:0,0,0,0">

<tr valign="top"> <td height="10" style="padding-top:5" align="left"> <table cellpadding=0 cellspacing=0> <tr> <td><img border=0 src="/Quickstart/aspplus/samples/portal/VB/images\personal.gif"></td> <td><a id="anchorCustomize" runat="server"><img border=0 src="/Quickstart/aspplus/samples/portal/VB/images\content.gif"></a></td> <td><img border=0 src="/Quickstart/aspplus/samples/portal/VB/images\space.gif"></td> <td><a id="anchorOptions2" runat="server"><img border=0 src="/Quickstart/aspplus/samples/portal/VB/images\layout.gif"></a></td> </tr>

Page 237: Presentación del tutorial de ASP

</table> </td> </tr> <tr> <td> <!-- BEGIN DYNAMIC LEFT MODULE LIST --> <asp:Panel id="Login" EnableViewState="false" visible="false" runat="server"> <LoginModule:LoginModule runat="server"/> </asp:Panel> <asp:PlaceHolder id="LeftUIModules" runat=server/>

<!-- END DYNAMIC LEFT MODULE LIST --> </td> </tr> </table> </td> <td width="99%" valign="top">

<table border=0 width="100%" cellspacing=10 cellpadding=0>

<!-- BEGIN DYNAMIC RIGHT MODULE LIST -->

<asp:PlaceHolder id="RightUIModules" runat=server/>

<!-- END DYNAMIC RIGHT MODULE LIST -->

</table> </td> </tr> </table>

</form>

</body></html>

Establecimiento de comercio electrónico

La siguiente aplicación de ejemplo es una maqueta de un típico establecimiento de comercio electrónico. La aplicación muestra los elementos más habituales de este tipo de aplicaciones: un explorador de productos, un carrito de compra para la sesión, detalles de productos, etc. Para almacenar los datos de los productos, se utiliza una base de datos SQL Server, mientras que para compilar esos datos, se utilizan los controles DataList y Repeater. La parte de acceso a datos de la aplicación se implementa como un componente administrado.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="Market" %>

<html><head>

<title>GrocerToGo</title> <link rel="stylesheet"href="grocerstyle.css">

<style> div.details { background-color:ffffcc; padding-top:15; padding-bottom:20; } div.details table { width:375; } div.details table td { font-family:Verdana; font-size:8pt; } </style>

<script language="VB" runat=server>

public Sub Page_Load(sender As Object, e As EventArgs)

if (Not IsPostBack) Then

Page 238: Presentación del tutorial de ASP

ProductListing.SelectedIndex = 0

UpdateProducts() UpdateShoppingCart() End If End Sub

public Sub CategoryList_Select(sender As Object, e As EventArgs) CurrentCategory.Text = CategoryList.Items(CategoryList.SelectedIndex).Text UpdateProducts() End Sub

public Sub ProductListing_Select(sender As Object, e As EventArgs) UpdateProducts() End Sub

public Sub AddBtn_Click(sender As Object, e As ImageClickEventArgs) Dim productID As Integer productID = Int32.Parse(ProductListing.DataKeys(ProductListing.SelectedIndex).ToString())

Dim market As InventoryDB market = new InventoryDB()

Dim product As DataRow product = market.GetProduct(productID)

Dim shoppingCart As Market.OrderList shoppingCart = Ctype(Session("ShoppingCart"), Market.OrderList)

shoppingCart.Add(new Market.OrderItem(productID, CStr(product("ProductName")), Double.Parse(product("UnitPrice").ToString()), 1))

UpdateShoppingCart() End Sub

public Sub Recalculate_Click(sender As Object, e As ImageClickEventArgs)

' Obtain Shopping Cart From Session State

Dim shoppingCart As Market.OrderList shoppingCart = Ctype(Session("ShoppingCart"), Market.OrderList)

' Iterate over items in shopping cart (update cart with current row qty textbox value

Dim i As Integer Dim qty As HtmlInputText

for i = 0 To ShoppingCartList.Items.Count - 1 qty = Ctype(ShoppingCartList.Items(i).FindControl("Qty"), HtmlInputText) Try shoppingCart(CStr(ShoppingCartList.DataKeys(i))).Quantity = CInt(qty.Value) Catch exc As Exception End Try Next i

UpdateShoppingCart() End Sub

Page 239: Presentación del tutorial de ASP

public Sub ClearCart_Click(sender As Object, e As ImageClickEventArgs)

' Obtain access to Shopping Cart From Session State

Dim shoppingCart As Market.OrderList shoppingCart = Ctype(Session("ShoppingCart"), Market.OrderList)

' Clear Items From Shopping Cart and then Update UI

shoppingCart.ClearCart() UpdateShoppingCart() End Sub

Sub UpdateProducts()

Dim market As New InventoryDB

' Update Product Listing at Bottom of Page

Dim categoryID As Integer categoryID = Int32.Parse(CategoryList.Items(CategoryList.SelectedIndex).Value) ProductListing.DataSource = market.GetProducts(categoryID).DefaultView ProductListing.DataBind()

' Update Product Information

Dim productID As Integer productID = Int32.Parse(ProductListing.DataKeys(ProductListing.SelectedIndex).ToString())

Dim product As DataRow product = market.GetProduct(productID)

Name.Text = product("ProductName").ToString() SelectedProdPicture.Src = product("ImagePath").ToString() ServingSize.Text = product("ServingSize").ToString() Servings.Text = product("Servings").ToString()

' Update Product Calory Information

DetailsListing.DataSource = market.GetProductCalories(productID).DefaultView DetailsListing.DataBind() End Sub

Sub UpdateShoppingCart()

' Update Shopping Cart UI from Basket Stored in Session State

Dim shoppingCart As Market.OrderList shoppingCart = Ctype(Session("ShoppingCart"), Market.OrderList)

SubTotal.Text = System.String.Format("{0:C}", shoppingCart.SubTotal) Tax.Text = System.String.Format("{0:C}", shoppingCart.Tax) Total.Text = System.String.Format("{0:C}", shoppingCart.Total)

ShoppingCartList.DataSource = shoppingCart.Values ShoppingCartList.DataBind() End Sub

</script>

</head><body topmargin="0" leftmargin="0" marginwidth="0" marginheight="0">

Page 240: Presentación del tutorial de ASP

<form runat="server">

<table cellspacing=0 cellpadding=3 bgcolor="DC6035" border=0 width="100%"> <tr> <td align="left"><img src="images/logo.gif"></td> <td align="right"> <a><img border=0 src="images/home.gif"></a> </td> </tr> <tr> <td align="right" class="seleccionar" colspan="2">

<b>Seleccionar una categoría: &nbsp;</b>

<select id="CategoryList" style="width:75" runat="server"> <option selected value="1">Leche</option> <option value="2">Cereales</option> <option value="3">Refresco</option> </select>

<asp:button text="Seleccionar" OnClick="CategoryList_Select" runat=server/>

</td> </tr> </table>

<table border=0 width=100% cellspacing=0 cellpadding=15 bgcolor="ffffcc"> <tr>

<td valign=top bgcolor=ffffcc>

<p>

<h3> <b>Categoría del producto: <asp:label id="CurrentCategory" runat=server>Leche</asp:label><b> </h3>

<table width="100%" cellpadding=0 cellspacing=0 > <tr style="padding-left:12" > <td align="center" style="border-style:inset;" bgcolor="EDBE7B" width=140> <img id="SelectedProdPicture" runat=server> </td> <td align="center" bgcolor="ffffcc" style="padding-right:0;">

<div class="details">

<table cellpadding=1 > <tr> <td colspan=3> <b><font face="Verdana" size=3><asp:label id="Name" runat=server> </asp:label></font></b> </td> <td align=right> <asp:imagebutton ImageUrl="images/addcart.gif" OnClick="AddBtn_Click" runat=server/> </td> </tr> <tr> <td colspan=4 > Tamaño de la ración <asp:label id="ServingSize" runat=server> </asp:label> </td> </tr>

Page 241: Presentación del tutorial de ASP

<tr> <td colspan=4> Raciones por envase <asp:label id="Servings" runat=server> </asp:label> </td> </tr> <tr> <td height=5 colspan=4 bgcolor="000000"></td> </tr> </table>

<asp:repeater id="DetailsListing" runat="server">

<ItemTemplate> <table cellpadding=0> <tr> <td colspan=3> <b><%# DataBinder.Eval(Container.DataItem, "Name") %></b> <%# DataBinder.Eval(Container.DataItem, "Grams") %> </td> <td align=right> <b><%# DataBinder.Eval(Container.DataItem, "Percent") %>%</b> </td> </tr> </table> </ItemTemplate>

<SeparatorTemplate> <table cellpadding=0 > <tr> <td colspan=4 height=1 bgcolor="000000"></td> </tr> </table> </SeparatorTemplate>

<FooterTemplate> <table cellpadding=0 > <tr> <td colspan=4 height=5 bgcolor="000000"></td> </tr> </table> </FooterTemplate>

</asp:repeater>

</div> </td> </tr> </table>

<p>

<table> <tr> <td class="Products">

<asp:datalist id="ProductListing" DataKeyField="ProductID" showheader=false showfooter=false OnSelectedIndexChanged="ProductListing_Select" repeatdirection="horizontal" borderwidth=0 runat="server">

<ItemTemplate> <table> <tr> <td width="150">

Page 242: Presentación del tutorial de ASP

<asp:imagebutton borderwidth=6 bordercolor="#ffffcc" commandname="Select" ImageUrl='<%# DataBinder.Eval(Container.DataItem, "ImagePath") %>' runat=server/>

<p>

<%# DataBinder.Eval(Container.DataItem, "ProductName") %> <br> <%# DataBinder.Eval(Container.DataItem, "UnitPrice", "{0:C}") %><br> </td> </tr> </table> </ItemTemplate>

<SelectedItemTemplate> <table> <tr> <td width="150">

<asp:imagebutton borderwidth=6 bordercolor="red" commandname="select" ImageUrl='<%# DataBinder.Eval(Container.DataItem, "ImagePath") %>' runat=server/>

<p>

<%# DataBinder.Eval(Container.DataItem, "ProductName") %><br> <%# DataBinder.Eval(Container.DataItem, "UnitPrice", "{0:C}") %><br> </td> </tr> </table> </SelectedItemTemplate>

</asp:datalist>

</td> </tr> </table>

</td>

<td width="315" valign=top class="cart" bgcolor="#EDBE7B">

<h3>Carro de la compra</h3>

<asp:datalist id="ShoppingCartList" DataKeyField="Name" borderwidth=0 runat="server">

<HeaderTemplate>

<table width="100%"> <tr> <td width=35> <b>Ctd.</b> </td> <td width=175> <b>Producto</b> </td> <td width=50> <b>Precio</b> </td> <td align="right" style="padding-right:10"> <b>Total</b> </td>

Page 243: Presentación del tutorial de ASP

</tr>

</HeaderTemplate>

<ItemTemplate>

<tr> <td width=35> <input type=text size=1 id="Qty" runat=server value='<%# DataBinder.Eval(Container.DataItem, "Quantity") %>'> </td> <td width=175> <%# DataBinder.Eval(Container.DataItem, "Name") %> </td> <td width=50> <%# DataBinder.Eval(Container.DataItem, "Price", "{0:C}") %> </td> <td align=right style="padding-right:10"> <%# DataBinder.Eval(Container.DataItem, "Total", "{0:C}") %> </td> </tr>

</ItemTemplate>

<FooterTemplate>

</table>

</FooterTemplate>

</asp:datalist>

<table border=0 width="100%"> <tr> <td colspan=4><hr></td> </tr> <tr> <td width=52></td> <td width=225 colspan="2" align="left"> <b>Subtotal</b> </td> <td align="right" style="padding-right:10"> <asp:label id="Subtotal" runat=server/> </td> </tr> <tr> <td width=52></td> <td width=225 colspan="2" align="left"> <b>Impuestos</b> </td> <td align="right" style="padding-right:10"> <asp:label id="Tax" runat=server/> </td> </tr> <tr> <td width=52></td> <td width=225 colspan="2" align="left"> <b>Total general</b> </td> <td align="right" style="padding-right:10"> <b><asp:label id="Total" runat=server/></b> </td> </tr>

Page 244: Presentación del tutorial de ASP

</table>

<p>

<div id="CheckoutPanel" runat="server"> <center> <asp:imagebutton borderwidth=0 OnClick="Recalculate_Click" ImageUrl='images\recalculate.gif' runat=server/> <asp:imagebutton borderwidth=0 ImageUrl='images\checkout.gif' runat=server/> <asp:imagebutton borderwidth=0 OnClick="ClearCart_Click" ImageUrl='images\clear_cart.gif' runat=server/> </center> </div>

</td> </tr> </table> </form>

</body></html>

Aplicación de un examinador de clases

La siguiente aplicación de ejemplo implementa un examinador de clases basado en .NET Framework y utiliza las API System.Reflection para recopilar información acerca de una clase. Para simplificar el código .aspx, la aplicación emplea un componente administrado que encapsula los detalles de la reflexión. La propia página .aspx depende enormemente de varios controles DataList para obtener los espacios de nombres, clases y detalles de clases. El ejemplo también muestra el uso de controles DataList anidados para procesar las listas de parámetros. Si desea ver el ejemplo, haga clic en el icono que aparece a continuación.

<%@ Page Debug="true" %><%@ Import NameSpace="ClassInfoVB" %><%@ Import NameSpace="System.Collections" %><%@ Import NameSpace="System.Collections.Specialized" %><%@ Import NameSpace="System.Reflection" %>

<html><head>

<title>Explorador de clases de .NET Framework</title> <link rel="stylesheet" href="../classstyle.css">

<script runat="server" language="VB">

public SelectedAssembly As String public SelectedNameSpace As String public ModuleName As New ArrayList()

Protected Sub Page_Load(Sender As Object, e As EventArgs)

Dim ConfigSettings As NameValueCollection = CType(Context.GetConfig("system.web/ClassBrowser"), NameValueCollection) Dim I As Integer

For I = 0 To ConfigSettings.Count - 1 ModuleName.Add(ConfigSettings(I).ToString()) Next

DisplayNamespaces()

If Request.QueryString("namespace") Is Nothing Then SelectedNameSpace = "System"

Page 245: Presentación del tutorial de ASP

Else SelectedNameSpace = Request.QueryString("namespace") End If

If Request.QueryString("assembly") Is Nothing Or Request.QueryString("assembly") = "" Then SelectedAssembly = "mscorlib" Else SelectedAssembly = Request.QueryString("assembly") End If

If Not Request.QueryString("class") Is Nothing And Not Request.QueryString("assembly") Is Nothing Then DisplayClass(Request.QueryString("assembly"), Request.QueryString("class")) Else DisplayClassList(SelectedNameSpace) End If End Sub

Private Sub DisplayNamespaces()

Dim NameSpaceList As New ArrayList() Dim NameSpaceHash As New Hashtable()

Dim Y As Integer For Y = 0 To ModuleName.Count - 1

Dim CorRuntime() As System.Reflection.Module = System.Reflection.Assembly.Load(ModuleName(y).ToString()).GetModules() Dim CorClasses() As Type = CorRuntime(0).GetTypes()

Dim X As Integer For X = 0 To CorClasses.Length - 1 If Not CorClasses(x).Namespace Is Nothing If Not NameSpaceHash.ContainsKey(CorClasses(x).Namespace) And CorClasses(x).IsPublic NameSpaceHash.Add(CorClasses(x).Namespace,"") NameSpaceList.Add(CorClasses(x).Namespace) End If End If Next Next

NameSpaceList.Sort() Namespace1.DataSource = NameSpaceList Namespace1.DataBind() End Sub

Private Sub DisplayClassList(CurrentNameSpace As String)

Dim ClassList As New ArrayList() Dim InterfaceList As New ArrayList() Dim Y As Integer

For Y = 0 To ModuleName.Count - 1

Dim CorRuntime() As System.Reflection.Module = System.Reflection.Assembly.Load(ModuleName(y).ToString()).GetModules() Dim CorClasses() As Type = CorRuntime(0).GetTypes() Dim X As Integer

For X = 0 To CorClasses.Length - 1

If CorClasses(x).Namespace = CurrentNameSpace And CorClasses(x).IsPublic Dim props As New SortTable("GetType") props("GetType") = CorClasses(x).Name

Page 246: Presentación del tutorial de ASP

props("Namespace") = CorClasses(x).Namespace props("Assembly") = CorClasses(x).Assembly.ToString()

If CorClasses(x).IsInterface InterfaceList.Add(props) Else ClassList.Add(props) End If End If Next Next

If InterfaceList.Count > 0 Then IHeader.Visible = true

If ClassList.Count > 0 Then CHeader.Visible = true

ClassList.Sort() Classes.DataSource = ClassList Classes.DataBind()

InterfaceList.Sort() Interfaces.DataSource = InterfaceList Interfaces.DataBind() End Sub

Private Sub DisplayClass(asmName As String, className As String)

If asmName Is Nothing Or asmName = "" Then DisplayClassList(SelectedNamespace) Return End If

Dim a As System.Reflection.Assembly = System.Reflection.Assembly.Load(asmName) Dim ClassType As Type = a.GetType(SelectedNameSpace.ToString() & "." & className, False, True)

If ClassType Is Nothing Then DisplayClassList(SelectedNameSpace) Return End If

Dim SubClassDetails As ArrayList = New DisplaySubClasses(ClassType, ModuleName) Dim ConstructorDetails As DisplayConstructors = New DisplayConstructors(ClassType) Dim FieldDetails As DisplayFields = New DisplayFields(ClassType) Dim PropertyDetails As DisplayProperties = new DisplayProperties(ClassType) Dim MethodDetails As DisplayMethods = new DisplayMethods(ClassType, className) Dim SuperClassDetails As DisplaySuperclasses = new DisplaySuperclasses(ClassType) Dim InterfaceDetails As DisplayInterfaces = new DisplayInterfaces(ClassType) Dim EventDetails As DisplayEvents = new DisplayEvents(ClassType)

If ConstructorDetails.Count <> 0 Then Constructors.DataSource = ConstructorDetails If SubClassDetails.Count <> 0 Then SubClasses.DataSource = SubClassDetails If FieldDetails.Count <> 0 Then Fields.DataSource = FieldDetails If PropertyDetails.Count <> 0 Then Properties.DataSource = PropertyDetails If MethodDetails.Count <> 0 Then Methods.DataSource = MethodDetails If InterfaceDetails.Count <> 0 Then Interface1.DataSource = InterfaceDetails If SuperClassDetails.Count <> 0 Then SuperClasses.DataSource = SuperClassDetails If EventDetails.Count <> 0 Then Events.DataSource = EventDetails

DataBind()

If ClassType.IsInterface Then spnClassName.InnerHtml = "Interfaz" & SelectedNameSpace & "." & className Else

Page 247: Presentación del tutorial de ASP

spnClassName.InnerHtml = "Clase" & SelectedNameSpace & "." & className End If

NameSpacePanel.Visible = False ClassPanel.Visible = True End Sub

Function GetUrl(objTable As Object) As String If TypeOf objTable Is String Then return "classbrowser.aspx?assembly=" & SelectedAssembly & "&namespace=" & SelectedNameSpace & "&class=" & objTable End If

If Not TypeOf objTable Is Hashtable Then Response.Write(objTable.GetType().ToString()) Response.End() End If

Dim table As Hashtable = CType(objTable, Hashtable) Return "classbrowser.aspx?assembly=" & table("Assembly") & "&namespace=" & table("Namespace") & "&class=" & table("GetType") End Function

</script>

</head><body> <form runat="server"> <table class="top" width=100% height="40" cellpadding=0 cellspacing=0> <tr> <td bgcolor=#000666> <b><font size=5 color=white>Explorador de clases de .NET Framework</b> </td> </tr> </table>

<table width=100% height=700 cellpadding=0 cellspacing=0> <tr> <td width=25% bgcolor=#CCCCFF valign=top > <br> <asp:DataList EnableViewState="false" runat=server id="Namespace1" RepeatLayOut="flow" ItemStyle-Font-Size="9pt" HeaderStyle-Font-Size="12pt" > <HeaderTemplate> <div left-margin="10"> <font size=4> <b>Espacios de nombres</b> </font> <br> </HeaderTemplate>

<ItemTemplate> <asp:HyperLink runat="server" text=<%# Container.DataItem %> NavigateUrl=<%# "classbrowser.aspx?namespace=" + Container.DataItem %> /> </ItemTemplate>

<SelectedItemTemplate> <b><asp:HyperLink runat=server text=<%# Container.DataItem %>/></b> </SelectedItemTemplate> </asp:DataList> <p> </td>

<td valign=top >

<div id="ClassPanel" style="margin-top:15;margin-left:10" visible="false" runat="server"> <b><font size=4 color="000666"><span style="text-indent:8" id="spnClassName" EnableViewState="false" runat="server"/></font></b>

Page 248: Presentación del tutorial de ASP

</div>

<div id="NameSpacePanel" runat="server">

<table class="main" width=100%> <tr> <td class="main_header"> <span runat=server id="CHeader" visible="false" style="text-indent:8"> <b><font size=4 color=#000666>Classes in <%= SelectedNameSpace %> </b> </font></span> </td> </tr> <tr> <td align="left"> <asp:DataList EnableViewState="false" runat=server id="Classes" RepeatColumns="3" Gridlines=None borderstyle=none borderwidth=0 > <ItemTemplate> <asp:HyperLink runat=server text=<%# CType(Container.DataItem, SortTable)("GetType") %> NavigateUrl=<%# GetUrl(Container.DataItem) %> /> </ItemTemplate> </asp:DataList> </td> </tr> </table>

<table class="main" width=100% > <tr> <td class="main_header" > <span runat=server id="IHeader" visible="false" style="text-indent:8"> <b><font size=4 color=#000666>Interfaces in <%= SelectedNameSpace %> </font> </b> </span> </td> </tr> <tr> <td align="left"> <asp:DataList EnableViewState="false" runat=server id="Interfaces" RepeatColumns="4" Gridlines=None borderstyle=none borderwidth=0 > <ItemTemplate> <asp:HyperLink runat=server text=<%# CType(Container.DataItem, SortTable)("GetType") %> NavigateUrl=<%# GetUrl(Container.DataItem) %>/> </ItemTemplate> </asp:DataList> </td> </tr> </table>

</div>

<table class="main" width=100% cellpadding=0 cellspacing=0 > <tr> <td class="main_header" valign="top" > <asp:DataList EnableViewState="false" id="Constructors" runat="server" Gridlines=None borderstyle="none" borderwidth=0 width="100%"> <HeaderTemplate> <table cellspacing=0 width="100%"> <tr><td class="class_header"><b><font size=2> Constructores </font></b></td></tr> <tr bgcolor="eeeeee"> <td width="75" > <b><u> Visibilidad </u> </td> <td width="100"> <b><u> Constructor </u> </td> <td> <b><u> Parámetros </u> </td> </tr> </HeaderTemplate>

Page 249: Presentación del tutorial de ASP

<ItemTemplate> <tr bgcolor="eeeeee"> <td width="75"> <span runat=server InnerHtml=<%# CType(Container.DataItem, SortTable)("Access") %> /> </td> <td width="100"> <span runat=server InnerHtml =<%# CType(Container.DataItem, SortTable)("Name") %> /> </td> <td width="1000"> <asp:DataList EnableViewState="false" runat=server RepeatDirection="Horizontal" RepeatLayout=Flow showfooter=true datasource=<%# CType(Container.DataItem, SortTable)("Params") %> > <HeaderTemplate> ( </HeaderTemplate> <ItemTemplate> <asp:HyperLink text=<%# CType(Container.DataItem, SortTable)("ParamType") %> NavigateUrl=<%# GetUrl(Container.DataItem) %> runat=server /> <span innerhtml=<%# CType(Container.DataItem, SortTable)("ParamName") %> runat=server /> </ItemTemplate> <SeparatorTemplate>, </SeparatorTemplate> <FooterTemplate> ) </FooterTemplate> </asp:DataList> </td> </tr> </ItemTemplate>

<FooterTemplate> </table> </FooterTemplate> </asp:DataList>

<p>

<asp:DataList EnableViewState="false" id="Fields" runat="server" Gridlines=None BorderStyle="none" width="100%" BorderWidth=0>

<HeaderTemplate> <table cellspacing=0 width="100%"> <tr><td class="class_header"><b><font size=2> Campos </font></b></td></tr> <tr bgcolor="eeeeee"> <td width="120" ><b><u> Visibilidad </td> <td width="100"><b><u> Tipo </td> <td ><b><u> Nombre </td> </tr> </HeaderTemplate>

<ItemTemplate> <tr bgcolor="eeeeee"> <td width="120"> <nobr><span InnerHTML=<%# CType(Container.DataItem, SortTable)("Access") %> runat=server /></nobr> </td> <td width="100"> <asp:HyperLink text=<%# CType(Container.DataItem, SortTable)("Type") %> NavigateUrl=<%# GetUrl(Container.DataItem) %> runat=server/> </td> <td> <span InnerHTML=<%# CType(Container.DataItem, SortTable)("Name") %> runat=server />

Page 250: Presentación del tutorial de ASP

</td> </tr> </ItemTemplate>

<FooterTemplate> </table> </FooterTemplate> </asp:DataList>

<p>

<asp:DataList EnableViewState="false" id="Events" runat="server" Gridlines=None BorderStyle="none" width="100%" BorderWidth=0>

<HeaderTemplate> <table cellspacing=0 width="100%"> <tr><td class="class_header"><b><font size=2> Eventos </font></b></td></tr> <tr bgcolor="eeeeee"> <td width="120" ><b><u> Multidifusión </td> <td width="100"><b><u> Tipo </td> <td ><b><u> Nombre </td> </tr> </HeaderTemplate>

<ItemTemplate> <tr bgcolor="eeeeee"> <td width="120"> <nobr><span InnerHTML=<%# CType(Container.DataItem, SortTable)("Access")%> runat=server /></nobr> </td> <td width="100"> <asp:HyperLink text=<%# CType(Container.DataItem, SortTable)("Type") %> NavigateUrl=<%# GetUrl(Container.DataItem) %> runat=server/> </td> <td> <span InnerHTML=<%# CType(Container.DataItem, SortTable)("Name") %> runat=server /> </td> </tr> </ItemTemplate>

<FooterTemplate> </table> </FooterTemplate> </asp:DataList>

<p>

<asp:DataList EnableViewState="false" id="Properties" runat="server" Gridlines=None BorderStyle="none" BorderWidth=0 width="100%">

<HeaderTemplate> <table cellspacing=0 width="100%" > <tr><td class="class_header"><b><font size=2> Propiedades </font></b></td></tr> <tr bgcolor="eeeeee"> <td width="75"><b><u>Visibilidad</td> <td width="100"><b><u>Tipo</td> <td width="150"><b><u>Nombre</td> <td><b><u>Accesibilidad</td> </tr> </HeaderTemplate>

<ItemTemplate> <tr bgcolor="eeeeee">

Page 251: Presentación del tutorial de ASP

<td width="75"><span InnerHTML=<%# CType(Container.DataItem, SortTable)("Visibility") %> runat=server /> </td> <td width="100"> <asp:HyperLink runat=server runat="server" text=<%# CType(Container.DataItem, SortTable)("Type") %> NavigateUrl=<%# GetUrl(Container.DataItem) %>/> </td> <td width="150"><span InnerHTML=<%# CType(Container.DataItem, SortTable)("Name")%> runat=server/> <asp:DataList EnableViewState="false" runat=server RepeatLayout="Flow" ShowFooter=true RepeatDirection="Horizontal" datasource=<%# CType(Container.DataItem, SortTable)("Params") %>> <ItemTemplate> ( <asp:HyperLink runat=server text=<%# CType(Container.DataItem, SortTable)("ParamType")%> NavigateUrl=<%# GetUrl(Container.DataItem) %> /> <span InnerHtml=<%# CType(Container.DataItem, SortTable)("ParamName") %> runat=server /> ) </ItemTemplate> </asp:DataList>

</td> <td><span InnerHTML=<%# CType(Container.DataItem, SortTable)("Access")%> runat=server/></td> </tr> </ItemTemplate>

<FooterTemplate> </table> </FooterTemplate> </asp:DataList> <p> <asp:DataList EnableViewState="false" id="Methods" runat="server" Gridlines=None borderstyle="none" borderwidth="0" width="100%">

<HeaderTemplate> <table cellspacing=0 > <tr><td class="class_header"><b><font size=2> Métodos </font></b></td></tr> <tr bgcolor="eeeeee"> <td width="75" ><b><u>Visibilidad</td> <td width="170"><b><u>Tipo devuelto </td> <td width="100"><b><u>Nombre</td> <td width="600"><b><u>Parámetros</td> </tr> </HeaderTemplate>

<ItemTemplate> <tr bgcolor="eeeeee"> <td width="75"><nobr><span runat=server InnerHtml=<%# CType(Container.DataItem, SortTable)("Access")%> /></nobr></td> <td width="100"> <asp:HyperLink runat=server NavigateUrl=<%# GetUrl(Container.DataItem) %> text=<%# CType(Container.DataItem, SortTable)("Type")%>/> </td> <td width="100"><span runat=server InnerHtml=<%# CType(Container.DataItem, SortTable)("Name")%>/></td> <td width="900"> <asp:DataList EnableViewState="false" runat=server datasource=<%# CType(Container.DataItem, SortTable)("Params") %> RepeatLayout=Flow RepeatDirection="Horizontal" showfooter=true > <HeaderTemplate> ( </HeaderTemplate> <ItemTemplate>

Page 252: Presentación del tutorial de ASP

<asp:HyperLink text=<%# CType(Container.DataItem, SortTable)("ParamType").ToString() %> NavigateUrl=<%# GetUrl(Container.DataItem) %> runat=server /> <span InnerHtml=<%# CType(Container.DataItem, SortTable)("ParamName") %> runat=server /> </ItemTemplate> <SeparatorTemplate> ,</SeparatorTemplate> <FooterTemplate> ) </FooterTemplate> </asp:DataList> </td> </tr> </ItemTemplate>

<FooterTemplate> </table> </FooterTemplate> </asp:DataList>

<p>

<asp:DataList EnableViewState="false" id="SuperClasses" style="margin-left:10" runat="server" RepeatLayout="Flow" RepeatDirection="horizontal" width="100%">

<HeaderTemplate> <font size=2><b> Jerarquía </b></font> <br> </HeaderTemplate>

<ItemTemplate> <asp:HyperLink runat=server NavigateUrl=<%# GetUrl(Container.DataItem) %> text=<%# CType(Container.DataItem, SortTable)("FullName")%> /> </ItemTemplate>

<SeparatorTemplate> <font face="Verdana" style="font-size:8pt"><nobr>---></nobr> </SeparatorTemplate> </asp:DataList>

<p>

<asp:DataList EnableViewState="false" id="Interface1" runat=server style="margin-left:10" RepeatDirection="horizontal" RepeatLayout="Flow" width="100%">

<HeaderTemplate> <font size=2><b> Implementaciones </b></font> <br> </HeaderTemplate>

<ItemTemplate> <asp:HyperLink runat=server NavigateUrl=<%# GetUrl(Container.DataItem) %> text=<%# CType(Container.DataItem, SortTable)("FullName")%> /> </ItemTemplate>

<SeparatorTemplate> <font face="Verdana" style="font-size:8pt">, </SeparatorTemplate>

</asp:DataList>

<p>

<asp:DataList EnableViewState="false" id="SubClasses" style="margin-left:10" runat=server

Page 253: Presentación del tutorial de ASP

RepeatLayout="Flow" RepeatDirection="horizontal" width="100%"> <HeaderTemplate> <font size=2> <b>Subclases implementadas por </b></font> <br> </HeaderTemplate> <ItemTemplate> <asp:HyperLink runat=server NavigateUrl=<%# GetUrl(Container.DataItem) %> text=<%# CType(Container.DataItem, SortTable)("FullName")%> /> </ItemTemplate>

<SeparatorTemplate> <font face="Verdana" style="font-size:8pt">, </SeparatorTemplate> </asp:DataList> <p> </td> </tr> </table> </td> </tr> </table> </form> </body></html>

El examinador de clases también utiliza el sistema de configuración de ASP.NET para determinar qué módulos se deben cargar y reflejar. Se asigna una sección de configuración al controlador HashtableSectionHandler, que mantiene pares clave/valor para el archivo y el nombre de ensamblado. Se pueden agregar ensamblados a esta lista si se incluye una línea en la sección de configuración de la aplicación del examinador de clases, como se indica a continuación:

<configuration> <configSections> <sectionGroup name="system.web"> <section name="ClassBrowser" type="System.Configuration.NameValueSectionHandler, System,Version=1.0.3300.0,Culture=neutral,PublicKeyToken=b77a5c561934e089"/> </sectionGroup> </configSections> <system.web> <ClassBrowser> <add key="ASP.NET Class Library" value="System.Web, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> <add key=".NET Framework class Library" value="mscorlib, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> </ClassBrowser> </system.web>

</configuration>