You are on page 1of 309

Curso de Desarrollo web con Visual Studio 2005

En este curso se tratan todas las cuestiones fundamentales que le permitirán crear
aplicaciones web con Visual Studio 2005. Al final del curso sabrá todo lo necesario
para crear sus propias aplicaciones Web orientadas a datos y con multitud de
características avanzadas.

Este curso le enseñara entre otras cosas:

· Cómo crear aplicaciones web con Visual Studio 2005


· A utilizar controles web y controles HTML
· Los métodos básicos de acceso a base de datos.
· Los controles enlazados a datos.
· A manejar las sesiones desde ASP.NET.
· El modo de crear aplicaciones con un interfaz de usuario consistente y fácil de
personalizar.
· Control de acceso de usuarios.
· Cómo exponer funcionalidad a otras aplicaciones mediante servicios Web.

Es recomendable que el alumno disponga de nociones de programación Web en otras


plataformas web como ASP, si bien no es indispensable. Para facilitar la transición de
los programadores de ASP y Visual Basic 6 al nuevo entorno, en el contenido se hace
mención a las diferencias con los lenguajes VBScript y VB6. Si no los conoce
simplemente haga caso omiso de esos comentarios.

Le recomendamos también que vea la aplicación MSDN Video, que desarrollaremos al


finalizar el curso y de la que podrá consultar su código fuente y videos explicativos.

¡Disfrute del curso!

Acerca del autor

Jose Alarcón Aguín


ASP/ASP.NET MVP

José Manuel es ingeniero superior industrial y especialista


universitario en consultoría de empresa. Es autor de varios libros,
habiendo publicado hasta la fecha cerca de 300 artículos sobre
informática e ingeniería en publicaciones especializadas. Es
colaborador habitual de MSDN y de las más importantes revistas
del sector como PC World, dotnetMania o Windows TI Magazine.

En la actualidad es socio de Grupo Femxa, "soluciones al servicio


del conocimiento", siendo también su responsable técnico. A
través de Krasis [www.krasis.com], la empresa tecnológica del
grupo, dirige para sus diferentes clientes proyectos y productos
orientados a la comunicación, la mejora de procesos y los
sistemas de teleformación.

Puede usted visitar su Blog sobre tecnología .NET en www.jasoft.org.


Contenido
Este módulo presenta con carácter general la plataforma .NET y cómo ésta se
diferencia de otros sistemas de desarrollo tradicionales, como ASP.

· Lección 1: Introducción a la plataforma .NET


o ¿Qué es la plataforma .NET?
o El entorno de ejecución CLR
· Lección 2: El lenguaje intermedio y el CLS
o El lenguaje intermedio
o La especificación común de los lenguajes .NET
o El sistema de tipos comunes
· Lección 3: La biblioteca de clases de .NET
o La BCL
o Los espacios de nombres
· Lección 4: Acceso a datos con ADO.NET
o ADO.NET
o La arquitectura de ADO.NET
o Capa conectada de datos
o Capa desconectada
· Lección 5: Aplicaciones Windows Forms
o Introducción
· Lección 6: Aplicaciones Web Forms
o Introducción
Introducción a la plataforma .NET
Simplificando mucho las cosas para poder dar una definición corta y comprensible, se
podría decir que la plataforma .NET es un amplio conjunto de bibliotecas de
desarrollo que pueden ser utilizadas por otras aplicaciones para acelerar
enormemente el desarrollo y obtener de manera automática características avanzadas
de seguridad, rendimiento, etc...

En realidad .NET es mucho más que eso ya que ofrece un entorno gestionado de
ejecución de aplicaciones, nuevos lenguajes de programación y compiladores, y
permite el desarrollo de todo tipo de funcionalidades: desde programas de consola o
servicios Windows hasta aplicaciones para dispositivos móviles, pasando por
desarrollos de escritorio o para Internet. Son estos últimos de los que nos
ocuparemos en este curso. Pero antes conviene conocer los fundamentos en los que
se basa cualquier aplicación creada con .NET, incluyendo las que nos interesan.

El entorno de ejecución CLR

.NET ofrece un entorno de ejecución para sus aplicaciones conocido como Common
Language Runtime o CLR. La CLR es la implementación de Microsoft de un estándar
llamado Common Language Infrastructure o CLI. Éste fue creado y promovido por la
propia Microsoft pero desde hace años es un estándar reconocido mundialmente por
el ECMA.

El CLR/CLI esencialmente define un entorno de ejecución virtual independiente en el


que trabajan las aplicaciones escritas con cualquier lenguaje .NET. Este entorno
virtual se ocupa de multitud de cosas importantes para una aplicación: desde la
gestión de la memoria y la vida de los objetos hasta la seguridad y la gestión de
subprocesos.
Todos estos servicios unidos a su independencia respecto a arquitecturas
computacionales convierten la CLR en una herramienta extraordinariamente útil
puesto que, en teoría, cualquier aplicación escrita para funcionar según la CLI puede
ejecutarse en cualquier tipo de arquitectura de hardware. Por ejemplo Microsoft
dispone de implementación de .NET para Windows de 32 bits, Windows de 64 bits e
incluso para Windows Mobile, cuyo hardware no tiene nada que ver con la
arquitectura de un ordenador común.
El Lenguaje Intermedio y el CLS
Al contrario que otros entornos, la plataforma .NET no está atada a un determinado
lenguaje de programación ni favorece a uno determinado frente a otros. En la
actualidad existen implementaciones para varias decenas de lenguajes que permiten
escribir aplicaciones para la plataforma .NET. Los más conocidos son Visual Basic
.NET, C# o J#, pero existen implementaciones de todo tipo, incluso de ¡COBOL!.

Lo mejor de todo es que cualquier componente creado con uno de estos lenguajes
puede ser utilizado de forma transparente desde cualquier otro lenguaje .NET.
Además, como ya se ha comentado, es posible ejecutar el código .NET en diferentes
plataformas y sistemas operativos.

¿Cómo se consigue esta potente capacidad?

Dentro de la CLI, existe un lenguaje llamado IL (Intermediate Language o Lenguaje


Intermedio) que está pensado de forma independiente al procesador en el que se
vaya a ejecutar. Es algo parecido al código ensamblador pero de más alto nivel y
creado para un hipotético procesador virtual que no está atado a una arquitectura
determinada.

Cuando se compila una aplicación escrita en un lenguaje .NET cualquiera (da igual
que sea VB, C# u otro de los soportados), el compilador lo que genera en realidad es
un nuevo código escrito en este lenguaje intermedio. Así, todos los lenguajes .NET se
usan como capa de más alto nivel para producir código IL.

Un elemento fundamental de la CLR es el compilador JIT (just-in-time). Su cometido


es el de compilar bajo demanda y de manera transparente el código escrito en
lenguaje intermedio a lenguaje nativo del procesador físico que va a ejecutar el
código.
Al final, lo que se ejecuta es código nativo que ofrece un elevado rendimiento. Esto es
cierto también para las aplicaciones Web escritas con ASP.NET y contrasta con las
aplicaciones basadas en ASP clásico que eran interpretadas, no compiladas, y que
jamás podrían llegar al nivel de desempeño que ofrece ASP.NET.

La siguiente figura muestra el aspecto que tiene el código intermedio de una


aplicación sencilla y se puede obtener usando el desemsamblador que viene con la
plataforma .NET.

Figura 1.1. Código en lenguaje intermedio obtenido con ILDASM.exe

La especificación común de los lenguajes y el sistema de tipos


comunes

Para conseguir la interoperabilidad entre lenguajes no sólo llega con el lenguaje


intermedio, sino que es necesario disponer de unas "reglas del juego" que definan un
conjunto de características que todos los lenguajes deben incorporar. A este conjunto
regulador se le denomina Common Language Specification (CLS) o, en castellano,
especificación común de los lenguajes.
Entre las cuestiones que regula la CLS se encuentran la nomenclatura, la forma de
definir los miembros de los objetos, los metadatos de las aplicaciones, etc... Una de
las partes más importantes de la CLS es la que se refiere a los tipos de datos.

Si alguna vez ha programado la API de Windows o ha tratado de llamar a una DLL


escrita en C++ desde Visual Basic 6 habrá comprobado lo diferentes que son los tipos
de datos de VB6 y de C++. Para evitar este tipo de problemas y poder gestionar de
forma eficiente y segura el acceso a la memoria, la CLS define un conjunto de tipos
de datos comunes (Common Type System o CTS) que indica qué tipos de datos se
pueden manejar, cómo se declaran y se utilizan éstos y de qué manera se deben
gestionar durante la ejecución.

Si nuestras bibliotecas de código utilizan en sus interfaces hacia el exterior datos


definidos dentro de la CTS no existirán problemas a la hora de utilizarlos desde
cualquier otro código escrito en la plataforma .NET.

Cada lenguaje .NET utiliza una sintaxis diferente para cada tipo de datos. Así, por
ejemplo, el tipo común correspondiente a un número entero de 32 bits
(System.Int32) se denomina Integer en Visual Basic .NET, pero se llama int en C#.
En ambos casos representan el mismo tipo de datos que es lo que cuenta.

Nota:
En ASP 3.0 se suele usar VBScript como lenguaje de programación. En
este lenguaje interpretado, al igual que en VB6, un Integer representaba
un entero de 16 bits. Los enteros de 32 bits eran de tipo Long. Es un
fallo muy común usar desde Visual Basic .NET el tipo Integer pensando
que es de 16 bits cuando en realidad es capaz de albergar números
mucho mayores. Téngalo en cuenta cuando empiece a programar.

Existen tipos por valor (como los enteros que hemos mencionado o las
enumeraciones) y tipos por referencia (como las clases). En el siguiente módulo se
profundiza en todas estas cuestiones.

Ver vídeo 1 de esta lección (Lenguaje intermedio)


La biblioteca de clases de .NET
Todo lo que se ha estado comentando hasta ahora en el curso constituye la base de la
plataforma .NET. Si bien es muy interesante y fundamental, por sí mismo no nos
serviría de mucho para crear programas si debiésemos crear toda la funcionalidad
desde cero.

Obviamente esto no es así, y la plataforma .NET nos ofrece infinidad de


funcionalidades "de fábrica" que se utilizan como punto de partida para crear las
aplicaciones. Existen funcionalidades básicas (por ejemplo todo lo relacionado con la
E/S de datos o la seguridad) y funcionalidades avanzadas en las que se fundamentan
categorías enteras de aplicaciones (acceso a datos, creación de aplicaciones Web...).

Toda esta funcionalidad está implementada en forma de bibliotecas de funciones que


físicamente se encuentran en diversas DLL (bibliotecas de enlazado dinámico). A su
conjunto se le denomina Base Classes Library (Biblioteca de clases base o BCL) y
forman parte integral de la plataforma .NET, es decir, no se trata de añadidos que se
deban obtener o adquirir aparte.

La siguiente figura ilustra a vista de pájaro la arquitectura conceptual de la plataforma


.NET. En ella se pueden observar los elementos que se han mencionado en apartados
anteriores (lenguajes, CLR, CLS...) y en qué lugar de se ubican las bibliotecas de
clases base:
Figura 1.2. Distintos elementos de la plataforma .NET y cómo se relacionan
entre sí.

Resulta muy útil para comprender lo explicado hasta ahora. No se preocupe si hay
elementos que no conoce, más adelante los estudiaremos todos.

Todo lo que se encuentra en la BCL forma parte de la plataforma .NET. De hecho


existe tal cantidad de funcionalidad integrada dentro de estas bibliotecas (hay
decenas de miles de clases) que el mayor esfuerzo que todo programador que se
inicia en .NET debe hacer es el aprendizaje de las más importantes. De todos modos
Visual Studio ofrece mucha ayuda contextual (documentación, Intellisense...) y una
vez que se aprenden los rudimentos resulta fácil ir avanzando en el conocimiento de
la BCL a medida que lo vamos necesitando.

Los espacios de nombres

Dada la ingente cantidad de clases que existen debe haber algún modo de
organizarlas de un modo coherente. Además hay que tener en cuenta que podemos
adquirir más funcionalidades (que se traducen en clases) a otros fabricantes, por no
mencionar que crearemos continuamente nuevas clases propias.

Para solucionar este problema existen en todos los lenguajes .NET los espacios de
nombres o namespaces.

Un espacio de nombres no es más que un identificador que permite organizar de


modo estanco las clases que estén contenidas en él así como otros espacios de
nombres.
Así, por ejemplo, todo lo que tiene que ver con el manejo de estructuras de datos
XML en la plataforma .NET se encuentra bajo el espacio de nombres System.Xml. La
funcionalidad fundamental para crear aplicaciones Web está en el espacio de nombres
System.Web. Éste a su vez contiene otros espacios de nombres más especializados
como System.Web.Caching para la persistencia temporal de datos,
System.Web.UI.WebControls, que contiene toda la funcionalidad de controles Web
para interfaz de usuario, etc...

Ver vídeo 1 de esta lección (Utilización de las clases base)


Acceso a datos con ADO.NET
El acceso a fuentes de datos es algo indispensable en cualquier lenguaje o plataforma
de desarrollo. La parte de la BCL que se especializa en el acceso a datos se denomina
de forma genérica como ADO.NET.

Si usted ha programado con Visual Basic 6.0 o con ASP, ha empleado en su código
con total seguridad la interfaz de acceso a datos conocida como ADO (ActiveX Data
Objects), puede que combinado con ODBC (Open Database Connectivity). Si además
es usted de los programadores con solera y lleva unos cuantos años en esto es
probable que haya usado RDO o incluso DAO, todos ellos métodos mucho más
antiguos.

ADO.NET ofrece una funcionalidad completamente nueva, que tiene poco que ver con
lo existente hasta la fecha en el mercado. Sin embargo, con el ánimo de retirar
barreras a su aprendizaje, Microsoft denominó a su nuevo modelo de acceso a datos
con un nombre similar y algunas de sus clases recuerdan a objetos de propósito
análogo en el vetusto ADO.

ADO.NET es un modelo de acceso mucho más orientado al trabajo desconectado de


las fuentes de datos de lo que nunca fue ADO. Si bien este último ofrecía la
posibilidad de desconectar los Recordsets y ofrecía una forma de serialización de estos
a través de las diferentes capas de una aplicación, el mecanismo no es ni de lejos tan
potente como el que nos ofrece ADO.NET.

El objeto más importante a la hora de trabajar con el nuevo modelo de acceso a datos
es el DataSet. Sin exagerar demasiado podríamos calificarlo casi como un motor de
datos relacionales en memoria. Aunque hay quien lo asimila a los clásicos Recordsets
su funcionalidad va mucho más allá como se verá en el correspondiente módulo.
Arquitectura de ADO.NET

El concepto más importante que hay que tener claro sobre ADO.NET es su modo de
funcionar, que se revela claramente al analizar su arquitectura:

Figura 1.3.- Arquitectura de ADO.NET

Existen dos capas fundamentales dentro de su arquitectura: la capa conectada y la


desconectada.

Capa conectada

La primera de ellas contiene objetos especializados en la conexión con los orígenes de


datos. Así, la clase genérica Connection se utiliza para establecer conexiones a los
orígenes de datos. La clase Command se encarga de enviar comandos de toda índole
al origen de datos. Por fin la clase DataReader está especializada en leer los
resultados de los comandos mientras se permanece conectado al origen de datos.

La clase DataAdapter hace uso de las tres anteriores para actuar de puente entre la
capa conectada y la desconectada.

Estas clases son abstractas, es decir, no tienen una implementación real de la que se
pueda hacer uso directamente. Es en este punto en donde entran en juego los
proveedores de datos. Cada origen de datos tiene un modo especial de
comunicarse con los programas que los utilizan, además de otras particularidades que
se deben contemplar. Un proveedor de datos de ADO.NET es una implementación
concreta de las clases conectadas abstractas que hemos visto, que hereda de éstas y
que tiene en cuenta ya todas las particularidades del origen de datos en cuestión.

Así, por ejemplo, las clases específicas para acceder a SQL Server se llaman
SqlConnection, SqlCommand, SqlDataReader y SqlDataAdapter y se
encuentran bajo el espacio de nombres System.Data.SqlClient. Es decir, al
contrario que en ADO clásico no hay una única clase Connection o Command que se
use en cada caso, si no que existen clases especializadas para conectarse y recuperar
información de cada tipo de origen de datos.

Nota:
El hecho de utilizar clases concretas para acceso a las fuentes de datos
no significa que no sea posible escribir código independiente del origen
de datos. Todo lo contrario. La plataforma .NET ofrece grandes
facilidades de escritura de código genérico basadas en el uso de herencia
e implementación de interfaces. De hecho la versión 2.0 de .NET ofrece
grandes novedades específicamente en este ámbito.

Existen proveedores nativos, que son los que se comunican directamente con el
origen de datos (por ejemplo el de SQL Server o el de Oracle), y proveedores
"puente", que se utilizan para acceder a través de ODBC u OLEDB cuando no existe
un proveedor nativo para un determinado origen de datos.

Nota:
Estos proveedores nativos, si bien muy útiles en determinadas
circunstancias, ofrecen un rendimiento menor debido a la capa
intermedia que están utilizando (ODBC u OLEDB). Un programador novel
puede sentir la tentación de utilizar siempre el proveedor puente para
OLEDB y así escribir código compatible con diversos gestores de datos
de forma muy sencilla. Se trata de un error y siempre que sea posible es
mejor utilizar un proveedor nativo.

Capa desconectada

Una vez que ya se han recuperado los datos desde cualquier origen de datos que
requiera una conexión ésta ya no es necesaria. Sin embargo sigue siendo necesario
trabajar con los datos obtenidos de una manera flexible. Es aquí cuando la capa de
datos desconectada entra en juego. Además, en muchas ocasiones es necesario tratar
con datos que no han sido obtenidos desde un origen de datos relacional con el que
se requiera una conexión. A veces únicamente necesitamos un almacén de datos
temporal pero que ofrezca características avanzadas de gestión y acceso a la
información.

Por otra parte las conexiones con las bases de datos son uno de los recursos más
escasos con los que contamos al desarrollar. Su mala utilización es la causa más
frecuente de cuellos de botella en las aplicaciones y de que éstas no escalen como es
debido. Esta afirmación es especialmente importante en las aplicaciones Web en las
que se pueden recibir muchas solicitudes simultáneas de cualquier parte del mundo.
Finalmente otro motivo por el que es importante el uso de los datos desconectado de
su origen es la transferencia de información entre capas de una aplicación. Éstas
pueden encontrarse distribuidas por diferentes equipos, e incluso en diferentes
lugares del mundo gracias a Internet. Por ello es necesario disponer de algún modo
genérico y eficiente de poder transportar los datos entre diferentes lugares, utilizarlos
en cualquiera de ellos y posteriormente tener la capacidad de conciliar los cambios
realizados sobre ellos con el origen de datos del que proceden.

Todo esto y mucho más es lo que nos otorga el uso de los objetos DataSet. Es obvio
que no se trata de tareas triviales, pero los objetos DataSet están pensados y
diseñados con estos objetivos en mente. Como podremos comprobar más adelante en
este curso es bastante sencillo conseguir estas funcionalidades tan avanzadas y
algunas otras simplemente usando de manera adecuada este tipo de objetos.

Nota:
Otra interesante característica de los DataSet es que permiten gestionar
simultáneamente diversas tablas (relaciones) de datos, cada una de un
origen diferente si es necesario, teniendo en cuenta las restricciones y
las relaciones existentes entre ellas.

Los DataSet, como cualquier otra clase no sellada de .NET, se pueden extender
mediante herencia. Ello facilita una técnica avanzada que consiste en crear tipos
nuevos de DataSet especializados en la gestión de una información concreta (por
ejemplo un conjunto de tablas relacionadas). Estas nuevas tipos clases se denominan
genéricamente DataSet Tipados, y permiten el acceso mucho más cómodo a los
datos que representan, verificando reglas de negocio, y validaciones de tipos de datos
más estrictas.
Aplicaciones Windows Forms
Las aplicaciones de escritorio son aquellas basadas en ventanas y controles comunes
de Windows que se ejecutan en local. Son el mismo tipo de aplicaciones que antes
construiríamos con Visual Basic 6 u otros entornos similares.

En la plataforma .NET el espacio de nombres que ofrece las clases necesarias para
construir aplicaciones de escritorio bajo Windows se denomina Windows Forms. Este
es también el nombre genérico que se le otorga ahora a este tipo de programas
basados en ventanas.

Windows Forms está constituido por multitud de clases especializadas que ofrecen
funcionalidades para el trabajo con ventanas, botones, rejillas, campos de texto y
todo este tipo de controles habituales en las aplicaciones de escritorio.

Visual Studio ofrece todo lo necesario para crear visualmente este tipo de programas,
de un modo similar aunque más rico al que ofrecía el entorno de desarrollo integrado
de Visual Basic.
Figura 1.4.- Diseñador de interfaces de aplicaciones de escritorio con
Windows Forms en Visual Studio 2005.

Al contrario que en VB6, .NET proporciona control sobre todos los aspectos de las
ventanas y controles, no dejando nada fuera del alcance del programador y otorgando
por lo tanto la máxima flexibilidad. Los formularios (ventanas) son clases que heredan
de la clase base Form, y cuyos controles son miembros de ésta. De hecho se trata
únicamente de código y no es necesario (aunque sí muy recomendable) emplear el
diseñador gráfico de Visual Studio para crearlas.

Este es el aspecto que presenta parte del código que genera la interfaz mostrada en
la anterior figura:
Figura 1.5.- Código autogenerado por Visual Studio para crear la interfaz de
la figura anterior.

Al contrario que en Visual Basic tradicional, en donde siempre existían instancias por
defecto de los formularios que podíamos usar directamente, en .NET es necesario
crear un objeto antes de poder hacer uso de los formularios:

Dim frm As New MiFormulario


frm.Show()

Todos los controles heredan de una clase Control por lo que conservan una serie de
funcionalidades comunes muy interesantes, como la capacidad de gestionarlos en el
diseñador (moviéndolos, alineándolos...), de definir márgenes entre ellos o hacer que
se adapten al tamaño de su contenedor.

Este tipo de aplicaciones se salen del ámbito de este curso por lo que no se
profundizará más en ellas.
Aplicaciones Web Forms
Tradicionalmente las aplicaciones Web se han desarrollado siguiendo un modelo mixto
que intercalaba código HTML y JavaScript propio de páginas Web (parte cliente), junto
con código que se ejecutaría en el servidor (parte servidora). Este modelo contrastaba
por completo con el modelo orientado a eventos seguido por las principales
herramientas de desarrollo de aplicaciones de escritorio.

En el modelo orientado a eventos se define la interfaz de usuario colocando controles


en un contenedor y se escribe el código que actuará como respuesta a las
interacciones de los usuarios sobre estos controles. Si conoce el diseñador de VB6 o
de Windows Forms mencionado en el apartado anterior sabe exactamente a qué nos
referimos.

Hacer esto en una aplicación de escritorio no tiene mayor dificultad ya que todo el
código se ejecuta en el mismo lugar. La principal característica de las aplicaciones
Web sin embargo es que se la interfaz de usuario (lo que los usuarios de la aplicación
ven) se ejecuta en un lugar diferente al código de la aplicación que reside en un
servidor. Para mayor desgracia estas aplicaciones se basan en el uso del protocolo
HTTP que es un protocolo sin estado y que no conserva la conexión entre dos
llamadas consecutivas.

Por ejemplo, el siguiente código ilustra el código que es necesario escribir en ASP
para disponer de una página que rellena una lista de selección con unos cuantos
nombres (podrían salir de una base de datos y aún sería más complicado), y que
dispone de un botón que escribe un saludo para el nombre que se haya elegido de la
lista.
Figura 1.6.- Código ASP sencillo que genera una lista de selección y saluda al
presionar un botón.

Obviamente se podría haber simplificado sin enviar el formulario al servidor usando


JavaScript en el cliente para mostrar el saludo, pero la intención es ilustrar la mezcla
de código de cliente y de servidor que existe en este tipo de aplicaciones.

Las principales desventajas de este tipo de codificación son las siguientes:

1. No existe separación entre el diseño y la lógica de las aplicaciones. Si


queremos cambiar sustancialmente la apariencia de la aplicación Web lo
tendremos bastante complicado puesto que el código del servidor está
mezclado entre el HTML.
2. En ASP clásico no existe el concepto de control para la interfaz de usuario.
Lo único que hay es HTML y JavaScript que se deben generar desde el servidor.
En el ejemplo de la figura para generar un control de lista con unos elementos
no podemos asignar una propiedad de la lista (porque no existe tal lista), sino
que tenemos que crear un bucle que genere los elementos HTML necesarios
para generarla. Tampoco disponemos de un diseñador visual que nos permita
gestionar los controles y elementos HTML existentes, y menos cuando éstos se
encuentran mezclados con el código del servidor.
3. No disponemos de forma de detectar en el servidor que se ha realizado
algo en el cliente. El cliente se encuentra desconectado desde el momento en
que se termina de devolver la página. Sólo se recibe información en el servidor
cuando se solicita una nueva página o cuando se envía un formulario tal y como
se hace en el ejemplo, debiéndonos encargar nosotros de averiguar si la
petición es la primera vez que se hace o no, y de dar la respuesta adecuada. En
cualquier caso es mucho menos intuitivo que el modelo de respuesta a eventos
de una aplicación de escritorio.
4. No existe constancia del estado de los controles de cada página entre
las llamadas. En cada ejecución de la página tendremos que recrear
completamente la salida. Por ejemplo si presionamos el botón “Di Hola”
tenemos que escribir además de la etiqueta “Hola, nombre” el resto de la
pantalla, incluyendo la lista con todos los nombres dejando seleccionado el
mismo que hubiese antes. Si estos nombres viniesen de una base de datos esto
puede ser todavía más ineficiente y tendremos que buscar métodos alternativos
para generarlos ya que en ASP tampoco se deben almacenar en los objetos de
sesión y/o aplicación Recordsets resultado de consultas.
5. No existe el concepto de Propiedad de los controles. En una aplicación
Windows asignamos el texto de un campo usando una propiedad (por ejemplo
Text1.Text = "Hola") y ésta se asigna y permanece en la interfaz sin que
tengamos que hacer nada. En una aplicación Web clásica tenemos que
almacenarlas en algún sitio (una variable de sesión o un campo oculto) para
conservarlas entre diferentes peticiones de una misma página.
6. Los controles complejos no tienen forma de enviar sus valores al
servidor. Si intentamos crear una interfaz avanzada que utilice tablas y otros
elementos que no son controles de entrada de datos de formularios de HTML
tendremos que inventarnos mecanismos propios para recoger esos datos y
enviarlos al servidor.

La principal aportación de ASP.NET al mundo de la programación es que ha llevado a


la Web el paradigma de la programación orientada a eventos propia de aplicaciones
de escritorio, ofreciendo:

· Separación entre diseño y lógica.


· Componentes de interfaz de usuario, tanto estándar como de terceras empresas
o propios.
· Diseñadores gráficos.
· Eventos.
· Estado.
· Enlazado a datos desde la interfaz.

Esto como se verá marca un antes y un después en la programación para Internet.

Ver vídeo 1 de esta lección (Diseñador de formularios Web)


Nota: Para mayor simplicidad los videos de este módulo están realizados con aplicaciones de
consola. Si quiere reproducir los ejemplos mostrados en estos videos en una aplicación web
puede modificar las instrucciones Console.WriteLine por Response.Write.

Contenido

· Lección 1: El sistema de tipos


o Tipos primitivos
o Variables y constantes
o Enumeraciones
o Arrays (matrices)

· Lección 2: Clases y estructuras


o Clases
o Definir una clase
o Instanciar una clase
o Estructuras
o Accesibilidad
o Propiedades
o Interfaces

· Lección 3: Manejo de excepciones


o Manejo de excepciones

· Lección 4: Eventos y delegados


o Eventos
o Definir y producir eventos en una clase
o Delegados
o Definir un evento bien informado

· Lección 5: Atributos
o Atributos

Ver video de introducción (Crear un proyecto de consola)


Introducción

En esta primera lección veremos los tipos de datos que .NET Framework pone a
nuestra disposición, así mismo veremos las diferencias con respecto a los tipos de
datos de VBScript/VB6 y las equivalencias entre los tipos de ambos entornos, de esta
forma nos resultará más fácil familiarizarnos. Aunque no debemos olvidar que en .NET
los tipos de datos tienen un tratamiento, en algunos casos, especial que pueden
llevarnos a confusión, por tanto en los casos que pueda existir esa posibilidad de
funcionamiento diferente, veremos ejemplos de cómo los manejábamos en
VBScript/VB6 y cómo tendremos que usarlos desde Visual Basic .NET.

A continuación daremos un repaso a conceptos básicos o elementales sobre los tipos


de datos, que si bien nos serán familiares, es importante que lo veamos para poder
comprender mejor cómo están definidos y organizados los tipos de datos en .NET y de
paso veremos las equivalencias con respecto a VBScript/VB6.

Tipos de datos de .NET


Visual Basic .NET está totalmente integrado con .NET Framework, por tanto los tipos
de datos que podremos usar con este lenguaje serán los definidos en este "marco de
trabajo", por este motivo vamos a empezar usando algunas de las definiciones que
nos encontraremos al recorrer la documentación que acompaña a este lenguaje de
programación.

En los siguientes enlaces tenemos los temas a tratar en esta primera lección del
módulo sobre las características del lenguaje Visual Basic .NET
· Tipos primitivos
o Sufijos o caracteres y símbolos identificadores para los tipos
o Tipos por valor y tipos por referencia

· Variables y constantes
o Consejo para usar las constantes
o Declarar variables
o Declarar variables y asignar el valor inicial
o El tipo de datos Char
o Obligar a declarar las variables con el tipo de datos
o Aplicar Option Strict On a un fichero en particular
o Aplicar Option Stict On a todo el proyecto
o Más opciones aplicables a los proyectos

· Enumeraciones: Constantes agrupadas


o El nombre de los miembros de las enumeraciones
o Los valores de una enumeración no son simples números

· Arrays (matrices)
o Declarar e inicializar un array
o Cambiar el tamaño de un array
o Eliminar el contenido de un array
o Los arrays son tipos por referencia
Tipos primitivos

Veamos en la siguiente tabla los tipos de datos definidos en .NET Framework y los
alias utilizados en Visual Basic 2005, así como el equivalente de VBScript/VB6.

.NET Framework VB.NET VBScript/VB6


System.Boolean Boolean Boolean *
System.Byte Byte Byte
System.Int16 Short Integer
System.Int32 Integer Long
System.Int64 Long N.A.
System.Single Single Single
System.Double Double Double
System.Decimal Decimal Currency *
System.Char Char N.A. (ChrW)
System.String String String *
System.Object Object Variant / Object *
System.DateTime Date Date *

System.SByte SByte N.A.


System.UInt16 UShort N.A.
System.UInt32 UInteger N.A.
System.UInt64 ULong N.A.

Tabla 1.1. Tipos de datos y equivalencia entre lenguajes


En la columna de equivalencias con VBScript/VB6 tenemos algunos indicados con
N.A., estos tipos no tienen equivalencia. Por otro lado, los marcados con un asterisco
(*) no tienen equivalencia directa, pero el o los tipos indicados serían los que más se
aproximarían.

El caso del tipo String es un caso especial, realmente un String de .NET es casi como
uno de VBScript/VB6, con la diferencia de que las cadenas en .NET son inmutables.
Esto quiere decir que una vez que se han creado no se pueden modificar, y en caso
de que queramos cambiar el contenido, .NET se encarga de usar la anterior y crear
una nueva cadena. Por tanto si usamos las cadenas para realizar concatenaciones
(unión de cadenas para crear una nueva), el rendimiento es inferior incluso al que
teníamos en VBScript/VB6. Existe una clase en .NET que es ideal para estos casos y
cuyo rendimiento es superior al de VBScript/VB6: la clase StringBuilder que ya
hemos visto someramente en un vídeo del módulo anterior.

Las últimas filas mostradas en la tabla son tipos especiales que si bien son parte del
sistema de tipos comunes (CTS) no forman parte de la Common Language
Specification (CLS), es decir la especificación común para los lenguajes "compatibles"
con .NET. Por tanto, si queremos crear aplicaciones que puedan interoperar con todos
los lenguajes de .NET, no debemos usar estos tipos como valores de devolución de
funciones ni como tipo de datos usado en los argumentos de las funciones,
propiedades o procedimientos públicos.

Los tipos mostrados en la tabla 1 son los tipos primitivos de .NET y por extensión de
Visual Basic 2005, es decir son tipos "elementales" para los cuales cada lenguaje
define su propia palabra clave equivalente con el tipo definido en el CTS de .NET
Framework. Todos estos tipos primitivos podemos usarlos tanto por medio de los
tipos propios de Visual Basic, los tipos definidos en .NET o bien como literales. Por
ejemplo, podemos definir un número entero literal indicándolo con el sufijo I: 12345I
o bien asignándolo a un valor de tipo Integer o a un tipo Sytem.Int32 de .NET. La
única excepción de los tipos mostrados en la tabla 1 es el tipo de datos Object, este
es un caso especial del que nos ocuparemos en la próxima lección.

Sufijos o caracteres y símbolos identificadores para los tipos

Cuando usamos valores literales numéricos en Visual Basic 2005, el tipo de datos que
por defecto le asigna el compilador es el tipo Double. Si nuestra intención es utilizar
sin embargo un tipo de datos diferente, podemos indicarlo añadiendo una letra como
sufijo al tipo. Por ejemplo para indicar que queremos considerar un valor entero
podemos usar la letra I o el signo %, de igual forma, un valor de tipo entero largo
(Long) lo podemos indicar usando L o &, en la siguiente tabla podemos ver los
caracteres o letra que podemos usar como sufijo en un literal numérico para que el
compilador lo identifique sin ningún lugar a dudas.

Tipo de datos Símbolo Carácter


Short N.A. S
Integer % I
Long & L
Single ! F
Double # R
Decimal @ D
UShort N.A. US
UInteger N.A. UI
ULong N.A. UL

Tabla 1.2. Sufijos para identificar los tipos de datos

El uso de estos caracteres nos puede resultar de utilidad particularmente para los
tipos de datos que no se pueden convertir en un valor doble.

Por ejemplo, si queremos asignar este valor literal a un tipo Decimal:


12345678901234567890, tal como vemos en la Figura 2, el IDE de Visual Studio
2005 nos indicará que existe un error de desbordamiento (Overflow) ya que esa cifra
es muy grande para usarlo como valor Double, pero si le agregamos el sufijo D o @
ya no habrá dudas de que estamos tratando con un valor Decimal.

Figura 2.1. Error de desbordamiento al intentar asignar un valor Double a


una variable Decimal

Tipos por valor y tipos por referencia

Los tipos de datos de .NET los podemos clasificar en dos grupos:

· Tipos por valor


· Tipos por referencia

Los tipos por valor son tipos de datos cuyo valor se almacena en la pila o en la
memoria "cercana", como los numéricos que hemos visto. Podemos decir que el
acceso al valor contenido en uno de estos tipos es directo, es decir se almacena
directamente en la memoria reservada para ese tipo y cualquier cambio que hagamos
lo haremos directamente sobre dicho valor. De igual forma cuando copiamos valores
de un tipo por valor a otro, estaremos haciendo copias independientes.
Por otro lado, los tipos por referencia se almacenan en el "montículo" (heap) o
memoria "lejana". A diferencia de los tipos por valor, los tipos por referencia lo único
que almacenan es una referencia (o puntero) al valor asignado. Si hacemos copias de
tipos por referencia, realmente lo que copiamos es la referencia propiamente dicha,
pero no el contenido.

Estos dos casos los veremos en breve con más detalle.

Ver video 1 de esta lección (Tipos de datos primitivos)


Ver video 2 de esta lección (Tipos por valor y por referencia)
Variables y constantes

Disponer de todos estos tipos de datos no tendría ningún sentido si no los pudiéramos
usar de alguna otra forma que de forma literal. Y aquí es donde entran en juego las
variables y constantes.

Consejo para usar las constantes

Siempre que tengamos que indicar un valor constante, ya sea para indicar el máximo
o mínimo permitido en un rango de valores o para comprobar el término de un bucle,
deberíamos usar una constante en lugar de un valor literal. De esta forma si ese valor
lo usamos en varias partes de nuestro código, si en un futuro decidimos que dicho
valor debe ser diferente, nos resultará más fácil realizar un solo cambio que cambiarlo
en todos los sitios en los que lo hemos usado. Además de que de esta forma nos
aseguramos de que el cambio se realiza adecuadamente y no tendremos que
preocuparnos de las consecuencias derivadas de no haber hecho el cambio en todos
los sitios que deberíamos.

Pero esto no es algo nuevo, las constantes se definen de la misma forma que en
VBScript/VB6, salvo que ahora podemos obligarnos a indicar el tipo de datos que esa
constante va a contener. Esto lo veremos en la siguiente sección.

Declarar variables

La declaración de las variables en Visual Basic 2005 se hace de la misma forma que
en VBScript/VB6/VB6, o casi, las excepciones vienen dadas, como hemos comentado
antes, de la posibilidad de obligar a definir el tipo de cada variable y de cómo
podemos definir más de una variable en una misma instrucción Dim.
Para no embrollar mucho la cosa, veamos ejemplos de cómo definir o declarar
variables siguiendo las buenas costumbres, es decir, indicando siempre el tipo de
datos de la variable.

En VB6 era posible definir más de una variable en una misma instrucción Dim, aunque
dependiendo de cómo hiciéramos esa declaración podía ser que, normalmente para
nuestro pesar, que no todas las variables fuesen del tipo que "supuestamente"
habíamos querido indicar.
Por ejemplo, con esta declaración:

Dim a, b, c As Integer

A primera vista estamos declarando tres variables de tipo Integer, pero realmente
solo declara con el tipo indicado a la última variable, las otras dos, se declaran con
tipo Variant o el tipo de datos predefinido (que es el que tiene por defecto todas las
variables en VBScript/VB6 para ASP clásico).

En Visual Basic 2005 al usar esa misma línea de código estamos declarando tres
variables de tipo Integer, esto es algo que debemos tener en cuenta, sobre todo si
nuestra intención era hacer precisamente lo que VB6, hacía, es decir, declarar dos
variables de tipo Variant y una de tipo Integer.

Declarar variables y asignar el valor inicial

En Visual Basic 2005 también podemos inicializar una variable con un valor distinto al
predeterminado, que en los tipos numéricos es un cero, en las fechas es el 1 de enero
del año 1 a las doce de la madrugada (#01/01/0001 12:00:00AM#) y en la
cadenas es un valor nulo (Nothing), para hacerlo, simplemente tenemos que indicar
ese valor, tal como veremos es muy parecido a como se declaran las constantes. Por
ejemplo:

Dim a As Integer = 10

En esa misma línea podemos declarar y asignar más variables, pero todas deben
estar indicadas con el tipo de datos:

Dim a As Integer = 10, b As Integer = 25

Por supuesto, el tipo de datos puede ser cualquiera de los tipos primitivos:

Dim a As Integer = 10, b As Integer = 25, s As String = "Hola"

Aunque para que el código sea más legible, y fácil de depurar, no deberíamos mezclar
en una misma instrucción Dim más de un tipo de datos.

Nota:
Es importante saber que en las cadenas de Visual Basic 2005 el valor de
una variable de tipo String no inicializada NO es una cadena vacía como
ocurría en VBScript/VB6, sino un valor nulo (Nothing).

El tipo de datos Char

En Visual Basic 2005 podemos declarar valores de tipo Char, este tipo de datos es un
carácter Unicode y podemos declararlo y asignarlo a un mismo tiempo. El problema
con el que nos podemos encontrar es a la hora de indicar un carácter literal.
Si bien en VBScript/VB6 no existe el tipo de datos Char, si podemos convertir un valor
numérico en un carácter (realmente en una cadena) o bien podemos convertir un
carácter en su correspondiente valor numérico.

Dim c As String

c = Chr(65)

Dim n As Integer

n = Asc("A")

En Visual Basic 2005 también podemos usar esas mismas funciones, aunque en el
caso de Chr, el valor que devuelve esta función es un valor de tipo Char, no de tipo
String como ocurre en VBScript/VB6, pero debido a que un valor de tipo Char se
puede convertir en una cadena, podemos hacer una asignación como la mostrada en
el código anterior sin ningún tipo de problemas.

Si nuestra intención es asignar un valor Char a una variable, además de la función


Chr, podemos hacerlo con un literal, ese valor literal estará encerrado entre comillas
dobles, (al igual que una cadena), aunque para que realmente sea un carácter
debemos agregarle una c justo después del cierre de las comillas dobles:

Dim c As Char = "A"c

Obligar a declarar las variables con el tipo de datos

La obligatoriedad, a la que hacíamos referencia anteriormente, de declarar las


variables y constantes con el tipo de datos adecuado, la podemos aplicar a todo el
proyecto o a un módulo en particular. Para ello tenemos que usar la instrucción
Option Strict On, una vez indicado, se aplicará a todo el código, no solo a las
declaraciones de variable, constantes o al tipo de datos devuelto por las funciones y
propiedades, sino también a las conversiones y asignaciones entre diferentes tipos de
datos.

No debemos confundir Option Strict con el clásico Option Explicit de VB. Este
último, al igual que en VBScript/VB6, nos obliga a declarar todas las variables,
mientras que el primero lo que hace es obligarnos a que esas declaraciones tengan un
tipo de datos.
Tanto una como la otra tienen dos estados: conectado o desconectado dependiendo
de que agreguemos On u Off respectivamente.

Como recomendación para buenas prácticas, debemos "conectar" siempre estas dos
opciones, si bien Option Explicit On ya viene como valor por defecto, cosa que no
ocurre con Option Strict, que por defecto está desconectado.

Aplicar Option Strict On a un fichero en particular

Cuando en VBScript/VB6 agregábamos un nuevo formulario, módulo BAS o módulo de


clase, se agregaba automáticamente la instrucción Option Explicit, en Visual Basic
2005 esta opción está predefinida y no se agrega a ningún módulo, pero eso no
significa que no se aplique, aunque siempre podemos escribir esas instrucciones (con
el valor On al final) en cada uno de los módulos o ficheros de código que agreguemos
a nuestro proyecto. Lo mismo podemos hacer con Option Strict On, en caso de que
nos decidamos a hacerlo, esas líneas de código deben aparecer al principio del fichero
y solamente pueden estar precedidas de comentarios (instrucciones REM o líneas
iniciadas con una comilla simple).

En la Figura 2 mostrada anteriormente tenemos una captura del editor de Visual Basic
2005 en la que hemos indicado que queremos tener comprobación estricta.

Aplicar Option Strict On a todo el proyecto

También podemos hacer que Option Strict funcione igual que Option Explicit, es decir,
que esté activado a todo el proyecto, en este caso no tendríamos que indicarlo en
cada uno de los ficheros de código que formen parte de nuestro proyecto, si bien
solamente será aplicable a los que no tengan esas instrucciones. Aclaremos esto
último: si Option Strict (u Option Explicit) está definido de forma global al proyecto,
podemos desactivarlo en cualquiera de los ficheros, para ello simplemente habría que
usar esas declaraciones pero usando Off en lugar de On. De igual forma, si ya está
definido globalmente y lo indicamos expresamente, no se producirá ningún error. Lo
importante aquí es saber que siempre se usará el estado indicado en cada fichero,
independientemente de cómo lo tengamos definido para el ámbito del proyecto.

Para que siempre se usen estas asignaciones en todo el proyecto, vamos a ver cómo
indicarlo en el entorno de Visual Studio 2005.

Abrimos Visual Studio 2005 y una vez que se haya cargado, (no hace falta crear
ningún nuevo proyecto, de este detalle nos ocuparemos en breve), seleccionamos la
opción Herramientas>Opciones... se mostrará un cuadro de diálogo y del panel
izquierdo seleccionamos la opción Proyectos y soluciones, la expandimos y
seleccionamos Valores predeterminados de VB y veremos ciertas opciones, tal
como podemos comprobar en la Figura 2.2:
Figura 2.2. Opciones de proyectos (opciones mínimas)

Nota:
La figura mostrada corresponde a la versión Visual Web Developer de
Visual Studio. El contenido de su pantalla variará dependiendo de la
versión de Visual Studio 2005 de la que disponga.

De la lista despegable Option Strict, seleccionamos On. Por defecto ya estarán


seleccionadas las opciones On de Option Explicit y Binary de Option Compare,
por tanto no es necesario realizar ningún cambio más, para aceptar los cambios y
cerrar el cuadro de diálogo, presionamos el botón Aceptar.

Nota:
Aunque en esta captura muestre: C:\vbexpB1 en Default project
location, salvo que lo cambiemos, aparecerá el path por defecto dentro
de Mis documentos.

Como podemos observar, aparecen pocas opciones, si bien podemos hacer que se
muestren todas las disponibles, para hacerlo, debemos marcar la casilla que está en
la parte inferior izquierda en la que podemos leer: Mostrar todas las
configuraciones, al seleccionar esa opción nos mostrará un número mayor de
opciones, tal como podemos ver en la Figura 2.3:
Figura 2.3. Opciones de proyectos (todas las opciones)

Desde este momento el compilador de VB se volverá estricto en todo lo relacionado a


las declaraciones de variables y conversiones, tal como vemos en la Figura 2.4 al
intentar declarar una variable sin indicar el tipo de datos.

Figura 2.4. Aviso de Option Strict al declarar una variable sin tipo
Nota:
Una de las ventajas del IDE (Integrated Development Environment,
entorno de desarrollo integrado) de Visual Studio 2005 es que nos avisa
al momento de cualquier fallo que cometamos al escribir el código, este
"pequeño" detalle, aunque alguna veces puede llegar a parecer
fastidioso, nos facilita la escritura de código, ya que no tenemos que
esperar a realizar la compilación para que tengamos constancia de esos
fallos.

Más opciones aplicables a los proyectos

Aunque en estos módulos no trataremos a fondo el entorno de desarrollo, ya que la


finalidad de este curso es tratar más en el código propiamente dicho, vamos a
mostrar otro de los sitios en los que podemos indicar que se haga una comprobación
estricta de tipos y, como veremos, también podremos forzar algunas "nuevas
peculiaridades" de Visual Basic 2005, todas ellas relacionadas con el tema que
estamos tratando.

Cuando tengamos un proyecto cargado en el IDE de Visual Studio, (pronto veremos


cómo crear uno), podemos mostrar las propiedades del proyecto, para ello
seleccionaremos del menú Proyecto la opción Propiedades de
<NombreDelProyecto> y tendremos un cuadro de diálogo como el mostrado en la
Figura 2.5.
Figura 2.5. Ficha Compilar de las opciones del proyecto actual

Seleccionando la ficha Compilar, además de las típicas opciones de Option Strict,


Option Explicit y Option Compare, (estas asignaciones solo serán efectivas para el
proyecto actual), tendremos cómo queremos que reaccione el compilador si se cumple
algunas de las condiciones indicadas. Entre esas condiciones, tenemos algo que
muchos desarrolladores de Visual Basic siempre hemos querido tener: Que nos avise
cuando una variable la hemos declarado pero no la utilizamos (Variable local no
utilizada). Al tener marcada esta opción (normalmente como una Advertencia), si
hemos declarado una variable y no la usamos en el código, (siempre que no le
hayamos asignado un valor al declararla), nos avisará, tal como podemos ver en la
Figura 2.6:

Figura 2.6. Aviso de variable no usada

Ver video 1 de esta lección (Declarar variables)


Ver video 2 de esta lección (Definir constantes)
Enumeraciones: Constantes agrupadas

Una enumeración es un grupo de constantes que están relacionadas entre sí y que


queremos utilizar para restringir el rango de posibles valores para una variable o
parámetro.

En Visual Basic 2005 las enumeraciones pueden ser de cualquier tipo numérico
entero, incluso enteros sin signo, aunque el valor predefinido es el tipo Integer.

Podemos declarar una enumeración de varias formas:

1- Simplemente indicando el nombre de la enumeración y el de sus valores:

Enum Colores

Rojo

Verde

Azul

End Enum

En este primer caso, el tipo de datos de cada miembro de la enumeración será


Integer.

2- Indicando el tipo de datos que realmente se utilizará:

Enum Colores As Long


Rojo

Verde

Azul

End Enum

En este segundo caso, el valor máximo que podemos asignar a los miembros de una
enumeración será el que pueda contener un tipo de datos Long.

3- Indicando el atributo FlagsAttibute, (realmente no hace falta indicar el sufijo


Attribute cuando usamos los atributos) de esta forma podremos indicar unos valores
concretos para los miembros de la enumeración. Se usa también para indicar valores
que se pueden "sumar" o complementar entre sí, pero sin perder el nombre, en breve
veremos qué significa esto de "no perder el nombre".

<Flags()> _

Enum Colores As Byte

Rojo = 1

Verde = 2

Azul = 4

End Enum

Nota:
Los atributos los veremos con más detalle en otra lección de este mismo
módulo.

El nombre de los miembros de las enumeraciones

Tanto si indicamos o no el atributo Flags a una enumeración, la podemos usar de esta


forma:

Dim c As Colores = Colores.Azul Or Colores.Rojo

Es decir, podemos "sumar" los valores definidos en la enumeración.

Como hemos comentado, las enumeraciones son constantes con nombres, pero esta
definición se queda algo corta. De hecho, podemos saber "el nombre" de un valor de
una enumeración, para ello tendremos que usar el método ToString, (el cual se usa
para convertir en una cadena cualquier valor numérico).
Por ejemplo, si tenemos la siguiente asignación:

Dim s As String = Colores.Azul.ToString

La variable s contendrá la palabra "Azul" no el valor 4.

Esto es aplicable a cualquier tipo de enumeración, se haya o no usado el atributo


FlagsAttribute.

Una vez aclarado este comportamiento de las enumeraciones en Visual Basic 2005,
veamos que es lo que ocurre cuando sumamos valores de enumeraciones a las que
hemos aplicado el atributo Flags y a las que no se lo hemos aplicado. Empecemos por
este último caso.

Si tenemos este código:

Enum Colores As Byte

Rojo = 1

Verde = 2

Azul = 4

End Enum

Dim c As Colores = Colores.Azul Or Colores.Rojo

Dim s As String = c.ToString

El contenido de la variable s será "5", es decir, la representación numérica del valor


contenido: 4 + 1, ya que el valor de la constante Azul es 4 y el de la constante Rojo
es 1.

Pero si ese mismo código lo usamos de esta forma (aplicando el atributo Flags a la
enumeración):

<Flags()> _

Enum Colores As Byte

Rojo = 1

Verde = 2

Azul = 4
End Enum

Dim c As Colores = Colores.Azul Or Colores.Rojo

Dim s As String = c.ToString

El contenido de la variable s será: "Rojo, Azul", es decir, se asignan los nombres de


los miembros de la enumeración que intervienen en ese valor, no el valor "interno".

Los valores de una enumeración no son simples números

Los miembros de las enumeraciones realmente son valores de un tipo de datos entero
(en cualquiera de sus variedades) tal como podemos comprobar en la Figura 2.7:

Figura 2.7. Los tipos subyacentes posibles de una enumeración

Por tanto cabe pensar que podemos usar cualquier valor para asignar a una variable
declarada como una enumeración, al menos si ese valor está dentro del rango
adecuado. Es decir, en el ejemplo de la figura usar un 1 en lugar de Colores.Rojo.

En Visual Basic 2005 esto no es posible, al menos si lo hacemos de forma "directa" y


con Option Strict conectado, ya que recibiremos un error indicándonos que no
podemos convertir, por ejemplo, un valor entero en un valor del tipo de la
enumeración. En la Figura 2.8 podemos ver ese error al intentar asignar el valor 3 a
una variable del tipo Colores (definida con el tipo predeterminado Integer).
Figura 2.8. Error al asignar un valor "normal" a una variable del tipo Colores

El error nos indica que no podemos realizar esa asignación, pero el entorno integrado
de Visual Studio 2005 también nos ofrece alternativas para que ese error no se
produzca, esa ayuda se obtiene presionando el signo de admiración que tenemos
justo donde está el cursor del mouse, pero no solo nos dice cómo corregirlo, sino que
también nos da la posibilidad de que el propio IDE se encargue de hacerlo, tal como
podemos apreciar en la Figura 2.9.

Figura 2.9. Opciones de corrección de errores

Lo único que tendríamos que hacer es presionar la sugerencia de corrección, que en


este caso es la única que hay, pero en otros casos pueden ser varias las opciones y
tendríamos que elegir la que creamos adecuada.

El código final (una vez corregido) quedaría de la siguiente forma:

Dim c As Colores = CType(3, Colores)

CType es una de las formas que nos ofrece Visual Basic 2005 de hacer conversiones
entre diferentes tipos de datos, en este caso convertimos un valor entero en uno del
tipo enumerado Colores.

Si compilamos y ejecutamos la aplicación, ésta funcionará correctamente.

Es posible que usando CType no asignemos un valor dentro del rango permitido. En
este caso, el valor 3 podríamos darlo por bueno, ya que es la suma de 1 y 2 (Rojo y
Verde), pero ¿qué pasaría si el valor asignado es, por ejemplo, 15? En teoría no
deberíamos permitirlo.

Estas validaciones podemos hacerlas de dos formas:

1- Con la clásica solución de comprobar el valor indicado con todos los valores
posibles.
2- Usando funciones específicas del tipo Enum. Aunque en este último caso, solo
podremos comprobar los valores definidos en la enumeración.

En el siguiente ejemplo podemos hacer esa comprobación.

Sub mostrarColor(ByVal c As Colores)

' comprobar si el valor indicado es correcto

' si no está¡ definido, usar el valor Azul

If [Enum].IsDefined(GetType(Colores), c) = False Then

c = Colores.Azul

End If

Console.WriteLine("El color es {0}", c)

End Sub

Este código lo que hace es comprobar si el tipo de datos Colores tiene definido el
valor contenido en la variable c, en caso de que no sea así, usamos un valor
predeterminado.

Nota:
La función IsDefined sólo comprueba los valores que se han definido en
la enumeración, no las posibles combinaciones que podemos conseguir
sumando cada uno de sus miembros, incluso aunque hayamos usado el
atributo FlagsAttribute.

Ver video 1 de esta lección (Enumeraciones 1)


Ver video 2 de esta lección (Enumeraciones 2)
Ver video 3 de esta lección (Enumeraciones 3)
Ver video 4 de esta lección (Enumeraciones 4)
Arrays (matrices)

Los arrays (o matrices) también es algo que podíamos usar en VBScript/VB6, si bien
la forma en que se almacena en la memoria y la forma en que podemos usarlas en
Visual Basic 2005 ha cambiado.

En Visual Basic 2005 la declaración de un array la haremos de la misma forma que en


VBScript/VB6, en el siguiente ejemplo declaramos un array de tipo String llamado
nombres:

Dim nombres() As String

Al igual que en VBScript/VB6, podemos indicar el número de elementos que


contendrá el array:

Dim nombres(10) As String

Tal como ocurre en VBScript/VB6, al realizar esta declaración lo que conseguimos es


definir un array de 11 elementos: desde cero hasta 10. Si bien, en VB6 tenemos la
posibilidad de indicar cual es el valor del índice inferior predeterminado de los arrays,
podemos elegir entre cero y uno mediante la instrucción Option Base indicando a
continuación un 0 o un 1. En Visual Basic 2005 no existe esa instrucción. Es más en
Visual Basic 2005 todos los arrays deben tener como índice inferior el valor cero.

Otra posibilidad que teníamos en VBScript/VB6 era indicar el rango de índices que
podíamos asignar, esto lo lográbamos usando la cláusula To al definir un array, por
ejemplo:
Dim nombres(10 To 25) As String

Pero esta declaración es ilegal en Visual Basic 2005, por el hecho de que los arrays
de .NET siempre deben tener el valor cero como índice inferior.

Lo que si podemos hacer en Visual Basic 2005 es usar To para indicar el valor máximo
del índice, aunque no tiene la misma "potencia" que en VBScript/VB6, al menos de
esta forma el código resultará más legible:

Dim nombres(0 To 10) As String

Declarar e inicializar un array

Lo que no podemos hacer en VBScript/VB6 era declarar un array y al mismo tiempo


asignarle valores.
En Visual Basic 2005 esto lo hacemos indicando los valores a asignar justo después de
la declaración y encerrándolos entre llaves:

Dim nombres() As String = {"Pepe", "Juan", "Luisa"}

Con el código anterior estamos creando un array de tipo String con tres valores cuyos
índices van de cero a dos.

Si el array es bidimensional (o con más dimensiones), también podemos inicializarlos


al declararlo, pero en este caso debemos usar doble juego de llaves:

Dim nombres(,) As String = {{"Juan", "Pepe"}, {"Ana", "Eva"}}

En este código tendríamos un array bidimensional con los siguientes valores:

nombres(0,0)= Juan
nombres(0,1)= Pepe
nombres(1,0)= Ana
nombres(1,1)= Eva

Cambiar el tamaño de un array

Para cambiar el tamaño de un array, al igual que en VBScript/VB6, usaremos la


instrucción ReDim, pero a diferencia de VBScript/VB6 no podemos usar ReDim para
definir un array, en Visual Basic 2005 siempre hay que declarar previamente los
arrays antes de cambiarles el tamaño.

Lo que también podemos hacer en Visual Basic 2005 es cambiar el tamaño de un


array y mantener los valores que tuviera anteriormente, para lograrlo debemos usar
ReDim Preserve.
Si bien tanto ReDim como ReDim Preserve se pueden usar en arrays de cualquier
número de dimensiones, en los arrays de más de una dimensión solamente podemos
cambiar el tamaño de la última dimensión.

Eliminar el contenido de un array

Una vez que hemos declarado un array y le hemos asignado valores, es posible que
nos interese eliminar esos valores de la memoria, para lograrlo, podemos hacerlo de
tres formas:

1. Redimensionando el array indicando que tiene cero elementos, aunque en el


mejor de los casos, si no estamos trabajando con arrays de más de una
dimensión, tendríamos un array de un elemento, ya que, como hemos
comentado anteriormente, los arrays de .NET el índice inferior es cero.
2. Usar la instrucción Erase. Al igual que en VBScript/VB6, Erase elimina
totalmente el array de la memoria.
3. Asignar un valor Nothing al array. Esto funciona en Visual Basic 2005 pero no
en VBScript/VB6, debido a que en Visual Basic 2005 los arrays son tipos por
referencia

Los arrays son tipos por referencia

Como acabamos de ver, en Visual Basic 2005 los arrays son tipos por referencia, y tal
como comentamos anteriormente, los tipos por referencia realmente lo que contienen
son una referencia a los datos reales no los datos propiamente dichos.

¿Cual es el problema?

Veámoslo con un ejemplo y así lo tendremos más claro.

Dim nombres() As String = {"Juan", "Pepe", "Ana", "Eva"}

Dim otros() As String

otros = nombres

nombres(0) = "Antonio"

En este ejemplo definimos el array nombres y le asignamos cuatro valores. A


continuación definimos otro array llamado otros y le asignamos lo que tiene
nombres. Por último asignamos un nuevo valor al elemento cero del array nombres.
Si mostramos el contenido de ambos arrays nos daremos cuenta de que realmente
solo existe una copia de los datos en la memoria, y tanto nombres(0) como
otros(0) contienen el nombre "Antonio". En VBScript/VB6 cada array era
independiente del otro y esa asignación a nombres(0) no afectaba al valor contenido
en otros(0).
¿Qué ha ocurrido?

Que debido a que los arrays son tipos por referencia, solamente existe una copia de
los datos y tanto la variable nombres como la variable otros lo que contienen es una
referencia (o puntero) a los datos.

Si realmente queremos tener copias independientes, debemos hacer una copia del
array nombres en el array otros, esto es fácil de hacer si usamos el método CopyTo.
Éste método existe en todos los arrays y nos permite copiar un array en otro
empezando por el índice que indiquemos. El único requisito es que el array de
destino debe estar inicializado y tener espacio suficiente para contener los elementos
que queremos copiar.

En el siguiente código de ejemplo hacemos una copia del contenido del array
nombres en el array otros, de esta forma, el cambio realizado en el elemento cero
de nombres no afecta al del array otros.

Dim nombres() As String = {"Juan", "Pepe", "Ana", "Eva"}

Dim otros() As String

ReDim otros(nombres.Length)

nombres.CopyTo(otros, 0)

nombres(0) = "Antonio"

Además del método CopyTo, los arrays tienen otros miembros que nos pueden ser de
utilidad, como por ejemplo la propiedad Length usada en el ejemplo para saber
cuantos elementos tiene el array nombres.

Para averiguar el número de elementos de un array, también podemos usar la función


UBound, que es la que nos servía en VBScript/VB6 para saber esa información.
Sin embargo, el uso de la función LBound, (que sirve para averiguar el índice inferior
de un array), no tiene ningún sentido en Visual Basic 2005, ya que todos los arrays
siempre tienen un valor cero como índice inferior.

Para finalizar este tema, solo nos queda por decir, que los arrays de Visual Basic 2005
realmente son tipos de datos derivados de la clase Array y por tanto disponen de
todos los miembros definidos en esa clase, aunque de esto hablaremos en la próxima
lección, en la que también tendremos la oportunidad de profundizar un poco más en
los tipos por referencia y en como podemos definir nuestros propios tipos de datos,
tanto por referencia como por valor.
Ver video 1 de esta lección (Agregar proyectos a una solución, Arrays)
Ver video 2 de esta lección (Arrays 2)
Ver video 3 de esta lección (Arrays 3)
Introducción

En la lección anterior vimos los tipos de datos predefinidos en .NET Framework, en


esta lección veremos cómo podemos crear nuestros propios tipos de datos, tanto por
valor como por referencia.

También tendremos ocasión de ver los distintos niveles de accesibilidad que podemos
aplicar a los tipos, así como a los distintos miembros de esos tipos de datos. De los
distintos miembros que podemos definir en nuestros tipos, nos centraremos en las
propiedades para ver en detalle los cambios que han sufrido con respecto a las clases
de VBScript/VB6. También veremos temas relacionados con la programación
orientada a objetos (POO) en general y de forma particular los que atañen a las
interfaces.

Clases y estructuras

· Clases: Tipos por referencia definidos por el usuario


o Las clases: El corazón de .NET Framework
§ La herencia: Característica principal de la Programación Orientada
a Objetos
§ Encapsulación y Polimorfismo: Dos viejos conocidos de VB
§ Object: La clase base de todas las clases de .NET
o Definir una clase
§ Una clase especial: Module
§ Los miembros de una clase
§ Características de los métodos y propiedades
§ Accesibilidad, ámbito y miembros compartidos
§ Parámetros y parámetros opcionales
§ Array de parámetros opcionales (ParamArray)
§ Sobrecarga de métodos y propiedades
§ Parámetros por valor y parámetros por referencia
o Instanciar: Crear un objeto en la memoria
§ Declarar y asignar en un solo paso
§ El constructor: El punto de inicio de una clase
§ Constructores parametrizados
§ Cuando Visual Basic 2005 no crea un constructor
automáticamente
§ El destructor: El punto final de la vida de una clase

· Estructuras: Tipos por valor definidos por el usuario


o Definir una estructura
o Constructores de las estructuras
o Destructores de las estructuras
o Los miembros de una estructura
o Cómo usar las estructuras

· Accesibilidad y ámbito
o Ámbito
§ Ámbito de bloque
§ Ámbito de procedimiento
§ Ámbito de módulo
§ Ámbito de espacio de nombres
o La palabra clave Global
o Accesibilidad
§ Accesibilidad de las variables en los procedimientos
o Las accesibilidades predeterminadas
o Anidado de tipos
§ Los tipos anidables
§ El nombre completo de un tipo
§ Importación de espacios de nombres
§ Alias de espacios de nombres

· Propiedades
o Definir una propiedad
o Propiedades de solo lectura
o Propiedades de solo escritura
o Diferente accesibilidad para los bloques Get y Set
o Propiedades predeterminadas
§ Sobrecarga de propiedades predeterminadas

· Interfaces
o ¿Qué es una interfaz?
o ¿Qué contiene una interfaz?
o Una interfaz es un contrato
o Las interfaces y el polimorfismo
o Usar una interfaz en una clase
o Acceder a los miembros implementados
o Saber si un objeto implementa una interfaz
o Implementación de múltiples interfaces
o Múltiple implementación de un mismo miembro
o ¿Dónde podemos implementar las interfaces?
o Un ejemplo práctico usando una interfaz de .NET
Clases: Tipos por referencia definidos por el usuario

Tal como vimos en la lección anterior, los tipos de datos se dividen en dos grupos:
tipos por valor y tipos por referencia. Los tipos por referencia realmente son clases,
de la cuales debemos crear una instancia para poder usarlas. Esa instancia o copia, se
crea siempre en la memoria lejana (heap) y las variables lo único que contienen es
una referencia a la dirección de memoria en la que el CLR (Common Language
Runtime, motor en tiempo de ejecución de .NET), ha almacenado el objeto recién
creado.

Al igual que ocurre en VBScript/VB6, también podemos crear nuestras propias clases
en Visual Basic 2005, aunque como veremos tanto la forma de definirlas como de
instanciarlas ha cambiado un poco. Aunque ese cambio es solo, digamos, en la forma,
ya que en el fondo es lo mismo, siempre salvando las distancias, ya que como
veremos, las clases de Visual Basic 2005 pueden llegar a ser mucho más versátiles y
potentes que las de VBScript/VB6.

Antes de entrar en detalles sintácticos, veamos la importancia que tienen las clases
en .NET Framework y como repercuten en las que podamos definir nosotros usando
Visual Basic 2005.

Las clases: el corazón de .NET Framework

Prácticamente todo lo que podemos hacer en .NET Framework lo hacemos mediante


clases. La librería de clases de .NET Framework es precisamente el corazón del propio
.NET, en esa librería de clases está todo lo que podemos hacer dentro de este marco
de programación; para prácticamente cualquier tarea que queramos realizar existen
clases, y si no existen, las podemos definir nosotros mismos, bien ampliando la
funcionalidad de alguna clase existente mediante la herencia, bien implementando
algún tipo de funcionalidad previamente definida o simplemente creándolas desde
cero.

La herencia: Característica principal de la Programación Orientada a Objetos

El concepto de Programación Orientada a Objetos (POO) es algo intrínsico al propio


.NET Framework, por tanto es una característica que todos los lenguajes basados en
este "marco de trabajo" tienen de forma predeterminada, entre ellos el Visual Basic
2005. De las características principales de la POO tenemos que destacar la herencia,
que en breve podemos definir como una característica que nos permite ampliar la
funcionalidad de una clase existente sin perder la que ya tuviera previamente. Gracias
a la herencia, podemos crear una nueva clase que se derive de otra. Esta nueva clase
puede cambiar el comportamiento de la clase base (de la cual hereda) y/o ampliarlo,
de esta forma podemos adaptar la clase original a nuestras necesidades.

El tipo de herencia que .NET Framework soporta es la herencia simple, es decir, solo
podemos usar una clase como base de la nueva, si bien, como veremos más adelante,
podemos agregar múltiples interfaces. La herencia nos permitirá aprovechar la
funcionalidad de muchas de las clases existentes en la plataforma .NET.

Encapsulación y Polimorfismo: Dos viejos conocidos de VB

La encapsulación y el polimorfismo son otras dos características de la programación


orientada a objetos. Aunque estas dos no nos resultarán desconocidas, ya que desde
la versión 5.0 de Visual Basic y VBScript estaban a nuestra disposición.

La encapsulación nos permite abstraer la forma que tiene de actuar una clase sobre
los datos que contiene o manipula, para poder lograrlo se exponen como parte de la
clase los métodos y propiedades necesarios para que podamos manejar esos datos
sin tener que preocuparnos cómo se realiza dicha manipulación.

El polimorfismo es una característica que nos permite realizar ciertas acciones o


acceder a la información de los datos contenidos en una clase de forma semi-
anónima, al menos en el sentido de que no tenemos porqué saber sobre que tipo
objeto realizamos la acción, ya que lo único que nos debe preocupar es que podemos
hacerlo, por la sencilla razón de que estamos usando ciertos mecanismos que siguen
unas normas que están adoptadas por la clase.

El ejemplo clásico del polimorfismo es que si tengo un objeto que "sabe" cómo
morder, da igual que lo aplique a un ratón o a un dinosaurio, siempre y cuando esas
dos "clases" expongan un método que pueda realizar esa acción... y como decía la
documentación de Visual Basic 5.0, siempre será preferible que nos muerda un ratón
antes que un dinosaurio.

Object: La clase base de todas las clases de .NET

Todas las clases de .NET se derivan siempre de forma automática de la clase Object,
lo indiquemos o no explícitamente. Cualquier clase que definamos tendrá el
comportamiento heredado de esa clase. El uso de la clase Object como base del resto
de las clases de .NET es la única excepción a la herencia simple soportada por .NET.
Esta característica nos asegura que siempre podremos usar un objeto del tipo Object
para acceder a cualquier clase de .NET, de manera similar a como un variant servía
en VBScript/VB6 para tratar cualquier tipo de objeto.

De los miembros que tiene la clase Object debemos resaltar el método ToString, el
cual ya lo vimos en la lección anterior cuando queríamos convertir un tipo primitivo en
una cadena. Este método está pensado para devolver una representación en formato
cadena de un objeto. El valor que obtengamos al usar este método dependerá de
cómo esté definido en cada clase y por defecto lo que devuelve es el nombre
completo de la clase. En la mayoría de los casos, sin embargo, el valor obtenido al
usar este método debería ser más intuitivo: por ejemplo los tipos de datos primitivos
tienen definido este método para devuelva el valor que contienen. De igual forma,
nuestras clases también deberían devolver un valor adecuado al contenido
almacenado. De cómo hacerlo, nos ocuparemos en breve.

Nota:
Todos los tipos de datos de .NET, ya sean por valor o por referencia
siempre están derivados de la clase Object, por tanto podremos llamar a
cualquiera de los métodos que están definidos en esa clase.
Aunque en el caso de los tipos de datos por valor, cuando queremos
acceder a la clase Object que contienen, .NET Framework primero debe
convertirla en un objeto por referencia (operación denominada boxing) y
cuando hemos dejado de usarla y queremos volver a asignar el dato a la
variable por valor, tiene que volver a hacer la conversión inversa
(unboxing).

Ver video de esta lección (Clases 1)


Definir una clase

En Visual Basic 6.0, cada vez que definimos una clase tenemos que agregar al
proyecto un fichero con extensión .cls, y a partir de ese momento, todo el código
escrito en ese fichero formaba parte de la clase. En VBScript (tanto en ASP como en
otros entornos) y en Visual Basic 2005 esto se hace mediante el uso de la palabra
clave Class. Aunque lo habitual es que usemos un fichero independiente para cada
clase que escribamos, esto solo es algo opcional, porque en VB2005 solo existe un
tipo de fichero para escribir el código: un fichero con extensión .vb, en el que
podemos escribir una clase o cualquiera de los tipos que el lenguaje nos permite.

Nota:
En Visual Basic 2005 cualquier código que queramos escribir estará
dentro de una clase.

En Visual Basic 2005 las clases se definen usando la palabra clave Class seguida del
nombre de la clase, esa definición acaba indicándolo con End Class, exactamente igual
que en VBScript.

En el siguiente ejemplo definimos una clase llamada Cliente que tiene dos campos
públicos.

Class Cliente

Public Nombre As String

Public Apellidos As String

End Class
Una vez definida la clase podemos agregar los elementos (o miembros) que creamos
conveniente. En el ejemplo, para simplificar, hemos agregado dos campos públicos,
aunque también podríamos haber definido cualquiera de los miembros permitidos en
las clases.

Una clase especial: Module

En Visual Basic 2005 también podemos definir una clase especial llamada Module.
Este tipo de clase tiene un tratamiento especial y es el equivalente a los módulos BAS
de VB6. La definición se hace usando la instrucción Module seguida del nombre a usar
y acaba con End Module.

Cualquier miembro definido en un Module siempre estará accesible en todo el


proyecto y para usarlos no tendremos que crear ningún objeto en memoria. Las
clases definidas con la palabra clave Module realmente equivalen a las clases en las
que todos los miembros están compartidos y por tanto siempre disponibles a toda la
aplicación.

De todos estos conceptos nos ocuparemos en las siguientes lecciones, pero es


necesario explicar que existe este tipo de clase ya que será el tipo de datos que el IDE
de Visual Basic 2005 usará al crear aplicaciones del tipo consola, que será el tipo de
proyecto que crearemos para practicar con el código mostrado en este primer
módulo.

Los miembros de una clase

Una clase puede contener cualquiera de estos elementos (miembros):

· Enumeraciones
· Campos
· Métodos (funciones o procedimientos)
· Propiedades
· Eventos

Las enumeraciones, como vimos en la lección anterior, podemos usarlas para


definir valores constantes relacionados, por ejemplo para indicar los valores posibles
de cualquier "característica" de la clase.

Los campos son variables usadas para mantener los datos que la clase manipulará.

Los métodos son las acciones que la clase puede realizar, normalmente esas
acciones serán sobre los datos que contiene. Dependiendo de que el método devuelva
o no un valor, podemos usar métodos de tipo Function o de tipo Sub respectivamente.

Las propiedades son las "características" de las clases y la forma de acceder


"públicamente" a los datos que contiene. Por ejemplo, podemos considerar que el
nombre y los apellidos de un cliente son dos características del cliente. Normalmente
encapsulan el acceso a miembros privados de la clase.

Los eventos son mensajes que la clase puede enviar para informar que algo está
ocurriendo en la clase.
Características de los métodos y propiedades

Accesibilidad, ámbito y miembros compartidos

Aunque estos temas los veremos en breve con más detalle, para poder comprender
mejor las características de los miembros de una clase o tipo, daremos un pequeño
adelanto sobre estas características que podemos aplicar a los elementos que
definamos.

Accesibilidad y ámbito son dos conceptos que están estrechamente relacionados.


Aunque en la práctica tienen el mismo significado, ya que lo que representan es la
"cobertura" o alcance que tienen los miembros de las clases e incluso de las mismas
clases que definamos.

Si bien cada uno de ellos tienen su propia "semántica", tal como podemos ver a
continuación:

Ámbito
Es el alcance que la definición de un miembro o tipo puede tener. Es decir, cómo
podemos acceder a ese elemento y desde dónde podemos accederlo.
El ámbito de un elemento de código está restringido por el "sitio" en el que lo hemos
declarado. Estos sitios pueden ser:

· Ámbito de bloque: Disponible únicamente en el bloque de código en el que se


ha declarado.
· Ámbito de procedimiento: Disponible únicamente dentro del procedimiento
en el que se ha declarado.
· Ámbito de módulo: Disponible en todo el código del módulo, la clase o la
estructura donde se ha declarado.
· Ámbito de espacio de nombres: Disponible en todo el código del espacio de
nombres.

Accesibilidad
A los distintos elementos de nuestro código (ya sean clases o miembros de las clases)
podemos darle diferentes tipos de accesibilidad. Estos tipos de "acceso" dependerán
del ámbito que queramos que tengan, es decir, desde dónde podremos accederlos.
Los modificadores de accesibilidad son:

· Public: Acceso no restringido.


· Protected: Acceso limitado a la clase contenedora o a los tipos derivados de
esta clase.
· Friend: Acceso limitado al proyecto actual.
· Protected Friend: Acceso limitado al proyecto actual o a los tipos derivados de
la clase contenedora.
· Private: Acceso limitado al tipo contenedor.
Por ejemplo, podemos declarar miembros privados a una clase, en ese caso, dichos
miembros solamente los podremos acceder desde la propia clase, pero no desde fuera
de ella.

Nota:
Al igual que ocurre en VBScript/VB6, al declarar una variable con Dim,
por regla general, el ámbito que le estamos aplicando es privado, pero
como veremos, en Visual Basic 2005, dependiendo del tipo en el que
declaremos esa variable, el ámbito puede ser diferente a privado.

Miembros compartidos
Por otro lado, los miembros compartidos de una clase o tipo, son elementos que no
pertenecen a una instancia o copia en memoria particular, sino que pertenecen al
propio tipo y por tanto siempre están accesibles o disponibles, dentro del nivel del
ámbito y accesibilidad que les hayamos aplicado, y su tiempo de vida es el mismo que
el de la aplicación.

Nota:
Lo más parecido en VB6 a los miembros compartidos de Visual Basic
2005 son los métodos y campos declarados en un módulo .BAS.

Del ámbito, la accesibilidad y los miembros compartidos nos ocuparemos con más
detalle en una lección posterior, donde veremos ciertas peculiaridades, como puede
ser la limitación del ámbito de un miembro que aparentemente tiene una accesibilidad
no restringida.

Parámetros y parámetros opcionales

En Visual Basic 2005, tanto los miembros de una clase, (funciones y métodos Sub),
como las propiedades pueden recibir parámetros. Esto no es algo nuevo, ya que en
VB6 podíamos tener parámetros en estos tipos de miembros (o elementos) de una
clase, además se siguen soportando tanto los parámetros opcionales (Optional) como
los arrays de parámetros (ParamArray). Con los parámetros opcionales lo que
conseguimos es permitir que el usuario no tenga que introducir todos los parámetros,
sino solo los que realmente necesite, del resto, (los no especificados), se usará el
valor predeterminado que hayamos indicado, ya que una de las "restricciones" de este
tipo de parámetros con respecto a VBScript/VB6 es que siempre que indiquemos un
parámetro opcional, debemos indicar también el valor por defecto que debemos usar
en caso de que no se especifique. Esto realmente sigue funcionando igual que en VB6
cuando al parámetro opcional le indicamos el tipo de datos. Lo que Visual Basic 2005
no permite es definir un parámetro opcional de tipo Variant, entre otras cosas, porque
ese tipo de datos no existe en VB2005.

Veamos unos ejemplo para aclarar nuestras ideas:


Function Suma(n1 As Integer, Optional n2 As Integer = 15) As Integer

Suma = n1 + n2

End Function

En este primer ejemplo, el primer parámetro es obligatorio (siempre debemos


indicarlo) y el segundo es opcional, si no se indica al llamar a esta función, se usará el
valor 15 que es el predeterminado.

Para llamar a esta función, lo podemos hacer de estas tres formas:

' 1- indicando los dos parámetros (el resultado será 4= 1 + 3)

t = Suma(1, 3)

' 2- Indicando solamente el primer parámetro (el resultado será 16=


1 + 15)

t = Suma(1)

' 3- Indicando los dos parámetros, pero en el opcional usamos el


nombre

t = Suma(1, n2:= 9)

El tercer ejemplo solamente tiene utilidad si hay más de un parámetro opcional.

Nota:
Los parámetros opcionales deben aparecer en la lista de parámetros del
método, después de los "obligatorios"

Array de parámetros opcionales (ParamArray)

En cuanto al uso de ParamArray, este tipo de parámetro opcional nos permite indicar
un número indeterminado de parámetros, aunque el funcionamiento con respecto a
VBScript/VB6 es algo diferente, en Visual Basic 2005 siempre debe ser una array de
un tipo específico e internamente se trata como un array normal y corriente, es decir,
dentro del método o propiedad se accede a él como si de un array se tratara... entre
otras cosas, ¡porque es un array!

En VB6 no siempre era así y algunas veces teníamos verdaderos problemas al acceder
a esos datos, por suerte, en Visual Basic 2005 el uso de los parámetros opcionales
con ParamArray es más simple e intuitivo, aunque a algunos puede que le parezca
menos "potente", ya que en VBScript/VB6, cualquiera de los parámetros podía ser a
su vez un array. Pero en Visual Basic 2005, al menos si tenemos activado Option
Strict, esto no es posible, ya que solo aceptará los parámetros del tipo indicado.

Veamos lo que VB6 permite y cómo hacerlo en VB2005:


Function Suma(ParamArray n() As Variant) As Long

Dim total As Long

Dim i As Long

Dim j As Long

'

For i = 0 To UBound(n)

If IsArray(n(i)) Then

For j = LBound(n(i)) To UBound(n(i))

total = total + n(i)(j)

Next

Else

total = total + n(i)

End If

Next

Suma = total

End Function

' Para usarlo:

Dim t As Long

Dim a(2) As Long

a(0) = 1: a(1) = 2: a(2) = 3

t = Suma(a, 4, 5, 6)

MsgBox(t)

Para usar este código en Visual Basic 2005 tendremos que desactivar Option Strict y
cambiar el tipo Variant por Object, (cosa que el IDE hará automáticamente si
pegamos el código), lo demás se puede quedar igual, incluso los tipos de datos,
aunque debemos recordar que un Long de VBScript/VB6 es un Integer de VB2005.
Pero como no debemos "acostumbrarnos" a desactivar Option Strict, vamos a ver el
código "bueno" de Visual Basic 2005, aunque no nos permita usar un array en los
parámetros al llamar a la función.

Function Suma(ByVal ParamArray n() As Integer) As Integer

Dim total As Integer

For i As Integer = 0 To n.Length - 1

total += CInt(n(i))

Next

Return total

End Function

' Para usarlo:

Dim t As Integer = Suma(1, 2, 3, 4, 5, 6)

'

MsgBox(t)

Aquí vemos el nuevo modo de trabajar de Visual Basic 2005:

- Usamos Return para devolver el valor en lugar de usar el nombre de la función,


aunque se sigue soportando.
- Podemos inicializar la variable a usar con el bucle For, esto lo veremos con más
detalle cuando tratemos la accesibilidad.
- Usamos propiedades para saber la cantidad de elementos del array.
- Comprobamos que en Visual Basic 2005 tenemos otra forma de incrementar el
contenido de una variable: t += CInt(n(i)) es lo mismo que t = t + CInt(n(i)),
que aunque parezca un snobismo, realmente ahorra algún ciclo de reloj y es más
cómodo para programar. Ya lo hemos visto en uno de los vídeos del módulo anterior.

Nota:
Cuando queramos usar ParamArray para recibir un array de parámetros
opcionales, esta instrucción debe ser la última de la lista de parámetros
de la función (método).

Tampoco se permite tener parámetros opcionales y ParamArray en la


misma función.
Sobrecarga de métodos y propiedades

La sobrecarga de funciones (realmente de métodos y propiedades), es una


característica que nos permite tener una misma función con diferentes tipos de
parámetros, ya sea en número o en tipo.

Aunque nos pueda parecer que la sobrecarga de métodos la podemos "simular" en


VB6 con los parámetros opcionales, realmente no es lo mismo. Ahora veremos el
porqué.

Supongamos que queremos tener dos funciones (o más) que nos permitan hacer
operaciones con diferentes tipos de datos, y que, según el tipo de datos usado, el
valor que devuelva sea de ese mismo tipo. En VB6 es imposible hacer esto. Salvo que
creemos dos funciones con nombres diferentes, pero en ese caso ya no estaremos
usando la sobrecarga.

En este ejemplo, tenemos dos funciones que se llaman igual pero una recibe valores
de tipo entero y la otra de tipo decimal:

Function Suma(n1 As Integer, n2 As Integer) As Integer

Return n1 + n2

End Function

Function Suma(n1 As Double, n2 As Double) As Double

Return n1 + n2

End Function

Como podemos comprobar las dos funciones tienen el mismo nombre, pero tanto una
como otra reciben parámetros de tipos diferentes.

Con Visual Basic 2005 podemos sobrecargar funciones, pero lo interesante no es que
podamos hacerlo, sino cómo podemos usar esas funciones. En el código anterior
tenemos dos funciones llamadas Suma, la primera acepta dos parámetros de tipo
Integer y la segunda de tipo Double. Lo interesante es que cuando queramos usarlas,
no tenemos que preocuparnos de cual vamos a usar, ya que será el compilador el que
decida la más adecuada al código que usemos, por ejemplo:

' En este caso, se usará la que recibe dos valores enteros

MsgBox( Suma(10, 22) )

' En este caso se usará la que recibe valores de tipo Double

MsgBox( Suma(10.5, 22)


El compilador de Visual Basic 2005 es el que decide que función usar, esa decisión la
toma a partir de los tipos de parámetros que hayamos indicado. En el segundo
ejemplo de uso, el que mejor coincide es el de los dos parámetros de tipo Double.

También podemos tener sobrecarga usando una cantidad diferente de parámetros,


aunque éstos sean del mismo tipo. Por ejemplo, podemos añadir esta declaración al
código anterior sin que exista ningún tipo de error, ya que esta nueva función recibe
tres parámetros en lugar de dos:

Function Suma(n1 As Integer, n2 As Integer, n3 As Integer) As


Integer

Return n1 + n2 + n3

End Function

Por tanto, cuando el compilador se encuentre con una llamada a la función Suma en
la que se usen tres parámetros, intentará usar esta última.

Nota:
Para que exista sobrecarga, la diferencia debe estar en el número o en el
tipo de los parámetros, no en el tipo del valor devuelto.

Cuando, desde el IDE de Visual Studio 2005, queremos usar los métodos
sobrecargados, nos mostrará una lista de opciones indicándonos las posibilidades de
sobrecarga que existen y entre las que podemos elegir, tal como vemos en la Figura
2.10:

Figura 2.10. Lista de parámetros soportados por un método

Tal como comentábamos hace unas líneas, en VB6 se puede simular la sobrecarga
mediante los parámetros opcionales, aunque realmente no es lo mismo. Pero esa
"simulación" nos viene como anillo al dedo para dar un consejo o advertencia:
¡Cuidado con las funciones que reciben parámetros opcionales, ya que el
compilador puede producir un error si esa función entra en conflicto con otra
"sobrecargada"!

Mejor lo vemos con un ejemplo:

Function Suma(n1 As Integer, Optional n2 As Integer = 33) As Integer


Return n1 + n2

End Function

Si tenemos esta declaración además de las anteriores, el programa no compilará, ya


que si hacemos una llamada a la función Suma con dos parámetros enteros, el
compilador no sabrá si usar esta última o la primera que declaramos, por tanto
producirá un error.

Parámetros por valor y parámetros por referencia

Al igual que tenemos dos tipos de datos diferentes, en los parámetros de las
funciones también podemos tenerlos, esto tampoco es una novedad de Visual Basic
2005, ya que en VBScript/VB6 también podemos usar ByVal o ByRef para indicar al
compilador cómo debe tratar a los parámetros.

Cuando un parámetro es por valor (ByVal), el runtime antes de llamar a la función


hace una copia de ese parámetro y pasa la copia a la función, por tanto cualquier
cambio que hagamos a ese parámetro dentro de la función no afectará al valor usado
"externamente".

En el caso de que el parámetro sea por referencia (ByRef), el compilador pasa una
referencia que apunta a la dirección de memoria en la que están los datos, por tanto
si realizamos cambios dentro de la función, ese cambio si que se verá reflejado en el
parámetro usado al llamar a la función.

Nota:
Hay que tener en cuenta que si pasamos un objeto a una función, da
igual que lo declaremos por valor o por referencia, ya que en ambos
casos se pasa una referencia a la dirección de memoria en la que están
los datos, porque, como sabemos, las variables de los tipos por
referencia siempre contienen una referencia a los datos, no los datos en
sí.

Hasta aquí todo como en VBScript/VB6.

Lo que cambia en Visual Basic 2005 es que ahora los parámetros en los que no se
indique si es por valor (ByVal) o por referencia (ByRef), serán tratados como
parámetros por valor. En VBScript/VB6 era al revés.

Nota:
Si usamos el IDE de Visual Studio para escribir el código, no debemos
preocuparnos de este detalle, ya que si no indicamos si es por valor o
por referencia, automáticamente le añadirá la palabra clave ByVal, para
que no haya ningún tipo de dudas.
Ver video 1 de esta lección (Clases 2: Los miembros de las clases)
Ver video 2 de esta lección (Clases 3: Las propiedades)
Ver video 3 de esta lección (Clases 4: Accesibilidad)
Instanciar: Crear un objeto en la memoria

Una vez que tenemos una clase definida, lo único de lo que disponemos es de una
especie de plantilla o molde a partir del cual podemos crear objetos en memoria.

La forma de crear esos objetos en Visual Basic 2005 no ha cambiado con respecto al
VBScript/VB6, salvo en pequeños detalles, veamos algunos ejemplos y así
aclararemos esas diferencias, que por otro lado son importantes.

Lo primero que tenemos que hacer es declarar una variable del tipo que queremos
instanciar, esto lo hacemos usando la instrucción Dim:

Dim c As Cliente

Con esta línea de código lo que estamos indicando al Visual Basic es que tenemos
intención de usar una variable llamada c para acceder a una clase de tipo Cliente.
Esa variable, cuando llegue el momento de usarla, sabrá todo lo que hay que saber
sobre una clase Cliente, pero hasta que no tenga una "referencia" a un objeto de ese
tipo no podremos usarla.
La asignación de una referencia a un objeto Cliente podemos hacerla de dos formas
distintas:
La primera es creando un nuevo objeto en la memoria:

c = New Cliente
Nota:
En VBScript/VB6 esta forma de crear un nuevo objeto produciría un
error, ya que para "asignar" a una variable un nuevo objeto tenemos
que usar la instrucción Set, en Visual Basic 2005 ya no es necesario el
uso de esa instrucción, de hecho si la usamos, recibiremos un error.

A partir de este momento, la variable c tiene acceso a un nuevo objeto del tipo
Cliente, por tanto podremos usarla para asignarle valores y usar cualquiera de los
miembros que ese tipo de datos contenga:

c.Nombre = "Antonio"

c.Apellidos = "Ruiz Rodríguez"

Declarar y asignar en un solo paso

Con las clases o tipos por referencia también podemos declarar una variable y al
mismo tiempo asignarle un nuevo objeto, esto tampoco es una novedad, aunque sí lo
es la forma de comportarse. Veamos primero cómo hacerlo y después entraremos en
detalles.

Dim c As New Cliente

O también:

Dim c As Cliente = New Cliente

Las dos formas producen el mismo resultado, por tanto es recomendable usar la
primera.

En VBScript/VB6 este tipo de declaración y asignación no era recomendable, ya que


añade trabajo extra al compilador de VB. Ese trabajo extra consiste en comprobar si
el objeto al que queremos acceder ya está creado, en caso de que no sea así, primero
lo crea y después lo usa, el inconveniente es que todas estas comprobaciones las
realiza ¡cada vez que usemos la variable!
En Visual Basic 2005 este comportamiento ya no es así, el objeto se crea en la
memoria y se asigna a la variable, a partir de ese momento no se comprueba si ya
está creado o no, ya que se supone que sí está creado.

Lo que debemos tener muy presente es que además de la sobrecarga de trabajo que
añade VBScript/VB6 a este tipo de instanciación, el comportamiento es muy diferente
al de Visual Basic 2005. Por ejemplo, si en VBScript/VB6 asignamos un valor nulo
(Nothing) a una variable declarada de esta forma y acto seguido usamos la variable,
el motor en tiempo de ejecución (runtime) crea un nuevo objeto y asunto arreglado.
Pero en Visual Basic 2005, si asignamos un valor nulo a una variable, le estamos
diciendo al CLR (el runtime de .NET) que ya no queremos usar más ese objeto, por
tanto lo elimina de la memoria; si después queremos volver a usar la variable,
debemos crear otro objeto, (con New), de no hacerlo, se producirá un error, ya que
en Visual Basic 2005 no existe la auto-instanciación.

El constructor: El punto de inicio de una clase

Cada vez que creamos un nuevo objeto en memoria estamos llamando al constructor
de la clase. En VBScript/VB6 el constructor es el método Class_Initialize. Sin embargo
en Visual Basic 2005 el constructor es un método de tipo Sub llamado New.

En el constructor de una clase podemos incluir el código que creamos conveniente,


pero realmente solamente deberíamos incluir el que realice algún tipo de
inicialización, en caso de que no necesitemos realizar ningún tipo de inicialización, no
es necesario definir el constructor, ya que el propio compilador lo hará por nosotros.
Esto es así porque todas las clases deben implementar un constructor, por tanto si
nosotros no lo definimos, lo hará el compilador de Visual Basic 2005.

Si nuestra clase Cliente tiene un campo para almacenar la fecha de creación del
objeto podemos hacer algo como esto:

Class Cliente

Public Nombre As String

Public Apellidos As String

Public FechaCreacion As Date

'

Public Sub New()

FechaCreacion = Date.Now

End Sub

End Class

De esta forma podemos crear un nuevo Cliente y acto seguido comprobar el valor del
campo FechaCreacion para saber la fecha de creación del objeto.

En los constructores también podemos hacer las inicializaciones que, por ejemplo
permitan a la clase a conectarse con una base de datos, abrir un fichero o cargar una
imagen gráfica, etc., aunque en algunos de estos casos nos facilitará la tarea una
nueva característica que VBScript/VB6 no tiene.

Constructores parametrizados

De la misma forma que podemos tener métodos y propiedades sobrecargadas,


también podemos tener constructores sobrecargados, ya que debemos recordar que
en Visual Basic 2005, un constructor realmente es un método de tipo Sub, y como
todos los métodos y propiedades de Visual Basic 2005, también admite la sobrecarga.

La ventaja de tener constructores que admitan parámetros es que podemos crear


nuevos objetos indicando algún parámetro, por ejemplo un fichero a abrir o, en el
caso de la clase Cliente, podemos indicar el nombre y apellidos del cliente o cualquier
otro dato que creamos conveniente.

Para comprobarlo, podemos ampliar la clase definida anteriormente para que también
acepte la creación de nuevos objetos indicando el nombre y los apellidos del cliente.

Class Cliente

Public Nombre As String

Public Apellidos As String

Public FechaCreacion As Date

'

Public Sub New()

FechaCreacion = Date.Now

End Sub

'

Public Sub New(elNombre As String, losApellidos As String)

Nombre = elNombre

Apellidos = losApellidos

FechaCreacion = Date.Now

End Sub

End Class

Teniendo esta declaración de la clase Cliente, podemos crear nuevos clientes de dos
formas:

Dim c1 As New Cliente

Dim c2 As New Cliente("Jose", "Sánchez")

Como podemos comprobar, en ciertos casos es más intuitiva la segunda forma de


crear objetos del tipo Cliente, además de que así nos ahorramos de tener que
asignar individualmente los campos Nombre y Apellidos.
Esta declaración de la clase Cliente la podríamos haber hecho de una forma diferente.
Por un lado tenemos un constructor por omisión (el que no recibe parámetros) en el
que asignamos la fecha de creación y por otro el constructor que recibe los datos del
nombre y apellidos. En ese segundo constructor también asignamos la fecha de
creación, ya que, se instancie como se instancie la clase, nos interesa saber siempre
la fecha de creación. En este ejemplo, por su simpleza no es realmente un problema
repetir la asignación de la fecha, pero si en lugar de una inicialización necesitáramos
hacer varias, la verdad es que nos encontraríamos con mucha "duplicidad" de código.
Por tanto, en lugar de asignar los datos en dos lugares diferentes, podemos hacer
esto otro:

Class Cliente

Public Nombre As String

Public Apellidos As String

Public FechaCreacion As Date

'

Public Sub New()

FechaCreacion = Date.Now

End Sub

'

Public Sub New(elNombre As String, losApellidos As String)

New()

Nombre = elNombre

Apellidos = losApellidos

End Sub

End Class

Es decir, desde el constructor con argumentos llamamos al constructor que no los


tiene, consiguiendo que también se asigne la fecha.

Esta declaración de la clase Cliente realmente no compilará. Y no compila por la razón


tan "simple" de que aquí el compilador de Visual Basic 2005 no sabe cual es nuestra
intención, ya que New es una palabra reservada que sirve para crear nuevos objetos
y, siempre, una instrucción tiene preferencia sobre el nombre de un método, por
tanto, podemos recurrir al objeto especial Me el cual nos sirve, al igual que en
VBScript/VB6, para representar al objeto que actualmente está en la memoria, así
que, para que esa declaración de la clase Cliente funcione, debemos usar Me.New()
para llamar al constructor sin parámetros.
Nota:
El IDE de Visual Studio 2005 colorea las instrucciones y tipos propios del
lenguaje, tal y como lo hace el de VBScript/VB6, en el caso de Me,
VBScript/VB6 no la colorea, pero este no es el motivo de esta nota, lo
que no debe confundirnos es que cuando declaramos Sub New, tanto
Sub como New se muestran coloreadas, cuando solo se debería colorear
Sub; sin embargo cuando usamos Me.New, solo se colorea Me y no New
que es correcto, tal como vemos en la Figura 2.11, ya que en este caso
New es el nombre de un procedimiento y los procedimientos no son
parte de las instrucciones y tipos de .NET.

Figura 2.11. Coloreo erróneo de New

Cuando Visual Basic 2005 no crea un constructor automáticamente

Tal como hemos comentado, si nosotros no definimos un constructor (Sub New), lo


hará el propio compilador de Visual Basic 2005, y cuando lo hace automáticamente,
siempre es un constructor sin parámetros.

Pero hay ocasiones en las que nos puede interesar que no exista un constructor sin
parámetros, por ejemplo, podemos crear una clase Cliente que solo se pueda
instanciar si le pasamos, por ejemplo el número de identificación fiscal, (NIF), en caso
de que no se indique ese dato, no podremos crear un nuevo objeto Cliente, de esta
forma, nos aseguramos siempre de que el NIF siempre esté especificado.
Seguramente por ese motivo, si nosotros definimos un constructor con parámetros,
Visual Basic 2005 no crea uno automáticamente sin parámetros. Por tanto, si
definimos un constructor con parámetros en una clase y queremos que también tenga
uno sin parámetros, lo tenemos que definir nosotros mismos.

El destructor: El punto final de la vida de una clase

De la misma forma que una clase tiene su punto de entrada o momento de


nacimiento en el constructor, también tienen un sitio que se ejecutará cuando el
objeto creado en la memoria ya no sea necesario, es decir, cuando acabe la vida del
objeto creado.
El destructor de Visual Basic 2005 es un método llamado Finalize, el cual se hereda de
la clase Object, por tanto no es necesario que escribamos nada en él. El propio CLR se
encargará de llamar a ese método cuando el objeto ya no sea necesario. En
VBScript/VB6, el destructor es el método Class_Terminate.

La principal diferencia con VBScript/VB6 en lo referente a la forma en que se


destruyen los objetos, es que en VBScript/VB6 si a un objeto le asignamos un valor
nulo (Nothing) el destructor se ejecuta inmediatamente y el objeto se elimina de la
memoria, pero en .NET la forma en que se destruyen los objetos es diferente. Nunca
podremos tener la certeza de cuando se destruye, incluso si le asignamos un valor
nulo. Esto es así debido a que en .NET los objetos no se destruyen inmediatamente y
existe un "sistema" que se encarga de realizar esta gestión de limpieza: El recolector
de basura o de objetos no usados (Garbage Collector, GC). Este recolector de
objetos no usados se encarga de comprobar constantemente cuando un objeto no se
está usando y es el que decide cuando hay que llamar al destructor.

Debido a esta característica de .NET, si nuestra clase hace uso de recursos externos
que necesiten ser eliminados cuando la el objeto ya no se vaya a seguir usando,
debemos definir un método que sea el encargado de realizar esa liberación, pero ese
método debemos llamarlo de forma manual, ya que, aunque en .NET existen formas
de hacer que esa llamada sea automática, nunca tenderemos la seguridad de que se
llame en el momento oportuno, y esto es algo que, según que casos, puede ser un
inconveniente.

Recomendación:
Si nuestra clase utiliza recursos externos, por ejemplo un fichero o una
base de datos, debemos definir un método que se encargue de liberarlos
y a ese método debemos encargarnos de llamarlo cuando ya no lo
necesitemos.
Por definición a este tipo de métodos se les suele dar el nombre Close o
Dispose, aunque este último tiene un significado especial y por
convención solo debemos usarlo siguiendo las indicaciones de la
documentación.

Ver video de esta lección (Clases 5: Constructores)


Estructuras: Tipos por valor definidos por el usuario

De la misma forma que podemos definir nuestros propios tipos de datos por
referencia, Visual Basic 2005 nos permite crear nuestros propios tipos por valor.
Para crear nuestros tipos de datos por referencia, usamos la "instrucción" Class, por
tanto es de esperar que también exista una instrucción para crear nuestros tipos por
valor, y esa instrucción es: Structure, por eso en Visual Basic 2005 a los tipos por
valor definidos por el usuario se denominan estructuras.

Nota:
El equivalente en VB6 a una estructura de Visual Basic 2005 es Type (se
le solía denominar tipo definido por el usuario o UDT) aunque es solo
eso: un parecido, pero realmente no es equivalente, al menos al 100%,
tal como tendremos oportunidad de comprobar en esta lección.

Las estructuras pueden contener los mismos miembros que las clases, aunque
algunos de ellos se comporten de forma diferente o al menos tengan algunas
restricciones, como que los campos definidos en las estructuras no se pueden
inicializar al mismo tiempo que se declaran o no pueden contener constructores
"simples", ya que el propio compilador siempre se encarga de crearlo, para así poder
inicializar todos los campos definidos.

Otra de las características de las estructuras es que no es necesario crear una


instancia para poder usarlas, ya que es un tipo por valor y los tipos por valor no
necesitan ser instanciados para que existan.
Definir una estructura

Las estructuras se definen usando la palabra Structure seguida del nombre y acaba
usando las instrucciones End Structure.

El siguiente código define una estructura llamada Punto en la que tenemos dos
campos públicos.

Structure Punto

Public X As Integer

Public Y As Integer

End Structure

Para usarla podemos hacer algo como esto:

Dim p As Punto

p.X = 100

p.Y = 75

También podemos usar New al declarar el objeto:

Dim p As New Punto

Aunque en las estructuras, usar New, sería algo redundante.

Las estructuras siempre se almacenan en la pila, por tanto deberíamos tener la


precaución de no crear estructuras con muchos campos o con muchos miembros, ya
que esto implicaría un mayor consumo del "preciado" espacio de la pila.

Constructores de las estructuras

Tal y como hemos comentado, las estructuras siempre definen un constructor sin
parámetros, este constructor no lo podemos definir nosotros, es decir, siempre existe
y es el que el propio compilador de Visual Basic 2005 define.
Por tanto, si queremos agregar algún constructor a una estructura, este debe tener
parámetros y, tal como ocurre con cualquier método o como ocurre en las clases,
podemos tener varias sobrecargas de constructores parametrizados en las
estructuras. La forma de definir esos constructores es como vimos en las clases:
usando distintas sobrecargas de un método llamado New, en el caso de las
estructuras también podemos usar la palabra clave Me para referirnos a la instancia
actual.

Esto es particularmente práctico cuando los parámetros del constructor se llaman de


la misma forma que los campos declarados en la estructura, tal como ocurre en el
constructor mostrado en la siguiente definición de la estructura Punto.
Structure Punto

Public X As Integer

Public Y As Integer

'

Sub New(ByVal x As Integer, ByVal y As Integer)

Me.X = x

Me.Y = y

End Sub

End Structure

Nota:
Tanto en las estructuras como en las clases podemos tener
constructores compartidos, en el caso de las estructuras, este tipo de
constructor es el único que podemos declarar sin parámetros.

Destructores de las estructuras

Debido a que las estructuras son tipos por valor y por tanto una variable declarada
con un tipo por valor "contiene el valor en si misma", no podemos destruir este tipo
de datos, lo más que conseguiríamos al asignarle un valor nulo (Nothing) sería
eliminar el contenido de la variable, pero nunca podemos destruir ese valor. Por
tanto, en las estructuras no podemos definir destructores.

Los miembros de una estructura

Como hemos comentado, los miembros o elementos que podemos definir en una
estructura son los mismos que ya vimos en las clases. Por tanto aquí veremos las
diferencias que existen al usarlos en las estructuras.

Campos
Como vimos, las variables declaradas a nivel del tipo, son los campos. La principal
diferencia con respecto a las clases, es que los campos de una estructura no pueden
inicialiarse en la declaración y el valor que tendrán inicialmente es un valor "nulo",
que en el caso de los campos de tipo numéricos es un cero.
Por tanto, si necesitamos que los campos tengan algún valor inicial antes de usarlos,
deberíamos indicarlo a los usuarios de nuestra estructura y proveer un constructor
que realice las inicializaciones correspondientes, pero debemos recordar que ese
constructor debe tener algún parámetro, ya que el predeterminado sin parámetros no
podemos "reescribirlo".

Los únicos campos que podemos inicializar al declararlos son los campos compartidos,
pero como tendremos oportunidad de ver, estos campos serán accesibles por
cualquier variable declarada y cualquier cambio que realicemos en ellos se verá
reflejado en el resto de "instancias" de nuestro tipo.

Métodos y otros elementos


El resto de los miembros de una estructura se declaran y usan de la misma forma que
en las clases, si bien debemos tener en cuenta que el modificador de accesibilidad
predeterminado para los miembros de una estructura es Public, (incluso si son
campos declarados con D¡m).

Otro detalle a tener en cuenta es que en una estructura siempre debe existir al menos
un evento o un campo no compartido, no se permiten estructuras en las que solo
tienen constantes, métodos y/o propiedades, estén o no compartidos.

Cómo usar las estructuras

Tal como hemos comentado las estructuras son tipos por valor, para usar los tipos por
valor no es necesario instanciarlos explícitamente, ya que el mero hecho de
declararlos indica que estamos creando un nuevo objeto en memoria. Por tanto, a
diferencia de las clases o tipos por referencia, cada variable definida como un tipo de
estructura será independiente de otras variables declaradas, aunque no las hayamos
instanciado.

Esta característica de las estructuras nos permite hacer copias "reales" no copia de la
referencia (o puntero) al objeto en memoria como ocurre con los tipos por referencia.
Veámoslos con un ejemplo.

Dim p As New Punto(100, 75)

Dim p1 As Punto

p1 = p

p1.X = 200

' p.X vale 100 y p1.X vale 200

En este trozo de código definimos e instanciamos una variable del tipo Punto, a
continuación declaramos otra variable del mismo tipo y le asignamos la primera, si
estos tipos fuesen por referencia, tanto una como la otra estarían haciendo referencia
al mismo objeto en memoria, y cualquier cambio realizado a cualquiera de las dos
variables afectarían al mismo objeto, pero en el caso de las estructuras (y de los tipos
por valor), cada cambio que realicemos se hará sobre un objeto diferente, por tanto la
asignación del valor 200 al campo X de la variable p1 solo afecta a esa variable,
dejando intacto el valor original de la variable p.
Ver video de esta lección (Estructuras)
Accesibilidad y ámbito

Tal y como comentamos anteriormente, dependiendo de dónde y cómo estén


declarados los tipos de datos y los miembros definidos en ellos, tendremos o no
acceso a esos elementos.

Recordemos que el ámbito es el alcance con el que podemos acceder a un elemento y


depende de dónde esté declarado, por otro lado, la accesibilidad depende de cómo
declaremos cada uno de esos elementos.

Ámbito

Dependiendo de donde declaremos un miembro o un tipo, éste tendrá mayor alcance


o cobertura, o lo que es lo mismo, dependiendo del ámbito en el que usemos un
elemento, podremos acceder a él desde otros puntos de nuestro código.
A continuación veremos con detalle los ámbitos en los que podemos declarar los
distintos elementos de Visual Basic 2005.

· Ámbito de bloque: Disponible únicamente en el bloque de código en el que se


ha declarado.
Por ejemplo, si declaramos una variable dentro de un bucle For o un If Then,
esa variable solo estará accesible dentro de ese bloque de código.
· Ámbito de procedimiento: Disponible únicamente dentro del procedimiento
en el que se ha declarado. Cualquier variable declarada dentro de un
procedimiento (método o propiedad) solo estará accesible en ese procedimiento
y en cualquiera de los bloques internos a ese procedimiento.
· Ámbito de módulo: Disponible en todo el código del módulo, la clase o la
estructura donde se ha declarado. Las variables con ámbito a nivel de módulo,
también estarán disponibles en los procedimientos declarados en el módulo
(clase o estructura) y por extensión a cualquier bloque dentro de cada
procedimiento.
· Ámbito de espacio de nombres: Disponible en todo el código del espacio de
nombres. Este es el nivel mayor de cobertura o alcance, aunque en este nivel
solo podemos declarar tipos como clases, estructuras y enumeraciones, ya que
los procedimientos solamente se pueden declarar dentro de un tipo.

Nota:
Por regla general, cuando declaramos una variable en un ámbito, dicha
variable "ocultará" a otra que tenga el mismo nombre y esté definida en
un bloque con mayor alcance, aunque veremos que en Visual Basic 2005
existen ciertas restricciones dependiendo de dónde declaremos esas
variables.

En VBScript/VB6 los ámbitos disponibles solamente eran los de módulo y


procedimiento, en estos casos, cualquier variable declarada en un procedimiento
oculta a una definida en un módulo.

En Visual Basic 2005 podemos definir una variable dentro de un bloque de código, en
ese caso dicha variable solo será accesible dentro de ese bloque. Aunque, como
veremos a continuación, en un procedimiento solamente podremos definir variables
que no se oculten entre sí, estén o no dentro de un bloque de código.

Ámbito de bloque
En los siguientes ejemplos veremos cómo podemos definir variables para usar
solamente en el bloque en el que están definidas.

Los bloques de código en los que podemos declarar variables son los bucles, (For, Do,
While), y los bloques condicionales, (If, Select).
Por ejemplo, dentro de un procedimiento podemos tener varios de estos bloques y por
tanto podemos definir variables "internas" a esos bloques:

Dim n As Integer = 3

'

For i As Integer = 1 To 10

Dim j As Integer

j += 1

If j < n Then

'...

End If

Next

'
If n < 5 Then

Dim j As Integer = n * 3

End If

'

Do

Dim j As Integer

For i As Integer = 1 To n

j += i

Next

If j > 10 Then Exit Do

Loop

La variable n estará disponible en todo el procedimiento, por tanto podemos acceder


a ella desde cualquiera de los bloques.
En el primer bucle For, definimos la variable i como la variable a usar de contador,
esta variable solamente estará accesible dentro de este bucle For. Lo mismo ocurre
con la variable j.
En el primer If definimos otra variable j, pero esa solo será accesible dentro de este
bloque If y por tanto no tiene ninguna relación con la definida en el bucle For anterior.
En el bucle Do volvemos a definir nuevamente una variable j, a esa variable la
podemos acceder solo desde el propio bucle Do y cualquier otro bloque de código
interno, como es el caso del bucle For, en el que nuevamente declaramos una variable
llamada i, que nada tiene que ver con el resto de variables declaradas con el mismo
nombre en los otros bloques.

Lo único que no podemos hacer en cualquiera de esos bloques, es declarar una


variable llamada n, ya que al estar declarada en el procedimiento, el compilador de
Visual Basic 2005 nos indicará que no podemos ocultar una variable previamente
definida fuera del bloque, tal como podemos ver en la Figura 2.12.

Figura 2.12. Error al ocultar una variable definida en un procedimiento

Esta restricción solo es aplicable a las variables declaradas en el procedimiento, ya


que si declaramos una variable a nivel de módulo, no habrá ningún problema para
usarla dentro de un bloque. Esto es así porque en un procedimiento podemos declarar
variables que se llamen de la misma forma que las declaradas a nivel de módulo,
aunque éstas ocultarán a las del "nivel" superior.

Ámbito de procedimiento
Las variables declaradas en un procedimiento tendrán un ámbito o cobertura que será
el procedimiento en el que está declaradas, y como hemos visto, ese ámbito incluye
también cualquier bloque de código declarado dentro del procedimiento.
Estas variables ocultarán a las que se hayan declarado fuera del procedimiento, si
bien, dependiendo del tipo de módulo, podremos acceder a esas variables "externas"
indicando el nombre completo del módulo o bien usando la instrucción Me, tal como
vimos en el código del constructor parametrizado de la estructura Punto.

Pero mejor veámoslo con un ejemplo. En el siguiente código, definimos una clase en
la que tenemos un campo llamado Nombre, también definimos un método en el que
internamente se utiliza una variable llamada nombre, para acceder a la variable
declarada en la clase, tendremos que usar la instrucción o palabra clave Me.

Public Class Cliente

Public Nombre As String = "Juan"

Function Mostrar() As String

Dim nombre As String = "Pepita"

Return "Externo= " & Me.Nombre & ", interno= " & nombre

End Function

End Class

En este ejemplo, el hecho de que una variable esté declarada con la letra ene en
mayúscula o en minúscula no implica ninguna diferencia, ya que Visual Basic 2005 al
igual que VBScript/VB6 no hace distinciones de este tipo, si bien, en Visual Basic 2005
no se cambia automáticamente las mayúsculas y minúsculas de las variables, salvo
cuando están en el mismo nivel de ámbito; en cambio, si en VBScript/VB6 declaramos
una variable con el mismo nombre, aunque esté en un ámbito diferente, siempre se
usará el estado de mayúsculas/minúsculas de la última definición.

Ámbito de módulo
Cuando hablamos de módulos, nos estamos refiriendo a una clase, a una estructura o
a cualquier otro tipo de datos que nos permita .NET.
En estos casos, las variables declaradas dentro de un tipo de datos serán visibles
desde cualquier parte de ese tipo, siempre teniendo en cuenta las restricciones
mencionadas en los casos anteriores.
Ámbito de espacio de nombres
Los espacios de nombres son los contenedores de tipos de datos de mayor nivel, y
sirven para contener definiciones de clases, estructuras, enumeraciones y delegados
como ya comentamos en el primer módulo. Cualquier tipo definido en el ámbito del
espacio de nombres estará disponible para cualquier otro elemento definido en el
mismo espacio de nombres.

Al igual que ocurre en el resto de ámbitos "inferiores", si definimos un tipo en un


espacio de nombres, podemos usar ese mismo nombre para nombrar a un
procedimiento o a una variable, en cada caso se aplicará el ámbito correspondiente y,
tal como vimos anteriormente, tendremos que usar nombres únicos para poder
acceder a los nombres definidos en niveles diferentes.

La palabra clave Global

En Visual Basic 2005 podemos definir espacios de nombres cuyos nombres sean los
mismos que los definidos en el propio .NET Framework, para evitar conflictos de
ámbitos, podemos usar la palabra clave Global para acceder a los que se han definido
de forma "global" en .NET.
Por ejemplo, si tenemos el siguiente código en el que definimos una clase dentro de
un espacio de nombres llamado System y queremos acceder a uno de los tipos
definidos en el espacio de nombres System de .NET, tendremos un problema:

Namespace System

Class Cliente

Public Nombre As String

Public Edad As System.Int32

End Class

End Namespace

El problema es que el compilador de Visual Basic 2005 nos indicará que el tipo Int32
no está definido, ya que intentará buscarlo dentro del ámbito que actualmente tiene,
es decir, la declaración que nosotros hemos hecho de System, por tanto para poder
acceder al tipo Int32 definido en el espacio de nombres "global" System de .NET
tendremos que usar la instrucción Global, por suerte el IDE de Visual Studio 2005
reconoce este tipo de error y nos ofrece ayuda para poder solventar el conflicto, tal
como vemos en la Figura 2.13:
Figura 2.13. Ayuda del IDE en los conflictos de espacios nombres globales

Nota:
Afortunadamente este conflicto con los espacios de nombres no será
muy habitual para los desarrolladores que usemos el idioma de
Cervantes, por la sencilla razón de que los espacios de nombres de .NET
Framework suelen estar definidos usando palabras en inglés.

Accesibilidad

La accesibilidad es la característica que podemos aplicar a cualquiera de los elementos


que definamos en nuestro código. Dependiendo de la accesibilidad declarada
tendremos distintos tipos de accesos a esos elementos.

Los modificadores de accesibilidad que podemos aplicar a los tipos y elementos


definidos en nuestro código pueden ser cualquiera de los mostrados en la siguiente
lista:

· Public: Acceso no restringido. Este es modificador de accesibilidad con mayor


"cobertura", podemos acceder a cualquier miembro público desde cualquier
parte de nuestro código. Aunque, como veremos, este acceso no restringido
puede verse reducido dependiendo de dónde lo usemos.
· Protected: Acceso limitado a la clase contenedora o a los tipos derivados de
esta clase. Este modificador solamente se usa con clases que se deriven de
otras.
· Friend: Acceso limitado al proyecto actual. Visual Basic 2005 aplica este
modificador de forma predeterminada a los procedimientos declarados en las
clases.
· Protected Friend: Acceso limitado al proyecto actual o a los tipos derivados de
la clase contenedora. Una mezcla de los dos modificadores anteriores.
· Private: Acceso limitado al tipo contenedor. Es el más restrictivos de todos los
modificadores de accesibilidad y en el caso de los campos declarados en las
clases (Class) equivale a usar Dim.

Estos modificadores de accesibilidad los podemos usar tanto en clases, estructuras,


interfaces, enumeraciones, delegados, eventos, métodos, propiedades y campos.
Aunque no serán aplicables en espacios de nombres (Namespace) ni clases de tipo
Module, en estos dos casos siempre tendrán cobertura pública, si bien no se permite
el uso de ningún modificador.

Accesibilidad de las variables en los procedimientos


Las variables declaradas dentro de un procedimiento solo son accesibles dentro de
ese procedimiento, en este caso solo se puede aplicar el ámbito privado, aunque no
podremos usar la instrucción Private, sino Dim o Static.

Nota:
La palabra clave Static, tiene el mismo significado que en VB6, y nos
permite definir una variable privada (o local) al procedimiento para que
mantenga el valor entre diferentes llamadas a ese procedimiento; esto
contrasta con el resto de variables declaradas en un procedimiento cuya
duración es la misma que la vida del propio procedimiento, por tanto, las
variables no estáticas pierden el valor al terminar la ejecución del
procedimiento.

Las accesibilidades predeterminadas

La accesibilidad de una variable o procedimiento en la que no hemos indicado el


modificador de accesibilidad dependerá del sitio en el que la hemos declarado.

Por ejemplo, en las estructuras si definimos los campos usando Dim, estos tendrán un
ámbito igual que si le hubiésemos aplicado el modificador Public; sin embargo, esa
misma variable declarada en una clase (Class o Module) tendrá una accesibilidad
Private. Así mismo, si el elemento que declaramos es un procedimiento y no
indicamos el modificador de ámbito, éste tendrá un ámbito de tipo Public si lo
definimos en una estructura y si el lugar en el que lo declaramos es una clase (o
Module), éste será Friend.

En la siguiente tabla tenemos la accesibilidad predeterminada de cada tipo (clase,


estructura, etc.), así como de las variables declaradas con Dim y de los
procedimientos en los que no se indican el modificador de accesibilidad.

Tipo del tipo de las variables de los


declaradas con procedimientos
Dim
Class Friend Private Friend
Module Friend Private Friend
Structure Friend Public Public
Enum Public N.A. N.A.
Friend (los miembros
siempre son
públicos)
Interface Friend N.A. Public
(no se pueden (no se permite
declarar variables) indicarlo)

Tabla 1.3. La accesibilidad predeterminada de los tipos

Tal como podemos ver en la tabla 1.3, la accesibilidad predeterminada, (la que tienen
cuando no se indica expresamente con un modificador), de todos los tipos es Friend,
es decir, accesible a todo el proyecto, aunque en el caso de las enumeraciones el
modificador depende de dónde se declare dicha enumeración, si está declarada a nivel
de espacio de nombres será Friend, en el resto de los casos será Public.
En la tercera columna tenemos la accesibilidad predeterminada cuando declaramos
las variables con Dim, aunque en las interfaces y en las enumeraciones no se
permiten declarar variables.
La última columna es la correspondiente a los procedimientos, en el caso de las
interfaces no se puede aplicar ningún modificador de accesibilidad y de forma
predeterminada son públicos.

En esta otra tabla tenemos la accesibilidad permitida en cada tipo así como las que
podemos indicar en los miembros de esos tipos.

Tipo del tipo de los miembros


Class Public Public
Friend Friend
Private Private
Protected Protected
Protected Friend Protected Friend
Module Public Public
Friend Friend
Private
Structure Public Public
Friend Friend
Private Private
Enum Public N.A.
Friend
Private
Interface Public N.A.
Friend Siempre son públicos
Private
Protected
Protected Friend

Tabla 1.4. Accesibilidades permitidas en los tipos

Algunos de los modificadores que podemos indicar en los tipos dependen de dónde
declaremos esos tipos, por ejemplo, tan solo podremos indicar el modificador privado
de las enumeraciones cuando estas se declaren dentro de un tipo. En el caso de las
clases e interfaces, los modificadores Protected y Protected Friend solo podremos
aplicarlos cuando están declaradas dentro de una clase (Class).
Anidado de tipos

Tal como hemos comentado en el párrafo anterior, podemos declarar tipos dentro de
otros tipos, por tanto el ámbito y accesibilidad de esos tipos dependen del ámbito y
accesibilidad del tipo que los contiene. Por ejemplo, si declaramos una clase con
acceso Friend, cualquier tipo que esta clase contenga siempre estará supeditado al
ámbito de esa clase, por tanto si declaramos otro tipo interno, aunque lo declaremos
como Public, nunca estará más accesible que la clase contenedora, aunque en estos
casos no habrá ningún tipo de confusión, ya que para acceder a los tipos declarados
dentro de otros tipos siempre tendremos que indicar la clase que los contiene.
En el siguiente código podemos ver cómo declarar dos clases "anidadas". Tal como
podemos comprobar, para acceder a la clase Salario debemos indicar la clase Cliente,
ya que la única forma de acceder a una clase anidada es mediante la clase
contenedora.

Friend Class Cliente

Public Nombre As String

Public Class Salario

Public Importe As Decimal

End Class

End Class

' Para usar la clase Salario debemos declararla de esta forma:

Dim s As New Cliente.Salario

s.Importe = 2200

Los tipos anidables


Cualquiera de los tipos mostrados en la tabla 1.4, excepto las enumeraciones, pueden
contener a su vez otros tipos. La excepción es el tipo Module que aunque puede
contener a otros tipos, no puede usarse como tipo anidado. Una enumeración siempre
puede usarse como tipo anidado.

Nota:
Los espacios de nombres también pueden anidarse y contener a su vez
cualquiera de los tipos mostrados en la tabla 1.4, incluso tipos Module.
El nombre completo de un tipo
Tal como hemos visto, al poder declarar tipos dentro de otros tipos y estos a su vez
pueden estar definidos en espacios de nombres, podemos decir que el nombre
"completo" de un tipo cualquiera estará formado por el/los espacios de nombres y
el/los tipos que los contiene, por ejemplo si la clase Cliente definida anteriormente
está a su vez dentro del espacio de nombres Ambitos, el nombre completo será:
Ambitos.Cliente y el nombre completo de la clase Salario será:
Ambitos.Cliente.Salario.
Aunque para acceder a la clase Cliente no es necesario indicar el espacio de
nombres, al menos si la queremos usar desde cualquier otro tipo declarado dentro de
ese espacio de nombres, pero si nuestra intención es usarla desde otro espacio de
nombre externo a Ambitos, en ese caso si que tendremos que usar el nombre
completo.

Por ejemplo, en el siguiente código tenemos dos espacios de nombres que no están
anidados, cada uno de ellos declara una clase y desde una de ellas queremos acceder
a la otra clase, para poder hacerlo debemos indicar el nombre completo, ya que en
caso contrario, el compilador de Visual Basic 2005 sería incapaz de saber a que clase
queremos acceder.

Namespace Uno

Public Class Clase1

Public Nombre As String

End Class

End Namespace

Namespace Dos

Public Class Clase2

Public Nombre As String

Sub Main()

Dim c1 As New Uno.Clase1

c1.Nombre = "Pepe"

End Sub

End Class

End Namespace

Esto mismo lo podemos aplicar en el caso de que tengamos dos clases con el mismo
nombre en espacios de nombres distintos.
Nota:
En el mismo proyecto podemos tener más de una declaración de un
espacio de nombres con el mismo nombre, en estos casos el compilador
lo tomará como si todas las clases definidas estuvieran dentro del mismo
espacio de nombres, aunque estos estén definidos en ficheros diferentes.

Importación de espacios de nombres


Tal como hemos comentado, los espacios de nombres pueden contener otros espacios
de nombres y estos a su vez también pueden contener otros espacios de nombres o
clases, y como hemos visto, para poder acceder a una clase que no esté dentro del
mismo espacio de nombres debemos indicar el "nombre completo".

Para evitar estar escribiendo todos los espacios de nombres en los que está la clase
que nos interesa declarar, podemos usar una especie de acceso directo o para que lo
entendamos mejor, podemos crear una especie de "Path", de forma que al declarar
una variable, si esta no está definida en el espacio de nombres actual, el compilador
busque en todos los espacios de nombres incluidos en esas rutas (paths).

Esto lo conseguimos usando la instrucción Imports seguida del espacio de nombres


que queremos importar o incluir en el path de los espacios de nombres.
Podemos usar tantas importaciones de espacios de nombres como necesitemos y
estas siempre deben aparecer al principio del fichero, justo después de las
instrucciones Options.

Por ejemplo, si tenemos el código anterior y hacemos la importación del espacio de


nombres en el que está definida la clase Clase1:

Imports Uno

podremos acceder a esa clase de cualquiera de estas dos formas:

Dim c1 As New Uno.Clase1

Dim c1 As New Clase1

Alias de espacios de nombres


Si hacemos demasiadas importaciones de nombres, el problema con el que nos
podemos encontrar es que el IntelliSense de Visual Basic 2005 no sea de gran ayuda,
ya que mostrará una gran cantidad de clases, y seguramente nos resultará más difícil
encontrar la clase a la que queremos acceder, o también podemos encontrarnos en
ocasiones en las que nos interese usar un nombre corto para acceder a las clases
contenidas en un espacio de nombres, por ejemplo, si queremos indicar de forma
explícita las clases de un espacio de nombres como el de Microsoft.VisualBasic,
podemos hacerlo de esta forma:

Imports vb = Microsoft.VisualBasic

De esta forma podemos usar el "alias" vb para acceder a las clases y demás tipos
definidos en ese espacio de nombres.
En las figuras 1.14 1.15 podemos ver las dos formas de acceder a las clases del
espacio de ese espacio de nombres, en el primer caso sin usar un alias y en el
segundo usando el alias vb.

Figura 2.14. Los miembros de un espacio de nombres usando el nombre


completo
Figura 2.15. Acceder a los miembros de un espacio de nombres usando un
alias

Ver video 1 de esta lección (Ámbito y accesibilidad, herencia)


Ver video 2 de esta lección (Ámbito 2)
Propiedades

Las propiedades son los miembros de los tipos que nos permiten acceder a los datos
que dicho tipo manipula. Normalmente una propiedad está relacionada con un campo,
de forma que el campo sea el que realmente contenga el valor y la propiedad
simplemente sea una especie de método a través del cual podemos acceder a ese
valor.

Debido a que el uso de las propiedades realmente nos permite acceder a los valores
de una clase (o tipo), se suelen confundir los campos con las propiedades. De hecho
en VBScript/VB6 si definimos una variable pública, ésta se convierte en una propiedad
de la clase. En Visual Basic 2005 casi ocurre lo mismo, pero realmente un campo (o
variable) público no es una propiedad, al menos en el sentido de que el propio .NET
Framework no lo interpreta como tal, aunque en la práctica nos puede parecer que es
así, ya que se utilizan de la misma forma. Pero no debemos dejarnos llevar por la
comodidad y si no queremos perder funcionalidad, debemos diferenciar en nuestro
código las propiedades de los campos.

Lo primero que debemos tener presente es que gracias a esta diferenciación que hace
.NET Framework, (realmente VBScript/VB6 también la hace), podemos poner en
práctica una de las características de la programación orientada a objetos: la
encapsulación, de forma, que la manipulación de los datos que una clase contiene
siempre se deben hacer de forma "interna" o privada a la clase, dejando a las
propiedades la posibilidad de que externamente se manipulen, de forma controlada,
esos datos. De esta forma tendremos mayor control sobre cómo se acceden o se
asignan los valores a esos datos, ya que al definir una propiedad, tal como hemos
comentado, realmente estamos definiendo un procedimiento con el cual podemos
controlar cómo se acceden a esos datos.
Este concepto no ha cambiado, al menos en el fondo, con respecto a como lo
hacemos con VBScript/VB6, ya que en estos lenguajes también podemos declarar
procedimientos del tipo propiedad, aunque la forma de hacerlo en Visual Basic 2005 si
que ha cambiado un poco, tal como tendremos ocasión de ver a continuación.

Definir una propiedad

Debido a que una propiedad realmente nos permite acceder a un dato que la clase (o
estructura) manipula, siempre tendremos un campo relacionado con una propiedad.
El campo será el que contenga el valor y la propiedad será la que nos permita
manipular ese valor.

En Visual Basic 2005, las propiedades las declaramos usando la instrucción Property y
la definición de la misma termina con End Property, esto es prácticamente igual (o
casi) que en VBScript/VB6. La diferencia principal es que en VBScript/VB6 cuando
definimos una propiedad, debemos hacerlo en partes, por ejemplo, si queremos
definir cuando accedemos al valor, declaramos esa acción usando las instrucciones
Property Get y para definir la parte de la propiedad que asigna un nuevo valor, lo
hacemos mediante Property Let. En Visual Basic 2005 esas dos acciones, la de lectura
(GET) y asignación (LET), las debemos indicar en dos bloques dentro de la propia
declaración de la propiedad, el bloque que nos permite acceder al valor de la
propiedad estará indicado por la instrucción Get y acaba con End Get, por otra parte,
el bloque usado para asignar un valor a la propiedad se define mediante la instrucción
Set y acaba con End Set.

Nota:
En VBScript/VB6 realmente disponemos de tres bloques o partes en la
definición de una propiedad:
Property Get, que nos permite acceder al valor de la propiedad.
Propery Let, que nos permite asignar un valor a la propiedad.
Property Set, el cual lo usamos cuando la propiedad representa un
objeto y queremos asignar dicho objeto a la propiedad.
La diferencia entre Let y Set es que el primero se usará en valores de
tipos por valor y el segundo en tipos por referencia, aunque el
compilador de VBScript/VB6 realmente usará uno u otro según
asignemos el valor directamente a la propiedad o usemos la instrucción
Set para hacer esa asignación.

Tal como indicamos en la nota anterior, en VBScript/VB6 existen dos formas de


asignar valores a una propiedad de una clase, pero en Visual Basic 2005 no existe ese
"conflicto" en la forma de asignar los valores, ya que siempre que se asigna un valor
a una propiedad se hace sin usar la instrucción Set, de hecho no se puede usar Set
para realizar asignaciones a propiedades en VB2005.

Veamos como definir una propiedad en Visual Basic 2005:

Public Class Cliente

Private _nombre As String

Public Property Nombre() As String


Get

Return _nombre

End Get

Set(ByVal value As String)

_nombre = value

End Set

End Property

End Class

Como podemos comprobar tenemos dos bloques de código, el bloque Get que es el
que se usa cuando queremos acceder al valor de la propiedad, por tanto devolvemos
el valor del campo privado usado para almacenar ese dato. El bloque Set es el usado
cuando asignamos un valor a la propiedad, este bloque tiene definido un parámetro
(value) que representa al valor que queremos asignar a la propiedad.

Aunque en Visual Basic 2005 las definiciones para obtener o asignar el valor de la
propiedad se hacen en bloques definidos dentro de un procedimiento del tipo
Property, esta forma de definir las propiedades no se diferencia demasiado a como lo
hacemos en VBScript/VB6, y una vez que nos acostumbremos lo veremos como una
forma más "compacta" de hacerlo.

Propiedades de solo lectura

En ciertas ocasiones nos puede resultar interesante que una propiedad sea de solo
lectura, de forma que el valor que representa no pueda ser cambiado.

En VBScript/VB6 para definir una propiedad de solo lectura bastaba con definir solo la
parte Get de la propiedad., En Visual Basic 2005 también se hace de esa forma, pero
debemos indicar expresamente que esa es nuestra intención. Por tanto no solo
basta con definir solo el bloque Get, sino que debemos usar el modificador ReadOnly
para que el compilador de Visual Basic 2005 acepte la declaración:

Public ReadOnly Property Hoy() As Date

Get

Return Date.Now

End Get

End Property
Propiedades de solo escritura

De igual forma, si queremos definir una propiedad que sea de solo escritura, solo
definiremos el bloque Set, pero al igual que ocurre con las propiedades de solo
lectura, debemos indicar expresamente que esa es nuestra intención, para ello
usaremos la palabra clave WriteOnly:

Public WriteOnly Property Password() As String

Set(ByVal value As String)

If value = "blablabla" Then

' ok

End If

End Set

End Property

Diferente accesibilidad para los bloques Get y Set

En las propiedades normales (de lectura y escritura), podemos definir diferentes


niveles de accesibilidad a cada uno de los dos bloques que forman una propiedad. Por
ejemplo, podríamos definir el bloque Get como público, (siempre accesible), y el
bloque Set como Private, de forma que solo se puedan realizar asignaciones desde
dentro de la propia clase.

Por ejemplo, el salario de un empleado podríamos declararlo para que desde cualquier
punto se pueda saber el importe, pero la asignación de dicho importe solo estará
accesible para los procedimientos definidos en la propia clase:

Public Class Empleado

Private _salario As Decimal

Public Property Salario() As Decimal

Get

Return _salario

End Get

Private Set(ByVal value As Decimal)

_salario = value

End Set
End Property

End Class

Para hacer que el bloque Set sea privado, lo indicamos con el modificador de
accesibilidad Private, al no indicar ningún modificador en el bloque Get, éste será el
mismo que el de la propiedad.

En VBScript/VB6 podemos hacer esto mismo definiendo como público la declaración


de Property Get y como privado la de la asignación: Property Let.

Nota:
El nivel de accesibilidad de los bloques Get o Set debe ser igual o inferior
que el de la propiedad, por tanto si la propiedad la declaramos como
Private, no podemos definir como público los bloques Get o Set.

Propiedades predeterminadas

Una cosa que echamos en falta en Visual Basic 2005 son las propiedades
predeterminadas. Aunque existen no son exactamente igual que en VB6. En VB6
podemos definir como propiedad predeterminada cualquiera de las propiedades de la
clase, aunque la forma de hacerlo es bastante "rebuscada" y poco intuitiva. Al menos
podemos definir como predeterminada la que más nos interese.
En Visual Basic 2005 no podemos definir como predeterminada cualquier propiedad,
ya que debido a como se realizan las asignaciones de objetos en .NET (sin necesidad
de usar Set), siempre debemos indicar la propiedad a la que queremos asignar el
valor, porque en caso de que no se indique ninguna, el compilador interpretará que lo
que queremos asignar es un objeto y no un valor a una propiedad.
Para evitar conflictos o tener que usar alguna instrucción "extra" para que se sepa si
lo que queremos asignar es un valor o un objeto, en Visual Basic 2005 las
propiedades predeterminadas siempre deben ser parametrizadas, es decir, tener
como mínimo un parámetro.
Para indicar que una propiedad es la propiedad por defecto lo debemos hacer usando
la instrucción Default:

Default Public ReadOnly Property Item(ByVal index As Integer) As


Empleado

Get

' ...

End Get

End Property

Como vemos en este ejemplo, una propiedad por defecto puede ser de solo lectura y
también de solo escritura o de lectura/escritura.
Para usar esta propiedad, al ser la propiedad por defecto, no es necesario indicar el
nombre de la propiedad, aunque si así lo deseamos podemos indicarla, aunque en
este caso no tendría mucha utilidad el haberla definido como propiedad por defecto:

Dim e As New Empleado

Dim e1 As Empleado = e(2)

' También podemos usarla indicando el nombre de la propiedad:

Dim e2 As Empleado = e.Item(2)

Sobrecarga de propiedades predeterminadas


Debido a que las propiedades predeterminadas de Visual Basic 2005 deben recibir un
parámetro, podemos crear sobrecargas de una propiedad predeterminada, aunque
debemos recordar que para que esa sobrecarga pueda ser posible, el tipo o número
de argumentos deben ser distintos entre las distintas sobrecargas, por ejemplo
podríamos tener una sobrecarga que reciba un parámetro de tipo entero y otra que lo
reciba de tipo cadena:

Default Public ReadOnly Property Item(ByVal index As Integer) As


Empleado

Get

' ...

End Get

End Property

Default Public Property Item(ByVal index As String) As Empleado

Get

' ...

End Get

Set(ByVal value As Empleado)

'

End Set

End Property
Incluso como vemos en este código una de las sobrecargas puede ser de solo lectura
y la otra de lectura/escritura. Lo que realmente importa es que el número o tipo de
parámetros de cada sobrecarga sea diferente.

Las propiedades predeterminadas tienen sentido en Visual Basic 2005 cuando


queremos que su uso sea parecido al de un array. Por tanto es habitual que las clases
de tipo colección sean las más indicadas para definir propiedades por defecto. Aunque
no siempre el valor devuelto debe ser un elemento de una colección o array, ya que
podemos usar las propiedades predeterminadas para acceder a los miembros de una
clase "normal", de forma que se devuelva un valor según el parámetro indicado, esto
nos permitiría, por ejemplo, acceder a los miembros de la clase desde un bucle For. Si
definimos una propiedad predeterminada como en el siguiente código:

Public Class Articulo

Public Descripción As String

Public PrecioVenta As Decimal

Public Existencias As Decimal

Default Public ReadOnly Property Item(ByVal index As Integer) As


String

Get

Select Case index

Case 0

Return Descripción

Case 1

Return PrecioVenta.ToString

Case 2

Return Existencias.ToString

Case Else

Return ""

End Select

End Get

End Property

End Class

La podemos usar de esta forma:

For i As Integer = 0 To 2
Console.WriteLine( art(i) )

Next

Resumiendo:
Las propiedades predeterminadas en Visual Basic 2005 siempre deben tener un
parámetro, para que su uso se asemeje a un array, es decir, se use como indizador
de la clase. Por convención, cuando se usan como indizador, el nombre de la
propiedad predeterminada suele ser Item.

Ver video de esta lección (Clases 3: Las propiedades)


Interfaces

Las interfaces son un elemento bastante importante en .NET Framework, ya que de


hecho se utiliza con bastante frecuencia. En esta lección veremos que son las
interfaces y como utilizarlas en nuestros proyectos. También veremos que papel
juegan en .NET y cómo aplicar algunas de las definidas en la biblioteca base.

¿Qué es una interfaz?

Las interfaces son una forma especial de una clase, aunque la diferencia principal con
las clases es que las interfaces no contienen código ejecutable, solo definen los
miembros.
Para entenderlo mejor, veamos las interfaces desde el punto de vista de Visual Basic
6.0.

En Visual Basic 6.0, cuando definimos una clase, realmente estamos haciendo dos
cosas:

1- Definiendo una interfaz con cada uno de los miembros que la clase contiene: métodos,
propiedades, eventos, etc.
2- Definiendo el código a utilizar por cada uno de esos miembros.

Desde ese punto de vista, podemos decir que una interfaz define cada uno de los
miembros de una clase, es decir, que tipo de método es, si los métodos tienen
parámetros, cuantos y de que tipos son, que propiedades o eventos define la clase,
etc.
Por tanto, podemos decir que la interfaz de una clase indica los miembros que dicha
clase expone, y como hemos indicado anteriormente, cuando en VB6 definimos una
clase, también estamos definiendo una interfaz, de hecho en Visual Basic 6.0 no hay
forma de definir interfaces como algo independiente de una clase; cuando queremos
definir una interfaz en VB6, lo más que podemos hacer es definir una clase sin código
ejecutable.

Pero Visual Basic 2005, va aún más lejos, ya que las interfaces las definimos de forma
independiente de las clases. Es más, cuando definimos una clase NO estamos
definiendo una interfaz.

Para definir una interfaz en VB2005 tenemos que usar la instrucción Interface seguida
del nombre y terminar la declaración con End Interface:

Public Interface IAnimal

'...

End Interface

Nota:
Según las indicaciones de nomenclatura de .NET Framework, se
recomienda que todas las interfaces empiecen con una I mayúscula
seguida del nombre al que hacer referencia la interfaz.

¿Qué contiene una interfaz?

Al principio de esta lección hemos comentado que las interfaces no contienen código,
solo define los miembros que contiene. Esa definición la haremos como cualquier otra,
con la diferencia de que no incluimos ningún código, solo la "firma" o el prototipo de
cada uno de esos miembros.
En el siguiente código definimos una interfaz que contiene los cuatros tipos de
miembros típicos de cualquier clase:

Public Interface IPrueba

Sub Mostrar()

Function Saludo(ByVal nombre As String) As String

Property Nombre() As String

Event DatosCambiados()

End Interface
El primer miembro de esta interfaz, es un método de tipo Sub que no recibe
parámetros.
El siguiente método es una función que devuelve un valor de tipo String y recibe un
parámetro también de tipo cadena.
A continuación definimos una propiedad que devuelve una cadena.
Por último, definimos un evento.

Como podemos observar, lo único que tenemos que hacer es indicar el tipo de
miembro y si recibe o no algún parámetro o argumento.

Dos cosas importantes sobre las interfaces:

1- No se pueden definir campos.


2- Los miembros de las interfaces siempre son públicos, tal como indicábamos en la
tabla 2.3.

Una interfaz es un contrato

Siempre que leemos sobre las interfaces, lo primero con lo que nos solemos encontrar
es que una interfaz es un contrato. Veamos que nos quieren decir con esa frase.

Tal como acabamos de ver, las interfaces solo definen los miembros, pero no el
código a usar en cada uno de ellos, esto es así precisamente porque el papel que
juegan las interfaces es el de solo indicar que es lo que una clase o estructura puede,
o mejor dicho, debe implementar.

Si en una clase indicamos que queremos "implementar" una interfaz, esa clase debe
definir cada uno de los miembros que la interfaz expone. De esta forma nos
aseguramos de que si una clase implementa una interfaz, también implementa todos
los miembros definidos en dicha interfaz.

Cuando una clase implementa una interfaz está firmando un contrato con el que se
compromete a definir todos los miembros que la clase define, de hecho el propio
compilador nos obliga a hacerlo.

Las interfaces y el polimorfismo

Como comentamos anteriormente, el polimorfismo es una característica que nos


permite acceder a los miembros de un objeto sin necesidad de tener un conocimiento
exacto de ese objeto (o de la clase a partir del que se ha instanciado), lo único que
tenemos que saber es que ese objeto tiene ciertos métodos (u otros miembros) a los
que podemos acceder. También hemos comentado que las interfaces representan un
contrato entre las clases que las implementan, por tanto las interfaces pueden ser,
(de hecho lo son), un medio para poner en práctica esta característica de la
programación orientada a objetos. Si una clase implementa una interfaz, esa clase
tiene todos los miembros de la interfaz, por tanto podemos acceder a esa clase, que
en principio pude sernos desconocida, desde un objeto del mismo tipo que la interfaz.
Usar una interfaz en una clase

Para poder utilizar una interfaz en una clase, o dicho de otra forma: para
"implementar" los miembros expuestos por una interfaz en una clase debemos
hacerlo mediante la instrucción Implements seguida del nombre de la interfaz:

Public Class Prueba

Implements IPrueba

Y como comentábamos, cualquier clase que implemente una interfaz debe definir
cada uno de los miembros de esa interfaz, por eso es el propio Visual Basic el
encargado de crear automáticamente los métodos y propiedades que la interfaz
implementa, aunque solo inserta el "prototipo" de cada uno de esos miembros,
dejando para nosotros el trabajo de escribir el código.

Usando la definición de la interfaz IPrueba que vimos antes, el código que añadirá
VB será el siguiente:

Public Class Prueba

Implements IPrueba

Public Event DatosCambiados() Implements IPrueba.DatosCambiados

Public Sub Mostrar() Implements IPrueba.Mostrar

End Sub

Public Property Nombre() As String Implements IPrueba.Nombre

Get

End Get

Set(ByVal value As String)

End Set

End Property
Public Function Saludo(ByVal nombre As String) As String _

Implements IPrueba.Saludo

End Function

End Class

Como podemos apreciar, no solo ha añadido las definiciones de cada miembro de la


interfaz, sino que también añade código extra a cada uno de esos miembros: la
instrucción Implements seguida del nombre de la interfaz y el miembro al que se hará
referencia.

Nota:
Si el lector antes ha utilizado las interfaces en VB6, esto no le resultará
extraño, ya que en ese lenguaje, cuando implementamos una interfaz
también se crean automáticamente las definiciones de los miembros que
contiene la interfaz, aunque el formato utilizado por VBScript/VB6 es:
<Nombre de la interfaz> <guión bajo> <nombre del método>, por
ejemplo: Private Sub IPrueba_Mostrar().

La utilidad de que en cada uno de los miembros se indique expresamente el método


al que se hace referencia, es que podemos usar nombres diferentes al indicado en la
interfaz. Por ejemplo, si implementamos esta interfaz en una clase que solo utilizará
la impresora, al método Mostrar lo podríamos llamar Imprimir que sería más
adecuado, en ese caso simplemente cambiamos el nombre del método de la clase
para que implemente el método Mostrar de la interfaz:

Public Sub Imprimir() Implements IPrueba.Mostrar

End Sub

De esta forma, aunque en la clase se llame de forma diferente, realmente hace


referencia al método de la interfaz.

Acceder a los miembros implementados

Una vez que tenemos implementada una interfaz en nuestra clase, podemos acceder
a esos miembros de forma directa, es decir, usando un objeto creado a partir de la
clase:

Dim prueba1 As New Prueba

prueba1.Mostrar()
O bien de forma indirecta, por medio de una variable del mismo tipo que la interfaz:

Dim prueba1 As New Prueba

Dim interfaz1 As IPrueba

interfaz1 = prueba1

interfaz1.Mostrar()

¿Qué ha ocurre aquí?


Como ya comentamos anteriormente, cuando asignamos variables por referencia,
realmente lo que asignamos son referencias a los objetos creados en la memoria, por
tanto la variable interfaz1 está haciendo referencia al mismo objeto que prueba1,
aunque esa variable solo tendrá acceso a los miembros de la clase Prueba que
conoce, es decir, los miembros definidos en IPrueba.
Si la clase define otros miembros que no están en la interfaz, la variable interfaz1 no
podrá acceder a ellos.

Saber si un objeto implementa una interfaz

Si las interfaces sirven para acceder de forma anónima a los métodos de un objeto, es
normal que en Visual Basic tengamos algún mecanismo para descubrir si un objeto
implementa una interfaz.

Para realizar esta comprobación podemos usar en una expresión If/Then la instrucción
TypeOf... Is, de forma que si la variable indicada después de TypeOf contiene el tipo
especificado después de Is, la condición se cumple:

If TypeOf prueba1 Is IPrueba Then

interfaz1 = prueba1

interfaz1.Mostrar()

End If

De esta forma nos aseguramos de que el código se ejecutará solamente si la variable


prueba1 contiene una definición de la interfaz IPrueba.

Implementación de múltiples interfaces

En Visual Basic 2005, una misma clase puede implementar más de una interfaz. Para
indicar que implementamos más de una interfaz podemos hacerlo de dos formas:

1- Usando nuevamente la instrucción Implements seguida del nombre de la interfaz:


Public Class Prueba

Implements IPrueba

Implements IComparable

2- Indicando las otras interfaces en la misma instrucción Implements, pero


separándolas con comas:

Public Class Prueba

Implements IPrueba, IComparable

De cualquiera de las dos formas es válido implementar más de una interfaz, aunque
en ambos casos siempre debemos definir los miembros de cada una de esas
interfaces.

Múltiple implementación de un mismo miembro

Como acabamos de comprobar, una misma clase puede implementar más de una
interfaz, y esto nos puede causar una duda:
¿Qué ocurre si dos interfaces definen un método que es idéntico en ambas?
En principio, no habría problemas, ya que el propio Visual Basic crearía dos métodos
con nombres diferentes y a cada uno le asignaría la implementación de ese método
definido en cada interfaz.
Por ejemplo, si tenemos otra interfaz que define el método Mostrar y la
implementamos en la clase Prueba, la declaración podría quedar de esta forma:

Public Interface IMostrar

Sub Mostrar()

End Interface

Public Sub Mostrar1() Implements IMostrar.Mostrar

End Sub

Aunque si ambos métodos hacen lo mismo, en este ejemplo mostrar algo, podríamos
hacer que el mismo método de la clase sirva para implementar el de las dos
interfaces:

Public Sub Mostrar() Implements IPrueba.Mostrar, IMostrar.Mostrar


End Sub

Es decir, lo único que tendríamos que hacer es indicar la otra implementación


separándola con una coma.

¿Dónde podemos implementar las interfaces?

Para ir acabando este tema nos queda por saber, entre otras cosas, dónde podemos
implementar las interfaces, es decir, en que tipos de datos podemos usar
Implements.

La implementación de interfaces la podemos hacer en las clases (Class), estructuras


(Structure) y en otras interfaces (Interface).

Debido a que una interfaz puede implementar otras interfaces, si en una clase
implementamos una interfaz que a su vez implementa otras, esa clase tendrá
definidas cada una de las interfaces, lo mismo ocurre con una clase que "se derive" de
otra clase que implementa alguna interfaz, la nueva clase también incorporará esa
interfaz.

Nota:
Cuando una interfaz implementa otras interfaces, éstas no se pueden
indicar mediante Implements, en lugar de usar esa instrucción debemos
usar Inherits.

Public Interface IPrueba2


Inherits IMostrar

Si en una clase implementamos una interfaz que a su vez implementa otras


interfaces, esa clase tendrá definiciones de todos los miembros de todas las
interfaces, por ejemplo, si tenemos la siguiente definición de la interfaz IPrueba2
que "implementa" la interfaz IMostrar:

Public Interface IPrueba2

Inherits IMostrar

Function Saludo(ByVal nombre As String) As String

Property Nombre() As String

Event DatosCambiados()

End Interface

Y la clase Prueba2 implementa IPrueba2, la definición de los miembros quedaría de


la siguiente forma:
Public Class Prueba2

Implements IPrueba2

Public Sub Mostrar() Implements IMostrar.Mostrar

End Sub

Public Event DatosCambiados() Implements IPrueba2.DatosCambiados

Public Property Nombre() As String Implements IPrueba2.Nombre

Get

End Get

Set(ByVal value As String)

End Set

End Property

Public Function Saludo(ByVal nombre As String) As String _

Implements IPrueba2.Saludo

End Function

End Class

En este código, el método Mostrar se indica mediante la interfaz IMostrar, pero


también se puede hacer por medio de IPrueba2.Mostrar, ya que IPrueba2 también
lo implementa (o hereda).

Si dejamos que Visual Basic cree los miembros, no tendremos problemas a la hora de
definirlos. Pero si lo hacemos manualmente, aunque dentro del IDE de Visual Basic,
éste nos ayuda indicándonos que interfaces implementamos y qué miembros son los
que se adecuan a la declaración que estamos usando, tal como podemos comprobar
en la Figura 2.02.16:
Figura 2.02.16 IntelliSense solo muestra los métodos que mejor se adecuan
a la declaración

Un ejemplo práctico usando una interfaz de .NET

Tal como comentamos al principio, el propio .NET está "plagado" de interfaces, cada
una de ellas tiene un fin concreto, por ejemplo, si queremos definir una clase que
pueda ser clasificada por el propio .NET, esa clase debe implementar la interfaz
IComparable, ya que el método Sort, (de la clase que contiene los elementos del tipo
definido por nosotros), que es el encargado de clasificar los elementos, hará una
llamada al método IComparable.CompareTo de cada uno de los objetos que queremos
clasificar, por tanto, si la clase no ha definido esa interfaz, no podremos clasificar los
elementos que contenga.

En el siguiente código tenemos la definición de una clase llamada Empleado que


implementa la interfaz IComparable y en el método CompareTo hace la comprobación
de que objeto es mayor o menor, si el de la propia clase o el indicado en el parámetro
de esa función:

Public Class Empleado

Implements IComparable

Public Nombre As String

Public Sub New(ByVal nombre As String)

Me.Nombre = nombre

End Sub

' Si el objeto es del tipo Empleado, comparamos los nombres.

' Si no es del tipo Empleado, devolvemos un cero

' que significa que los dos objetos son iguales.

Public Function CompareTo(ByVal obj As Object) As Integer _

Implements System.IComparable.CompareTo

If TypeOf obj Is Empleado Then


Dim e1 As Empleado = CType(obj, Empleado)

Return String.Compare(Me.Nombre, e1.Nombre)

Else

Return 0

End If

End Function

End Class

En el método CompareTo hacemos una comprobación de que el objeto con el que


debemos realizar la comparación es del tipo Empleado, en ese caso convertimos el
objeto pasado en uno del tipo Empleado y comparamos los nombres.
Si el objeto que recibe el método no es del tipo Empleado, devolvemos un cero, para
indicar que no haga ninguna clasificación, ya que ese valor indica que los dos objetos
son iguales.
Esta comparación no es estrictamente necesaria, ya que si no indicamos el valor que
debe devolver una función, devolverá un valor cero, al menos en este caso, ya que el
tipo a devolver es un número entero.

Esta clase la podemos usar de esta forma:

' Una colección de datos del tipo Empleado.

Dim empleados As New System.Collections.Generic.List(Of Empleado)

' Añadimos varios empleados a la colección.

empleados.Add(New Empleado("Pepe"))

empleados.Add(New Empleado("Bernardo"))

empleados.Add(New Empleado("Juan"))

empleados.Add(New Empleado("Ana"))

' Clasificamos los empleados de la colección.

empleados.Sort()

' Mostramos los datos una vez clasificados.

For Each e1 As Empleado In empleados


Console.WriteLine(e1.Nombre)

Next

Ver video 1 de esta lección (Interfaces 1)


Ver video 2 de esta lección (Interfaces 2)
Ver video 3 de esta lección (Interfaces 3 y herencia)
Introducción

Es indiscutible que por mucho que nos lo propongamos, nuestras aplicaciones no


estarán libres de errores, y no nos referimos a errores sintácticos, ya que,
afortunadamente, el IDE (Integrated Development Envirnment, entorno de desarrollo
integrado) de Visual Studio 2005 nos avisará de cualquier error sintáctico e incluso de
cualquier asignación no válida (al menos si tenemos activado Option Strict On). Pero
de lo que no nos avisará, como es lógico, será de los errores que se produzcan en
tiempo de ejecución. Para estos casos, Visual Basic pone a nuestra disposición el
manejo de excepciones. Veamos pues cómo utilizarlo, sobre todo el sistema de
excepciones estructuradas que es el recomendable para cualquier desarrollo con .NET
Framework.

Manejo de excepciones

· Manejo de excepciones
o Manejo de excepciones no estructuradas
o Manejo de excepciones estructuradas
§ Bloque Try
§ Bloque Catch
§ Varias capturas de errores en un mismo bloque Try/Catch
§ Evaluación condicional en un bloque Catch
§ Bloque Finally
o Captura de errores no controlados
Manejo de excepciones

En Visual Basic 2005 el tratamiento de errores (excepciones) ha cambiado con


respecto a como lo hacemos en VBScript/VB6, ahora podemos usar un tratamiento de
excepciones estructurado, de esta forma podemos detectar los errores que se
produzcan en nuestras aplicaciones de una forma más "ordenada".
En VBScript/VB6 la única forma de detectar errores es usando On Error. Esta forma
no estructurada de tratar los errores se sigue soportando en Visual Basic 2005, pero a
todas luces no es la forma recomendada. Debemos adaptar nuestra mente al formato
estructurado, ya que en un principio nos parecerá que no es tan efectivo como On
Error.

En esta lección veremos cómo tratar los errores de forma estructurada, ya que el
"viejo" On Error sigue funcionando de la misma forma que en VBScript/VB6.

Manejo de excepciones no estructuradas

Como hemos comentado, en Visual Basic 2005 también podemos usar el "viejo"
sistema de tratamientos de errores, es decir, el "clásico" On Error... que ahora se
llama tratamiento de errores no estructurado. La forma de utilizar On Error es la
misma que en VBScropt/VB6, por tanto no vamos a entrar en detalles de cómo usar
esta forma de interceptar errores, solo aclarar un par de cosas que debemos tener en
cuenta:

La primera es: intentar no usar esta forma de detectar errores, es preferible, aunque
al principio cueste adaptarse, utilizar los errores estructurados.
La segunda es que no podemos usar los dos sistemas al mismo tiempo, por lo menos
en un mismo método o propiedad, o utilizamos On Error o utilizamos Try/Catch. De
todas formas, si utilizamos el IDE (entorno integrado) de Visual Basic, será el propio
compilador el que nos avise cuando mezclemos las dos formas de detectar los
errores.

Manejo de excepciones estructuradas

Las excepciones en Visual Basic 2005 las podemos controlar usando las instrucciones
Try / Catch / Finally. Estas instrucciones realmente son bloques de instrucciones, al
estilo de If / Else.
Cuando queramos controlar una parte del código que puede producir un error lo
incluimos dentro del bloque Try. Si se produce un error, éste lo podemos detectar en
el bloque Catch. Por último, independientemente de que se produzca o no una
excepción, podemos ejecutar el código que incluyamos en el bloque Finally, para
indicar el final del bloque de control de excepciones lo haremos con End Try.

Cuando creamos una estructura de control de excepciones no estamos obligados a


usar los tres bloques, aunque el primero: Try sí es necesario, ya que es el que le
indica al compilador que tenemos intención de controlar los errores que se produzcan.
Por tanto podemos crear un "manejador" de excepciones usando los tres bloques,
usando Try y Catch o usando Try y Finally.

Veamos ahora con más detalle cada uno de estos bloques y que es lo que podemos
hacer en cada uno de ellos.

Bloque Try
En este bloque incluiremos el código en el que queremos comprobar los errores.
El código a usar será un código normal, es decir, no tenemos que hacer nada en
especial, ya que en el momento que se produzca el error se usará (si hay) el código
del bloque Catch.

Bloque Catch
Si se produce una excepción, ésta la capturamos en un bloque Catch.

En el bloque Catch podemos indicar que tipo de excepción queremos capturar, para
ello usaremos una variable de tipo Exception, la cual pude ser del tipo de error
específico que queremos controlar o de un tipo genérico.
Por ejemplo, si sabemos que nuestro código puede producir un error al trabajar con
ficheros, podemos usar un código como este:

Try

' código para trabajar con ficheros, etc.

Catch ex As System.IO.IOException

' el código a ejecutar cuando se produzca ese error


End Try

Si nuestra intención es capturar todos los errores que se produzcan, es decir, no


queremos hacer un filtro con errores específicos, podemos usar la clase Exception
como tipo de excepción a capturar. La clase Exception es la más genérica de todas las
clases para manejo de excepciones, por tanto capturará todas las excepciones que se
produzcan.

Try

' código que queremos controlar

Catch ex As Exception

' el código a ejecutar cuando se produzca cualquier error

End Try

Aunque si no vamos usar la variable indicada en el bloque Catch, pero queremos que
no se detenga la aplicación cuando se produzca un error, podemos hacerlo de esta
forma:

Try

' código que queremos controlar

Catch

' el código a ejecutar cuando se produzca cualquier error

End Try

Varias capturas de errores en un mismo bloque Try/Catch


En un mismo Try podemos capturar diferentes tipos de errores, para ello podemos
incluir varios bloques Catch, cada uno de ellos con un tipo de excepción diferente.

Es importante tener en cuenta que cuando se produce un error y usamos varios


bloques Catch, Visual Basic buscará la captura que mejor se adapte al error que se ha
producido, pero siempre lo hará examinando los diferentes bloques Catch que
hayamos indicado empezando por el indicado después del bloque Try, por tanto
deberíamos poner las más genéricas al final, de forma que siempre nos aseguremos
de que las capturas de errores más específicas se intercepten antes que las genéricas.

Evaluación condicional en un bloque Catch


Además de indicar la excepción que queremos controlar, en un bloque Catch podemos
añadir la cláusula When para evaluar una expresión. Si la evaluación de la expresión
indicada después de When devuelve un valor verdadero, se procesará el bloque
Catch, en caso de que devuelva un valor falso, se ignorará esa captura de error.

Esto nos permite poder indicar varios bloques Catch que detecten el mismo error,
pero cada una de ellas pueden tener diferentes expresiones indicadas con When.

En el siguiente ejemplo, se evalúa el bloque Catch solo cuando el valor de la variable


y es cero, en otro caso se utilizará el que no tiene la cláusula When:

Dim x, y, r As Integer

Try

x = CInt(Console.ReadLine())

y = CInt(Console.ReadLine())

r = x \ y

Console.WriteLine("El resultado es: {0}", r)

Catch ex As Exception When y = 0

Console.WriteLine("No se puede dividir por cero.")

Catch ex As Exception

Console.WriteLine(ex.Message)

End Try

Bloque Finally
En este bloque podemos indicar las instrucciones que queremos que se ejecuten, se
produzca o no una excepción. De esta forma nos aseguramos de que siempre se
ejecutará un código, por ejemplo para liberar recursos, se haya producido un error o
no.

Nota:
Hay que tener en cuenta de que incluso si usamos Exit Try para salir del
bloque de control de errores, se ejecutará el código indicado en el
bloque Finally.

Captura de errores no controlados

Como es lógico, si no controlamos las excepciones que se puedan producir en


nuestras aplicaciones, estas serán inicialmente controladas por el propio runtime de
.NET. En estos casos la aplicación se detiene y se muestra el error al usuario. Pero
esto es algo que no deberíamos consentir. Siempre deberíamos detectar todos los
errores que se produzcan en nuestras aplicaciones, pero a pesar de que lo
intentemos, es muy probable que no siempre podamos conseguirlo.
Por suerte, en Visual Basic 2005 tenemos dos formas de interceptar los errores no
controlados:

· La primera es iniciando nuestra aplicación dentro de un bloque Try/Catch, de


esta forma, cuando se produzca el error, se capturará en el bloque Catch.
· La segunda forma de interceptar los errores no controlados es mediante el
evento: UnhandledException, disponible por medio del objeto My.Application.

Nota:
De los eventos nos ocuparemos en la siguiente lección, pero como el
evento UnhandledException está directamente relacionado con la
captura de errores, lo mostramos en esta, aunque recomendamos al
lector que esta sección la vuelva a leer después de ver todo lo
relacionado con los eventos.

Este evento se "dispara" cuando se produce un error que no hemos interceptado, por
tanto podríamos usarlo para prevenir que nuestra aplicación se detenga o bien para
guardar en un fichero .log la causa de dicho error para posteriormente actualizar el
código y prevenirlo. Ya que cuando se produce el evento UnhandledException,
podemos averiguar el error que se ha producido e incluso evitar que la aplicación
finalice. Esa información la obtenemos mediante propiedades expuestas por el
segundo parámetro del evento, en particular la propiedad Exception nos indicará el
error que se ha producido y por medio de la propiedad ExitApplication podemos
indicar si terminamos o no la aplicación.

Nota:
Cuando ejecutamos una aplicación desde el IDE (entorno de desarrollo),
los errores no controlados siempre se producen, independientemente de
que tengamos o no definida la captura de errores desde el evento
UnhandledException. Ese evento solo se producirá cuando ejecutemos la
aplicación fuera del IDE de Visual Basic.

Ver video 1 de esta lección (Excepciones 1)


Ver video 2 de esta lección (Excepciones 2)
Ver video 3 de esta lección (Excepciones 3)
Introducción

La forma que tienen nuestras clases y estructuras de comunicar que algo está
ocurriendo, es por medio de eventos. Los eventos no les son desconocidos a los
desarrolladores de VB6, ya que también existen y se pueden definir en ese lenguaje.

En Visual Basic 2005 se siguen usando de la misma forma que en VB6, aunque
seguramente siempre que hemos leído sobre el tema aparece la palabra delegado. Y
es que, aunque VB2005 nos oculte (o facilite) el trabajo con los eventos, éstos
siempre están relacionados con los delegados. En esta lección veremos que son los
delegados y que relación tienen con los eventos, también veremos que podemos tener
mayor control sobre cómo se interceptan los eventos e incluso cómo y cuando se
asocian los eventos en la aplicación cliente, aunque primero empezaremos viendo
cómo declarar y utilizar eventos en nuestros tipos de datos.

Eventos y delegados

· Eventos
o Interceptar los eventos de los controles de un formulario
§ Interceptar eventos en Visual Basic 6.0
§ Interceptar eventos en Visual Basic 2005
o Asociar un evento con un control
o Formas de asociar los eventos con un control
§ 1- Asociar el evento manualmente por medio de Handles
§ 2- Asociar el evento desde la ventana de código
§ 3- Asociar el evento desde el diseñador de formularios
o Asociar varios eventos a un mismo procedimiento
o Declarar una variable para asociar eventos con Handles

· Definir y producir eventos en una clase


o Definir eventos en una clase
o Producir un evento en nuestra clase
o Otra forma de asociar los eventos de una clase con un método
§ Asociar eventos mediante AddHandler
§ Desasociar eventos mediante RemoveHandler

· Delegados
o ¿Qué ocurre cuando se asigna y se produce un evento?
o ¿Qué papel juegan los delegados en todo este proceso?
o Definición "formal" de delegado
o Utilizar un delegado para acceder a un método

· Definir un evento bien informado


Eventos

En Visual Basic 2005 podemos usar los eventos de la misma forma que en Visual
Basic 6.0. Al menos en lo que se refiere a la forma de declararlos en nuestras clases y
cómo "lanzarlos", ya que la forma de interceptarlos en una aplicación ha cambiado un
poco, pero como veremos, incluso puede ser más fácil definir los métodos o
procedimientos que utilizamos para interceptarlos.

Interceptar eventos en Visual Basic 2005


La forma más sencilla de asociar el evento de un control con el código que se usará,
es haciendo doble-click en el control; por ejemplo, si en nuestro formulario tenemos
un botón, al hacer doble pulsación sobre él tendremos asociado el evento Click del
botón. Esto es así para los eventos predeterminados de los controles.

La declaración del código usado para interceptar el evento difiere un poco de VB6, tal
como podemos apreciar en el siguiente código:

Private Sub Button1_Click(ByVal sender As Object, ByVal e As


EventArgs) _

Handles Button1.Click

End Sub

Lo primero que podemos notar es que en Visual Basic 2005 utiliza dos argumentos,
esto siempre es así en todos los eventos producidos por los controles. El primero
indica el control que produce el evento, (en nuestro ejemplo sería una referencia al
control Button1), y el segundo normalmente contiene información sobre el evento que
se produce. Si el evento en cuestión no proporciona información extra, como es el
caso del evento Click, ese parámetro será del tipo EventArgs. Sin embargo en otros
eventos el segundo argumento tendrá información que nos puede resultar útil, por
ejemplo para saber qué elemento se ha seleccionado en una lista o cual es el
elemento seleccionado en una rejilla Web. En la figura 2.04.1 podemos ver las
propiedades relacionadas con el evento RowDataBound de un control GridView:

Figura 2.04.01 Propiedades relacionadas con un evento de una rejilla

Asociar un evento con un control

Siguiendo con el código que intercepta el evento Click de un botón, podemos apreciar
que el IDE de Visual Studio 2005 añade al final de la declaración del procedimiento de
evento la instrucción Handles seguida del control y el evento que queremos
interceptar: Handles Button1.Click. Eesta la forma habitual de hacerlo en VB2005,
ya que, a diferencia de VB6, aquí no hay nombres "mágicos" para asociar un evento
con un control o incluso con el propio formulario, sino que esa asociación se hace de
forma explícita y la forma que tiene Visual Basic 2005 de hacerlo es usando la
cláusula Handles.

Nota:
En Visual Basic 2005 el nombre del procedimiento de evento no está
relacionado con el evento, a diferencia de VB6, en el que si que hay una
relación directa entre el evento a interceptar y el nombre del
procedimiento.
Aunque por defecto, el nombre usado sería el equivalente al de VB6, en
la nueva versión puede tener cualquier nombre.

Tal como resaltamos en la nota anterior, en Visual Basic 2005, el nombre asociado a
un evento puede ser el que queramos, lo realmente importante es que indiquemos la
instrucción Handles seguida del evento que queremos interceptar.

Formas de asociar los eventos con un control

Cuando estamos trabajando con el diseñador de formularios, tenemos tres formas de


asociar un evento con el código correspondiente:
La forma más sencilla es la expuesta anteriormente, es decir, haciendo doble click en
el control, esto hará que se muestre el evento predeterminado del control. En el caso
del control Button, el evento predeterminado es el evento Click.

Si queremos escribir código para otros eventos podemos hacerlo de tres formas,
aunque la primera que explicaremos no será la más habitual, ya que debemos saber
exactamente qué parámetros utiliza el evento.

1- Asociar el evento manualmente por medio de Handles


Con esta forma, simplemente escribimos el nombre del procedimiento (puede ser
cualquier nombre), indicamos los dos argumentos que recibe: el primero siempre es
de tipo Object y el segundo dependerá del tipo de evento (aunque en los controles
Web suele ser a menudo del tipo System.EventArgs), y finalmente por medio de la
cláusula Handles lo asociamos con el control y el evento en cuestión.

2- Asociar el evento desde la ventana de código


Al igual que en VB6, en Visual Basic 2005 también podemos seleccionar los eventos
disponibles de una lista desplegable. Esto lo haremos desde la ventana de código. En
la parte superior derecha tenemos una la lista con los controles que hemos añadido al
formulario, seleccionamos el control que nos interese y en la lista que hay a su
izquierda tenemos los eventos que ese control produce. Por tanto podemos
seleccionar de esa lista de eventos el que nos interese interceptar, tal como podemos
ver en la Figura 2.04.02

Figura 2.04.02 Lista de eventos de un control

De esta forma el propio IDE será el que cree el "esqueleto" del procedimiento de
evento usando los parámetros adecuados.

3- Asociar el evento desde el diseñador de formularios


La tercera forma de asociar un evento con un control, es hacerlo desde el diseñador
de formularios. En la ventana de propiedades del control, (tal como podemos apreciar
en la Figura 2.04.3), tenemos una lista con los eventos más importantes de cada
control, seleccionando el evento de esa lista y haciendo doble-click en el que nos
interese, conseguiremos exactamente el mismo resultado que con el paso anterior.

Figura 2.04.03 Ventana de propiedades con los eventos del control


seleccionado

Asociar varios eventos a un mismo procedimiento

Cuando utilizamos Handles para asociar eventos, podemos indicar que un mismo
procedimiento sirva para interceptar varios eventos. Veamos un caso práctico en el
que tenemos varios controles de tipo Button y queremos que cuando se pulsen
siempre se ejecute el mismo código. Podemos hacerlo indicando después de la
cláusula Handles todos los controles que queremos asociar con ese procedimiento.
En el siguiente código indicamos dos controles Button (uno arriba y otro abajo de la
página, por ejemplo):

Private Sub Button_Click( _

ByVal sender As System.Object, _

ByVal e As System.EventArgs) _

Handles Button1.Click, Button2.Click

End Sub

Esta asociación la podemos hacer manualmente, simplemente indicando en la cláusula


Handles cada uno de los eventos a continuación del anterior separándolos por comas.
O bien desde el diseñador de formularios. En este segundo caso, cuando
seleccionamos un control y desde la ventana de propiedades, seleccionamos un
evento, nos muestra los procedimientos que tenemos definidos en nuestro código que
utilizan los mismos parámetros que el evento en cuestión, si seleccionamos uno de
esos procedimientos, el propio IDE añadirá ese control a la lista Handles.
Declarar una variable para asociar eventos con Handles

Para que podamos usar la instrucción Handles para asociar manualmente un


procedimiento con un evento, o para que el diseñador de Visual Basic 2005 pueda
hacerlo, la variable del control o clase que tiene los eventos que queremos interceptar
tenemos que declararla con la instrucción WithEvents.

Esto es exactamente lo mismo que en Visual Basic 6.0, lo que ocurre es que cuando
trabajamos con formularios desde VB6, no nos tenemos que preocupar de este
detalle, en Visual Basic 2005 tampoco debemos preocuparnos, ya que el propio
diseñador de formularios lo hace por nosotros.
Aunque a diferencia de VB6, hay que hacerlo de forma explícita, ya que en la versión
anterior de Visual Basic no teníamos ningún control sobre como se definían o
declaraban los controles a usar en nuestro formulario, mientras que en esta nueva
versión siempre tendremos acceso a ello. No vamos a entrar en más detalles,
simplemente mostraremos cómo se declara el control Button1 para que exponga los
eventos:

Friend WithEvents Button1 As System.Web.UI.WebControls.Button

Si en lugar de estar trabajando con formularios y controles, lo hacemos con clases


"normales", la forma de declarar una variable que tiene eventos es por medio de la
instrucción WithEvents. Por ejemplo, en esta declaración indicamos que tenemos
intención de usar los eventos que la clase Empleado exponga:

Private WithEvents unEmpleado As Empleado

Y posteriormente podremos definir los métodos de eventos usando la instrucción


Handles:

Private Sub unEmpleado_DatosCambiados() Handles


unEmpleado.DatosCambiados

End Sub

Nota:
Usar WithEvents y Handles es la forma más sencilla de declarar y usar
una variable que accede a una clase que produce eventos, pero no es la
única forma que tenemos de hacerlo en Visual Basic 2005, tal como
tendremos oportunidad de comprobar.

Ver video de esta lección (Eventos 1)


Definir y producir eventos en una clase

Como comentábamos anteriormente, la forma de definir un evento en una clase de


Visual Basic 2005 es idéntica a como lo hacemos actualmente con VB6. Lo mismo
ocurre cuando queremos "lanzar" o producir el evento: en Visual Basic 2005 se hace
igual que en Visual Basic 6.0.

Definir eventos en una clase

Para definir un evento en una clase usamos la instrucción Event seguida del nombre
del evento y opcionalmente indicamos los argumentos que dicho evento recibirá.
En el siguiente trozo de código definimos un evento llamado DatosModificados que no
utiliza ningún argumento:

Public Event DatosModificados()

Esto es todo lo que necesitamos hacer.

Producir un evento en nuestra clase

Para producir un evento en nuestra clase, y de esta forma notificar a quién quiera
interceptarlo, simplemente usaremos la instrucción RaiseEvent seguida del evento
que queremos producir. Esto tampoco ha cambiado en Visual Basic 2005 con respecto
a VB6. Incluso cuando escribimos esa instrucción en el IDE, nos mostrará los distintos
eventos que podemos producir, tal como vemos en la Figura 2.04.04:
Figura 2.04.04 Lista de eventos que podemos producir

Otra forma de asociar los eventos de una clase con un método

Tal como hemos estado comentando, la forma más sencilla de declarar una variable
para interceptar eventos es declarándola usando WithEvents y para interceptar los
eventos lo hacemos por medio de la instrucción Handles.
Esta forma es la más recomendada, no solo por la facilidad de hacerlo, sino porque
también tenemos la ventaja de que todas las variables declaradas con WithEvents se
muestran en la lista desplegable de la ventana de código.

De esta forma podemos seleccionar la variable y posteriormente elegir el evento a


interceptar, tal como vimos en la Figura 2.04.02.

Asociar eventos mediante AddHandler


Pero Visual Basic 2005 también proporciona otra forma de asociar un procedimiento
con un evento. Aunque en este caso es algo más "manual" que todo lo que hemos
visto y, de alguna forma está más ligado con los delegados, y como los delegados los
veremos dentro de poco, ahora solamente mostraremos la forma de hacerlo y
después veremos con algo de más detalle cómo funciona.

La forma de de asociar eventos con su correspondiente método es por medio de la


instrucción AddHandler. A esta instrucción le pasamos dos argumentos, el primero
es el evento a asociar y el segundo es el procedimiento que usaremos cuando se
produzca dicho evento. Este último parámetro tendremos que indicarlo mediante la
instrucción AddressOf, que al igual que en VB6 sirve para pasar una referencia a una
función o procedimiento, y precisamente eso es lo que queremos hacer: indicarle que
procedimiento debe usar cuando se produzca el evento:

AddHandler unEmpleado.DatosCambiados, AddressOf


unEmpleado_DatosCambiados

En este caso, el uso de AddressOf es una forma "fácil" que tiene Visual Basic 2005 de
asociar un procedimiento de evento con el evento. Aunque por detrás, (y sin que nos
enteremos), realmente lo que estamos usando es un constructor a un delegado.

La ventaja de usar esta forma de asociar eventos con el procedimiento, es que


podemos hacerlo con variables que no están declaradas con WithEvents, realmente
esta sería la única forma de asociar un procedimiento de evento con una variable que
no hemos declarado con WithEvents.
Desasociar eventos mediante RemoveHandler
De la misma forma que por medio de AddHandler podemos asociar un procedimiento
con un evento, usando la instrucción RemoveHandler podemos hacer el proceso
contrario: desligar un procedimiento del evento al que previamente estaba asociado.
Los parámetros a usar con RemoveHandler son los mismos que con AddHandler.

Podemos usar RemoveHandler tanto con variables y eventos definidos con


AddHandler como con variables declaradas con WithEvents y ligadas por medio de
Handles.
Esto último es así porque cuando nosotros definimos los procedimientos de eventos
usando la instrucción Handles, es el propio Visual Basic el que internamente utiliza
AddHandler para ligar ese procedimiento con el evento en cuestión. Saber esto nos
facilitará comprender mejor cómo funciona la declaración de eventos mediante la
instrucción Custom, aunque de este detalle nos ocuparemos después de ver que son
los delegados.

Ver video 1 de esta lección (Eventos 2)


Ver video 2 de esta lección (Eventos 3)
Delegados

Como hemos comentado anteriormente los eventos son acciones que una clase puede
producir cuando ocurre algo. De esta forma podemos notificar a las aplicaciones que
hayan decidido interceptar esos mensajes para que tomen las acciones que crean
conveniente.

Visual Basic 2005 esconde al desarrollador prácticamente todo lo que ocurre cada vez
que decidimos interceptar un evento. Nosotros solo vemos una pequeña parte de todo
el trabajo que en realidad se produce, y el que no lo veamos no quiere decir que no
esté ocurriendo nada. También es cierto que no debe preocuparnos demasiado si no
sabemos lo que está pasando, pero si tenemos conciencia de que es lo que ocurre,
puede que nos ayude a comprender mejor todo lo relacionado con los eventos.

¿Qué ocurre cuando se asigna y se produce un evento?

Intentemos ver de forma sencilla lo que ocurre "por dentro" cada vez que definimos
un método que intercepta un evento y cómo hace el Visual Basic para comunicarse
con el receptor de dicho evento.

1. Cuando Visual Basic se encuentra con el código que le indica que un método
debe interceptar un evento, ya sea mediante AddHandler o mediante el uso de
Handles, lo que hace es añadir la dirección de memoria de ese método a una
especie de array.
En la Figura 2.04.05 podemos ver un diagrama en el que un mismo evento lo
interceptan tres clientes, cuando decimos que un cliente intercepta un evento,
realmente nos referimos a que hay un método que lo intercepta y el evento
realmente guarda la dirección de memoria de ese método.
Figura 2.04.05 El evento guarda la dirección de memoria de cada método que
lo intercepta

2. Cuando usamos la instrucción RaiseEvent para producir el evento, se examina


esa lista de direcciones y se manda el mensaje a cada uno de los métodos que
tenemos en el "array".
En este caso, lo que realmente ocurre es que se hace una llamada a cada uno
de los métodos, de forma que se ejecute el código al que tenemos acceso
mediante la dirección de memoria almacenada en la lista.
3. Cuando usamos la instrucción RemoveHandler, le estamos indicando al evento
que elimine de la lista el método indicado en esa instrucción, de esta forma, la
próxima vez que se produzca el evento, solo se llamará a los métodos que
actualmente estén en la lista.

Tanto el agregar nuevos métodos a esa lista como quitarlos, lo podemos hacer en
tiempo de ejecución, por medio de AddHandler y RemoveHandler respectivamente. Ya
que la instrucción Handles solo la podemos usar en tiempo de diseño.

Es más, podemos incluso indicar que un mismo evento procese más de un método en
una misma aplicación o que un mismo método sea llamado por más de un evento. Ya
que lo que realmente necesita cada evento es que exista un método que tenga una
"firma" concreta: la indicada al declarar el evento.

¿Qué papel juegan los delegados en todo este proceso?

Veamos primero que papel tienen los delegados en todo este proceso y después
veremos con más detalle lo que "realmente" es un delegado.

1. Cuando definimos un evento, estamos definiendo un delegado, (que en el fondo


es una clase con un tratamiento especial), y un método del mismo tipo que el
delegado
2. Cuando indicamos que un método intercepte un evento, realmente estamos
llamando al constructor del delegado, al que le pasamos la dirección de
memoria del método. El delegado almacena cada una de esas direcciones de
memoria para posteriormente usarlas
3. Cuando se produce el evento, (por medio de RaiseEvent), en realidad estamos
llamando al delegado para que acceda a todas las "direcciones" de memoria que
tiene almacenadas y ejecute el código que hayamos definido en cada uno de
esos métodos

Como podemos comprobar, y para decirlo de forma simple, un delegado realmente es


la forma que tiene .NET para definir un puntero. La diferencia principal es que los
punteros, (no vamos a entrar en demasiados detalles sobre los punteros, ya que no
estamos en un curso de C/C++), no tienen forma de comprobar si están accediendo a
una dirección de memoria correcta o, para decirlo de otra forma, a una dirección de
memoria "adecuada". En .NET, los "punteros" solo se pueden usar mediante
delegados, y éstos solamente pueden acceder a direcciones de memoria que tienen la
misma "firma" con el que se han definido. Para que lo entendamos un poco mejor, es
como si los delegados solo pudieran acceder a sitios en la memoria que contienen un
método con la misma "interfaz" que el que ha definido el propio delegado.

Seguramente es difícil de entender, y la principal razón es que hemos empezado la


casa por el techo.
Por tanto, veamos a continuación una definición "formal" de qué es un delegado y
veamos varios ejemplos para que lo comprendamos mejor.

Definición "formal" de delegado

Veamos que nos dice la documentación de Visual Basic 2005 sobre los delegados:

"Un delegado es una clase que puede contener una referencia a un método. A
diferencia de otras clases, los delegados tienen un prototipo (firma) y pueden
guardar referencias únicamente a los métodos que coinciden con su prototipo."

Esta definición, al menos en lo que respecta a su relación con los eventos, viene a
decir que los delegados determinan la forma en que debemos declarar los métodos
que queramos usar para interceptar un evento.

Por ejemplo, el evento DatosCambiados definido anteriormente, también lo


podríamos definir de la siguiente forma:

Public Delegate Sub DatosCambiadosEventHandler()

Public Event DatosCambiados As DatosCambiadosEventHandler

Es decir, el método que intercepte este evento debe ser del tipo Sub y no recibir
ningún parámetro.

Si nuestro evento utiliza, por ejemplo, un parámetro de tipo String, la definición del
delegado quedaría de la siguiente forma:
Public Delegate Sub NombreCambiadoEventHandler(ByVal nuevoNombre As
String)

Y la definición del evento quedaría de esta otra:

Public Event NombreCambiado As NombreCambiadoEventHandler

Como vemos al definir el evento ya no tenemos que indicar si recibe o no algún


parámetro, ya que esa definición la hemos hecho en el delegado.

Si nos decidimos a definir este evento de la forma "normal" de Visual Basic, lo


haríamos así:

Public Event NombreCambiado(ByVal nuevoNombre As String)

Como podemos comprobar, Visual Basic 2005 nos permite definir los eventos de dos
formas distintas: definiendo un delegado y un evento que sea del tipo de ese
delegado o definiendo el evento con los argumentos que debemos usar.

Declaremos como declaremos los eventos, los podemos seguir usando de la misma
forma, tanto para producirlo mediante RaiseEvent como para definir el método que
reciba ese evento.

Utilizar un delegado para acceder a un método

Ahora veamos brevemente cómo usar los delegados, en este caso sin necesidad de
que defina un evento.

Como hemos comentado, un delegado realmente es una clase que puede contener
una referencia a un método, además define el prototipo del método que podemos
usar como referencia. Sabiendo esto, podemos declarar una variable del tipo del
delegado y por medio de esa variable acceder al método que indiquemos, siempre
que ese método tenga la misma "firma" que el delegado. Parece complicado ¿verdad?
Y no solo lo parece, es que realmente lo es. Comprobemos esta "complicación" por
medio de un ejemplo. En este código, que iremos mostrando poco a poco, vamos a
definir un delegado, un método con la misma firma para que podamos usarlo desde
una variable definida con el mismo tipo del delegado.

Definimos un delegado de tipo Sub que recibe un valor de tipo cadena:

Delegate Sub Saludo(ByVal nombre As String)

Definimos un método con la misma firma del delegado:

Private Sub mostrarSaludo(ByVal elNombre As String)

Console.WriteLine("Hola, " & elNombre)

End Sub
Ahora vamos a declarar una variable para que acceda a ese método.
Para ello debemos declararla con el mismo tipo del delegado:

Dim saludando As Saludo

La variable saludando es del mismo tipo que el delegado Saludo. La cuestión es


¿cómo o que asignamos a esta variable?

Primer intento:
Como hemos comentado, los delegados realmente son clases, por tanto podemos
usar New Saludo y, según parece, deberíamos pasarle un nombre como argumento.
Algo así:

saludando = New Saludo("Pepe")

Pero esto no funciona, entre otras cosas, porque hemos comentado que un delegado
contiene (o puede contener) una referencia a un método, y "Pepe" no es un método
ni una referencia a un método.

Segundo intento:
Por lógica y, sobre todo, por sentido común, máxime cuando hemos declarado un
método con la misma "firma" que el delegado, deberíamos pensar que lo que
debemos pasar a esa variable es el método, ya que un delegado puede contener una
referencia a un método.

saludando = New Saludo(mostrarSaludo)

Esto tampoco funciona, ¿seguramente porque le falta el parámetro?

saludando = New Saludo(mostrarSaludo("Pepe"))

Pues tampoco.

Para usar un delegado debemos indicarle la dirección de memoria de un método, a


eso se refiere la definición que vimos antes, una referencia a un método no es ni más
ni menos que la dirección de memoria de ese método. Y en Visual Basic, desde la
versión 5.0, tenemos una instrucción para obtener la dirección de memoria de
cualquier método: AddressOf.
Por tanto, para indicarle al delegado dónde está ese método tendremos que usar
cualquiera de estas dos formas:

saludando = New Saludo(AddressOf mostrarSaludo)

Es decir, le pasamos al constructor la dirección de memoria del método que queremos


"asociar" al delegado.

En Visual Basic esa misma asignación la podemos simplificar de esta forma:


saludando = AddressOf mostrarSaludo

Ya que el compilador "sabe" que saludando es una variable de tipo delegado y lo que
esa variable puede contener es una referencia a un método que tenga la misma firma
que la definición del delegado, en nuestro caso, que sea de tipo Sub y reciba una
cadena.

Si queremos, también podemos declarar la variable y asignarle directamente el


método al que hará referencia:

Dim saludando As New Saludo(AddressOf mostrarSaludo)

Y ahora... ¿cómo podemos usar esa variable?

La variable saludando realmente está apuntando a un método y ese método recibe un


valor de tipo cadena, por tanto si queremos llamar a ese método (para que se ejecute
el código que contiene), tendremos que indicarle el valor del argumento, sabiendo
esto, la llamada podría ser de esta forma:

saludando("Pepe")

Y efectivamente, así se mostraría por la consola el saludo (Hola) y el valor indicado


como argumento.

Realmente lo que hacemos con esa llamada es acceder al método al que apunta la
variable y como ese método recibe un argumento, debemos pasárselo, en cuanto lo
hacemos, el runtime de .NET se encarga de localizar el método y pasarle el
argumento, de forma que se ejecute de la misma forma que si lo llamásemos
directamente:

mostrarSaludo("Pepe")

Con la diferencia de que la variable "saludando" no tiene porqué saber a qué método
está llamando, y lo más importante, no sabe dónde está definido ese método, solo
sabe que el método recibe un parámetro de tipo cadena y aparte de esa información,
no tiene porqué saber nada más.

Así es como funcionan los eventos, un evento solo tiene la dirección de memoria de
un método, ese método recibe los mismos parámetros que los definidos por el evento
(realmente por el delegado), cuando producimos el evento con RaiseEvent es como si
llamáramos a cada uno de los métodos que se han ido agregando al delegado, si es
que se ha agregado alguno, ya que en caso de que no haya ningún método asociado
a ese evento, éste no se producirá, por la sencilla razón de que no habrá ningún
código al que llamar.

Realmente es complicado y, salvo que lo necesitemos para casos especiales, no será


muy habitual que usemos los delegados de esta forma, aunque no está de más
saberlo, sobre todo si de así comprendemos mejor cómo funcionan los eventos.
Ver video de esta lección (Delegados)
Definir un evento bien informado

Para terminar con esta lección sobre los eventos y los delegados, vamos a ver otra
forma de definir un evento. Esta es exclusiva de Visual Basic 2005 y por medio de
esta declaración, tal como indicamos en el título de la sección, tendremos mayor
información sobre cómo se declara el evento, cómo se destruye e incluso cómo se
produce, es lo que la documentación de Visual Basic llama evento personalizado
(Custom Event).

Cuando declaramos un evento usando la instrucción Custom estamos definiendo tres


bloques de código que nos permite interceptar el momento en que se produce
cualquiera de las tres acciones posibles con un evento:

1. Cuando se "liga" el evento con un método, ya sea por medio de AddHandler o


mediante Handles
2. Cuando se desliga el evento de un método, por medio de RemoveHandler
3. Cuando se produce el evento, al llamar a RaiseEvent

Para declarar este tipo de evento, siempre debemos hacerlo por medio de un
delegado.

Veamos un ejemplo de una declaración de un evento usando Custom Event:

Public Delegate Sub ApellidosCambiadosEventHandler(ByVal nuevo As


String)
Public Custom Event ApellidosCambiados As
ApellidosCambiadosEventHandler

AddHandler(ByVal value As ApellidosCambiadosEventHandler)

' este bloque se ejecutará cada vez que asignemos un método


al evento

End AddHandler

RemoveHandler(ByVal value As ApellidosCambiadosEventHandler)

' este bloque se ejecutará cuando se desligue el evento de


un método

End RemoveHandler

RaiseEvent(ByVal nuevo As String)

' este bloque se ejecutará cada vez que se produzca el


evento

End RaiseEvent

End Event

Como podemos apreciar, debemos definir un delegado con la "firma" del método a
usar con el evento. Después definimos el evento por medio de las instrucciones
Custom Event, utilizando el mismo formato que al definir un evento con un delegado.
Dentro de la definición tenemos tres bloques, cada uno de los cuales realizará la
acción que ya hemos indicado en la lista numerada.

Nota:
Los eventos Custom Event solamente podemos definirlos e interceptarlos
en el mismo ensamblado.
Introducción

Esta es la definición que nos da la ayuda de Visual Basic sobre lo que es un atributo.

"Los atributos son etiquetas descriptivas que proporcionan información adicional


sobre elementos de programación como tipos, campos, métodos y propiedades.
Otras aplicaciones, como el compilador de Visual Basic, pueden hacer referencia
a la información adicional en atributos para determinar cómo pueden utilizarse
estos elementos."

En esta lección veremos algunos ejemplos de cómo usarlos en nuestras propias


aplicaciones y, aunque sea de forma general, cómo usar y aplicar algunos de los
atributos definidos en el propio .NET Framework, al menos los que más directamente
nos pueden interesar a los desarrolladores de Visual Basic.

Atributos

· Atributos
o Tipos de atributos que podemos usar en una aplicación
§ Atributos globales a la aplicación
§ Atributos particulares a las clases o miembros de las clases
o Atributos personalizados
§ Acceder a los atributos personalizados en tiempo de ejecución
o Atributos específicos de Visual Basic
o Marcar ciertos miembros de una clase como obsoletos
Atributos

Como hemos comentado en la introducción, los atributos son etiquetas que podemos
aplicar a nuestro código para que el compilador y, por extensión, el propio .NET
Framework los pueda usar para realizar ciertas tareas o para obtener información
extra sobre nuestro código.

De hecho en cualquier aplicación que creemos con Visual Basic 2005 estaremos
tratando con atributos, aunque nosotros no nos enteremos, ya que el propio
compilador los utiliza para generar los metadatos del ensamblado, es decir, la
información sobre todo lo que contiene el ejecutable o librería que hemos creado con
Visual Basic 2005.

Por otra parte, el uso de los atributos nos sirve para ofrecer cierta funcionalidad extra
a nuestro código, por ejemplo, cuando creamos nuestros propios controles, mediante
atributos podemos indicarle al diseñador de formularios si debe mostrar ciertos
miembros del control en la ventana de propiedades, etc.

Tipos de atributos que podemos usar en una aplicación

Como hemos comentado, existen atributos que son globales a toda la aplicación y
otros que podremos aplicar a elementos particulares, como una clase o un método.

Atributos globales a la aplicación


Estos se indican usando Assembly: en el atributo y los podremos usar en cualquier
parte de nuestro código, aunque lo habitual es usarlos en el fichero AssemblyInfo.vb.
Nota:
La palabra o instrucción Assembly: lo que indica es que el atributo tiene
un ámbito de ensamblado.

Estos los vamos a usar poco o nada en una aplicación Web.

Atributos particulares a las clases o miembros de las clases


Estos atributos solo se aplican a la clase o al miembro de la clase que creamos
conveniente, el formato es parecido a los atributos globales, ya que se utilizan los
signos de menor y mayor para encerrarlo, con la diferencia de que en este tipo de
atributos no debemos usar Assembly:, ya que esta instrucción indica que el atributo
es a nivel del ensamblado.

Cuando aplicamos un atributo "particular", este debe estar en la misma línea del
elemento al que se aplica, aunque si queremos darle mayor legibilidad al código
podemos usar un guión bajo para que el código continúe en otra línea:

<Microsoft.VisualBasic.HideModuleName()> _

Module MyResources

Atributos personalizados

Además de los atributos que ya están predefinidos en el propio .NET o Visual Basic,
podemos crear nuestros propios atributos, de forma que en tiempo de ejecución
podamos acceder a ellos mediante las clases del espacio de nombres Reflection. Este
tema se sale un poco de la intención de este curso, pero simplemente indicaremos
que los atributos personalizados son clases que se derivan de la clase
System.Attribute y que podemos definir las propiedades que creamos conveniente
utilizar en ese atributo para indicar cierta información a la que podemos acceder en
tiempo de ejecución.

En el siguiente código tenemos la declaración de una clase que se utilizará como


atributo personalizado, notamos que, por definición las clases para usarlas como
atributos deben terminar con la palabra Attribute después del nombre "real" de la
clase, que como veremos en el código que utiliza ese atributo, esa "extensión" al
nombre de la clase no se utiliza.

Veamos primero el código del atributo personalizado:

<AttributeUsage(AttributeTargets.All)> _

Public Class AutorAttribute

Inherits System.Attribute

'
Private _ModificadoPor As String

Private _Version As String

Private _Fecha As String

'

Public Property ModificadoPor() As String

Get

Return _ModificadoPor

End Get

Set(ByVal value As String)

_ModificadoPor = value

End Set

End Property

'

Public Property Version() As String

Get

Return _Version

End Get

Set(ByVal value As String)

_Version = value

End Set

End Property

'

Public Property Fecha() As String

Get

Return _Fecha

End Get

Set(ByVal value As String)

_Fecha = value

End Set
End Property

End Class

Para usar este atributo lo podemos hacer de la siguiente forma:

<Autor(ModificadoPor:="Guillermo 'guille'", _

Version:="1.0.0.0", Fecha:="13/Abr/2005")> _

Public Class PruebaAtributos

Nota:
Cuando utilizamos el atributo, en el constructor que de forma
predeterminada crea Visual Basic, los parámetros se indican por el orden
alfabético de las propiedades, pero que nosotros podemos alterar
usando directamente los nombres de las propiedades, tal como podemos
ver en el código de ejemplo anterior.

Acceder a los atributos personalizados en tiempo de ejecución


Para acceder a los atributos personalizados podemos hacer algo como esto,
(suponiendo que tenemos la clase AutorAttribute y una clase llamada PruebaAtributos
a la que hemos aplicado ese atributo personalizado):

Sub Main()

Dim tipo As Type

tipo = GetType(PruebaAtributos)

Dim atributos() As Attribute

atributos = Attribute.GetCustomAttributes(tipo)

For Each atr As Attribute In atributos

If TypeOf atr Is AutorAttribute Then

Dim aut As AutorAttribute

aut = CType(atr, AutorAttribute)


Console.WriteLine("Modificado por: " &
aut.ModificadoPor)

Console.WriteLine("Fecha: " & aut.Fecha)

Console.WriteLine("Versión: " & aut.Version)

End If

Next

End Sub

Atributos específicos de Visual Basic

Visual Basic utiliza una serie de atributos para indicar ciertas características de
nuestro código, en particular son tres:

· COMClassAttribute
o Este atributo se utiliza para simplificar la creación de componentes COM
desde Visual Basic.
· VBFixedStringAttribute
o Este atributo se utiliza para crear cadenas de longitud fija en Visual Basic
2005. Habitualmente se aplica a campos o miembros de una estructura,
principalmente cuando queremos acceder al API de Windows o cuando
queremos usar esa estructura para guardar información en un fichero,
pero utilizando una cantidad fija de caracteres.
· VBFixedArrayAttribute
o Este atributo, al igual que el anterior, lo podremos usar para declarar
arrays de tamaño fijo, al menos si las declaramos en una estructura, ya
que por defecto, los arrays de Visual Basic son de tamaño variable.

Marcar ciertos miembros de una clase como obsoletos

En ocasiones nos encontraremos con que escribimos cierto código que posteriormente
no queremos que se utilice, por ejemplo porque hemos creado una versión
optimizada.
Si ese código lo hemos declarado en una interfaz, no deberíamos eliminarlo de ella,
ya que así romperíamos el contrato contraído por las clases que implementan esa
interfaz. En estos casos nos puede venir muy bien el uso del atributo <Obsolete>,
ya que así podemos informar al usuario de que ese atributo no debería ser usado. En
el constructor de este atributo podemos indicar la cadena que se mostrará al usuario.
En el siguiente código se declara un método con el atributo Obsolete:

Public Interface IPruebaObsoleto


<Obsolete("Este método ya está obsoleto, utiliza el método
Mostrar")> _

Sub MostrarNombre()

Sub Mostrar()

End Interface

Si trabajamos con el IDE de Visual Basic, ese mensaje se mostrará al compilar o


utilizar los atributos marcados como obsoletos, tal como podemos apreciar en la
Figura 2.05.05:

Figura 2.05.01 Mensaje de que un método está obsoleto


Contenido
En este módulo aprenderemos todo lo fundamental acerca del desarrollo de
aplicaciones Web con ASP.NET 2.0, y en concreto mediante el uso de Visual Studio
2005.

Al terminar el módulo estarás preparado para trabajar en el entorno y crear


aplicaciones Web sin enlace a datos (esto se aprenderá más adelante, en el módulo
correspondiente).

· Lección 1: Introducción al desarrollo con ASP.NET 2.0


o Las páginas ASPX
o El entorno de desarrollo: Visual Studio 2005
o Trabajo con formularios y controles Web
· Lección 2: Los controles de ASP.NET
o Controles HTML
o Controles Web
o Controles de validación

· Lección 3: Los controles de usuario


o Introducción
o Definición de la funcionalidad pública
o Uso de los controles de usuario
o Carga dinámica de controles de usuari

· Lección 4: Técnicas de trabajo y consejos varios


o Navegación entre páginas
o Envío de datos entre páginas
o Transferir el control entre páginas
o Reutilización de código en una aplicación
Introducción al desarrollo con ASP.NET 2.0
Más que una evolución sobre ASP, ASP.NET es una auténtica revolución en el mundo
del desarrollo Web, y no sólo en el ámbito relacionado con Microsoft. Lo que mejor
define al desarrollo combinado con ASP.NET 2.0 y Visual Studio es que ha equiparado
la creación de aplicaciones para Internet a lo que era común en entornos de
escritorio, salvando las inherentes dificultades que ello conlleva de forma transparente
al programador.

Los programadores de ASP.NET disponen de todo lo que "siempre" han disfrutado los
programadores de Windows: diseñadores visuales, asistencia avanzada contextual,
código compilado de alto rendimiento, transparencia acerca de dónde se ejecuta cada
parte del código, enlace a datos, rejillas, etc...

· Las páginas ASPX


o Páginas ASPX
o Más código
o Estupendo, pero... ¿en qué hemos mejorado?
· El entorno de desarrollo: Visual Studio 2005
o Visual Studio 2005
o Explorando el entorno
o Explorador de soluciones
o Área de documentos
o Cuadro de herramientas
o Editor de propiedades
o Barras de herramientas y menús
· Trabajo con formularios Web y controles
o Trabajo con formularios web
o Creando la interfaz del ejemplo
o Respondiendo al evento del botón
§ Pero... ¿Cómo funciona esto por debajo?
§ El archivo .aspx de interfaz de usuario
§ El archivo .vb de lógica de la aplicación
§ El nexo entre interfaz y lógica
Las páginas ASPX

Si nos vamos a lo fundamental es posible crear una página ASP.NET usando el bloc de
notas y sin saber nada de la plataforma .NET y de sus diferentes espacios de nombre.

Las páginas de servidor de ASP.NET son en esencia archivos de texto que contienen
HTML y etiquetas y que tienen una extensión '.aspx'. Por ello se les denomina de
modo genérico páginas ASPX.

Al igual que las páginas ASP clásicas soportan el uso de etiquetas <% %> para
delimitar bloques de código. De hecho, por compatibilidad, se puede usar en gran
medida todo lo que conocemos de ASP 3.0, lo cual no quiere decir que sea lo más
recomendable. Sin embargo para familiarizarnos haremos un ejemplo sencillo.
Figura 3.1.1.- Página ASPX sencilla

El código de la figura no se distingue de una página ASP clásica salvo por la extensión
del archivo (.aspx en lugar de .asp). Sin embargo si navegamos hasta esta página
(ubicada en el raíz de nuestro servidor IIS) veremos que el resultado es el que
esperábamos:

Figura 3.1.2.- Resultado del código anterior

De todos modos, aunque no podamos percibirla, ya existe una sustancial diferencia


con una página ASP: el pequeño fragmento de código que hemos incluido se ha
compilado antes de ejecutarlo, en lugar de haberse interpretado como en ASP.
Obviamente en este caso no ofrece ventaja alguna, pero es importante conocer esta
característica pues nuestras aplicaciones obtendrán un mayor rendimiento por el mero
hecho de ser ASP.NET.

Más código

Siguiendo con el ejemplo vamos a añadir un poco más de código para comprobar
hasta que punto son compatibles las páginas ASPX con el código ASP.

Si lo modificamos para que tenga el siguiente aspecto:


Figura 3.1.3.- Más código añadido a la página ASPX

Ahora durante la carga (o la recarga) de la página verificamos si se le está pasando


un parámetro por POST, en cuyo caso sustituimos la caja de texto por un saludo al
nombre que se le esté pasando:

Figura 3.1.4.- Resultado de la ejecución.

Estupendo, pero... ¿en qué hemos mejorado?

Este ejemplo nos ha servido para ver qué ASPX sigue siendo compatible en cierta
medida con ASP, y que sólo por este hecho ya mejoraremos la escalabilidad de las
páginas. De todos modos el cambio de la extensión del archivo sólo funcionará en las
páginas más sencillas. En código no trivial tenemos una probabilidad tendente a 1 de
que no haya esa suerte.
Además, aunque así fuera, no habríamos ganado demasiado: seguiríamos con código
de cliente y de servidor entremezclado y difícil de mantener y no tendríamos ninguna
de las ventajas que hemos mencionado antes.

Si bien podemos escribir código ASP.NET de la manera correcta sólo con el bloc de
notas, la mejor forma de desarrollar páginas Web con ASP.NET es usando Visual
Studio 2005, que como veremos enseguida nos ofrece una forma visual de trabajo
junto con una separación estricta entre el código y la interfaz de usuario, que es lo
que pretendíamos.

Ver vídeo de esta lección (código compilado)


El entorno de desarrollo: Visual Studio 2005

Visual Studio 2005 es el entorno de desarrollo de aplicaciones Web para la versión 2.0
de la plataforma .NET. Ofrece todo tipo de herramientas para facilitar el trabajo del
programador: diseñadores gráficos de páginas y clases, asistentes de uso de bases de
datos, un servidor web de desarrollo, ayuda a la escritura de código, y en general
todo lo que se espera de un entorno de desarrollo rápido moderno y mucho más
todavía.

Para crear un nuevo proyecto ASP.NET utilice el menú Archivo·Nuevo·Sitio Web. Al


hacerlo aparecerá un diálogo como el de la figura:
Figura 3.1.5.- El diálogo de nuevo proyecto web.

Nota:
Las plantillas disponibles en el diálogo de nuevo proyecto web puede
diferir dependiendo de la versión de Visual Studio 2005 que utilice. Por
simplicidad se muestra el diálogo de la versión Visual Web Developer
Express, el más simple.

En él tenemos la oportunidad de elegir qué tipo de proyecto vamos a crear (por


defecto un sitio web de ASP.NET), en qué ubicación física queremos crearlo (lo
normal será en nuestro disco duro pero podríamos elegir un sitio gestionado por FTP o
HTTP) y con qué lenguaje de programación.

Existen diferentes lenguajes que nos sirven para crear el código de nuestras páginas.
Dado que en la práctica todos tienen las mismas capacidades escoger uno u otro es
una cuestión de elección personal. Dado que este curso está orientado
fundamentalmente a programadores que vienen de ASP o VB6, vamos a elegir Visual
Basic 2005 como lenguaje de todos nuestros ejemplos. En cualquier caso todo lo
explicado para éste es perfectamente válido para los otros lenguajes y le servirá igual
si se decide por ellos.

Explorando el entorno

Tras crear un proyecto nuevo, el entorno tiene un aspecto similar a este:


Figura 3.1.6.- El entorno de desarrollo de Visual Studio 2005 tras crear un
proyecto.

Antes de aprender a manejar Visual Studio 2005 vamos a ponernos en situación


conociendo los distintos elementos que forman parte del entorno.

Explorador de soluciones

Este elemento contiene un árbol con los proyectos


en los que estamos trabajando y los diferentes
archivos y carpetas que forman parte de ellos.
Nada más crear un nuevo proyecto Web sólo existe
una carpeta llamada App_Data, y una página ASPX
creada de forma predeterminada que está vacía y
es con la que comenzaremos a trabajar.
Los botones de la parte superior se usan para
realizar diversas acciones sobre el elemento que
tengamos seleccionado. Por ejemplo en el caso de
la página podemos abrir su diseño o su código
presionando respectivamente el tercer y cuarto
botones por la derecha.
Área de documentos

Figura 3.1.7.- Área de documentos mostrando un editor de HTML.

Es la zona situada en el centro del entorno de desarrollo. Contiene los diferentes


editores de código así como diseñadores de diversos tipos (de interfaz de usuario, de
clases, de DataSets...). Es en donde pasaremos la mayor parte del tiempo
trabajando.

Cuadro de herramientas
Figura 3.1.8.- Cuadro de herramientas y detalle de un algunos grupos de
éste.

El cuadro de herramientas contiene los diferentes elementos que podemos utilizar


para la definición de la interfaz de usuario de nuestra aplicación, así como algunos
otros componentes no visuales que también se pueden arrastrar hacia el diseñador
visual de páginas Web. Está situado por defecto en el lateral izquierdo del entorno.

Editor de propiedades

Al igual que en Visual Basic 6.0, Frontpage y otros entornos, el


editor de propiedades nos permite ajustar los valores de las
propiedades en tiempo de diseño de los objetos que hayamos
seleccionados.
Este editor sirve para ajustar las propiedades de todos los
objetos que podamos utilizar en el entorno: tanto de los
controles que se arrastran sobre un diseñador como de los
propios archivos del explorador de soluciones y las etiquetas
HTML o XML, etc...

Algunas propiedades se editan directamente en el espacio


disponible y otras lanzan un asistente o un diseñador que nos
ayuda con la tarea.

Se pueden ver las propiedades ordenadas alfabéticamente o


bien agrupadas en diferentes categorías (opción por defecto).
Además de las propiedades también se pueden ajustar los
gestores de eventos de los objetos usando el penúltimo botón
por la derecha. En la parte inferior se obtiene una concisa ayuda
sobre cada propiedad seleccionada.

Barras de herramientas y menús

Se sitúan en la parte superior y dan acceso al resto de las características de la


herramienta de trabajo. Según el contexto en el que nos encontremos las barras de
herramientas que veremos serán distintas. Visual Studio se encarga de mostrar y
ocultar la ingente cantidad de barras disponibles mostrando en cada caso sólo las que
necesitemos. Esto racionaliza el acceso a los elementos de la interfaz que de otro
modo serían inmanejables.

Ver vídeo de esta lección (El entorno de desarrollo)


Trabajo con formularios Web y controles

El entorno de trabajo que hemos explorado es muy intuitivo y fácil de utilizar. En la


mayor parte de los casos vamos a hacer uso únicamente de los tres diseñadores que
hemos visto en el vídeo de la lección anterior: diseñador visual de formularios Web,
diseñador de HTML y editor de código de VB.NET.

Creemos un ejemplo sencillo desde cero para ver cómo se trabaja. Luego
estudiaremos la estructura de los archivos generados para comprender su
funcionamiento. El ejemplo es el mismo que hemos creado en la primera lección del
módulo pero utilizando el modo de programar de ASP.NET y no el código "espaguetti"
típico de ASP clásico que hemos utilizado antes.

· Trabajo con formularios Web y controles


o Creando la interfaz gráfica del ejemplo
§ Respondiendo al evento del botón
o Pero... ¿Cómo funciona esto por debajo?
o Los archivos de código
§ El archivo .aspx de interfaz de usuario
§ El archivo .vb de lógica de la aplicación
§ El nexo entre interfaz y lógica
Creando la interfaz gráfica del ejemplo

Desde Visual Studio cree un nuevo proyecto de sitio Web. Abra el diseñador de la
página por defecto que se crea (Default.aspx) haciendo doble click sobre ella. Añada
un control de tipo etiqueta (Label) en la parte superior y establezca su propiedad
Text con el valor "¡Bienvenido a ASP.NET! ", eligiendo un tipo de letra de tamaño
grande y color rojo.

Justo debajo agregue un control TextBox, y establezca su propiedad ID como


"Nombre", Width como 200px y MaxLength ajústela a 40.

A continuación introduzca un botón (Button) y asígnele el texto "Saludar" (propiedad


Text) y otórguele el nombre de "cmdSaludar".

Por fin añada una etiqueta más con el nombre lblSaludar, y ajuste su propiedad
Visible a False para que no se vea en el formulario web una vez que lo ejecutemos.

Cuando acabe el aspecto del formulario debería ser muy similar a éste:
Figura 3.1.9.- Aspecto del ejemplo tras haber añadido los controles.

Nota:
Si no tiene claro cómo hacerlo vea el primer vídeo de esta lección al pie
de este documento en donde se desarrolla el ejemplo completo.

Respondiendo al evento del botón

Para saludar al usuario con el nombre que introduzca en el campo de texto debemos
responder a la pulsación del botón. En ASP clásico tendríamos que enviar un
formulario a otra página (o a la misma) y ver qué valores no es están pasando para
actuar en consecuencia. En ASP.NET esto no es necesario ya que trabajaremos según
el clásico paradigma orientado eventos, respondiendo a las acciones del usuario.

En este caso debemos interceptar la pulsación del botón por parte del usuario
¿verdad?. Pues lo único que tendremos que hacer es escribir un manejador para el
evento Click del botón, algo que resultará familiar e intuitivo a los programadores de
VB6. Para ello haga doble-clic sobre el botón en el diseñador. Esto hará que se abra el
editor de código y que automáticamente aparezca un manejador de eventos para el
evento Click, que es el predeterminado de los botones:

Figura 3.1.10.- Manejador del evento Click generado automáticamente por el


editor.

Al igual que en VB6 desde el código del evento (que se ejecutará en el servidor,
¡atención!) podemos hacer referencia a cualquier control de la página y acceder a sus
métodos y propiedades. De este modo se puede escribir el siguiente código simple:
Es decir, se concatena un saludo al nombre introducido en el campo de texto
asignándoselo como texto a mostrar a la etiqueta oculta, y se hace visible ésta
usando su propiedad Visible.

Si ejecutamos ahora el ejemplo (presionando F5 o el botón correspondiente en la


barra de herramientas) veremos que funciona sin problemas:

Figura 3.1.11.- Nuestro ejemplo antes y después de presionar el botón.

Nótese que no hemos añadido formulario alguno, ni tampoco JavaScript, y que en


realidad para nosotros es transparente el modo en como se gestionan los eventos e
incluso que lo que estamos creando sea una aplicación Web. No hemos usado HTML y
(hasta cierto punto) nos da igual que el código se ejecute en el servidor o en el
cliente.

Ver vídeo 1 de esta lección (Creación y ejecución del ejemplo)


Ver vídeo 2 de esta lección (Pequeñas mejoras del ejemplo)
Pero... ¿Cómo funciona esto por debajo?

Si echamos un vistazo al código de la página generada veremos que, aparte del HTML
que es más o menos obvio que debería haber dados los controles que hemos
utilizado, existe también bastantes líneas de código JavaScript y algunos campos
ocultos.

Los campos ocultos se utilizan para almacenar información sobre la página y el código
JavaScript se ocupa de su mantenimiento y de enviar el formulario al servidor ante
determinadas acciones del usuario (simulando los eventos).

Uno de los campos ocultos más importantes es el que se refiere al ViewState.

Figura 3.1.12.- Viewstate de una página sencilla.

El ViewState - que se podría traducir como "Estado de Visualización" - recoge de


manera automática el estado y el contenido de los controles de una página. Esta
información se utiliza para dejarlos como estaban en cada recarga de la página.
Cuando presionamos el botón de nuestro ejemplo la página se recarga y todo lo que
contiene debe estar igual que antes de hacerlo o no podríamos asimilarlo a un
formulario. A excepción de una pequeña demora por el viaje de ida y vuelta al
servidor, para el usuario no debe haber sensación de recarga de la página. El
ViewState se encarga de ello.

Nota:
A cada viaje de ida y vuelta de nuestra página al servidor como
consecuencia de un evento en el cliente se le denomina PostBack. Se
puede averiguar si la carga actual de la página es la primera o se trata
de un PostBack consultando el valor booleano de la propiedad
IsPostBack de la página (Me.IsPostback).

Como se aprecia en la figura se trata de información sobre la jerarquía de controles


de la página codificada para su envío al servidor. Al recibirla, la clase Page de la que
hereda nuestra página se encarga de decodificarlo, procesarlo e inicializar de nuevo el
estado de cada control según la información facilitada, sin necesidad de trabajo por
nuestra parte.

Por otra parte, para determinar qué evento se ha producido, se emplean también dos
campos ocultos y un poco de JavaScript:

Figura 3.1.13.- Campos ocultos de información sobre eventos

Figura 3.1.14.- JavaScript asociado a la notificación de eventos al servidor.

Cuando se efectúa alguna acción el JavaScript de la página se encarga de rellenar


estos campos y provocar un PostBack de la página. El evento es detectado en el
servidor a través de estos campos ocultos y se gestiona adecuadamente por ASP.NET,
que se encarga de notificar los eventos.

Los mecanismos de ViewState y de PostBack son los responsables de que, a efectos


prácticos, podamos trabajar en la Web utilizando el paradigma de programación
orientada a eventos.

¡Imagínate tener que gestionar eso tú mismo con código propio!


Los archivos de código

Centremos nuevamente nuestra atención en los archivos de código de servidor que


tenemos en Visual Studio 2005. Existen dos: uno que define la interfaz de usuario y
otro para la lógica de la aplicación. Veamos cómo están formados y cuál es el nexo de
unión entre ellos.

El archivo .aspx de interfaz de usuario.

En realidad todo el código se ejecuta en el servidor y, por poco intuitivo que sea para
un programador Web tradicional, el evento desencadenado por la pulsación se
gestiona en el servidor, no en el cliente. Veamos cómo funciona.

Para crear la interfaz de usuario sólo hemos tenido que arrastrar controles Web desde
el cuadro de herramientas al diseñador. Por detrás lo que ha estado ocurriendo es que
el código HTML de la página ha estado creciendo hasta ser como el siguiente:
Figura 3.1.15.- Código HTML de la interfaz de usuario ASPX.

Vemos que, sin que conscientemente lo hayamos hecho, se ha creado un formulario


(Form1) que al carecer de un atributo ACTION se enviará a si mismo. Este formulario
(y otros elementos tienen asignado el atributo runat="server". Éste se usa para
indicar que son controles de servidor y que, como tales, deberán estar disponibles
para el código que se ejecuta en el servidor, como por ejemplo nuestro manejador del
evento.

Nota:
En las páginas ASPX sólo se recomienda el uso de un formulario, que es
además el formulario que incluye automáticamente el entorno de
desarrollo. Lo cierto es que no se trata de limitación alguna puesto que
la filosofía de desarrollo es completamente distinta a la tradicional y no
los vamos a necesitar.

El resto de elementos que aparecen son etiquetas HTML normales (p.ej<br/> para
cambio de línea) y unas etiquetas especiales que llevan el prefijo asp:. Este prefijo
indica que son controles web de ASP.NET, y como tales son objetos de las clases
contenidas en el espacio de nombres System.Web.UI.WebControls. Al compilar la
página ASP.NET instancia dichas clases y las pone a disposición de nuestro código
pasando todo ello inadvertido para nosotros.

El archivo .vb de lógica de la aplicación

Por otro lado existe un archivo con extensión .vb, dependiente, según el explorador
de proyectos, del archivo .aspx anterior.
Figura 3.1.16.- Archivo .vb de lógica de la aplicación

Éste contiene la "lógica" de la aplicación, es decir, lo que hace que una interfaz de
usuario se comporte de un determinado modo. En nuestro caso contiene el manejador
del evento con lo que deseamos que ocurra al presionar el botón. En una aplicación
real podría contener multitud de cosas más.

Nota:
Aunque es muy tentador abusar de la capacidad de crecimiento de estos
archivos de código, suele ser mucho más recomendable repartir toda
aquella funcionalidad que no se refiera a la interfaz de usuario (es decir,
lo que no sean eventos normalmente) en otros archivos y clases
desligados de páginas aspx. Veremos cómo hacerlo en breve en este
curso.

Desde este archivo de código podemos responder a cualquier evento de los controles
de interfaz de usuario o de la propia página, y acceder a sus métodos y propiedades.

Gracias a la existencia de estos dos archivos podemos independizar el aspecto de la


aplicación (la interfaz) de lo que queremos hacer con ella. De hecho incluso se podría
programar con dos equipos distintos, cada uno encargado de una cosa. Esta
constituye también una de las grandes ventajas de ASP.NET.

¿En dónde se encuentra el nexo entre interfaz y lógica?

Tras esta pregunta tan aparentemente filosófica se encuentra una respuesta muy
sencilla: la directiva de página @Page y la existencia de clases parciales en .NET
2.0.

Si nos fijamos en el código de la página ASPX de la figura anterior, la primera línea es


una directiva @Page con diversos atributos.

· AutoEventWireUp: indica si los eventos se deben generar de forma


automática o no. A los efectos de este curso no hablaremos más de él.
· CodeFile: este atributo es específico de Visual Studio y le indica cuál es el
archivo de código (.vb) que contiene la definición de la lógica de la página.
· Inherits: indica de qué clase heredará la clase auto-generada por ASP.NET
para gestionar los contenidos de la página ASPX actual (repase el primer vídeo
de este módulo acerca de código compilado).

Nota:
Inherits es un atributo nuevo en ASP.NET 2.0 y modifica el modelo
utilizado en Visual Studio 2002 y 2003 para crear código de lógica de
negocio para las páginas, denominado 'code-behind'. Si has trabajado
algo con estas versiones anteriores te sorprenderá ver este cambio y lo
mucho que con él se reduce el código necesario para crear las páginas
ASPX, ya que no hay que declarar los controles ni inicializarlos en el
código de lógica.

Si ahora os fijamos en el código de lógica de la página (figura 3.1.10 anterior),


veremos que se define parcialmente una clase _Default. Y este es el nexo de unión
entre ambos archivos.

Cuando ASP.NET genera la página primero completa automáticamente el código de la


clase _Default con otra implementación parcial de ésta en la que se definen los
controles de la página (el código que ha desaparecido respecto a las versiones
anteriores del entorno). Luego define una nueva clase específica para la página que
hereda de _Default y es en ella donde inicializa los controles y hace el nexo entre la
interfaz y la lógica. Es por este motivo que ahora los manejadores de eventos de la
clase se declaran con accesibilidad protected, para que puedan utilizarse desde la
clase derivada.

Nota:
A esta novedosa forma de separar (y al mismo tiempo unir en tiempo de
ejecución) la interfaz de la lógica hay quien la denomina "code-
beside", como homenaje al hasta ahora utilizado "code-behind" de
ASP.NET 1.x.

Nota:
Todo lo explicado es muy fácil de verificar si se accede a la carpeta
temporal de ASP.NET tal y como se demostró en el primer vídeo de este
módulo. En él encontraremos los archivos de código autogenerados por
.NET en los que podremos comprobar línea por línea todo lo que he
comentado. Se trata de una iniciativa muy didáctica.
Los controles de ASP.NET
ASP.NET ofrece una gran cantidad de controles que se pueden usar en los desarrollos
de aplicaciones Web. Durante la lección anterior hemos podido ver algunos de ellos en
funcionamiento mientras que supimos de la existencia de otros al verlos en el cuadro
de herramientas.

En esta lección vamos a conocer desde un punto de vista general los tipos de
controles existentes y aprenderemos, con más detalle, la utilización unos controles
muy útiles: los controles de validación.

· Controles HTML
o Controles HTML
o Jerarquía de controles HTML

· Controles Web
o Controles Web
o Adaptación al navegador
o Jerarquía de controles Web
o Controles propios
· Controles de validación
o Controles Web de validación
o Uso de los controles de validación
o Validadores personalizados
o Colocar el foco en el error
Los controles HTML

Hasta ahora hemos visto lo sencillo que resulta


trabajar con controles de servidor arrastrándolos y
soltándolos en los formularios Web. Los controles
que hemos utilizado se definen usando una sintaxis
especial (<asp:...>) y como hemos podido
comprobar responden a un comportamiento
complejo.

Antes de ASP.NET cuando necesitábamos usar un


control en una página empleábamos alguno de los
definidos en HTML: controles de tipo <input>,
<textarea> o <img> entre otros. Con ASP.NET
disponemos también de la posibilidad de usarlos.

Desde el cuadro de herramientas disponemos del


grupo HTML (ver figura adjunta) que son controles
equivalentes a los de HTML.

Podemos arrastrarlos y soltarlos sobre nuestro


formulario al igual que los otros, pero al contrario
que éstos no se ejecutarán por defecto en el servidor. Sólo aparecerá su sintaxis
HTML pura y dura.

Se trata de controles muy útiles en determinadas ocasiones en las que no


necesitamos todas las ventajas que nos ofrecen los controles de servidor, por
ejemplo:
· No vamos a acceder a sus métodos y propiedades desde el servidor.
· Quizá no necesitamos que mantengan su estado o respondan a evento alguno.
· El uso del campo oculto ViewState puede cargar la página en exceso si hay muchos
controles, por no mencionar que hay que crear clases en el servidor que los
representen cuando se procesa la página. Todo ello reduce la respuesta de la página.

Por supuesto podemos convertirlos en controles de servidor simplemente asignando


su atributo runat, así:

<input id="Button1" type="button" value="button" runat="server" />

Como vemos es un botón corriente de HTML al que se la ha añadido el atributo runat.


Sólo con esto hemos conseguido que el control esté disponible desde nuestro código
de servidor (como en el ejemplo) y disfrutaremos de todas las cualidades conocidas:
acceso a sus propiedades y métodos, conservación del estado, etc...

En el área de diseño del formulario es muy fácil distinguir los controles de servidor de
los HTML porque los primeros tienen un pequeño triángulo verde que los marca. En la
siguiente figura todos los controles se ejecutan en el servidor excepto el botón
"button" de la derecha.

Figura 3.2.1.- Distinción entre Controles de servidor y HTML

Los controles HTML, en cualquier caso, son mucho más sencillos que los otros
controles Web. Tienen menos propiedades y eventos, los cuales se suelen
corresponder además con los mismos que tienen en HTML+Javascript. Son más
adecuados cuando no requerimos una gran flexibilidad y queremos cargar la página lo
mínimo posible.

Esta figura ilustra la jerarquía de los controles HTML en ASP.NET:


Figura 3.2.2.- Jerarquía de los controles HTML

Todos ellos, obviamente, heredan de la clase Object, pero también heredan de la


clase base HtmlControl que está contenida en el espacio de nombres
System.Web.UI.Control.

Sus propiedades se corresponden con los atributos HTML del control correspondiente,
por lo que la nomenclatura utilizada no es consistente con la utilizada en el resto de la
plataforma ASP.NET.

Ver vídeo de esta lección (controles HTML)


Los controles Web

Son controles nativos de ASP.NET. Aunque algunos parecen asimilables a controles


HTML, todos van mucho más allá en cuanto a características y capacidades. De hecho,
aunque algunos son relativamente sencillos (como una etiqueta, un botón o un cuadro
de texto), existen controles muy complejos que sería difícil recrear desde cero con
HTML y JavaScript. Por ejemplo el control calendario, las rejillas de datos, los
controles maestro-detalle, validadores, etc...

Sus métodos y propiedades tienen nombres consistentes con el resto de la


plataforma. Por ejemplo, para fijar el texto de un botón o de una etiqueta se usa la
misma propiedad Text. Para establecer el color de fondo todos usan BackColor. Esto
hace que sea más fácil el desarrollo porque no hay que memorizar nomenclaturas
diferentes para cada control.

Adaptación automática al cliente

Los controles Web que vienen con ASP.NET tienen otra característica que los hace
únicos y es la adaptación automática al navegador. ASP.NET detecta con qué cliente
se está accediendo (un navegador moderno o antiguo, un PDA, Internet Explorer o
Netscape, etc...) y de forma autónoma adapta el código que muestra a las
capacidades y restricciones concretas del navegador utilizado.

Esta adaptación tiene en cuenta el soporte de HTML y JavaScript, pero también


cuestiones como si se deben usar etiquetas básicas en lugar de hojas de estilo CSS
para el aspecto.

En esta nueva versión 2.0 de ASP.NET va un paso más allá permitiendo la adaptación
automática de los controles a diferentes navegadores y dispositivos incluso móviles
(teléfonos y PDA de cualquier marca). A esta característica se la conoce como
renderización adaptativa.

Figura 3.2.3.- Renderización adaptativa: un mismo calendario en un


navegador y visto desde un móvil WAP.

La renderización adaptativa tiene implicaciones importantes ya que (hasta cierto


punto) no hay que tener en cuenta de antemano si un desarrollo será para un
navegador concreto o incluso para dispositivos móviles basados en WAP. Por otra
parte, y tal vez más importante, está el hecho de que no hay que aprender todo un
conjunto diferente de habilidades para poder programar para dispositivos móviles.
¡Podemos reutilizar lo que estamos aprendiendo en este curso!.

La siguiente figura muestra la jerarquía de algunos controles Web de servidor


incluidos con ASP.NET 2.0:
Figura 3.2.4.- Jerarquía de controles Web de servidor.

Controles de terceras empresas

Aparte de los controles que vienen con ASP.NET 2.0 también es posible utilizar desde
nuestras aplicaciones cualquier otro control Web diseñado por terceras empresas.
Existen infinidad de ellos de todos los tipos, algunos realmente potentes.

Figura 3.2.5.- Algunos ejemplos de controles Web de terceras empresas.


En www.asp.net/controlgallery/ podrá encontrar un extenso catálogo clasificado de
controles Web de servidor.

Controles propios

Como no podría ser de otra manera, ASP.NET no nos limitará a la hora de crear
controles propios para reutilizar funcionalidad en nuestras aplicaciones o incluso para
venderlos a otras empresas.

Existen dos tipos de controles que podremos crear:

· Controles Web: son controles como los que hemos visto hasta ahora y
equiparables en todos sus aspectos a los controles nativos de ASP.NET 2.0.
· Controles de usuario: permiten la reutilización de partes completas de la
interfaz de usuario y de la lógica asociada a ésta, aunque el soporte para
configurarlos en tiempo de diseño es mucho más reducido que en el caso de los
anteriores. Sin embargo son muy fáciles de crear y ofrecen un método sencillo
de encapsular funcionalidades que incluyan interfaz de usuario.

La creación de controles Web (primer tipo) es una cuestión compleja que se sale del
ámbito de este curso, por lo que no los estudiaremos. Sin embargo en la siguiente
lección veremos la forma de crear nuestros propios controles de usuario.

Investigue por su cuenta la funcionalidad de algunos de los controles disponibles en


ASP.NET. Es la mejor forma de aprender. Descubrirá que la mayoría son muy fáciles
de utilizar y sus propiedades y métodos son de uso sencillo. Deje de momento los
enlazados a datos pues serán objeto de un módulo posterior y suelen tener más
complicación.

Ver vídeo de esta lección (Adaptación al navegador)


Controles Web de validación

Dentro de la plétora de controles de ASP.NET existe un grupo "Validación" que, como


es fácil imaginarse, contiene una serie de controles que permiten realizar de manera
cómoda la validación de datos introducidos por los usuarios.

Lo habitual en las aplicaciones Web es realizar una


doble validación. Por un lado se suele implementar
una primera validación en el cliente (es decir, en
el navegador de los usuarios) utilizando para ello
código JavaScript. Esto permite una primera barrera
que no implica el envío de datos innecesarios al
servidor. Como principales desventajas de usar
JavaScript para la validación se encuentran la de ser
código tedioso de escribir y, sobre todo, que es muy
fácil evitarla. Bastaría con que un usuario no tuviese
JavaScript habilitado para que no funcionara en
absoluto.

Por otra parte también se suele realizar una segunda comprobación en el servidor.
En aras de la seguridad, como máxima de cualquier desarrollo deberíamos tomar
siempre la siguiente: "Jamás deberé fiarme de nada que me llegue de un origen fuera
de mi control". En este caso aunque hayamos habilitado una primera validación en el
cliente con Javascript no debemos fiarnos en absoluto de que ésta se haya realizado.
Por ello debemos validar todos los datos siempre en el servidor. Si hay que quitar una
validación que sea siempre la del cliente.

Esta doble validación suele ser bastante engorrosa y supone un esfuerzo de desarrollo
adicional que sería estupendo poder obviar. Pensando en facilitarnos este tipo de
tareas ASP.NET nos ofrece los controles de validación.
Estos controles permiten definir reglas de validación en la entrada de datos. Dichas
reglas se asocian con otros controles que forman parte del formulario web, y se
combinan entre ellos para especificar múltiples restricciones sobre los datos
introducidos.

Las condiciones típicas son, por ejemplo, que un campo no se puede quedar vacío,
que tiene que estar comprendido dentro de un rango determinado o incluso que debe
cumplir con una expresión regular que indiquemos. Por supuesto es posible también
definir reglas propias personalizadas.

La principal ventaja de estos controles es que permiten la definición de reglas de


validación de forma declarativa, es decir, no hace falta escribir código para usarlos.
Ello facilita mucho el desarrollo y el mantenimiento de las reglas de validación.

Una vez que definamos las reglas para un formulario los controles de validación se
encargan automáticamente de validarlas tanto en el cliente como en el servidor.
En el lado cliente se convertirán en código JavaScript muy parecido al que nosotros
usaríamos, actuando de primera barrera y evitando viajes innecesarios al servidor.
Las comprobaciones del lado del servidor nos evitan problemas cuando, por el motivo
que sea, no han actuado las validaciones en el cliente.

Nota:
Se puede desactivar la validación en el lado del cliente de un control
estableciendo su propiedad EnableClientScript a False. Podemos
deshabilitar la validación del lado cliente de todos los controles
estableciendo la propiedad ClientTarget de la página actual con la
cadena "DownLevel" desde el evento de carga de la página. Con ello sólo
se realizará la validación en el servidor.

Uso de los controles de validación

Para hacer uso de uno de estos útiles controles basta con arrastrarlos al formulario.
Veremos que al hacerlo se muestran como si fueran etiquetas normales, aunque con
el texto de color rojo. Este es el aspecto que tendrán si se hace necesaria su
actuación. Mientras no se produce una situación en la que la validación fracasa serán
invisibles. Toda esta funcionalidad se consigue utilizando JavaScript, es decir, que no
se envía nada al servidor (no se hace un post-back).

Cada control de validación que arrastremos se debe asociar al control del que deberá
"estar pendiente". Por supuesto es posible arrastrar varios validadores y asociarlos a
un mismo control para así verificar varias condiciones. Lo contrario no es cierto, es
decir, no se puede usar un solo validador para verificar varios controles. El control a
verificar se asigna mediante la propiedad ControlToValidate del control de
validación.

Aunque su utilidad es bastante intuitiva, la siguiente tabla indica el uso apropiado de


cada uno de los controles disponibles:

Control Utilidad
RequiredFiledValidator Verifica que el control asociado no
se encuentra vacío.
RangeValidator Genera un mensaje de error
cuando el contenido de su control
asociado está fuera de un rango
de valores dado. Permite validar
intervalos numéricos (enteros o
decimales o monedas), fechas y
cadenas de texto.
RegularExpressionValidator Compara un texto introducido por
el usuario con una expresión
regular.
CompareValidator Permite comparar el valor
introducido por el usuario con una
constante o con el valor de la
propiedad de otro control.
CustomValidator Se usa para implementar lógica
de validación propia tanto en el
cliente como en el servidor.

Tabla 3.2.1.- Controles de validación y su utilidad.

El control ValidationSummary (abajo de todo en el grupo de controles de la figura


anterior) se usa para mostrar un resumen de todo lo que está mal en un formulario
en lugar de mostrar cada uno de los mensajes de error individualmente.

No todos los controles se pueden validar con los controles de validación. De hecho
sólo un pequeño subconjunto de todos los controles Web son adecuados para un uso
conjunto. En cualquier caso los incluidos cubren la mayor parte de las necesidades
normales de introducción de datos, y son los siguientes:

Control Tipo Propiedad


HtmlinputText Entrada de texto Value
HtmlTextArea Entrada de texto Value
TextBox Entrada de texto Text
HtmlSelect Lista de selección Value
ListBox Lista de selección SelectedItem.Value
DropDownList Lista de selección SelectedItem.Value
RadioButtonList Botones de selección SelectedItem.Value
HtmlInputFile Envío de archivos Value

Figura 3.2.2.- Controles que se pueden validar y las propiedades que se


validan en ellos.

Aparte de mostrar la información de error al usuario, en los eventos de la página


gestionados en el servidor podemos comprobar el valor de la propiedad IsValid del
objeto Page. Ésta será False si alguno de los controles de validación ubicados en la
página no ha pasado la prueba de verificación. Esto es muy útil para realizar acciones
complementarias en el servidor en caso de haber errores.
Sabiendo todo esto es fácil utilizar cualquiera de los controles de este tipo. Consulte el
vídeo de esta lección para ver una práctica de uso.

Validadores personalizados

Tal vez el validador que necesite más explicación es el CustomValidator. Como su


propio nombre indica se usa para crear normas de validación propias que no se
adapten a ninguna de las contempladas con los demás controles. Para conseguirlo se
definen funciones de validación en el cliente y en el servidor.

Las funciones de validación en el cliente se escriben en JavaScript o en VBScript y


se asocian con el control mediante la propiedad ClientValidationFunction. la
función definida puede tener cualquier nombre pero debe tomar dos argumentos, así:

function miValidador(origen, argumentos)

El primer argumento, origen, obtiene una referencia al control de validación (un


elemento <span></span> de HTML que contiene el mensaje a mostrar) que nos
permite mostrarlo u ocultarlo o cambiar el contenido del mensaje. El segundo
argumento es más importante puesto que se trata de un objeto con dos propiedades:
Value y IsValid. La primera contiene el valor a validar que está extraído del control
asignado en la propiedad ControlToValidate. La segunda se usa para indicar si la
validación ha tenido éxito o no.

Por ejemplo, para validar que el número introducido en un control es impar,


arrastraríamos un control CustomValidator al formulario Web, asignaríamos su
propiedad ControlToValidate para que apunte a un campo de texto, y estableceríamos
la propiedad ClientValidationFunction con el valor "ValidaImpar" que es el nombre de
la función JavaScript. A continuación incluiríamos en la cabecera de la página un
fragmento de JavaScript como este:

<script language="javascript">

function ValidaImpar(origen, args)

if (args.Value % 2 == 0) //Si es divisible entre 2 es par

args.IsValid = false;

else

args.IsValid = true;

</script>
Ahora todavía falta la validación en el servidor que de hecho es la más importante.
Su funcionamiento es igual de sencillo. Hay que gestionar el evento ServerValidate
del control CustomValidator. Este evento obtiene argumentos del tipo
ServerValidateEventArgs que son funcionalmente equivalentes a los que acabamos
de ver en el caso del cliente, es decir, disponen de las propiedades Value e IsValid.
Para rematar nuestro ejemplo con la validación en el servidor sólo es necesario
escribir el siguiente código VB.NET:

Como se puede observar es funcionalmente idéntico al código que escribimos para el


cliente en JavaScript.

Nota:
Este control de validación personalizada ya existía en versiones
anteriores de ASP.NET. En éstas el evento de validación no se notificaba
cuando el control a validar estaba vacío. Para conservar la
compatibilidad el control CustomValidator de ASP.NET 2.0 trabaja de la
misma manera. Sin embargo es posible forzar la validación incluso con el
campo vacío si establecemos la propiedad ValidateEmptyText del
CustomValidator a True.

Colocar el foco en el error

Otra acción muy común a la hora de validar datos en un formulario es colocar el foco
sobre el control que contiene información errónea. De este modo se facilita al usuario
la introducción del nuevo valor pues no tiene que activar el control con el ratón.
Podemos hacer que los controles de validación hagan esto por nosotros con sólo
establecer a True su propiedad SetFocusOnError. Esta característica es nueva en
ASP.NET 2.0.

Ver vídeo de esta lección (Uso de los controles de validación)


Controles de usuario
Como ya hemos adelantado en la lección anterior, aparte de la compleja creación de
controles Web personalizados del estilo de los que vienen con ASP.NET, existe una
forma rápida y sencilla de reutilizar partes completas de funcionalidad e interfaz de
usuario. Para ello no es necesario tener profundos conocimientos de la plataforma
.NET. Ni siquiera hacen falta conocimientos de HTML. Se trata de los controles de
usuario.

En esta lección veremos qué son, cómo se crean y cómo se utilizan.

· Controles de usuario
o Introducción
o Definición de la funcionalidad pública
o Uso de los controles de usuario
o Carga dinámica de controles de usuario
Controles de usuario

Los controles de usuario son tan fáciles de crear que, de hecho, ya conoce casi todo lo
que necesita para construirlos. Se crean exactamente igual que los formularios Web y
disponen de un diseñador visual idéntico que permite arrastrar otros controles sobre
su superficie. De hecho cualquier formulario Web (página ASPX) puede transformarse
directamente en un control reutilizable con sólo unos pocos cambios de sintaxis.

Para añadir un nuevo control de usuario pulse con el botón secundario sobre el nodo
raíz del proyecto en el explorador de soluciones y escoja la opción "Agregar
elemento...". En el diálogo que aparece (ya sobradamente conocido) seleccione el
icono correspondiente a Control de usuario Web, como se ilustra en la siguiente
figura:
Figura 3.3.1.- Diálogo para añadir un nuevo control de usuario.

Si se fija en la figura detenidamente verá que, salvo por el icono elegido, no hay
diferencia alguna con añadir un nuevo formulario Web. De hecho la única diferencia
existente en este punto es la extensión que se le otorgará al archivo resultante, que
es .ascx en lugar de .aspx.

El archivo resultante se distingue fácilmente en el


explorador de proyectos porque Visual Studio le
asigna un icono diferente (vea la figura lateral).

Al igual que un formulario Web corriente un


control de usuario dispone de un diseñador visual
con doble vista (Diseño y Origen) y de un editor
de código asociado en caso de haber escogido la
separación de código e interfaz (opción marcada
en la figura anterior).

Del mismo modo están disponibles para arrastrar


sobre la superficie de diseño los mismos controles web que en el caso de los
formularios Web.

La primera diferencia con una página ASPX la encontramos al ver las etiquetas que
constituyen la parte de interfaz de usuario del control. En los formularios aparece al
principio una directiva <%@Page %>, pero en los controles la directiva se llama
<%@Control %> si bien se usa de un modo muy similar:
Figura 3.3.2.- Código origen de un control de usuario sobre el que se han
arrastrado tres controles Web.

Otra diferencia fundamental de un control con una página es que hereda de la clase
UserControl y no de Page. Sin embargo ambas clases base heredan a su vez de la
clase TemplateControl, por lo que conservan multitud de características en común.

Figura 3.3.3.- Código de la clase code-beside de un control de usuario. en el


que se ve que ésta hereda de UserControl .

De hecho la similitud es tal que el entorno de desarrollo le asigna al manejador del


evento Load del control el nombre Page_Load (en lugar de, por ejemplo,
UserControl_Load) como se observa en la figura.

Definición de la funcionalidad pública del control de usuario

Todo a partir de ahora es exactamente igual que en el caso de los formularios Web.
Podemos arrastrar cualquier control Web desde el cuadro de herramientas sobre la
superficie del control, asignar sus propiedades y recibir post-backs que generan
eventos en los controles que hemos incluido.

Por supuesto, como en esencia un control de usuario no es más que una clase de
.NET, podemos extenderla añadiéndole nuestros propios métodos y propiedades.
Todos los miembros públicos que agreguemos estarán disponibles desde la página
que albergue al control del mismo modo que lo están las propiedades y métodos de
cualquier control Web normal. Esto es muy útil para encapsular el acceso a ciertas
funcionalidades que hayamos incluido.
Por supuesto los controles que coloquemos en la superficie del control se verán
adecuadamente en la página que lo contenga y se comportarán del modo esperado,
esto es, recibiendo eventos, conservando su estado en el ViewState, etc...

Uso de los controles de usuario

Ahora que ya sabemos crear controles de usuario veamos la forma de usarlos desde
los formularios Web.

El modo más sencillo de incluir un control de usuario en una página ASPX es


simplemente arrastrándolo desde el explorador de proyectos sobre su superficie de
diseño. Esto hará que se visualice el control completo dentro de la página:

Figura 3.3.4.- Control de usuario arrastrado sobre la superficie de un


formulario Web (los puntos rojos se usan para destacarlo, no estaban en la imagen original).

Nota:
Visual Studio 2005 ofrece un mayor soporte en tiempo de diseño para
los controles de usuario que permite ver el contenido del control y
ajustar sus propiedades desde el explorador de propiedades. En
versiones anteriores de Visual Studio .NET lo único que se veía de estos
controles era un cuadro de color gris con su nombre y eran por tanto
más difícil trabajar con ellos y encajarlos en un diseño general.

Cuando observamos el código de la página ASPX desde la que estamos utilizando el


control de usuario, vemos que al principio de ésta se ha incluido una directiva
Register:

Quizá el atributo más importante de esta directiva sea el último, TagPrefix. El valor
asignado a él será el que se utilice para distinguir de manera única un control de
usuario dentro de la página. De este modo se pueden utilizar sin problemas controles
de usuario con el mismo nombre en una misma página. Así, según la línea anterior
podríamos definir en la página un control del tipo Micontrol usando una sintaxis como
esta:
Las propiedades que hayamos definido para la clase Micontrol se pueden establecer
mediante atributos (al igual que ID en la línea anterior, por ejemplo) siempre que se
trate de tipos simples.

Podemos modificar la directiva Register para incluir un prefijo que nos guste más o
sea más descriptivo que el que Visual Studio ha puesto por defecto.

Carga dinámica de controles de usuario

Otro modo de hacer uso de los controles de usuario es cargarlos dinámicamente


según los necesitemos. El método LoadControl de la clase Page o del formulario de
la página es el que nos va a ayudar a conseguirlo. Sólo hay que pasarle como
argumento el nombre del archivo ASCX que contiene la definición del control de
usuario, de modo similar a este:

Dim c As Control

c = Me.LoadControl("Micontrol.ascx")

Me.Form.Controls.Add(c)

En lugar de usar un tipo genérico (Control) como en este fragmento también podemos
usarlo como el verdadero tipo del control y así llamar a sus métodos y propiedades
antes de añadirlo al formulario:

Dim c As Control

Dim miC As Micontrol

c = Me.LoadControl("Micontrol.ascx")

miC = CType(c, Micontrol)

miC.ValorInicial = "Bienvenido a esta página"

Me.Form.Controls.Add(miC)

En este ejemplo hemos usado el control con su verdadero tipo para poder asignar la
propiedad ValorInicial antes de agregarlo a la página.

Ver vídeo 1 de esta lección (controles de usuario)


Ver vídeo 2 de esta lección (ampliando la capacidad básica de los
controles de usuario)
Técnicas de trabajo y consejos varios
Para finalizar con este intenso módulo de fundamentos de las aplicaciones ASP.NET
vamos a comentar algunas técnicas de trabajo comunes que le serán de ayuda en su
quehacer diario.

· Técnicas de trabajo y consejos varios


o Navegación entre páginas
o Envío de datos entre páginas
o Transferir el control entre páginas
o Reutilización de código en una aplicación
Técnicas de trabajo y consejos varios

Navegación entre páginas

Antes de nada me parece necesario comentar que en las aplicaciones ASP.NET se


utilizan seguramente muchas menos páginas que en el caso de que la misma
aplicación estuviese escrita con ASP clásico. Esto se debe a que, gracias a los post-
back y los correspondientes eventos de servidor se encapsula más la funcionalidad en
un sólo archivo. Por ejemplo en una aplicación ASP 3.0 clásica para recoger los datos
de un usuario con mucha probabilidad escribiríamos dos (o incluso tres o cuatro)
páginas: la página HTML con el formulario, la página ASP a la que se envían los datos
del formulario y puede que una página o dos para informar al usuario del éxito o del
fracaso de su acción. En ASP.NET todo esto se resolvería con una sola página ASPX,
un evento de servidor y probablemente mostrando y ocultando controles Web desde
dicho evento.

Por supuesto una aplicación estará formada de todos modos por un número más o
menos elevado de páginas a las que se debe dirigir al usuario. Existen diversas
maneras de dirigir a un usuario hacia otra página o recurso de la aplicación.

La primera de ellas, y la más sencilla, consiste en utilizar controles del tipo


HyperLink que tiene este aspecto en el cuadro de herramientas: .
Estableciendo su propiedad NavigateUrl estaremos indicando a qué página queremos
enviar al usuario cuando pulse sobre el enlace resultante. Si la página es una de las
que pertenecen a nuestra aplicación será muy fácil seleccionarla gracias al diálogo
especial que aparece para ello:
Figura 3.4.1.- Diálogo de selección de páginas de nuestra aplicación.

Además si ajustamos la propiedad Text de este control ese será el texto que se
muestre para el enlace de la página. Es posible utilizar un gráfico en lugar de texto
para el enlace si se utiliza la propiedad ImageUrl.

Nota:
Existen controles especializados en crear árboles de navegación en una
página pero por debajo usan enlaces como éstos. Los estudiaremos en
un próximo módulo.

La otra manera de enviar a los usuarios a una página propia o ajena consiste en hacer
uso del método Redirect de la clase HttpResponse del contexto de llamada de la
página. Así podremos controlar desde un evento de servidor a dónde enviaremos al
usuario. Por ejemplo, si queremos enviarlo a una página diferente según lo que haya
escogido en un control de selección podríamos escribir algo similar a esto en el evento
de pulsación de un botón:

If opciones.ListIndex = 0 Then

Response.Redirect("opcion1.aspx")

Else

Response.Redirect("opcion2.aspx")

End If

El método Redirect envía al navegador del usuario una cabecera especial de


redirección que le indica que, en lugar de descargar los contenidos de la página
actual, debe solicitar una nueva. Esto provoca una nueva petición de página desde el
navegador por lo que no es la forma de navegación de mayor rendimiento (hay el
doble de viajes al servidor que en un enlace directo). Sin embargo dota de gran
flexibilidad a la hora de decidir qué hacer ante la solicitud de página de un usuario o
para redirigir al final de un proceso ejecutado en el servidor.

Envío de datos entre páginas

Como hemos visto el comportamiento normal durante la pulsación de un botón u otro


evento de controles servidor es el de realizar un post-back a la página actual. Sin
embargo puede haber ocasiones en las que, por el motivo que sea, se necesita
realizar ese envío de datos a otra página diferente, pero eso sí, conservando el acceso
a todos los controles y datos de la página original (que como sabemos están
contenidos en el ViewState).

ASP.NET 2.0 ha añadido una nueva funcionalidad denominada Cross Page Posting,
que permite precisamente esto. Para conseguirlo lo único que hay que hacer es
ajustar la propiedad PostBackUrl del control cuyos eventos queremos gestionar
desde otra página asignándole la ruta virtual de ésta última.

Los datos se reciben en la otra página pero todavía tenemos acceso a los datos de la
página original a través de la propiedad PreviousPage de la nueva. Se trata de un
objeto Page reconstruido a partir del ViewState recibido. Si la usamos así, de modo
genérico, tendremos que utilizar el método FindControl de la clase Page para
localizar cualquier control que hubiese en la página original. Por supuesto si ambas
páginas pertenecen al mismo espacio de nombres (o lo hemos declarado) podemos
forzar el uso de la página como la clase original de ésta usando CType y acceder
directamente a sus métodos, propiedades y controles.

También es posible determinar desde una página si los datos que está recibiendo son
de su propio Post-back o pertenece a otra página mediante la propiedad
IsCrossPagePostBack, que es muy similar a la propiedad IsPostBack que ya hemos
estudiado.

Esta técnica es de un uso poco frecuente pero se trata de una novedad poco conocida
que me ha parecido interesante incluir aquí.

Transferir el control entre páginas

A veces puede ser útil procesar el código de una página y justo después transferir el
control a otra página ejecutando también su código. El método Transfer de la clase
HttpServer ejecuta dinámicamente el código de una página desde otra cualquiera.

La forma de hacerlo es la siguiente:

Server.Transfer("otrapagina.aspx")

Al contrario que el uso de Redirect, Server.Transfer no cambia la URL que el usuario


ve en su navegador ya que desde fuera no se observa ninguna variación en la página
ejecutada. Es una ejecución en el servidor, sin redirección en el cliente.
Este método es análogo al del mismo nombre en ASP clásico, si bien en ASP.NET su
utilidad es menor puesto que, como veremos, existen maneras mucho mejores y más
recomendables de reutilizar código de uso común en las páginas.

Reutilización de código en una aplicación

A menudo, al escribir una aplicación, tenemos necesidades similares en diversas


partes de ésta. Por ello sería absurdo escribir una y otra vez el mismo código dentro
de los manejadores, al cargar una página y demás eventos. Lo mejor es encapsular el
código en diversos métodos y llamar a éstos cuando sea necesario. Todo esto sonará
de perogrullo a cualquier programador experimentado.

En ASP clásico, por ejemplo, se solían agrupar las funciones de uso común dentro de
archivos de inclusión que luego se utilizaban en las diferentes páginas gracias a una
directiva <!-- #include -->. Si bien esto constituía una manera básica de reutilizar
código no ofrecía la flexibilidad de un lenguaje capaz de crear bibliotecas de objetos y
métodos.

En .NET es posible crear nuevas clases que encapsulen funcionalidades comunes y


que sean reutilizables en cualquier punto de la aplicación. En ASP.NET 2.0 existen una
serie de carpetas con nombres especiales que cuelgan de la carpeta principal de la
aplicación y que llevan asociado un comportamiento especial. Una de estas carpetas
es 'App_Code'. Básicamente, todo el código que se coloque bajo esta carpeta
se compila de manera automática.

Tal y como hemos estudiado anteriormente, cuando se solicita una página ésta se
compila de manera automática junto con su archivo de "code-beside" de forma que, a
medida que se accede a las diferentes partes de una aplicación se va compilando por
completo. Dadas las características de Visual Studio, el código residente en archivos
que no sean páginas o clases parciales de "code-beside" no se compila ya que jamás
navegamos por él ni se referencia desde otras páginas. La excepción a esta regla es el
código que coloquemos en la carpeta App_Code que se compila automáticamente al
comenzar la aplicación.

Por ello, una buena forma de reutilizar código entre páginas es agregar clases
especializadas a la carpeta App_Code. De hecho si presionamos con el botón
secundario sobre el proyecto en el explorador de soluciones y agregamos un nuevo
elemento de tipo Clase se nos mostrará una advertencia diciendo, grosso modo, que
debemos añadirla a App_Code si queremos que funcione. Incluso se crea
automáticamente la carpeta si no lo hemos hecho ya con anterioridad:

Figura 3.4.2.- Advertencia para colocar código en App_Code.

Incluso si intentamos agregar un nuevo elemento a la carpeta App_Code sólo se nos


ofrecerá la posibilidad de incluir nuevas clases, archivos de texto (para comentarios,
por ejemplo) o DataSets. Estos últimos son DataSets tipados que no dejan de ser
clases con un diseñador asociado como veremos en el siguiente módulo.

Una vez creadas nuevas clases en estas carpeta podemos añadirle métodos,
propiedades y campos para dotarlas de la funcionalidad que requiramos. Por supuesto
son clases normales de .NET por lo que podremos derivarlas de otras clases para
obtener funcionalidad "gratis", hacer que implementen interfaces o incluirlas dentro
de módulos o espacios de nombres para ordenarlas.

Si lo que desea es compartir código entre distintas aplicaciones o encapsular


funcionalidad en una librería independiente, puede crear un proyecto de tipo librería
de clases. Este proyecto genera un archivo .DLL que puede compartir entre distintas
aplicaciones. Con esto podrá reutilizar sus clases en tantos proyectos como quiera,
incluso en distintos tipos de proyectos como Aplicaciones Web y Aplicaciones
Windows.

Nota:
Si dispone de la versión Visual Web Developer no podrá generar
bibliotecas DLL.

Ver vídeo de esta lección (Reutilización de código)


Contenido
Sin lugar a dudas uno de los ámbitos más importantes de un lenguaje o entorno de
programación es su capacidad de acceso a datos. Prácticamente todas las aplicaciones
conllevan la realización de accesos a datos.

Le gustará saber que la plataforma .NET, y por lo tanto ASP.NET, ofrecen un potente
modelo de acceso a fuentes de datos. Se le conoce con el nombre genérico de
ADO.NET.

Nota:
No se deje engañar por el nombre: ADO.NET no casi nada que ver con el
anterior ADO utilizado en los tiempos de ActiveX y COM. Sí, dispone de
conexiones, comandos e incluso una clase que recuerda a los Recordset,
pero créame cuando le digo que es mejor que se olvide para siempre de
todos ellos. Tanto la filosofía de trabajo como la tecnología son
diferentes por completo y es mejor que utilice una estrategia de "ojos
limpios" para acercarse correctamente a la nueva tecnología.

Los conocimientos adquiridos en este módulo le servirán para cualquier tipo de


desarrollo con .NET, no sólo para aplicaciones Web. Los conceptos explicados son
válidos también para cualquier versión de .NET no sólo para la 2.0.

· Lección 1: Introducción a ADO.NET


o Introducción
o La capa conectada
o La capa desconectada
o Vinculación de datos a controles Web
· Lección 2: Acceso a datos manual
o Escritura manual de código
o DataAdapter: puente entre dos mundos
o Consultas parametrizadas
o Altas bajas y modificaciones
· Lección 3: Acceso a datos con Visual Studio 2005
o Controles de datos
§ Orígenes de datos
§ Controles enlazados
§ Concurrencia optimista
§ Uso de los controles enlazados en la práctica
o DataSets tipados
Introducción a ADO.NET

Esta lección presenta los fundamentos de ADO.NET, su arquitectura y sus principales


clases. Una vez que sepamos por donde pisamos pasaremos a estudiar más a fondo
cada uno de los conceptos analizados aquí.

· Lección 1: Introducción a ADO.NET


o Introducción
o La capa conectada
o La capa desconectada
o Vinculación de datos a controles Web
Introducción a ADO.NET

Como cualquier otro modelo de acceso a datos, ADO.NET es un conjunto de clases


relacionadas entre sí que están especializadas en ofrecer toda la funcionalidad que un
programador necesita para realizar acceso a datos y manejarlos una vez los ha
obtenido.

Las clases genéricas expuestas por ADO.NET se encuentran bajo el espacio de


nombres System.Data. Este espacio de nombres define clases genéricas de acceso a
datos que posteriormente son extendidas para ofrecer características y funciones
específicas de cada proveedor.

El objeto más importante a la hora de trabajar con el nuevo modelo de acceso a datos
es el DataSet. Sin exagerar demasiado podríamos calificarlo casi como un motor de
datos relacionales en memoria. Aunque hay quien lo asimila a los clásicos Recordsets
su funcionalidad va mucho más allá como se verá en breve.

Arquitectura de ADO.NET

El concepto más importante que hay que tener claro sobre ADO.NET es su modo de
funcionar, que se revela claramente al analizar su arquitectura:
Figura 4.1.- Arquitectura de ADO.NET

Existen dos capas fundamentales dentro de su arquitectura: la capa conectada y la


desconectada.
La capa conectada

La capa conectada de ADO.NET contiene objetos especializados en la conexión con los


orígenes de datos. Así, la clase genérica Connection se utiliza para establecer
conexiones a los orígenes de datos. La clase Command se encarga de enviar
comandos de toda índole al origen de datos. Por fin la clase DataReader está
especializada en leer los resultados de los comandos.

La clase DataAdapter hace uso de las tres anteriores para actuar de puente entre la
capa conectada y la desconectada como veremos después.

Estas clases son abstractas, es decir, no tienen una implementación real de la que se
pueda hacer uso directamente. Es en este punto en donde entran en juego los
proveedores de datos. Cada origen de datos tiene un modo especial de
comunicarse con los programas que los utilizan, además de otras particularidades que
se deben contemplar. Un proveedor de datos de ADO.NET es una implementación
concreta de las clases conectadas abstractas que hemos visto, que hereda de éstas y
que tiene en cuenta ya todas las particularidades del origen de datos en cuestión.

Así, por ejemplo, las clases específicas para acceder a SQL Server se llaman
SqlConnection, SqlCommand, SqlDataReader y SqlDataAdapter y se
encuentran bajo el espacio de nombres System.Data.SqlClient. Es decir, al
contrario que en ADO clásico no hay una única clase Connection o Command que se
use en cada caso, si no que existen clases especializadas para conectarse y recuperar
información de cada tipo de origen de datos.

Nota:
El hecho de utilizar clases concretas para acceso a las fuentes de datos
no significa que no sea posible escribir código independiente del origen
de datos. Todo lo contrario. La plataforma .NET ofrece facilidades de
escritura de código genérico basadas en el uso de herencia e
implementación de interfaces. De hecho la versión 2.0 de .NET ofrece
grandes novedades específicamente en este ámbito.

Existen proveedores nativos, que son los que se comunican directamente con el
origen de datos (por ejemplo el de SQL Server o el de Oracle), y proveedores
"puente", que se utilizan para acceder a través de ODBC u OLEDB cuando no existe
un proveedor nativo para un determinado origen de datos.

Nota:
Estos proveedores puente, si bien muy útiles en determinadas
circunstancias, ofrecen un rendimiento menor debido a la capa
intermedia que están utilizando (ODBC u OLEDB). Un programador novel
puede sentir la tentación de utilizar siempre el proveedor puente para
OLEDB y así escribir código compatible con diversos gestores de datos
de forma muy sencilla. Se trata de un error y siempre que sea posible es
mejor utilizar un proveedor nativo.

La plataforma .NET proporciona "de serie" los siguientes proveedores de acceso a


datos.

Proveedor Espacio de nombres Descripción


ODBC .NET Data System.Data.Odbc Permite conectar nuestras
Provider aplicaciones a fuentes de
datos a través de ODBC.
OLE DB .NET System.Data.OleDb Realiza la conexión
Data Provider utilizando un proveedor
OLEDB, al igual que el
ADO clásico.
Oracle Client System.Data.OracleClient Proveedor de datos para
.NET Data acceder a Oracle.
Provider
SQL Server .NET System.Data.SqlClient Permite la conexión
Data Provider optimizada a SQL Server
7.0 o posterior,
incluyenbdo la última
versión SQL Server 2005.

Los proveedores de acceso a datos que distribuye Microsoft en ADO.NET y algunos


desarrollados por otras empresas o terceros, contienen los mismos objetos, aunque
los nombres de éstos, sus propiedades y métodos, pueden ser diferentes.

Existen, por supuesto, proveedores para tipos de orígenes de datos de cualquier


gestor de datos existente en el mercado. Éstos los desarrolla normalmente la empresa
responsable del producto. Si bien éstos optimizan el acceso a estos orígenes de datos
nosotros siempre podremos realizarlo mediante ODBC u OLEDB si tenemos necesidad.

En resumen: con la capa conectada de ADO.NET realizamos la conexión y


comunicación con los orígenes de datos. Cada proveedor de datos implementa su
propia versión de las clases Connection, Command, DataReader y DataAdapter (entre
otras).

· Las clases derivadas de Connection se utilizan para realizar la conexión y enviar


y recibir información.
· Las clases derivadas de Command permiten ejecutar sentencias SQL y
procedimientos almacenados en el gestor de datos.
· Las clases derivadas de DataReader se emplean para obtener los posibles
resultados de un comando utilizando para ello el conducto de comunicación
establecido por Connection.

Ver vídeo de esta lección (Las clases de datos conectadas)


La capa desconectada

Una vez que ya se han recuperado los datos desde un origen de datos la conexión a
éste ya no es necesaria. Sin embargo sigue siendo necesario trabajar con los datos
obtenidos de una manera flexible. Es aquí cuando la capa de datos desconectada
entra en juego. Además, en muchas ocasiones es necesario tratar con datos que no
han sido obtenidos desde un origen de datos relacional con el que se requiera una
conexión. A veces únicamente necesitamos un almacén de datos temporal pero que
ofrezca características avanzadas de gestión y acceso a la información.

Por otra parte las conexiones con las bases de datos son uno de los recursos más
escasos con los que contamos al desarrollar. Su mala utilización es la causa más
frecuente de cuellos de botella en las aplicaciones y de que éstas no escalen como es
debido. Esta afirmación es especialmente importante en las aplicaciones Web en las
que se pueden recibir muchas solicitudes simultáneas de cualquier parte del mundo.

Finalmente otro motivo por el que es importante el uso de los datos desconectado de
su origen es la transferencia de información entre capas de una aplicación.
Éstas pueden encontrarse distribuidas por diferentes equipos, e incluso en diferentes
lugares del mundo gracias a Internet. Por ello es necesario disponer de algún modo
genérico y eficiente de poder transportar los datos entre diferentes lugares, utilizarlos
en cualquiera de ellos y posteriormente tener la capacidad de conciliar los cambios
realizados sobre ellos con el origen de datos del que proceden. Todo esto y mucho
más es lo que nos otorga el uso de los objetos DataSet, núcleo central de la capa
desconectada de ADO.NET.

Nota:
Otra interesante características de los DataSet es que permiten
gestionar simultáneamente diversas tablas (relaciones) de datos, cada
una de un origen diferente si es necesario, teniendo en cuenta las
restricciones y las relaciones existentes entre ellas.

Los DataSet, como cualquier otra clase no sellada de .NET, se pueden extender
mediante herencia. Ello facilita una técnica avanzada que consiste en crear tipos
nuevos de DataSet especializados en la gestión de una información concreta (por
ejemplo un conjunto de tablas relacionadas). Estas nuevas tipos clases se denominan
genéricamente DataSet Tipados, y permiten el acceso mucho más cómodo a los
datos que representan, verificando reglas de negocio, y validaciones de tipos de datos
más estrictas.

Los objetos DataSet no dependen de proveedor de datos alguno y su funcionamiento


es independiente de cómo hayan sido obtenidos los datos que contienen. Este es el
concepto más importante que debemos recordar.

El proceso general de trabajo de ADO.NET para acceder a un gestor de datos (SQL


Server, por ejemplo) es casi siempre el mismo: se abre una conexión (clase
Connection), se lanza una consulta SQL o procedimiento almacenado mediante un
objeto de la clase Command, y se almacenan en memoria los resultados dentro de un
objeto DataSet, cerrando la conexión.

Nota:
Aunque este es el comportamiento habitual de una aplicación de datos
existen casos en los que no es recomendable almacenar en memoria (en
un DataSet) todos los resultados de una consulta, bien por ser muchos
registros o por contener datos muy grandes. En este tipo de casos se
puede usar u objeto DataReader directamente para leer la información,
tratarla y no almacenarla en lugar alguno. De todos modos lo más
frecuente es realizar el proceso descrito.

Unión entre capa conectada y desconectada

La clase DataAdapter se ha incluido anteriormente en la capa conectada por que está


implementada por cada proveedor de un modo diferente. En realidad es una clase que
pone sus pies en ambos mundos (conectado y sin conexión) y sirve de nexo entre
ellos.

Un DataAdapter sabe omo manejar correctamente los objetos proporcionados por su


proveedor específico (fundamentalmente los vistos: Connection, Command y
DataReader) y proporciona métodos para trasegar información desde el servidor a
DataSets desconectados y viceversa haciendo uso de dichos objetos específicos del
proveedor.

Así, por ejemplo, el método Fill de un DataSet se utiliza para introducir los resultados
de una consula dentro de un DataSet para luego trabajar con ellos sin preocuparnos
de su origen. Del mismo modo su método Update se utiliza para conciliar
automáticamente con el origen de datos los datos modificados en un DataSet
mientras no había conexión.
Otras clases dependientes de DataSet

Como hemos dicho antes un DataSet podría asimilarse a un pequeño gestor de datos
en memoria. Como tal un DataSet permite mantener diversas tablas así como las
relaciones entre ellas, incluso forzando que se cumplan restricciones de creación y
actualización, como en una base de datos "real". Para ello se apoya en el uso de otras
clases especializadas que son las siguientes:

· DataTable: representa una tabla o relación de datos. Se puede asimilar a una


tabla de un gestor de datos. Los datos obtenidos de una consulta de tipo
SELECT de SQL se almacenan en un objeto de esta clase.
· DataColumn: ofrece información sobre cada uno de los campos de los
registros almacenados en un DataTable, como su nombre o su tipo.
· DataRow: representa un registro de la tabla virtual definida por el DataTable.
Contiene tantos elementos como campos tiene la tabla, cada uno del tipo
definido por el objeto DataColumn correspondiente.
· Constraint: las clases Constraint se emplean para definir resticciones en los
tipos de datos contenidos en un DataTable. Por ejemplo se puede usar un
objeto de esta clase para indicar que un determinado campo debe ser único o
que se trata de una clave externa que debe ser tenida en cuenta en
actualizaciones o borrados en cascada.
· DataRelations: define la relación existente entre dos objetos DataTable.
Normalmente se suelen utilizar un identificador común a ambas tablas aunque
pueden ser combinaciones de más de uno de ellos. De este modo se sabe cómo
obtener información de una tabla a partir de información en otra. Por ejemplo el
identificador de una factura (su número, por ejemplo) puede servir para
relacionar su registro con los registros de detalle de factura que están
contenidos en otra tabla.
· DataView: representa una vista concreta de un DataTable. Normalmente se
trata de ordenaciones o filtros sobre los datos originales. Todas las tablas
disponen de una vista por dfecto (propiedad DefaultView) que ofrece los datos
tal y como se han introducido en ésta y es la vista que se usa habitualmente.

Ver vídeo de esta lección (Un vistazo a un DataSet)


Vinculación de datos a controles Web

Otra característica fundamental de ASP.NET que lo convierte en una herramienta


ventajosa para el desarrollo de aplicaciones Web es la capacidad de vincular datos a
controles Web.

La mayor parte de los controles que podemos arrastrar sobre una página ASPX se
pueden vincular a los objetos DataSet, DataTable y DataReader o a informaciones
concretas contenidas en éstos.

Ello facilita mucho el trabajo con datos desde la interfaz de usuario ya que no hay que
molestarse en generar tablas con ellos, escribir JavaScript o proporcionar complejos
medios propios para permitir su edición o navegación si hacemos un uso adecuado de
la vinculación y los controles disponibles.

Todo ello, gracias a ASP.NET y Visual Studio, equipara en muchos aspectos el


desarrollo Web al clásico desarrollo de aplicaciones de escritorio donde este tipo de
facilidades han estado disponibles desde hace años. Sin embargo en una aplicación
Web donde no existe una conexión permanente disponible entre la visualización
(navegador) y el lugar en el que se ejecuta el código no es algo fácil de conseguir. El
uso de un modelo consistente como ADO.NET (idéntico en Web, escritorio y otros
entornos) junto con las capacidades nativas de ASP.NET para abstraernos de estas
dificultades (ViewState, Postback...) consiguen el "milagro".

En este módulo veremos también lo sencillo que resulta crear interfaces para explotar
los datos desde una página Web.
Acceso a datos manual

Tras haber aprendido un poco de teoría sobre ADO.NET a continuación explicaremos


cómo se utilizan las clases de acceso datos para escribir código de acceso a datos de
manera manual.

Si bien es un tema algo árido y además en un gran porcentaje de los casos


utilizaremos herramientas que nos faciliten la creación automática de código, es
fundamental conocer la forma de trabajar sin ayuda para entender el funcionamiento
real de los objetos de datos.

Así que ánimo y estudie con detenimiento esta lección.

· Lección 2: Acceso a datos manual


o Escritura manual de código
o DataAdapter: puente entre dos mundos
o Consultas parametrizadas
o Altas bajas y modificaciones
Escritura manual de código

En este apartado vamos a analizar cómo es el código necesario para recuperar y


actualizar datos con ADO.NET. Posteriormente veremos como sacar partido a las
facilidades del entorno de desarrollo Visual Studio 2005 para no tener que escribir el
código a mano. Sin embargo es útil aprender a hacerlo de esta manera para entender
bien su funcionamiento.

Comandos de selección simples

La mayor parte de las consultas que se lanzan contra una base de datos suelen
utilizarse para obtener un conjunto de registros para tratar. Este tipo de consultas
suelen ser expresiones SQL de tipo SELECT. El siguiente fragmento de código muestra
los pasos necesarios para mostrar en una página los registros resultantes de una
consulta:
No se trata de un código optimizado (es más bien burdo) pero nos ayudará a
entender perfectamente el proceso. Los datos los obtendremos de la conocida base de
datos de ejemplo Northwind (que viene con todas las versiones de SQL Server).

Nota:
Para poder escribir código de acceso a datos en nuestro módulo
debemos agregar referencias a los espacios de nombres que contienen
las clases que vamos a utilizar. Para ello usamos las dos sentrencias
Imports siguientes:

La primera de ellas agrega las clases genéricas de acceso a datos (como


DataSet) y la siguiente las específicas de SQL Server. Si no lo hacemos
recibiremos un error.

Lo primero que se debe hacer es instanciar un objeto que represente la conexión a la


base de datos. Dado que nos estamos conectando a SQL Server esta conexión será
del tipo SqlConnection. Es lo que se hace en la primera línea del código anterior. La
conexión debe realizarse con un servidor de datos y un esquema de datos concreto.
Esto se indica mediante la cadena de conexión (al igual que se hacía en ADO
tradicional). En este caso la cadena de conexión es la típica de SQL Server. Cada
gestor de datos tiene la suya y hay que construirla de manera adecuada. El entorno
de desarrollo Visual Studio 2005 nos ayuda a crearlas como veremos luego.

Una vez creado el objeto con el que nos conectaremos hay que definir el comando a
lanzar a la base de datos. Para ello se utiliza un objeto SqlCommand. Las propiedades
básicas que hay que establecer para éste son la consulta que se lanzará (propiedad
CommandText) y la conexión que se empleará para lanzarla (propiedad
Connection) que es lo que se refleja en las líneas 6 y 7.

Ahora que ya sabemos cómo nos conectaremos y qué queremos obtener debemos
lanzar la consulta y recoger el resultado de alguna manera.

La clase Command dispone de diversos métodos para ejecutar consultas:

· ExecuteReader: este método lanza la consulta a la base de datos y devuelve


una referencia a una instancia de la clase DataReader (SqlDataReader en el
caso de SQL Server). Podemos utilizar el DataReader para recorrer los datos y
procesarlos.
· ExecuteNonQuery: ejecuta la consulta sin devolver resultado alguno.
Simplemente envía la consulta al servidor y devuelve el número de registros
afectados por ésta. Útil para realizar consultas de inserción (INSERT),
actualización (UPDATE) y borrado (DELETE).
· ExecuteScalar: lanza la consulta y devuelve un objeto con el valor del primer
campo del primer registro que se obtenga de dicha consulta. Es útil para lanzar
consultas de agregación como sumas (SUM), cuentas (SELECT COUNT * ....) y
similares de las que sólo necesitamos un valor como resultado.

En el ejemplo hemos utilizado el primer método ya que requerimos que devuelva


varios registros con diferentes campos. Entonces lo que hacemos es (líneas a partir
de la 9):

1. Abrir la conexión.
2. Crear una variable para contener una referencia a un objeto de la clase
DataReader que es el que nos permitirá acceder a los resultados. Fíjese en que
no se puede instanciar un DataReader (la declaración no lleva la palabra clave
New).
3. Se obtiene el resultado mediante el método ExecuteReader, recogiéndolo en la
variable (dr) recién declarada.
4. Se procesan los resultados (líneas 14-18).
5. Se cierra la conexión.

El objeto DataReader es asimilable a un cursor de servidor de sólo lectura y hacia


adelante (conocidos como de manguera de bombero o firehose). Es decir, los datos
devueltos por el DataReader sólo se pueden leer y no actualizar. Además de esto sólo
se pueden leer en secuencia hacia adelante (no hay forma de regresar sobre lo
andado).

La propiedad HasRows nos indica si hay o no resultados devueltos. El método Read


avanza una posición en los registros devolviendo True si quedan registros pendientes
de leer. Con esta información es muy fácil entender las líneas 14 a 18.

La cláusula Using

¿Qué ocurre si se produce un error durante el procesamiento del bucle anterior en el


que se trata el DataReader?. La respuesta es que la conexión, que debemos tener
abierta durante el procesamiento, no se cerrará pues el código no llega al final.
Esto es algo muy grave ya que las conexiones que no se cierran no se pueden
reutilizar y por lo tanto puede llegar un momento en que no tengamos conexiones
disponibles, lo que limita enormemente la escalabilidad del sistema.

Podemos evitar el problema escribiendo:

De este modo, con la cláusula Finally nos aseguramos que siempre se va a cerrar la
conexión.

De todos modos escribir este código es algo tedioso sobre todo si queremos que la
excepción se replique y sólo metemos la cláusula Finally por el hecho de cerrar la
conexión.

Para facilitar el trabajo VB.NET en .NET 2.0 incluye una cláusula especial denominada
Using que habilita la destrucción automática de los objetos a los que se hace
referencia. Así el código anterior quedaría simplemente:

Al terminar la cláusula Using (aunque haya un error por medio) se llama de manera
automática al método Dispose del objeto utilizado (en este caso una conexión). Entre
otras cosas este método se encarga de cerrar el objeto si estaba abierto, por lo que
no nos tendremos que preocupar de este aspecto.

Grupos de registros

Aunque los DataReader se asemejan al funcionamiento de un cursor firehose, en


realidad difieren bastante de éstos. Imaginemos que conectamos con la base de datos
en el ejemplo anterior y, mientras estamos procesando el bucle de los registros, se
interrumpe la conexión a la base de datos pr el motivo que sea.

En principio en el caso de un cursor firehose tradicional obtendríamos un error porque


se ha roto la conexión con el servidor. En el caso de un DataReader es posible que
sigamos ejecutando varias vueltas más del bucle sin problemas. Esto se debe a que,
en realidad, el DataReader obtiene los registros en grupos a través de la conexión y
va solicitando nuevos grupos a medida que los necesita.

Es algo que hay que tener en cuenta a la hora de utilizarlos.

Ventajas e inconvenientes

El código anterior, aunque sencillo, es un poco lioso y el uso de los DataReader está
algo limitado dada su idiosincrasia (de sólo lectura y hacia adelante). Este código es
adecuado si no necesitamos almacenar los resultados de la consulta en memoria ni
regresar sobre ellos una vez procesados una primjera vez. También es muy útil para
obtener resultados con miles o millones de registros que queremos tratar
secuencialmente pero no almacenar en memoria.

Sin embargo para un uso cotidiano se trata de un código muy poco útil y complicado
de utilizar salvo para cosas muy sencillas. Además sólo hemos utilizado clases de la
capa conectada de ADO.NET. Todavía debemos aprender a obtener los resultados
dentro de un DataSet para su explotación de manera cómoda. Hay que tender un
puente entre ambos mundos (conectado y desconectado): el DataAdapter.
DataAdapter: puente entre mundos

Si lo que deseamos es poder almacena temporalmente en memoria los datos


obtenidos de una consulta debemos recurrir al uso de objetos de la clase DataSet.
Como sabemos se trata de un almacenamiento en memoria desvinculado por
completo del origen de los datos.

Si el ejemplo anterior lo modificamos para convertirlo en una función que nos


devuelva un DataSet con los datos obtenidos a partir de la consulta, el resultado sería
similar a este:
La primera parte del código es como la anterior. Se crean una conexión y un
comando. Lo que difiere bastante es la segunda parte. Hay varias diferencias
importantes:

1. Ya no aparece en el código objeto DataReader de tipo alguno.


2. No se abre ni se cierra la conexión a la base de datos.
3. Se crea un nuevo DataSet y aparece un objeto de la clase DataAdapter.

Un DataAdapter es una clase que conoce las particularidades de la capa conectada de


un determinado proveedor y es capaz de "comunicar" información entre ésta y la capa
desconectada formada por los DataSet.

El método Fill de un DataAdapter se encarga e rellenar un DataSet con los datos


obtenidos a partir de una consulta (o procedimiento almacenado) definida a través de
un comando. Su propiedad SelectCommand se usa para indicar qué comando se
utilizará para rellenar el DataSet.

Internamente el método Fill emplea el objeto DataReader devuelto por ExecuteReader


y rellena el DataSet con él por lo que sería factible crear un código equivalente. Sin
embargo es muy cómodo y nos evita problemas y el tener que "reinventar la rueda"
en cada proyecto.

El objeto DataSet

Los objetos DataSet contienen a su vez objetos DataTable que son los que contienen
realmente los datos. Para acceder a las tablas de un DataSet se utiliza su colección
Tables. Se puede acceder a las tablas por posición (números enteros) o por nombre
si lo sabemos.
En un ejemplo sencillo como el anterior (y por otro lado uno de los más comunes) se
crea una única tabla en el DataSet de nombre "Table1" y posición 0.

Las tablas contienen dos colecciones interesantes:

· Columns: conjunto de objetos de tipo DataColumn que ofrecen información


sobre los campos de la tabla (nombre, tipo, longitud...).
· Rows: colección de objetos de la clase DataRow que contienen información
concreta sobre cada campo de un registro de la base de datos.

Con esta información resulta muy sencillo tratar los datos de una consulta. Podemos
acceder directamente a cualquier registro de la tabla usando su posición en la
colección de filas. Por ejemplo para acceder al quinto registro de una tabla basta con
escribir:

Dim dr As DataRow

dr = ds.Tables(0).Rows(4)

(nótese que las colecciones comienzan a numerarse en 0, de ahí que el quinto


registro tenga índice 4).

Podemos acceder a cualquier campo del registro usando su posición o bien su


nombre, así:

ds.Tables(0).Rows(4)("CompanyName")

que devolverá un objeto del tipo adecuado para el campo que representa (una
cadena, un objeto de fecha, un booleano, etc...).

Nota:
Es muy sencillo definir objetos DataTable que dispongan de los campos
que deseemos sin depender de origen alguno de datos. Emplee el
método Add de la colección Columns para crear nuevos campos, algunos
de los cuales pueden ser incluso derivados mediante una fórmula de los
valores de otros. Esto permite definir estructuras de almacenamiento a
medida en memoria sin preocuparnos de usar una base de datos para
ello.

Ventajas del uso de objetos DataSet

Es posible cargar datos de varias tablas en un mismo DataSet, incluso aunque


procedan de bases de datos diferentes, y relacionarlas en memoria. Es posible
establecer relaciones entre ellas para mantener la consistencia, así como hacer un
mantenimiento en memoria de los datos (altas, bajas y modificaciones).
Posteriormente se puede sincronizar el DataSet con el servidor usando un
DataAdapter, realizando el proceso contrario al de obtención de datos. Luego lo
veremos.
La principal ventaja de un DataSet es que podemos almacenarlo en donde queramos
(en una variable global, en caché, incluso a disco a una base de datos) para trabajar
con él sin estar conectado a una base de datos. De hecho se pueden transferir
DataSet completos entre diferentes máquinas o por Internet, trabajar con ellos en
remoto, almacenarlos, recuperarlos y finalmente transferirlos en cualquier momento
de nuevo al origen (enteros o sólo los cambios) para sincronizarlos.

Es posible moverse con libertad entre los registros de una tabla y sus registros
relacionados en otras tablas. Y sobre todo, se pueden vincular con elementos de la
interfaz gráfica para mostrar los datos automáticamente.
Consultas parametrizadas

Las consultas simples como la que acabamos de utilizar en los ejemplos anteriores
son muy raras. En la realidad las consultas son mucho más complejas, suelen
intervenir varias tablas y dependen de diversos parámetros que le añaden
condiciones.

Por ejemplo, si en la base de datos Northwind queremos obtener sólo aquellos


clientes de un país determinado. Podríamos escribir de nuevo la función DameClientes
para que se pareciese a la siguiente:
Esta función acepta un país como parámetro y lo único qe hace es concatenar a la
consulta una nueva condición que introduce el país.

Esto, sin duda, funcionaría. Sin embargo presenta multitud de problemas


seguramente no demasiado obvios para los programadores noveles. Los más
importantes son los siguientes:

1. Este código es vulnerable a problemas de seguridad provocados por ataques


de inyección de SQL. Esto puede poner en peligro fácilmente nuestra aplicación
e incluso todo nuestro sistema en casos graves. El estudio de esta problemática
se sale del ámbito de este curso, pero créame cuando le digo que se trata de un
gravísimo problema que debemos evitar a toda costa.
2. Si se solicitan 100 consultas idénticas al servidor en las que sólo varía el
nombre del país por el que se filtra, éste no tiene modo de saber que son las
mismas y sacar factor común. Es decir, no se puede reutilizar el plan de
consulta de la primera de ellas para las demás por lo que se debe calcular de
nuevo cada vez, incidiendo en el rendimiento y la escalabilidad de la aplicación.
Obviamente en consultas más compllicadas es un problema más importante que
en esta.
3. Este código es más difícil de transportar a otros sistemas de bases de datos ya
que hay que incluir los delimitadores y notaciones específicos de cada gestor.
Por ejemplo en SQL Server los delimitadores de fechas son comillas simples ('),
mientras que en Access son almohadillas (#) y la sentencia usada no se puede
reutilizar.

La forma correcta de realizar este tipo de consultas es utilizar parámetros en la


consulta. Ello evita los problemas enumerados.
Los objetos de tipo Command disponen de una colección llamada Parameters que
sirve para asignar dichos parámetros. Éstos previamente se definen en la consulta
utilizando comodines que marquen su ubicación.

Nota:
Cada proveedor de datos utiliza su propia convención para indicar la
posición de los parámetros en una consulta. En el caso de SQL Server se
indican con una arroba '@' seguida de un identificador. En otros
proveedores no se puede definir nombre para los parámetros, sólo se
marca su posición con un caracter especial.

La función anterior empleando parámetros sería la siguiente:

Se ha resaltado los cambios.

Como vemos en lugar de concatenar cadenas se marca la posición de las partes de la


consulta que varían con un parámetro que consta de una arroba seguida de un
identificador.

Luego hay que declarar el parámetro añadiéndolo a la colección de parámetros. Para


ello se indica su nombre y el tipo de datos que representa (en este caso un NVarChar
de SQL Server, que es una cadena de longitud variable).

Por fin se asigna su valor concreto antes de lanzar la consulta.

El proveedor de datos crea la consulta de la manera correcta, usando los


delimitadores adecuados y tratando los datos del modo que sea preciso para asegurar
que es correcta. Ello elimina los problemas de los que hablábamos anteriormente y
permite optimizar el uso de consultas.
Altas bajas y modificaciones

Con lo que hemos visto hasta ahora ya estamos en condiciones de escribir código
para realizar altas, bajas y modificaciones de registros. Al fin y al cabo éstas son
simplemente consultas SQL del tipo adecuado (INSERT, DELETE o UPDATE) que se
deben enviar al servidor.

Así pues, para crear un nuevo cliente podemos escribir una función como la siguiente:
Es un código muy similar al anterior que realizaba una selección de datos. En este
caso se ha definido una consulta de inserción con tres parámetros. En lugar de usar
ExecuteReader o un DataAdapter en este caso se utiliza el método ExecuteNonQuery.
Éste lanza la consulta (es decir, se inserta el nuevo registro) y devuelve el número de
registros afectados por la consulta (que en el caso de una inserción siempre es 1 si se
inserta correctamente o 0 si ha habido un error y lo capturásemosr).

Las actualizaciones y eliminaciones de registros se podrían conseguir de modo similar.

Trabajando desconectados

El código anterior funciona perfectamente pero no es la forma óptima de trabajar


cuando tenemos que tratar con datos desconectados contenidos en objetos DataSet.

Los DataSet normalmente circulan, independientes de cualquier origen de datos,


entre las diferentes capas de una aplicación e incluso se mueven entre contextos de
ejecución y máquinas (por ejemplo a través de un servicio Web como veremos).
Hemos dicho que son como pequeñas bases de datos, por lo que la forma habitual de
trabajar con ellos es añadir, eliminar y modificar registros directamente sobre sus
tablas, sin pensar en la ubicación real de estos datos.
Así pues, para crear un nuevo registro en un DataSet debemos usar el método
NewRow de la DataTable en la que lo queremos insertar. El nuevo registro tiene la
misma estructura (el mismo "esquema") que el resto de registros y sólo tendremos
que rellenar sus datos. Una vez rellenados añadiremos el nuevo registro a la colección
de filas de la tabla. Así por ejemplo si tenemos almacenado un DataSet con una tabla
con los datos de los clientes obtenidos con la función de ejemplo DameClientes,
podríamos agregar uno nuevo de la siguiente manera:

Es decir, se crea la nueva fila/registro, se rellenan sus campos y se añade a la


colección de filas con el método Add de ésta.

La actualización de datos es más sencilla aún ya que basta con acceder directamente
al registro que nos interesa modificar y cambiar los valores de sus campos. Por
ejemplo, para modificar la dirección del cliente cuyos datos están en el quinto registro
de nuestra tabla sólo hay que escribir:

Por fin, para eliminar un registro sólo hay que usar su método Delete, así:

que borraría el quinto registro de nuestra tabla en memoria.

Conciliando los cambios con el origen

Es muy fácil trabajar con los Dataset en memoria de este modo. Sólo hay un
"pequeño" problema: los cambios se realizan en memoria pero, al no estar vinculado
el DataSet con origen de datos alguno, no los veremos reflejados en la base de datos
que es lo que buscamos.

Debemos realizar una sincronización entre la representación en memoria de los datos


y su ubicación física real, para lo cual debemos hacer que los cambios trasciendan a la
capa conectada. Al igual que cuando los recuperamos, el trasiego en el otro sentido se
realiza con la ayuda del puente que representa la clase DataAdapter.

Al igual que utilizábamos la propiedad SelectCommand para indicar cómo se


recuperaban los datos hacia un DataSet, ahora debemos utilizar las propiedades
InsertCommand, UpdateCommand y DeleteCommand para indicar qué
comandos se deben usar para agregar, modificar y eliminar los registros modificados
en memoria a través del DataSet.
Una vez especificados estos comandos sólo resta llamar al método Update del
DataAdapter para que se ocupe de sincronizar automáticamente todos los cambios
que hayamos realizado en las tablas en memoria. Este método acepta como
argumentos un DataSet completo, una DataTable o incluso un DataRow si sólo
queremos actualizar un único registro.

Definición de los comandos

Los comandos de inserción, modificación o borrado para el DataAdapter se definen del


mismo modo que en el código de ejemplo al principio de este apartado, es decir, se
define la consulta apropieada y sus parámetros. En esta ocasión como los parámetros
serán rellenados automáticamente por el DataAdapter hay qeu utilizar una sobrecarga
del método Add de la colección de parámetros que incluye el nombre del campo
asociado con el parámetro, así:

En este caso se define el parámetro "@Dirección", de tipo NVarChar y longitud 60 que


se refiere siempre al valor del campo "Address" dela tabla.

Por ejemplo, para definir la consulta de eliminación de registros de la tabla de clientes


usaríamos el siguiente código:

siendo CustomerID la clave primaria de la tabla.

Una vez definidos los tres parámetros de alta, baja y modificación sólo resta llamar a
Update para que el DataAdapter se ocupe de toda la sincronización.

Ventajas

Este modelo está lleno de ventajas aunque a primera vista pueda parecer algo
engorroso.

Nota:
Luego veremos que podemos usar las herramientas que nos proporciona
Visual Studio 2005 para definir de manera automática los comandos de
manipulación de datos sin necesidad de pasar el trabajo de hacerlo
manualmente.

Para empezar podemos trabajar con los datos en total libertad sin preocuparnos de si
existe conexión o no con el origen y aunque el DataSet se manipule en una ubicación
física a miles de kilómetros del origen y desconectado de éste o los cambios se
almacenen a disco y se concilien días más tarde.
Se pueden realizar multitud de modificaciones en los datos y luego conciliarlas
simultáneamente en lugar de hacerlo en tiempo real de una en una.

El paso de datos entre capas y funciones se simplifica. Lo habitual antes era definir
funciones con tantos parámetros como datos se quisieran modificar en un registro.
Por ejemplo, para insertar un registro en una tabla que tiene 20 campos se definía
una función con 20 parámetros (muchos de ellos opcionales) o, en el mejor de los
casos, se pasaba una clase creada a medida que representaba lo valores del registro.
Ahora basta con pasar un DataSet, un DataTable o un dataRow que ya contiene toda
la información que necesitamos saber sobre los registros y su tabla asociada.

Lo mejor es que es posible saber mediante ciertas propiedades qué registros han
cambiado (nuevos, modificados, borrados) y mover entre capas exclusivamente
estos. La propiedad HasChanges de los DataSet, DataTable y DataRow nos informa
de si el objeto ha sufrido cambios de algún tipo.

El método GetChanges de los objetos DataSet y DataTable devuelve un subconjunto


de los datos que contiene exclusivamente los cambios. Así, aunque en un DataSet
tengamos 1000 registros, si sólo hemos modificado 2 sólo será necesario trasegar la
información de estos a la hora de enviarlos a otra capa o función para sincronizarlos
con la base de datos.

El método GetChanges se puede invocar sin parámetros o indicando qué tipo de


cambios queremos obtener, lo que se indica con un valor de la enumeración
DataRowState:

Valor Significado
Added Registros que no estaban
originalmente.
Deleted Registros que se han eliminado
Modified Registros cuyos valores se han
modificado
UnChanged Registros que no se han
modificado
Detached Registros que se han desasignado
de una tabla (pero no borrado con
Delete)

Se puede dejar un DataSet en estado sin modificar llamando a su método


AceptChanges. Esto es lo que hace un DataAdapter tras haber sincronizado los
cambios con el origen de datos.
Acceso a datos con Visual Studio 2005

Ahora que ya hemos visto la forma de trabajo manual con ADO.NET y dominamos los
fundamentos, vamos a sacar partido a todas las ventajas que nos proporciona un
entorno como Visual Studio 2005 que, como veremos, nos permiten hacer casi
cualquier tarea de datos sin necesidad de escribir código.

· Lección 3: Acceso a datos con Visual Studio 2005


o Controles de datos
§ Orígenes de datos
§ Controles enlazados
§ Concurrencia optimista
§ Uso de los controles enlazados en la práctica
o DataSets tipados
Controles de datos

Aparte de la escritura manual de código y el uso


directo de objetos de ADO.NET, ASP.NET 2.0
proporciona un nuevo modelo de trabajo declarativo
para acceso a datos que nos permite realziar tareas
comunes de acceso a datos sin escribir código alguno.

ASP.NET 2.0 presenta dos nuevos tipos de controles


Web que participan en este modelo declarativo de
enlace a datos. Estos controles nos abstraen por
completo de las complejidades de manejo de las
clases de ADO.NET por un lado, y de las dificultades
inherentes al modo de trabajo desconectado de las
páginas Web. Equiparan en muchos aspectos el
desarrollo web al tradicional desarrollo de aplicaciones
de escritorio.

Estos controles se encuentran agrupados en el cuadro de herramientas bajo el


nombre de "Datos", tal y como se ve en la figura.

Orígenes de datos

Estos controles de datos representan conexiones con diferentes tipos de orígenes de


información que van desde bases de datos a objetos de negocio. No disponen de
apariencia visual pero se arrastran igualmente sobre los formularios Web para
trabajar con ellos visualmente y poder usar sus paneles de tareas. Abstraen al
programador de las complejidades relacionadas con el manejo de los datos,
permitiendo de manera automática selecionarlos, actualizarlos, ordenarlos,
paginarlos, etc..

ASP.NET 2.0 ofrece los siguientes controles de origen de datos que se pueden ver en
la figura anterior:

Control Descripción
SqlDataSource Enlaza con cualquier base de
datos para que exista un
proveedor de ADO.NET.
AccessdataSource Esta especializado en trabajar
con bases de datos Microsoft
Access.
ObjectDataSource Se enlaza con objetos de
negocio y capas personalizadas
de acceso a datos.
XmlDataSource Trata datos contenidos en
documentos XML.
SiteMapDataSource Se enlaza con la jerarquía de
clases expuesta por el modelo
de navegación de sitios de
ASP.NET 2.0.

¡ATENCIÓN!: El control SqlDataSource sirve para enlazar con


cualquier gestor de datos relacional para la que haya proveedor
ADO.NET disponible, no sólo para enlazar con SQL Server. No se deje
despistar por su nombre.

Concurrencia optimista

Durante la configuración de un origten de datos SQL (luego lo verá en el primer vídeo


de esta lección) una de las opciones avanzadas que se presenta habla de la
Concurrencia optimista. La concurrencia optimista evita la actualización de
registros en el caso de que haya variado cualquiera de sus campos desde que se
obtuvieron de la fuente de datos. Este comportamiento puede ser bueno en ciertas
ocasiones, cuando queremos preservar los cambios realizados por cualquier usuario.
En otras sin embargo no resulta de utilidad y sí añade una gran sobrecarga al acceso
a datos.

Se le denomina concurrencia optimista porque parte de la base de que la posibilidad


de coincidencia de dos usuarios sobre el mismo registro es baja y es un caso que
apenas se dará. Al caso contrario a la concurrencia optimista se le denomina
concurrencia pesimista.

Nota:
En mi opinión debería llamarse "concurrencia realista" ya que, lo cierto
es que si se analiza con detenimiento la posibilidad de conflicto en un
sistema grande tiende a ser realmente pequeña en la mayoría de los
casos. Y de todos modos el sobreescribir cierta información no suele ser
un problema grave salvo cuando hablamos de cuestiones de dinero,
facturas y similares.

Cuando se establece la concurrencia optimista las consultas que genera el asistente


de Visual Studio incluyen todos los campos del registro como condición de búsqueda
del mismo, por ejemplo:

DELETE FROM [Customers] WHERE [CustomerID] = @original_CustomerID


AND

[CompanyName] = @original_CompanyName AND [ContactName] =


@original_ContactName

AND [ContactTitle] = @original_ContactTitle AND [Address] =


@original_Address AND

[City] = @original_City AND [Region] = @original_Region AND


[PostalCode] =

@original_PostalCode AND [Country] = @original_Country AND [Phone] =

@original_Phone AND [Fax] = @original_Fax

mientras que en un caso de concurrencia pesimista se emplea simplemente la clave


primaria del registro para localizarlo:

DELETE FROM [Customers] WHERE [CustomerID] = @original_CustomerID

Es decisión suya qué tipo de actualización utilizar pero en la mayor parte de los casos
usará seguramente la pesimista que, de hecho, es la que usted utiliza normalmente si
escribe las funciones a mano de manera independiente.

Controles enlazados a datos

Se trata de controles de interfaz de usuario que, haciendo uso de los anteriores,


muestran la información a través de la página. Pueden sacar partido a todas las
propiedades de los orígenes de datos y por lo tanto habilitan de manera sencilla la
edición, eliminación, ordenación, filtrado y paginación de los datos entre otras cosas.

Para conectar un control enlazado a un DataSource sólo hay que establecer su


propiedad DataSourceID indicando el nombre apropiado. Así de facil.

Nota:
Si usted ya conoce los controles enlazados a datos de ASP.NET 1.x como
el DataGrid, el DataRepeater, etc... estos controles le resultarán más
cómodos de utilizar pues al contrario que con aquellos no hay que
escribir código alguno. Aunque estos controles "antiguos" no aparecen
en el cuadro de herramientas siguen estando soportados y los puede
seguir utilizando. Incluso si lo desea puede añadirlos al cuadro de
herramientas. De todos modos se recomienda utilizar el nuevo modelo
declarativo de acceso a datos.
Uso de los controles de datos en la práctica

La mejor forma de conocer la manera de trabajar con estos controles es viéndolos


actuar en la práctica. Los vídeos de esta lección muestran un ejemplo completo de
uso de los controles en el que se explotan unos datos para su visualización, edición y
eliminación. Por favor, examínelo con atención y luego trate de practicarlo por su
cuenta con ejemplos similares.

Ver vídeo 1 de esta lección (Configurar un origen de datos)


Ver vídeo 2 de esta lección (Explotar el origen de datos)
Ver vídeo 3 de esta lección (Crear vistas maestro-detalle)
DataSet tipados

La clase DataSet, como cualquier otra clase no sellada de .NET, puede ser extendida
mediante herencia para añadirle nuevas funcionalidades y aprovechar las ya
existentes. Si creamos una nueva clase que herede de DataSet y ésta la
especializamos en el tratamiento de un conjunto de datos determinado que
conocemos de antemano nos encontramos un DataSet especializado.

Por ejemplo, podemos crear un DataSet especial que tengan en cuenta las
particularidades de los datos que maneja para validarlos antes de permitir su
inserción, que verifique las relaciones con otros datos o que los transforme o controle
el acceso a los mismos. Nosotros sólo tendríamos que ocuparnos de estas tareas
dejando a la clase DataSet de la que hemos heredado todos los puntos complejos que
tienen que ver con la gestión de datos pura y dura.

Esta es, en esencia, la idea que subyace bajo los DataSet tipados de Visual Studio
2005. Se trata de clases especializadas derivadas de DataSet que ofrecen una forma
más rápida y sencilla de acceder a los datos que albergan. Además Visual Studio nos
permite crear este tipo de DataSet de forma visual y usando asistentes. En el vídeo se
ilustra la manera de conseguirlo.

Los DataSet tipados parten del conocimiento preciso de la estructura de una base de
datos para definir su funcionalidad. Esta es su principal diferencia con los DataSet
normales puesto que éstos son genéricos y no saben nada acerca de los datos que
albergan. Los tipados sólo sirven para albergar una información muy concreta. Ganan
en especialización y pierden en versatilidad.

Para agregar un DataSet tipado a nuestro proyecto sólo hay que presionar con el
botón secundario sobre la carpeta App_Code y en el diálogo que aparece elegir el
icono de DataSet. La extensión del archivo generado es '.xsd' porque lo que en
realidad albergan es un esquema XML que define la estructura de los datos en los que
se van a especializar.

Una vez agregado el DataSet aparece una superficie de diseño y un asistente como el
de la figura.

Figura 4.5.- Primer paso del asistente de configuración de un TableAdapter.

Este asistente nos permite configurar un objeto TableAdapter que se encargará de


trasegar datos entre el DataSet tipado que estamos creando y la base de datos. Un
TableAdapter es una clase que encapsula a un DataAdapter especializado en los datos
que vamos a procesar con la tabla del dataSet tipado que estamos definiendo. De
hecho sus métodos son, por defecto, los mismos que los de un DataAdapter normal
(Fill, Update...).

Con él dispondremos de métodos para recuperar información, crear nuevos registros,


actualizarlos y eliminarlos, sólo que los correspondientes comandos estarán creados
de manera automática o asistiéndonos en ello. Así, por defecto, se definen un par de
métodos para obtener los datos subyacentes rellenando un DataSet que se le pase
(método Fill) o devolviendo directamente un DataTable (método GetData). Además
el método Update sirve para conciliar automáticamente los cambios del un DataSet
tipado con la base de datos original.

No vamos a analizar desde el texto la definición de estos objetos adaptadores pero


puede conocerlo viendo el vídeo de esta lección.

Truco:
Podemos ver el código que se genera de manera automática para crear
el DataAdapter si hacemos doble-clic sobre él desde el examinador de
objetos de Visual Studio (CTRL+ALT+J). Esto hará que se abra el archivo
de código auto-generado por ASP.NET desde la ubicación real de
ejecución (normalmente una ruta del estilo
C:\Windows\Microsoft.NET\....). Es muy interesante echarle un vistazo a
este código para aprender el funcionamiento interno de los DataSet
tipados.

Partes de un DataSet tipado

Al igual que un DataSet normal, uno tipado consta de un conjunto de tablas y


relaciones entre ellas. En este caso sin embargo podemos acceder a las tablas y a sus
campos utilizando directamente sus nombres en lugar de recorrer la colección de
tablas, lo cual lo hace más fácil de usar.

Cada una de las tablas del DataSet lleva asociado como mínimo un TableAdapter.
Entre los dos objetos (el DataTable y el o los TableAdapter relacionados) se reparten
el trabajo. El DataTable tipado mantiene en memoria la información y el TableAdapter
actúa de puente con la tabla real en la base de datos.

Nota:
Como sabemos (y veremos en el vídeo también) las tablas de un
DataSet no tienen porqué coincidir con tablas reales de una base de
datos ya que pueden ser resultados obtenidos de una consulta compleja
que involucre a varias tablas. En estos casos es más complicada la
actualziación y se suelen usar únicamente para recuperar datos. la
alternativa habitual es tratar de replicar la estructura física de la base de
datos en la estructura en memoria del DataSet de modo que se tiene
acceso estructurado a la misma información y gracias a las relaciones y
restricciones se conserva la consistencia de los datos tambiñen estando
desconectados.

El DataSet tipado dispone de propiedades que coinciden con los nombres de los
objetos que contienen. Así, por ejemplo, si tenemos una tabla "Clientes" con un
campo "Nombre" podemos acceder a él directamente con este código:

ds.Clientes(0).Nombre

que nos daría el nombre del primer cliente de la tabla de clientes en el DataSet 'ds'.
esta propiedad nombre además ya sería un campo de tipo String que es el tipo
adecuado para la información albergada, por lo que se simplifica mucho su uso.

En un DataSet normal para obtener lo mismo tendríamos que haber escrito:

ds..Tables(0).Rows(0)("Nombre").ToString()

que obviamente es mucho menos legible.

La cosa no termina aquí ya que además se definen clases específicas para representar
los registros de las tablas. Por ejemplo si la tabla se llama 'Clientes' existirá una clase
ClientesRow que dispone de propiedades con el mismo nombre y tipo que los campos
correspondientes en la tabla de la base de datos y que hemos usado en la línea de
ejemplo. Así, para añadir un cliente podríamos escribir:

Dim cliente As New Cliente

cliente.Nombre = "Pepe"

....

ds.Clientes.AddClientesRow(cliente)

Nótese que el método Add del DataTable se ha sustituido por uno con nombre
especializado que nos ayuda a saber mejor por donde pisamos, pero su función es
idéntica.

Para rellenar una tabla de un DataSet tipado se usa su correspondiente TableAdapter


así:

Dim clientes As New ClientesDS


Dim ta As New ClientesTableAdapters.ClientesTableAdapter()
ta.Fill(clientes.Clientes)

clientes.Clientes(0).Nombre = "Pepe"

....

o también usando el método GetData para obtener la tabla directamente si sólo nos
interesa esa parte concreta del DataSet (que puede constar de varias tablas):

Dim clientes As Clientes.ClientesDataTable


Dim ta As New ClientesTableAdapters.ClientesTableAdapter()
clientes = ta.GetData()

clientes(0).Nombre = "Pepe"

....

Para un mismo DataTable tipado se pueden definir diversos TableAdapter


especializados aparte del básico que permite llenar todos los datos: para filtrar podr
diversos parámetros normalmente.

El uso de DataSet tipados es muy recomendable puesto que simplifica mucho el


trabajo puesto que podemos realizar casi todo el trabajo utilizando asistentes y
accediendo a la información de manera muy cómodo. Además es un modo muy
sencillo de separar la funcionalidad de la base de datos del resto del código. Así, si se
hace necesario en el futuro, se puede exponer esta parte de manera independiente
mediante un, por ejemplo, un servicio Web que utilice el DataSet tipado y sus
TableAdapters para acceder a los datos desde una ubicación remota.

Ver vídeo de esta lección (Dataset tipados)


Contenido
En este módulo vamos a aprender ciertas cuestiones avanzadas de ASP.NET 2.0 que
nos ayudarán a crear aplicaciones más potentes y con menos esfuerzo que con
cualquier otra herramienta de desarrollo, incluyendo versiones anteriores de .NET.

Veremos como conseguir páginas con un aspecto y funcionalidad consistentes a lo


largo de toda la aplicación Web. También conoceremos los nuevos controles de
seguridad que vienen con ASP.NET 2.0, y estudiaremos la forma de mantener
información en memoria para agilizar las aplicaciones.

· Lección 1: Páginas principales o Master Pages


o Introducción
o ¿Qué son las Master Pages?
o Definición de una Master Page
o Master Pages anidadas
o Acceso a los elementos de una Master Page
· Lección 2: Temas y Skins
o Hojas de estilo
§ Soporte de estilos en ASP.NET
o Temas y máscaras (Skins)
§ La carpeta App_Themes
§ Estructura de un archivo .skin
§ Propiedades que se pueden personaliza
o Asignación de temas
§ Asignación global de temas
§ Precedencia de propiedades
§ Deshabilitar temas en controles concretos
§ Clases de un mismo tipo de control
§ Inclusión automática de hojas de estilo
§ Rutas de imágenes
· Lección 3: Estado de las aplicaciones
o Mantenimiento de sesiones
§ Variables de sesión
§ Funcionamiento básico de las sesiones
§ Sesiones sin cookies
§ Almacenamiento de sesiones
§ Tipos de objetos almacenables
o Información común
§ Variables de aplicación
§ Concurrencia en variables de aplicación
§ Almacenamiento en caché
o Caché de salida
§ Directiva Outputcache
§ Atributos de OutputCache
§ ¿Donde se hace la caché?
§ Almacenamiento en disco
§ Sustitución post-caché
· Lección 4: Seguridad de las aplicaciones
o Autenticación y autorización de usuarios
§ Autenticación de IIS/Windows
§ Autenticación Forms en ASP.NET
o Autorización de usuarios
§ Autorización de URLs
§ Autorización declarativa
§ Autorización imperativa
o La nueva API: Membership y Roles
§ Membership
§ Roles
§ Administración de seguridad de sitios Web
o Los controles Web de seguridad
§ El control Login
§ El control LoginStatus
§ El control LoginName
§ El control LoginView
§ Los controles restantes
Páginas principales o Master Pages

Lo más habitual cuando se crea una aplicación o un sitio Web es que las páginas que
lo conforman sean todas bastante parecidas o al menos que existan varios grupos de
páginas similares que sólo varían ciertos contenidos entre ellas. Por ejemplo, puede
haber una categoría de páginas para mostrar artículos en el que todas son iguales
excepto por el contenido del propio artículo en su parte central, mientras que en otra
zona de la aplicación el diseño es completamente diferente pero sus páginas se
parecen todas entre sí.

Por ejemplo, la siguiente figura muestra capturas de dos páginas pertenecientes al


portal MSDN:
Figura 5.1.- Ejemplo de dos páginas similares en MSDN

Ambas páginas difieren únicamente en el contenido y los menús que muestran en el


lateral (los banners del lateral son rotativos), y conservan una estática y una serie de
elementos que permanecen constantes en todas las páginas del sitio.

Tradicionalmente para conseguir esta similitud entre páginas había que crearlas
individualmente o recurrir a artificios propios como por ejemplo el de utilizar archivos
de inclusión para renderizar ciertas partes de las páginas desde un mismo origen en
disco. Aún en este último caso la capacidad de modificación era limitada y
normalmente se reducía a las cabeceras y pies de las páginas y poco más. En el
primero de los casos (retocar una a una) cualquier cambio estético de un sitio
medianamente grande era poco menos que una locura de realizar.

ASP.NET 2.0 ofrece una nueva característica destinada a paliar esta tradicional
carencia y permite definir páginas cuyo aspecto y funcionalidad deriva de unas
páginas especiales comunes llamadas Páginas principales o Master Pages.

Nota:
Aunque normalmente soy partidario de utilizar los términos en castellano
siempre que los haya, en este caso haré una excepción y durante el
resto del texto emplearé el término anglosajón Master Pages (o MP) para
referirme a esta característica. El motivo es que este término es el más
aceptado y el que veremos con más frecuencia.

¿Qué son las Master Pages?

Una Master Page proporciona una forma de definir una estructura e interfaz comunes
para un grupo de páginas pertenecientes a un mismo sitio Web. Esta estructura
común se almacena en un único archivo independiente. Ello facilita mucho su
mantenimiento puesto que, para actualizar todas las páginas que lo utilizan, basta
con editar dicho archivo.

Una MP es en realidad como una página ASPX normal que contiene código, elementos
HTML y controles Web de servidor. Sin embargo posee una extensión diferente
(.master) y utilizan una directiva <% @ master %> en lugar de una directiva <% @
page %>. Por lo demás se pueden considerar prácticamente equivalentes. Esto es
importante porque significa que ya sabemos todo lo necesario para crearlas.

Una página ASPX normal puede derivar su estructura a partir de una MP simplemente
añadiendo un atributo MasterPageFile a su directiva de página, así:

que indica el archivo de página principal que se utilizará para su estructura.

Definición de una Master Page

Para agregar una Master Page a nuestro proyecto sólo hay que elegir el icono Página
Principal en el diálgo de agregar nueva referencia de cualquier carpeta del mismo:

Figura 5.2.- Plantilla de Master Page.

Al abrir una MP aparece un diseñador idéntico al de una página ASPX normal.


Podemos arrastrar sobre su superficie cualquier control así como editar su HTML de la
manera usual. También lleva un archivo de código asociado en el que se puede
responder a sus diversos eventos. La única diferencia apreciable a simple vista
respecto a una página normal es que contiene por defecto un control de tipo
ContentPlaceHolder. La sintaxis de este control es análoga a la siguiente:

Su única propiedad interesante es precisamente su identificador ya que este tipo de


control se utiliza para marcar las posiciones en las que irán los diferentes contenidos
de las páginas derivadas dentro de la plantilla de estructura que es una Master Page.
De este modo, cuando una página normal derive de una MP, sólo se podrá introducir
código dentro de las zonas definidas por estos comodines de contenido.

Cuando añadimos una nueva página ASPX a nuestro proyecto y existe al menos una
Master Page, podemos marcar una opción para que, antes de crearla, nos permita
seleccionar de qué MP derivará:

Figura 5.3.- Selección de una MP al crear una nueva página.

Esto nos evita tener que escribir el atributo MasterPageFile manualmente.

Al editar una página que deriva de una Master Page aparece el aspecto y estructura
de la página principal en el diseñador, pero sólo se pueden tocar las partes
correspondientes a los comodines de contenido.

Master Pages anidadas

Una Master Page a su vez puede derivar de otra que previamente hayamos definido,
por lo que heredará la estructura y contenidos de ésta. Al igual que en el caso de las
páginas normales sólo hace falta establecer el atributo MasterPageFile.

Esta característica se suele utilizar para definir en una de ellas la estructura general
del sitio Web. En otra de las MP se define únicamente la estructura de los contenidos
que contiene la estructura general.

La única "pega" que tiene el uso de Master Pages anidadas es que no están
soportadas por el diseñador de Visual Studio, por lo que habrá que añadir los
controles directamente a mano desde la vista de marcado de la página. Siempre
podremos diseñarlas visualmente antes de hacerlas derivar de una Master Page
anidada y así salvar esta limitación.
Acceso a los elementos de una Master Page

Es posible acceder a los controles y elementos de una página principal desde el código
de una página derivada a través de una directiva especial llamada MasterType. Sólo
hay que indicar la ruta de la Master Page a la que queremos acceder, así:

Una vez hecho esto podemos acceder a los elementos de la página de la que deriva la
actual a través de su propiedad Master.

Por ejemplo, si nuestra Master Page tiene un elemento de imagen llamado 'Logo' y
queremos variarlo en cada página mediante código sólo habría que escribir:

Master.FindControl("Logo")

Lo mejor, sin embargo, es definir propiedades en la Master Page que encapsulen el


acceso a sus elementos. El código anterior devuelve un objeto de tipo Control que
deberemos convertir en un control de imagen y asignarle la ruta al logo a utilizar. Es
más mucho más fácil, menos propenso a errores y de mejor mantenimiento futuro el
definir una propiedad 'Logo' en nuestra Master Page y acceder a ella escribiendo, por
ejemplo:

Master.Logo = "http://www.microsoft.com/logo_msdn.gif"

que nos aislará de lo que ocurra debajo para gestionar ese logo.

Ver vídeo de esta lección (Master Pages)


Temas y Skins

Gracias a las Master Pages podemos definir una estructura común para las páginas de
nuestra aplicación Web. Sin embargo aún no hemos resuelto todas las cuestiones
sobre el mantenimiento de la interfaz que habíamos planteado. Los controles que
añadamos a las zonas de contenido de nuestras páginas todavía tendrán el aspecto
predeterminado. Podemos configurar su aspecto estableciendo las propiedades de
apariencia del control (como BackColor, Font, etc...). El problema que tiene este
método es que, si deseamos cambiar por completo la apariencia de estos controles,
tendríamos que tocar una por una todas las páginas. Esta no es una opción admisible
en cuanto la aplicación dispone de más de unas pocas páginas.

Veamos las opciones que nos ofrece ASP.NET 2.0 para solventar esta otra parte de un
mismo problema.

· Lección 2: Temas y Skins


o Hojas de estilo
§ Soporte de estilos en ASP.NET
o Temas y máscaras (Skins)
§ La carpeta App_Themes
§ Estructura de un archivo .skin
§ Propiedades que se pueden personaliza
o Asignación de temas
§ Asignación global de temas
§ Precedencia de propiedades
§ Deshabilitar temas en controles concretos
§ Clases de un mismo tipo de control
§ Inclusión automática de hojas de estilo
§ Rutas de imágenes
Hojas de estilo

HTML ofrece una interesante opción para independizar el aspecto de sus elementos
del contenido de las páginas a través del uso de las hojas de estilo en cascada
(Cascading Style-Sheets o CSS). Las hojas CSS permiten definir el aspecto de
cualquier elemento HTML contenido en una página. Aunque se pueden definir dentro
de la propia página, hacerlo así les hace perder su verdadero sentido que no es otro
que el de separar la definición del aspecto. Así, es posible crear archivos con
extensión '.css' que se vinculan a las diferentes páginas de un sitio y definen el
aspecto de sus elementos.

Dentro de una hoja CSS se definen fundamentalmente dos tipos de estilos:

· Redefinición de etiquetas: indican qué aspecto deben tener todas las


etiquetas de un determinado tipo en las páginas a las que esté vinculado el
archivo. Por ejemplo:

a {
text-decoration: none;
color: #DBB94F;
}

body {
background-color: #656565;
background-image: url(Images/background.gif);
background-repeat: repeat;
margin: 0;
padding: 0;
text-align: center;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 0.7em;
color: #FFFFFF;
}

En este fragmento, las etiquetas definen el aspecto que deben tomar los enlaces y el
cuerpo de la página.

· Clases: definen aspectos que no están asociados a una etiqueta HTML concreta
sino que se pueden asignar mediante el atributo class a cualquiera de ellas. Por
ejemplo:

EstiloSimple {
text-decoration: none;
color: #DBB94F;
}

.....

<a href="http://msdn.microsoft.com" class="EstiloSimple">MSDN</a>

En este caso se define la clase llamada EstiloSimple, y cualquier etiqueta HTML a la


que se le asigne mediante su atributo class adquirirá el aspecto marcado.

Para asignar un archivo CSS a una página sólo hay que incluir en su cabecera una
linea análoga a esta:

<link href="mi_hoja.css" rel="StyleSheet" />

La gran ventaja de esta técnica es que basta con tocar el archivo CSS para que todas
las páginas que lo utilizan vean su aspecto modificado de manera acorde a los
cambios. Da igual que sea sólo una o haya miles. Al tener el aspecto centralizado en
una ubicación única el mantenimiento es muy sencillo.

Soporte de estilos en ASP.NET

Lo anterior es una característica de HTML independiente de lenguajes de


programación como ASP.NET. Por supuesto ASP.NET tiene en cuenta y soporta
perfectamente las hojas CSS.

Se puede añadir una etiqueta <LINK> a la cabecera de una página ASPX y los
elementos HTML que se generan a partir de los controles que contiene se mostrarán
siguiendo las indicaciones de la misma.
El entorno de desarrollo de Visual Studio permite definir los estilos de los controles
HTML de servidor de ASP.NET utilizando su propiedad Style, tanto en tiempo de
diseño como durante la ejecución. El diseñador de páginas ASPX incluso ofrece un
diálogo especializado para crear los estilos.

Figura 5.4.- Diálogo de configuración de estilo para un


control HTML.

En el caso de los controles Web de servidor se utilizan sus


propiedades de aspecto (que se traducen en estilos al
renderizarse en un navegador moderno), y además disponen
de una propiedad llamada CssClass para establecer
mediante código el nombre de las clase que se quiere aplicar
al control (se traduce en un atributo Class normal de HTML
en tiempo de ejecución).

También se puede aplicar un estilo concreto a ciertos


controles directamente desde el marcado de la página ASPX,
como por ejemplo:
<ASP:Calendar ... runat="server">

<TitleStyle BorderColor="blue" BorderWidth="1"


BackColor="olivedrab" Height="50px" />
</ASP:Calendar>

si bien en este caso no se usan atributos normales de CSS.


Temas y máscaras (Skins)

Si bien las hojas de estilo resuelven en gran parte el problema de independizar el


aspecto de la definición de contenidos de una página, no se adaptan por completo a
los controles ASP.NET. Además dejan fuera algunas funcionalidades, como los valores
por defecto de propiedades de controles (por ejemplo el texto inicial de un campo) o
el aspecto de controles Web complejos como calendarios o rejillas que no se pueden
expresar únicamente con un estilo CSS.

ASP.NET 2.0 introduce una interesante novedad que se complementa la perfección


con las Master Pages para solventar el problema del aspecto: los temas y máscaras
de controles Web.

La funcionalidad ofrecida por los temas de ASP.NET 2.0 se entiende fácilmente si los
asemejamos con las hojas de estilo. Los temas de ASP.NET son como hojas de estilo
que se aplican a controles Web y sus elementos tienen una sintaxis prácticamente
idéntica a la de los controles cuyo aspecto definen. Enseguida lo entenderemos.

La carpeta App_Themes

Un tema de ASP.NET 2.0 está formado por uno o varios archivos de tipo .skin junto
con las imagenes y hojas de estilo CSS que éstos utilicen, que se almacenan dentro
de una carpeta con el nombre del tema dentro de una carpeta especial. Esta carpeta
de nombre especial es App_Themes.

Podemos añadir una carpeta de temas con el menú de agregar carpeta de un


proyecto de Visual Studio:
Figura 5.5.- Nueva carpeta especial App_Theme.

Una vez creada la carpeta de temas deberemos crear un archivo .skin para comenzar
a definir con él nuestro primer tema. Para ello agregaremos un nuevo elemento. En el
diálogo que aparece seleccionaremos un archivo de máscara, como se muestra en la
figura:

Figura 5.6.- Nuevo archivo de máscara (Skin)


El primer archivo de máscara que agreguemos definirá
automáticamente una carpeta de tema con su mismo
nombre para contenerlo. Podemos definir manualmente
tantas carpetas de tema como queramos usando el menú de
agregar nuevas carpetas. Dentro de cada una de esas
carpetas puede haber tantos archivos de máscara, hojas de
estilo y gráficos como necesitemos.

Por ejemplo el gráfico del lateral muestra una carpeta


App_Theme que contiene dos temas y cada uno de ellos a
su vez contiene un archivo .skin, hojas CSS y gráficos
específicos del tema.

Estructura de un archivo .skin

Un archivo de máscara contiene definiciones de controles ASP.NET a los que se le


establecen valores de propiedades que serán aplicadas de modo automático cuando
aparezcan en una página que use el tema al que pertenece la máscara.

Por ejemplo, si un archivo .skin contiene el siguiente código:

conseguiremos que las páginas que lo apliquen dispongan de controles TextBox con el
fondo gris claro y el borde punteado de 1 pixel de ancho. Las rejillas que se utilicen
automáticamente tendrán el fondo blanco y las líneas pares azules.

Si nos fijamos, la única diferencia entre un elemento del archivo de máscara y el


marcado de un control está en que los primeros carecen del atributo de identificación
ID. Por lo demás son prácticamente idénticos.

TRUCO:
Dado que Visual Studio no proporciona asistencia para crear archivos de
máscara (es un simple editor de texto), una forma sencilla de definir sus
elementos es utilizar una página ASPX vacía para arrastrar y configurar
controles con los aspectos deseados. Luego sólo hay que copiar su
definición al archivo .skin eliminando todos los atributos ID.

Propiedades que se pueden personalizar

Aunque los archivos de máscara están pensados para facilitar la aplicación de


propiedades estéticas de los controles, lo cierto es que se puede definir casi cualquier
otra propiedad de un control. Por ejemplo, si escribimos los siguiente en el archivo
.skin de un tema:
todos los controles de tipo TextBox de nuestra aplicación mostrarán el texto "(valor)"
al cargar las página que usen este tema (nótese el atributo Text en la primera línea).

En realidad cualquier propiedad de un control web puede personalizarse mediante un


archivo de máscara a menos que su creador haya indicado explícitamente lo
contrario. Esta particularidad puede resultar muy interesante en ciertos casos.

Nota:
Los creadores de controles pueden emplear el atributo
ThemeableAttribute para indicar que una propiedad no es
personalizable mediante una máscara.
Asignación de temas

Ahora que ya conocemos la teoría acerca de los temas y sabemos crear archivos de
máscara para los temas vamos a aprender a utilizarlos en la práctica.

Para que una página se adapte automáticamente a un tema definido en la carpeta


App_Themes lo único que tenemos que hacer es asignar un atributo Theme en la
directiva de la página, así:

El segundo atributo indica el tema que se va a aplicar. El entorno de desarrollo de


Visual Studio nos ofrece de manera automática una lista de temas disponibles al
escribir este atributo por lo que es muy fácil escribirlo.

Nada más establecer el atributo la página se personaliza en tiempo de ejecución


siguiendo los dictados de los archivos .skin que haya definidos para el tema.

Dado que podemos disponer de varios temas se puede asignar uno diferente a cada
grupo de páginas de una aplicación según las necesidades.

Nota:
Lo más interesante de este atributo Theme es que se puede establecer
en cualquier momento. Es decir, podemos desarrollar una aplicación
completa sin pensar siquiera en los temas, y añadírselos sin demasiado
esfuerzo al final con sólo asignarlo. Ello permite además separar la labor
de diseño de la de desarrollo que incluso la puede hacer un equipo de
trabajo diferente.
Asignación global de temas

El uso del atributo Theme es muy adecuado cuando sólo tenemos unas pocas páginas
a las que aplicárselo. En aplicaciones grandes el hecho de tener que recorrer las
páginas una por una para asignarlo puede ser muy tedioso, con lo que perderíamos
bastante de la productividad ganada.

Para evitar esta situación ASP.NET ofrece otra forma de establecer los temas de forma
global. Consiste en añadir un nuevo nodo de tipo <pages theme=.../> al archivo de
configuración de la aplicación, el archivo web.config. La entrada sería similar a la
siguiente:

Al hacerlo todas las páginas de la aplicación utilizarán el tema indicado sin necesidad
de establecerlo manualmente.

Si se define un tema específico para una página, éste tendrá precedencia sobre el
ajuste de Web.config. De hecho si deseamos que una página no utilice tema alguno
sólo hay que establecer su atributo Theme a una cadena vacía ("").

Nota:
Dado que una aplicación Web puede disponer de varios archivos de
configuración (uno por cada carpeta) si queremos que la aplicación
emplee un tema diferente en diferentes zonas sólo tenemos que guardar
todas las páginas de cada zona en una carpeta diferente y crear archivos
web.config específicos para indicar el tema en cada una de ellas. Las
carpetas que no dispongan de su propio web.config heredarán el tema
principal del sitio.

Precedencia de propiedades

Cuando un elemento de una máscara define un atributo para un control, éste tiene
precedencia sobre el mismo atributo en caso de que se haya asignado también en la
página. Por ejemplo, si en una página tenemos un TextBox cuyo color de fondo lo
hemos establecido en amarillo, pero en el archivo .skin del tema que utiliza la página
este color indica que debe ser gris, al ejecutar la aplicaicón se verá de color gris.

Una forma alternativa de definir un tema para una página que cambia este orden de
precedencia es emplear el atributo StylesheetTheme en lugar del más común Theme
que hemos visto hasta ahora:

...
En este caso el valor establecido en la página para una propiedad tiene preferencia
sobre el valor del archivo de máscara en caso de haber coincidencia. Justo al contrario
que en el caso habitual.

Una posibilidad poco frecuente es utilizar ambos atributos (Theme y


StylesheetTheme) simultáneamente. En este caso la precedencia lo que ocurre es lo
siguiente:

1. Se aplican los atributos del tema especificado en StylesheetTheme.


2. Se aplican los atributos definidos en la página que sobreescriben a los
del paso anterior en caso de coincidencia.
3. Se aplican los atributos del tema especificado en Theme que en caso de
coincidencia tienen por tanto preferencia sobre los anteriores.

Deshabilitar temas para controles concretos

En ocasiones es útil dejar un control con su aspecto habitual de forma que no se vea
afectado por el tema asignado a la página. La forma de conseguirlo es asignando su
propiedad EnableTheming a False.

Clases de un mismo tipo de control

El hecho de declarar un elemento genérico dentro de un archivo de máscara es útil


pero no lo suficiente. Lo más normal es que no todas las etiquetas o botones (por
poner un ejemplo) tengan exactamente el mismo aspecto sino más bien que existan
varios tipos de etiquetas y botones que se empleen según la ocasión.

Al igual que en el caso de las hojas de estilo en los temas se pueden definir distintas
clases de un mismo control, cada una con unos atributos específicos. La forma de
distinguir unos de otros es a través del atributo SkinID. Considere el siguiente
ejemplo:

En este caso se han definido tres clases de controles TextBox. Los dos primeros
tienen asignado un SkinID y sólo se aplicarán a aquellos controles TextBox cuya
propiedad SkinID coincida con el SkinID de la definición. La última definición no tiene
asignado identificador alguno por lo que se considera la clase por defecto y se aplicará
a todos los controles de texto que no hayan indicado específicamente un SkinID.
Lo habitual es separar los distintos grupos de controles es varios archivos .skin dentro
de la carpeta de un tema. Se reconocerán como si estuvieran todos en el mismo
archivo pero es más ordenado gestionarlos por separado.

Inclusión automática de hojas de estilo

Dentro de la carpeta de un tema tienen cabida también hojas de estilo como ya


hemos mencionado. Cuando se copia una o más hojas de estilo enla carpeta, éstas se
añaden de manera automática a la cabecera de las páginas que utilizan el tema
mediante etiquetas <link>. La única condición para que esto funcione es que el
atributo <head> de lapágina (que representa la cabecera) tenga el atributo de
ejecución en el servidor runat="server", cosa que todas las páginas cumplen de
manera predeterminada.

Rutas de imágenes

Por supuesto en las propiedades de los elementos de una máscara se pueden utilizar
imágenes como en este ejemplo:

Debemos guardar las imágenes en alguna subcarpeta de la carpeta de recursos del


tema y utilizar rutas relativas a ésta para los atributos que las utilicen, como en el
ejemplo. ASP.NET se encarga de modificar las rutas para que apunten al lugar
correcto tras la aplicación del tema.

Ver vídeo de esta lección (Uso de temas)


Estado de las aplicaciones

En esta lección vamos a aprender la manera de mantener información en memoria


durante la vida de una aplicación. Existen diferentes métodos para mantener el
estado de una aplicación y sus utilidades en la práctica son muchas, entre otras:

· Almacenar temporalmente datos costosos de obtener para mejorar el


rendimiento.
· Compartir información entre todas las páginas de una aplicación,
distinguiéndola por cada usuario o de manera común a todos.
· Guardar detalles de un usuario entre diferentes visitas a una aplicación.
· Mejorar el rendimiento evitando que se rendericen por completo o en parte las
páginas de nuestro sitio Web.

· Lección 3: Estado de las aplicaciones


o Mantenimiento de sesiones
§ Variables de sesión
§ Funcionamiento básico de las sesiones
§ Sesiones sin cookies
§ Almacenamiento de sesiones
§ Tipos de objetos almacenables
o Información común
§ Variables de aplicación
§ Concurrencia en variables de aplicación
§ Almacenamiento en caché
o Caché de salida
§ Directiva Outputcache
§ Atributos de OutputCache
§ ¿Donde se hace la caché?
§ Almacenamiento en disco
§ Sustitución post-caché
Mantenimiento de sesiones

HTTP es un protocolo sin estado y como tal no existe forma a priori de distinguir entre
las diferentes peticiones realizadas a un servidor Web. Sin embargo las aplicaciones
Web necesitan mantener de algún modo un estado que les permita agrupar de
manera lógica las llamadas. Sin ello no habría forma, por ejemplo, de distinguir dos
llamadas a una misma página de dos usuarios diferentes. Para conseguir este artificio
sobre un protocolo sin estado ASP.NET proporciona las sesiones.

Nota:
Cabría pensar que una buena forma de distinguir unas llamadas de otras
en una aplicación Web es a través de la dirección IP del cliente. Nada
más lejos de la realidad. Dos usuarios diferentes que accedan detrás de
un mismo router (por ejemplo, dos personas en una misma oficina)
tienen la misma IP exactamente. Además, desde el punto de vista de la
seguridad es muy mala idea usar invariantes de este estilo pues son
fáciles de falsear.

Variables de sesión

ASP.NET ofrece una colección llamada Session en el objeto Page que nos permite
almacenar parejas de claves y valores que están disponibles desde todas las páginas
de una aplicación y se distinguen automáticamente para cada cliente que las utilice.

Esto implica que podremos almacenar valores de cualquier tipo a los que podremos
cuando sea necesario mientras el usaurio que solicite las páginas sea el mismo. Por
convención a las parejas de claves y valores almacenados en el objeto Session se les
denomina variables de sesión.
Por ejemplo, si quiero contar cuantas veces ha pasado un usuario por una página
'a.aspx' puedo escribir en su evento Load lo siguiente:

Session("Cuenta") += 1;

Esto haría que el valor asociado con la clave "Cuenta" se incrementara en uno cada
vez que se cargue la página. Es decir, en palabras llanas, incrementa en la unidad el
valor de la variable de sesión "Cuenta".

Si creamos una segunda página 'b.aspx' en la que mostramos el valor de la variables


de sesión mediante:

Response.Write(Session("Cuenta"))

veremos que la variable se incrementa en cada ocasión como esperábamos, a pesar


de que, en principio, cada petición HTTP es independiente de las demás. Si abrimos
un nuevo navegador veremos que las páginas en éste generan una cuenta
independiente.

Funcionamiento básico de las sesiones

Si cada llamada HTTP es independiente de las demás, ¿cómo es posible el


comportamiento anterior?. Por defecto ASP.NET mantiene la ilusión de las sesiones
mediante el uso de cookies de sesión. Este es un sistema análogo (aunque
diferente) al que ya ofrecía ASP 3.0 clásico para el mismo propósito.

Una cookie de sesión no es más que una cabecera HTTP con un identificador único
que el servidor envía al cliente en la primera petición que éste hace y que el
navegador se encarga de reenviar automáticamente en las sucesivas peticiones. Así,
basta con comprobar esta cabecera en el servidor en cada petición para averiguar qué
usuario es el que la realiza. Así de simple.

Este es el aspecto de una cookie de sesión de ASP.NET:

ASP.NET_SessionId=43s3ir55u0vvlj55smesaj45

Este fragmento es el que se envía en la cabecera de las peticiones, siendo el valor


después del igual el identificador único utilizado para esa sesión. Estas cookies de
sesión no se almacenan jamás a disco y sólo existen mientras el navegador está
abierto. Si cerramos el navegador no podremos recuperar la anterior sesión.

Sesiones sin cookies

El sistema de cookies de sesión descrito funciona muy bien en la mayor parte de los
casos pero tiene algunos inconvenientes, el principal de los cuales es que si el usuario
ha deshabilitado las cookies de sesión todo dejará de funcionar.

Para evitar este problema ASP.NET dispone de un método alternativo para


mantenimiento de sesión que no utiliza cookies ni cabeceras, sino que introduce una
ruta virtual aleatoria en todas las peticiones de modo que todas son distintas. Si la
llamada a una página se efectuaba en esta URL:

http://www.miservidor.com/miapp/pagina.aspx

al habilitar las cookies sin sesión la llamada se convierte en algo similar a:

http://www.miservidor.com/miapp/(S(1rhk5t45tlxy3e45swjibe55))/pagina
.aspx

Como vemos se introduce una última carpeta virtual con un identificador aleatorio y
cifrado (del estilo del que iba en las cabeceras HTTP). Esta carpeta obviamente no
existe pero uno de los gestores de la llamada a las páginas de ASP.NET es capaz de
distinguir que se trata de un identificador de sesión y actuar de manera transparente
para mantenerla y enviar las peticiones a las páginas correctas.

Este método es menos propenso a fallos que el anterior pero tiene el inconveniente de
que se generan URLs mucho más largas y menos atractivas. Las sesiones sin cookies
no existían en ASP 3.0.

Habilitar este tipo de sesiones es muy sencillo. Basta con abrir el archivo de
configuración de la aplicación (web.config) y añadir el siguiente ajuste dentro del
nodo <system.web>:

<sessionState cookieless="UseUri" />

Almacenamiento de sesiones

La información referente a los datos de sesión se almacena por defecto en la memoria


del servidor. Ello suscita un problema importante cuando la aplicación debe ser muy
escalable y tener capacidad para gestionar miles de peticiones simultáneas. En estos
casos es frecuente ubicarla en varios servidores balanceados. A medida que llegan
nuevas petición a la granja de servidores, éstas se reparten por las distintas
máquinas que la constituyen, bien aleatoriamente, bien siguiendo algún sistema de
cuotas o asignándolas por orden (round-robin).

Dado que cada máquina mantiene su propia copia de las sesiones, a menos que cada
usuario se redirija siempre al mismo servidor, no habrá forma de mantener una
sesión coherente, pues no existe una ubicación compartida para almacenar los datos
de las sesiones.

Nota:
Existe forma de configurar las granjas de servidores con afinidad de
manera que cada usuario siempre se redirija al mismo servidor. Aún así
existen otros problemas con la sincronización de datos compartidos en
toda la aplciación y además el sistema reduce la escalabilidad.

ASP.NET 2.0 permite configurar el almacenamiento de sesiones para utilizar cualquier


proveedor de almacenamiento de sesión diferente al que usa por defecto (y que
guarda todo en la memoria RAM local). Con la plataforma .NET se entregan dos
proveedores de almacenamiento de sesión adicionales que permiten almacenar los
datos en un servidor ASP.NET central o en una base de datos SQL Server
respectivamente.

Se establece el modo de almacenamiento a través del archivo web.config:

Figura 5.7.- Configuración del modo de sesión

La configuración detallada de estas opciones avanzadas se sale del ámbito de este


curso, pero conviene saber que existe esta posibilidad y entenderla bien para poder
emplearla en casos de necesidad.

Nota:
Gracias al modelo de extensibilidad mediante proveedores que ofrece
ASP.NET 2.0 es posible escribir nuestros propios proveedores para
almacenar la sesión del modo que más nos convenga, si bien no es lo
habitual. Esta característica incluso nos permite definir un formato
propio para los identificadores de sesión aunque hacerlo no es en
general una buena idea.

Duración de las sesiones

Una sesión se mantiene en memoria en el servidor mientras no transcurra más de un


determinado tiempo sin recibir actividad por parte de un usuario. Por defecto Internet
Information Server mantiene vivas las sesiones durante 20 minutos, pero se puede
cambiar este ajuste desde las propiedades avanzadas de cualquier servidor o
directorio virtual.
Figura 5.8.- Establecimiento del tiempo de sesión en IIS

También se puede establecer desde la configuración de la aplicación (web.config)


usando el atributo timeOut del mismo nodo que hemos estado usando hasta ahora:

<sessionState timeout="10"/>

También se puede asignar mediante cádigo usando la propiedad Timeout del objeto
Session.

Session.Timeout = 10

Si transcurre el número de minutos marcados sin recibir nuevas peticiones de un


mismo usuario los datos de la sesión se eliminan automáticamente. Hay que tener
cuidado con esto porque nunca sabemos cuando puede ocurrir, por lo que jamás
debemos dar por hecho que una determinada variable de sesión existe.

Podemos forzar el abandono de una sesión desde el código llamado al método


Abandon del objeto Session:

Session.Abandon()

Tipos de objetos almacenables

En ASP 3.0 estaba limitado el tipo de objetos que podíamos almacenar en variables de
sesión. Por ejemplo, no se debían almacenar objetos RecordSet de datos o perdíamos
escalabilidad, y había otros tipos de objetos que generaban un error al intentar
almacenarlos. El uso directo de ciertos objetos como las matrices desde variables de
sesión penalizaba el rendimiento.
En el caso de ASP.NET no existen estas limitaciones y podemos almacenar cualqueir
objeto .NET sin problema cuando usamos el modelo en memoria. En el caso de los
modelos de almacenamiento centralizado los objetos a almacenar deben ser
serializables.

Ver vídeo de esta lección (Sesiones de usuario)


Información común

Variables de aplicación

Las variables de sesión permiten guardar información que concierne a un usuario


concreto y que no es accesible para los demás. Las variables de aplicación por el
contrario están pensadas para almacenar en memoria datos que están accesibles para
todos los usuarios al mismo tiempo.

Las variables de aplicación se gestionan exactamente de la misma manera que las de


sesión pero usando la propiedad Application de la página en lugar de Session.

Por ejemplo, implementar un contador de llamadas a páginas de una aplicación es


muy sencillo si utilizamos el objeto Application, ya que sólo tendríamos que escribir en
la carga de cada página:

Application("Contador") += 1

Esta variable estará accesible desde cualquier otra página de cualquier sesión de
usuario.

Concurrencia en variables de aplicación

El código anterior presenta un problema poco evidente que tiene que ver con la
concurrencia de uso de las variables de aplicación. En una aplicación Web pueden
estar ejecutándose al mismo tiempo varias páginas. De hecho es bastante probable
que existan muchas coincidencias a la hora de escribir en la variable anterior si
metemos ese código en todas las páginas de una aplicación y hay bastantes usuarios.
Ello provocaría inconsistencias en los valores y, a la larga, que el contenido de la
variable no contuviera el verdadero número de peticiones realizadas.

Para evitar estos problemas de concurrencia se definen dos métodos del objeto
HttpApplication que permiten el acceso en exclusiva a las variables de sesión por
parte de una página. La forma correcta del código anterior sería:

Application.Lock()
Application("Contador") += 1
Application.UnLock()

De este modo se bloquea el acceso de las demás páginas a las variables de aplicación
mientras no se llame al método de desbloqueo y se evitan así los problemas de
concurrencia. Hay que tener cuidado sin embargo con el uso que se hace de esta
característica pues puede afectar a la escalabilidad por bloqueos en las otras páginas.

Nota:
En ASP 3.0 esta era la única manera de compartir memoria en el ámbito
de aplicación, pero no está exenta de problemas. En ASP.NET se soporta
un método mucho mejor a través de objetos de caché que ahora
veremos y se desaconseja el uso de las variables de aplicación en la
medida de lo posible.

Almacenamiento en caché

ASP.NET ofrece un potente sistema de almacenamiento en caché que guarda los


objetos en memoria y es privado para cada aplicación. De forma análoga a los dos
casos anteriores, existe un objeto Cache que proporciona una interfaz indexada que
permite establecer y recuperar pares de clave-valor. Así podemos escribir:

Cache("MiClave") = miValor

....

Response.Write( Cache("MiClave") )

La caché se usa habitualmente para almacenar datos que son costosos de obtener y
que pueden ser aprovechados por todos los usuarios, como por ejemplo un DataSet
resultante de una compleja consulta a una base de datos.

La ventaja fundamental de la caché de ASP.NET frente a las variables de aplicación es


que es muy fácil conseguir la caducidad automática de sus elementos.

Por ejemplo, imaginemos que llemos en la variable 'datos' el contenido de un archivo


XML y queremos guardarlo en memoria para uso común y evitar así el contínuo
acceso a disco para leer el contenido. Si usamos una variable de aplicación ¿cómo nos
enteraremos de que el contenido del archivo ha cambiado y que, por lo tanto
debemos recargar los datos?
No hay forma de hacerlo. Podemos establecer un mecanismo propio que cada cierto
tiempo recargue la variable, pero la solución dista mucho de ser óptima.

Conseguirlo con la caché de ASP.NET es muy sencillo, basta con escribir la siguiente
línea:

Cache.Insert("MisDatos", datos,

New CacheDependency(Server.MapPath("misdatos.xml")))

En lugar de usar la interfaz de indexación directa de elementos del objeto Cache


usamos su método Insert que en una de sus versiones sobrecargadas permite agregar
una dependencia a un archivo. A partir de ese momento si el archivo 'misdatos.xml'
cambia, el elemento desaparece de la caché y lo volveremos a cargar la próxima vez
que se consulte.

También se puede establecer la fecha de caducidad de un elemento de la caché con


otra versión sobrecargada del método Insert:

Cache.Insert("MisDatos", datos, Nothing, DateTime.Now.AddHours(2),


TimeSpan.Zero)

Esta línea agrega el elemento sin dependencia en objeto alguno y con una fecha de
caducidad para dentro de dos horas.

Cache.Insert("MisDatos", datos, Nothing, DateTime.MaxValue,


TimeSpan.FromMinutes(30))

En este caso la caducidad es relativa e indica que el valor caducará en cuanto nadie
haga uso de el durante 30 minutos.

Se pueden agregar dependencias a archivos en disco y a claves en el registro y


también combinaciones de estas dependencias. La clase CacheDependency se
puede heredar para crear nuestros propios criterios de caducidad de caché. Incluso es
posible especificar un delegado a una función a la que queremos llamar
automáticamente cuando el elemento expire y así recargarlo de inmediato.

Si estamos trabajando con SQL Server 2005 podemos aprovechar la existencia de su


sistema de encolado de mensajes (Message Broker) para crear dependencias de
caché en consultas, de forma que cuando se modifiquen los datos de una base de
datos se reciba una notificación automática.

Este mecanismo es muy potente y conviene aprender a usarlo bien.


Caché de salida

La caché de salida de ASP.NET es un mecanismo que nos permite decidir qué partes
de una página deben generarse en cada solicitud y cuáles pueden ser aprovechadas
para posteriores peticiones. Su uso es extremadamente sencillo y pueden aumentar
de forma espectacular el rendimiento de cualquier aplicación.

Si se defineuna directiva de caché para una página o un control de usuario, ASP.NET


no tiene que generarlos ni crear instancias de objetos, etc... tras la primera petición,
por lo que se obtiene un rendimiento muy elevado ya que sólo debe leer el resultado
de memoria y devolverlo.

Como se pueden aplicar las directivas de caché de salida a los controles de usuario, si
necesitamos hace caché sólo de determinadas partes de una página y no de la página
entera, podemos convertir éstas en controles de usuario y conseguir el efecto
buscado.

Directiva OutputCache

Para poder hacer caché del contenido de una página o un control de usuario hay que
indicar a ASP.NET de qué depende la caducidad de dicho contenido. Para ello se utiliza
la directiva OutpuCache.

La caducidad puede depender del tiempo, de algún parámetro, de una cabecera, etc...

Por ejemplo, la siguiente directiva:


indica que el contenido completo de la página o control deberá mantenerse en caché
durante 60 segundos y que la página no varía en función de parámetro GET o POST
alguno que se le pase. Todas las peticiones a esta página recibidas mientras está en
caché se obtienen directamente de ahí, sin procesar la página. Tras pasar los 60
segundos la página se elimina de la caché y la siguiente petición se procesa de modo
normal volviendo a guardar el resultado en la caché durante otros 60 segundos.

En páginas que, por ejemplo, visualizan muchos resultados obtenidos de una base de
datos y que varían con poca frecuencia puede aumentar muchísimo el rendimiento.

Atributos de OutputCache

Lo más normal en muchas páginas es que el contenido varíe en función de diferentes


características, no que permanezca invariable sin más durante un determinado
tiempo. Para amoldarse a diferentes situaciones la directiva Outputcache dispone de
varios atributos que permiten elegir el criterio de caché de los contenidos y que son
los siguientes:

· VaryByParam: permite especificar que se haga una caché por cada valor
diferente que tome un parámetro que se le pasa a la página y que se indica en
este atributo. Por ejemplo, si tenemos una página a la que se le pasa un
parámetro "pais" y que devuelve un listado de clientes en dicho país, al indicar
VarByparam="Pais" conseguiremos una caché de resultados diferente para cada
valor del parámetro. Cuando se le pasa un nuevo valor se ejecuta la página y se
almacena el resultado para las restantes veces que se solicite con el mismo
parámetro (durante el tiempo especificado). Si hay más de un parámetro se
pueden especificar separándolos por punto y coma.
· VaryByHeader: guarda una caché para cada valor de la cabecera HTTP
indicada. Por ejemplo si se indica varByHeader="User-Agent" conseguiremos
una versión en caché de la página por cada tipo de navegador utilizado para
acceder a la página. Se pueden especificar varias cabeceras separándolas por
punto y coma.
· VaryByControl: en el caso de controles de usuario su caché se fragmentará
por cada valor de las propiedades del control indicadas en el atributo. No es
válido en directivas de página, sólo de controles de usuario y se debe
especificar siempre el VarByparam aunque sea con valor "None".
· VaryByCustom: permite implementar nuestro propio criterio de caché. Se
debe implementar la función global GetVaryByCustomString (dentro de
Global.asax) a la cual se le pasa el valor del atributo para que pueda decidir
cómo actuar. No vamos a entrar en más detalles en este curso.

¿Dónde se hace la caché?

En muchos casos (VarByParam y VarByHeader) es posible especificar en dónde


queremos que se realice la caché. Si indicamos que debe hacerse el cliente, las
peticiones ni siquiera se reciben en el servidor, por lo que todavía se gana más
rendimiento al evitar la petición. La ubicación de la caché se indica mediante el
atributo Location de la directiva OutputCache y sus valores posibles son:

· Server: valor por defecto. La caché se realiza en el servidor por ASP.NET.


· Client: la caché la hace el propio navegador. Las peticiones ni siquiera se
envían al servidor.
· Downstream: se intenta que la caché se haga en el servidor Proxy que realiza
las peticiones (de haberlo).
· None: no se realiza caché alguna.

Almacenamiento en disco

En ASP.NET 2.0 las versiones en caché de las páginas y controles se almacenan de


manera predeterminada también en disco, no sólo en
memoria. Esto sirve para disponer de una caché aunque
haya poca memoria disponible y hace que ésta sobreviva
incluso al reinicio del servidor. Se puede deshabilitar este almacenamiento a disco
estableciendo el atributo DiskCacheable a False en la directiva de caché de la página
o control.

Sustitución post-caché

Esta es otra característica de ASP.NET 2.0 que no estaba disponible en versiones


anteriores de la plataforma .NET.

En ASP.NET 1.x si hacíamos caché de una página en la que todo permanecía


invariable excepto una pequeña región que mostraba, por ejemplo, la hora,
estábamos obligados a dividir la página en controles de usuario para evitar la caché
de esa pequeña parte. ASP.NET 2.0 añade un nuevo tipo de control llamado
Substitution que permite hacer excepciones a la hora de almacenar en caché una
zona de una página. Este control está ubicado normalmente en la última posición de
la lista de controles estándar en el cuadro de herramientas.

El control dispone de una propiedad MethodName que permite especificar el nombre


de un método compartido (estático) que devuelve una cadena con el texto o HTML a
mostrar en su lugar cuando se genere la página a partir de la caché. Este método
toma como parámetro un objeto HttpContext que nos permite acceder al contexto
de la llamada.

Por ejemplo, definimos la siguiente función:

y añadimos un control Substitution como este:

Si hacemos caché de la página se mantendrá todo el contenido excepto el de este


control que mostrará en cada caso el valor devuelto por la función compartida
DameLahora.
Ver vídeo 1 de esta lección (Uso de la caché de salida)
Ver vídeo 2 de esta lección (Controles de usuario y post-caché)
Seguridad de las aplicaciones

Prácticamente cualquier aplicación que creemos va a necesitar seguridad en el acceso


a sus diversas secciones. Lo habitual es tener apartados a los que sólo pueden
acceder miembros registrados o una zona de administración restringida a los gestores
del sistema.

ASP.NET 2.0 ofrece multitud de novedades en este aspecto que nos van a simplificar
mucho el trabajo.

Esta lección habla sobre la seguridad de aplicaciones desde el punto de vista de la


autenticación y la autorización de usuarios. Aprenderemos los fundamentos de gestión
de acceso usando las nuevas características de ASP.NET 2.0.

· Lección 4: Seguridad de las aplicaciones


o Autenticación y autorización de usuarios
§ Autenticación de IIS/Windows
§ Autenticación Forms en ASP.NET
o Autorización de usuarios
§ Autorización de URLs
§ Autorización declarativa
§ Autorización imperativa
o La nueva API: Membership y Roles
§ Membership
§ Roles
§ Administración de seguridad de sitios Web
o Los controles Web de seguridad
§ El control Login
§ El control LoginStatus
§ El control LoginName
§ El control LoginView
§ Los controles restantes
Autenticación y autorización de usuarios

Disponemos de diversas barreras a la hora de controlar el acceso a los recursos en


nuestras aplicaciones Web. La primera de ellas la constituye el propio servidor de
aplicaciones que en entornos de producción es normalmente Internet Information
Server (IIS).

Autenticación IIS/Windows

IIS permite definir mediante su configuración tanto el modo en el que se autentica a


los usuarios como los permisos de acceso genéricos que se ofrecen a los recursos que
sirve. La siguiente figura muestra el aspecto de las ventana que permite ajustar los
métodos de autenticación de usuarios que solicitará IIS a los usuarios:
Figura 5.9.- Configuración de seguridad de IIS

La ventana de la figura permite configurar si se admiten o no usuarios anónimos, y en


este último caso cómo se autenticarán éstos contra el sistema. Se puede restringir
también el acceso a un recurso (carpeta, directorio virtual o archivo) en función de
direcciones IP y dominios.

No vamos a profundizar en este aspecto pues se sale del ámbito del curso, pero baste
decir que, para sitios Web grandes accesibles desde Internet la autenticación de IIS
(que se basa en usuarios de Windows) no es la más habitual porque se necesitaría
una licencia de acceso or cada usuario con nombre. ASP.NET ofrece un gran soporte
para trabajar con los usuarios una vez autenticados con el sistema,
fundamentalmente para averiguar si pertenecen a un determinado perfil de usuario,
incluso en entornos de seguridad centralizada con Directorio Activo.

Autenticación Forms de ASP.NET

Desde la primera versión de ASP.NET existe un método de autenticación conocido


genéricamente como "Forms" que permite a los programadores crear un sistema
propio de autenticación de usuarios. En lugar de utilizar usuarios del sistema (IIS
debe permitir el acceso anónimo a los recursos), en este caso es el programador el
que debe establecer su propio mecanismo de autenticación, por ejemplo lanzando
consultas contra una base de datos (más adelante volveremos sobre este asunto).

El funcionamiento es el siguiente:
Figura 5.10.- Lógica de autenticación personalizada en ASP.NET

Cuando un usuario solicita una página protegida ASP.NET comprueba si éste se


encuentra ya autenticado o no. Lo sabe porque cuando un usuario se ha autenticado
previamente esta información se almacena en una cookie encriptada. Si está
autenticado se le permite el acceso al recurso siendo responsabilidad del programador
definir cualquier comprobación de autorización posterior. En caso de no estar
autenticado se le dirige de forma automática a una página que le solicita las
credenciales de acceso.

Nota:
El uso de cookies era obligatorio en ASP.NET 1.x para utilizar la
autenticación Forms. ASP.NET 2.0 soporta la autenticación sin cookies de
un modo muy similar al que hemos visto para sesiones sin cookies:
introduciendo una ruta virtual codificada en todas las peticiones. Sólo
hay que utilizar el ajuste <forms cookieless="UseUri"> en el archivo de
configuración.

Activar la autenticación Forms en una aplicación es muy sencillo, y de hecho suele


estar activada por defecto cuando programamos con Visual Studio 2005. Basta con
editar el archivo web.config y añadir la siguiente línea en la configuración de
<system.web>:

<authentication mode="Forms"/>

Por defecto la página a la que se redirige a los usuarios anónimos para que se
autentiquen se llama "login.aspx" pero se puede especificar cualquier otra usando el
atributo loginUrl, por ejemplo:
<authentication mode="Forms">

<forms loginUrl="entrada.aspx"/>
</authentication>

A partir de ahora todos los usuarios anónimos se redirigirán a "entrada.aspx" para


solicitarle las credenciales de acceso.
Autorización de usuarios

Una vez que un usuario está autenticado, el proceso que regula a qué recursos tendrá
acceso se denomina autorización.

Para realizar el control de acceso no sólo llega con indicar qué método de
autenticación se usará (en este caso Forms) . Además hay que especificar a qué
recursos se debe controlar el acceso y de qué manera. En ASP.NET existen diversos
modos de definir la autorización de acceso.

Autorización de URLs

Consiste en la autorización de acceso a determinados recursos de la aplicación Web


(páginas ASPX, carpetas, archivos...) refriéndose a ellos a través de su ruta relativa
en el archivo de configuración de la aplicación.

Se especifica el nivel de acceso requerido mediante una o varias entradas en el nodo


raíz de web.config (nodo <configuration>) que tienen un aspecto análogo al
siguiente:

<location path=“facturas.aspx”>

<system.web>

<authorization>

<allow users=“jose, hector, pablo”>

<allow roles=“clientes, administradores”>


<deny users="?"/>

</authorization>

</system.web>

</location>

Como se puede observar, la sintaxis es muy sencilla y bastante evidente.

- El nodo location especifica la ruta relativa al directorio actual cuyo acceso se desea
controlar.

- Los nodos allow y deny se emplean para permitir o denegar el acceso a


determinados usuarios o roles de usuario. Se pueden especificar nombres concretos
para éstos o utilizar comodines especiales, como en el caso del nodo deny del
ejemplo:

· ? : representa a los usuarios anónimos. En el ejemplo anterior se deniega el


acceso a todos los usuarios que no se hayan autenticado previamente.
· * : indica que el criterio se aplica a todos los usuarios, tanto anónimos como
autenticados, independientemente de su perfil de usuario.

Pueden incluirse tantos nodos location como sea necesario dentro del archivo de
configuración.

Lo habitual es incluir junto con el nodo de autenticación un nodo de autorización


explícito que deniegue el acceso a todos los recursos a los usuarios anónimos, así:

<authentication mode="Forms"/>

<authorization>

<deny users="?"/>

</authorization>

Dado que puede existir un archivo web.config por cada carpeta de la aplicación se
pueden establecer distintos criterios de acceso a cada una, agrupando en ellas los
archivos con el mismo nivel de acceso.

Autorización declarativa

El sistema de autorización por URL es muy potente pues permite definir los permisos
de acceso de los usuarios a cualquier archivo controlado por ASP.NET y además
hacerlo sin tocar el código. Mediante la autorización declarativa de ASP.NET es
posible obtener un control mucho más granular, llegando incluso a controlar accesos a
clases o métodos concretos dentro de éstas. Es decir, regula la autorización dentro
del código de la aplicación, definiendo a qué partes de éste se debe conceder el
acceso y cómo.
Mediante simples atributos se controla el acceso a cada método o propiedad de una
clase o a la clase misma:

<PrincipalPermission(SecurityAction.Demand, Authenticated:=true)> _
Class CuentaBancaria
<PrincipalPermission(SecurityAction.Demand, Role:=“Cajero")> _
Public Function Consultar() As Integer

...

End Function

<PrincipalPermission(SecurityAction.Demand,
Role:="Interventor")> _
Public Sub ModificarSaldo()
...

End Sub
End Class

En este ejemplo se dispone de una clase llamada CuentaBancaria que representa un


componente de negocio de nuestra aplicación (ubicado, por ejemplo, en la carpeta
App_Code. Con los atributos especificados se está indicando lo siguiente:

· Para poder utilizar cualquier miembro de la clase es necesario que el usuario


actual se haya autenticado.
· Sólo los usuarios que pertenezcan al perfil "Cajero" tienen acceso a consultar
las cuentas.
· Sólo los usuarios que pertenezcan al rol "Interventor" pueden actualizar el saldo
de las cuentas.

Esta característica requiere agregar una referencia a System.Security.Permissions


en la sección Imports de la clase.

Autorización imperativa

Este tipo de autorización es la que nos permite entremezclar explícitamente en


nuestro código comprobaciones acerca de la identidad de los usuarios, tomando
decisiones en función de éstas. Ofrece un control todavía más granular, ya que puede
estar en cualquier lugar del código, dentro de un miembro de una clase.

Se fundamenta en el uso de la propiedad User de la clase Page . Esta propiedad


devuelve una referencia a un objeto homónimo, es decir, de tipo User. Su método
IsInRole permite averiguar la pertenencia del usuario actual a un determinado perfil
o rol y tomar decisiones en función del resultado.

Por ejemplo en la clase CuentaBancaria del ejemplo anterior permitiría limitar el


importe de una transferencia en función del tipo de usuario que la realice, así:
Public Sub Transferencia(cantidad As Decimal)

If cantidad > 1000 Then


If User.IsInRole(“interventor”) Then
'Transferir

End if

End If
End Sub

En este fragmento vemos como se usa el mencionado método para comprobar si el


usuario es un interventor antes de permitir cualquier transferencia de más de 1.000
euros.

La principal ventaja de esta forma de autorización es el fino control que concede. Sin
embargo conviene no abusar de su uso en la medida de lo posible ya que cualquier
cambio implica tocar el código convirtiéndolo enseguida en muy dificil de mantener.
La nuevas API: Membership y Roles

Todo lo mencionado hasta ahora esmuy interesante pero todavía carece de lo más
importante: ¿cómo autenticamos a los usuarios? ¿Cómo sabemos a qué roles
pertenecen?

Hasta ahora estas preguntas las debía contestar el propio programador ya que en
ASP.NET 1.x era su responsabilidad definir los métodos de autenticación de sus
aplicaciones. Ello implicaba normalmente escribir código de consulta contra bases de
datos que validase las credenciales de los usuarios y obtuviese los roles de los
mismos. Luego se creaban objetos de seguridad asignándole estos valores y
asociándolos al contexto de la aplicación.

ASP.NET 2.0 nos libera por fin de todo ello y ofrece de serie una completa API de
gestión de usuarios que nos evita tener que reinventar la rueda en cada aplicación.

Membership

Se trata de una nueva API que proporciona una sencilla interfaz de programación para
almacenar y recuperar información sobre los usuarios de nuestras aplicaciones. Lo
más interesante de todo es que, al igual muchas otras características nuevas de
ASP.NET 2.0, está basado en un patrón de diseño de proveedores, lo que permite
cambiar los métodos de trabajo y almacenamiento sin tocar el código de la aplicación.
La figura siguiente ilustra mejor este concepto:
Figura 5.11.- Patrón de Proveedores para gestión de usuarios

Por defecto esta API utiliza SQL Server 2005 para almacenar toda la información de
seguridad de una aplicación y va a ser lo que utilicemos durante toda esta lección.
Pero dada su arquitectura basada en proveedores podemos cambiar el modo de
gestión con sólo un ajuste en el archivo de configuración. incluso, como se ve aprecia
en la figura, es posible definir métodos de gestión de usuarios propios sin modificar el
código de nuestras aplicaciones. Esto nos permite reutilizar infraestructuras de
seguridad preexistentes que hubiésemos creado para ASP.NET 1.x sin perder el
trabajo.

Membership consta de una clase con este nombre que contiene ciertos métodos
compartidos para poder crear, eliminar y validar usuarios entre otras cosas. Así, por
ejemplo, para crear un usuario escribiríamos:

Membership.CreateUser("usuario", "clave")

y para validarlo sólo hay que usar su método ValidateUser:

Membership.ValidateUser("usuario", "clave")

que devuelve verdadero o falso en función de si las credenciales son o no válidas.


Existe también una clase MembershipUser que representa las propiedades de los
usuarios de la aplicación.

Roles

Esta API complementa a la anterior para permitir la gestión de los roles de un usuario
y, al igual que ésta, está basada en el mismo patrón de proveedores. No es necesario
utilizar el mismo proveedor para los roles que para los usuarios. Por ejemplo, se
pueden autenticar usuarios contra una base de datos SQL Server y obtener los roles
desde el Directorio Activo o las cuentas locales.

Al igual que en el caso anterior los métodos de la clase Roles son todos estáticos y
podemos usarlos sin instanciar nuevos objetos. Por ejemplo:

Roles.AdduserToRole("usuario", "administradores")

Este fragmento agrega un usuario al rol de administradores. Para obtener los roles a
los que pertenece un usuario podemos escribir:

Roles.getRolesforuser("usuario")

que devuelve una matriz de cadenas de texto. Si dejamos el parámetro usuario en


blanco nos devuelve los roles del usuario actualmente autenticado.

La API de roles ofrece también una característica de caché automática de credenciales


que, mediante el uso de una cookie HTTP encriptada, almacena los roles del usuario
durante la duración de las sesiones para no tener que consultar el origen de datos
cada vez que comprobemos la pertenencia a roles. Esto aumenta el rendimiento en
muchas aplicaciones.

Administración de seguridad de sitios Web

Con todo lo visto hasta ahora crear la seguridad de un sitio web es muy sencillo. Sólo
hay que establecer la autenticación Forms de ASP.NET y utilizar Membership y Roles
para gestionar a los usuarios.

Aún así todavía queda trabajo. Hay que crear formularios de autenticación y, antes de
nada, hay que gestionar los usuarios y los roles de la aplicación.

ASP.NET 2.0 tampoco nos va a dejar solos con esto. Ofrece una completa herramienta
de administración ya creada de serie y, como veremos enseguida, ni siquiera nos
obliga a crear interfaces comunes de autenticación y gestión de sesiones.

Antes de nada hay que administrar usuarios y roles. Para ello sólo tenemos que
presionar sobre el menú Sitio web·Configuración de ASP.NET y se abrirá una web que
nos permite configurar la seguridad de la aplicación.
Figura 5.12.- Sitio de configuración de la seguridad de ASP.NET 2.0

Con esta herramienta podemos dar de alta, modificar o


borrar usuarios y roles, además de realizar algunos otros
ajustes. Al utilizarla se encarga de modificar el archivo
web.config de nuestra aplicación si es necesario realizar
algún ajuste.

Además crea un archivo de base de datos llamado


'ASPNETDB.mdf' en la carpeta App_data. Este archivo es
una base de datos de SQL Server 2005 que se usa para
almacenar toda la información de seguridad de los
usuarios. Se hace uso de la nueva característica de
enlazado dinámico de bases de datos de SQL Server para
poder distribuir esta base de datos con la aplicación y
enlazarla al motor de base de datos sólo cuando se vaya a
utilizar.

Nota:
Si hace uso de esta herramienta para crear usuarios en la base de datos
tenga en cuenta que por defecto hay un ajuste de seguridad que implica
el uso de claves complejas. Cualquier clave que introduzca le devolverá
el enigmático mensaje "Introduzca otra contraseña" a menos que use
claves de al menos 7 caracteres de longitud e incluya en ellas un espacio
o un caracter no alfanumético como un asterisco o un guión bajo. Este
ajuste se puede cambiar mediante configuración.

El administrador de sitios Web utiliza las clases MemberShip y Roles para realizar su
trabajo. Disponemos de su código fuente completo en la carpeta
C:\WINDOWS\Microsoft.NET\Framework\v2.0.xxxxx\ASP.NETWebAdminFiles\ y es
interesante examinarlo.

Ver vídeo de esta lección (Uso de la administración de sitios y de la API


Membership)
Los controles Web de seguridad

Como hemos podido comprobar en los epígrafes anteriores, la gestión de la seguridad


de nuestras aplicaciones se ha simplificado muchísimo con ASP.NET 2.0. Gracias a los
objetos Membership y Roles crear una interfaz de administración de usuarios y control
de acceso es casi trivial. ¿Podría ser más fácil?

Pues lo cierto es que sí. ASP.NET 2.0 nos facilita todavía


más el trabajo relacionado con la seguridad gracias a la
inclusión de los nuevos controles Web de seguridad. Los
podemos encontrar en el grupo Inicio de sesión del
cuadro de herramientas de Visual Studio.

Estos controles nos dan ya hechas multitud de


operaciones comunes de seguridad relacionadas con la
interfaz de usuario. Por ejemplo, el control Login permite
disponer de un completo diálogo de autenticación con sólo
arrastrarlo sobre un formulario Web. El
CreateUserWizard es un asistente con varios pasos que
permite la creación automática de nuevos usuarios. Todos ellos permiten la
personalización, tanto parcial por medio de propiedades, como absoluta usando
plantillas. En el caso de los asistentes tenemos libertad de añadir nuevos pasos o
modificar los predeterminados a voluntad.

El control Login

Este control permite definir un completo diálogo de autenticación en cualquier página,


que además soporta el uso de temas.
Figura 5.13.- Control de inicio de sesión personalizado estéticamente.

Además de los elementos obvios permite configurar enlaces para acceder a la


creación de nuevos usuarios, muestra mensajes de fallo de autenticación, redirige
automáticamente a otras páginas al autenticar si es necesario, etc... Todo ello se
controla con facilidad desde la ventana Propiedades de Visual Studio.

Para validar a los usuarios utiliza el método ValidateUser de la clase Membership.

Por defecto, si ya hay un usuario autenticado, este control se oculta automáticamente


salvo cuando se ubica en la página principal. Este comportamiento se cambia con la
propiedad AutoHide en caso de necesitarlo. Así se permite cambiar la sesión de
usuario.

El control LoginStatus

Se utiliza para mostrar el estado actual de conexión de un usuario y permitir su


desconexión. Cuando hay un usuario autenticado el control muestra por defecto un
enlace que permite desconectarse. Lo que ello provoca es que se elimine la referencia
al usuario actual en la propiedad User de la clase Page y que se reenvíe a la página de
autenticación especificada en web.config para una iniciar nueva sesión.

Cuando no hay usuario alguno autenticado en el sistema el enlace apunta


automáticamente a la página de autenticación definida.

Se puede personalizar por completo para mostrar cualquier otra cosa en la superficie
del control.

El control LoginName

Muestra el nombre del usuario actualmente autenticado.

El control LoginView

Se trata de un completo control que permite definir el contenido de una zona de la


página en función de si el usuario está o no autenticado, y en caso de estarlo incluso
en función del rol o roles a los que esté asociado.

Es interesante observar su panel de tareas:


Figura 5.14.- Control LoginView y su correspondiente panel de tareas.

La primera de las acciones, Editar RoleGroups, permite definir los casos que
existirán en función de los roles. Por ejemplo si queremos mostrar un mesnaje
diferente según sea un usuario anónimo, un usuario autenticado en general y un
usuario que pertenece al rol de administradores, tendríamos que añadir un RoleGroup
con el texto "Administradores" ya que los otros dos casos siempre están definidospor
defecto.

La lista Vistas contiene un elemento por cada RoleGroup además de los


correspondientes ausuarios anónimos y autenticados en general. Al elegir un
elemento cualquiera de esta lista cambia la plantilla mostrada en el diseñador. En la
plantilla podemos introducir controles y cualquier otro elemento, y eso será lo que
verá el usuario indicado por el RoleGroup correspondiente.

Gracias al control RoleView es extremadamente sencillo personalziar las vistas para


cada usuario diseñándolo visualmente.

Los controles restantes

PasswordRecovery, ChangePassword y CreateUserWizard nos facilitan la


recuperación de claves, el cambio de clave y la creación de usuarios respectivamente.

Al igual que los anteriores ofrecen una altísima capacidad de personalziación que en el
caso del control de creación de usuarios permite incluso añadir nuevos pasos en el
asistente (se trata de un control heredado del control de tipo Wizard que nos permite
crear asistentes con facilidad).

Todos ellos utilizan la API de Membership para trabajar y responden a los ajustes
impuestos en la configuración de la aplicación. Así pues, por ejemplo, el control de
cambio de contraseña o el de creación de usuarios exigirán a las contraseñas la
complejidad indicada en la configuración, y el de recuperación de clave le hará una
pregunta de seguridad si así está definido.

Gracias a estos controles y a la utilidad de administración de seguridad de ASP.NET


podemos construir la seguridad completa de una aplicaicón Web en cuestión de
minutos, cuando lo habitual sería que tardásemos horas o días.

Ver vídeo de esta lección (Uso de controles Web de seguridad)


Contenido
Este módulo presenta al alumno los fundamentos de los Servicios Web y las
Arquitecturas Orientadas a Servicios (SOA).

Tras una introducción a los servicios Web y sus conceptos asociados se ve la forma de
crear y consumir servicios Web desde ASP.NET 2.0 usando Visual Studio 2005.

· Lección 1: Introducción a los servicios Web


o ¿Qué son los servicios Web?
o Comunicación entre componentes
o SOAP
§ Breve historia de SOAP
§ Las bases tecnológicas de SOAP
§ Descubrimiento de servicios: WSDL y UDDI
· Lección 2: Creación de un servicio Web con ASP.NET 2.0
o

Proyectos de servicios Web

o Archivos del servicio Web


o Tipos de datos
o Descripción de métodos
o Parámetros opcionales y sobrecarga de métodos
· Lección 3: Consumo de un servicio Web
o Examinando manualmente un servicio Web creado con ASP.NET
o Consumiendo un servicio Web desde ASP.NET 2.0
§ Generación del proxy
§ cambio de ubicación del servicio
§ Credenciales de acceso
¿Qué son los servicios Web?

La expresión "Servicio Web" se oye con fuerza desde hace unos años en el ámbito del
desarrollo de aplicaciones e incluso en ambientes poco técnicos y de dirección. Lo
cierto es que no se trata de un concepto tan novedoso como cabría esperar y las
innovaciones que conlleva no son tanto tecnológicas, como conceptuales.

En esta lección explicaremos desde un punto de vista no-técnico los conceptos


relacionados con los Servicios Web, cómo funcionan, de dónde vienen y a dónde van.

Un poco de historia: modelos de desarrollo

El hecho de poder comunicar componentes de software entre sí tiene una enorme


importancia. Hasta no hace tantos años era muy típico hacer aplicaciones de una
sola pieza, "monolíticas":
Figura 6.1.- Aplicación "monolítica" aunque distribuida.

Estos programas podían acceder a un sistema gestor de datos a través de la red, pero
toda la lógica del flujo de datos, la seguridad y las interacciones con las personas se
encontraban en el ordenador del usuario en forma de un gran ejecutable. Se suelen
conocer también como "fat clients". La situación descrita no es la ideal ya que implica
problemas de toda índole a la hora de instalar las aplicaciones y sobre todo cuando se
modifican o actualizan (mantenimiento complejo y engorroso).

Una metodología de desarrollo mucho mejor aunque más laboriosa a la hora de


programar es el modelo Cliente-Servidor en tres capas:

Figura 6.2.- Aplicación Cliente Servidor en tres capas.


En este modelo toda la lógica de los datos, su validación, los permisos, etc, residen en
un servidor intermedio y son utilizados por todos los clientes a través de una red. En
este caso en el ordenador del usuario lo único que hay es una capa de presentación
que se ocupa básicamente de recoger y recibir datos, es decir, actúa de intermediario
entre el usuario y las reglas de negocio residentes en la capa intermedia. Este modelo
es más eficiente y está muy evolucionado respecto al anterior pero aún se puede ir
más allá.

La arquitectura de desarrollo en n-capas (n-tier que dicen los anglosajones) lleva el


concepto cliente-servidor un paso hacia adelante, dividiendo la capa intermedia en
muchas otras capas especializadas cada una de las cuales puede residir en un
servidor diferente:

Figura 6.3.- Arquitectura de desarrollo basada en componentes.

En este modelo existe una gran variedad de componentes especializados en tareas


específicas como la validación de datos, la autenticación y seguridad o el acceso a
datos. Dichos componentes deben trabajar unos con otros como piezas de un
mecanismo, gestionando la información que circula entre el usuario y el servidor de
datos.

La belleza de este modelo radica en que cada uno de ellos (o cada grupo de ellos)
puede residir en un servidor diferente, siendo transparente su ubicación para los
clientes que los utilizan. Ello aumenta mucho la escalabilidad de las aplicaciones, pues
basta con añadir nuevos servidores e instalar los componentes en ellos para poder
atender más peticiones.

Por otra parte, y esto es muy interesante también, mientras sus interfaces de
programación sean las mismas, es posible sustituir cualquier componente por otro
actualizado o que actúe de manera distinta para corregir errores o cambiar el modo
de trabajo de la aplicación global, y todo ello sin que los clientes sean conscientes de
ello. Obviamente esto ofrece más ventajas aún ya que, por ejemplo, no es necesario
reinstalar la aplicación en cada cliente, sino que basta con sustituir un componente en
un único lugar y automáticamente todos los usuarios tendrán su aplicación
actualizada.

El concepto de Arquitectura Orientada a Servicios o SOA se basa en el uso de


este tipo de componentes que suplen las necesidades de una o varias aplicaciones,
son independientes entre sí y trabajan independientemente del sistema operativo o la
plataforma.

Aunque muchos programadores piensan que SOA está relacionado únicamente con los
Servicios Web lo cierto es que se pueden conseguir arquitecturas SOA con otras
tecnologías como veremos.
Comunicación entre componentes

Existen diversas dificultades técnicas a la hora de llevar a la práctica las arquitecturas


orientadas a servicios. Entre éstas están la comunicación entre las distintas capas y
componentes que constituyen la aplicación, la gestión de peticiones y el balanceado
de carga entre servidores cuando un mismo componente reside en varios de ellos
(para aplicaciones muy grandes con muchos clientes), la gestión de transacciones
entre componentes y algunas otras cosas más.

Existe la posibilidad de escribir un protocolo de comunicaciones propio que se ocupe


de todas estas cuestiones, pero por supuesto se trata de una opción muy poco
realista dada la complejidad que conllevaría. Para responder a estas necesidades de
los programadores, diversos fabricantes y asociaciones de la industria crearon
servicios y protocolos específicos orientados a la interacción distribuida de
componentes. Aunque existe una gran variedad, de todos ellos los más importantes
sin duda son:

• DCOM (Distributed Common Object Model), la propuesta de Microsoft, ligada a sus


sistemas Windows. Se trata de algo más que un protocolo de invocación remota de
procedimientos (RPC) ya que su última encarnación, COM+, incluye servicios
avanzados para balanceado de carga, gestión de transacciones o llamadas asíncronas.
Los parámetros son transmitidos a través de la red mediante un formato binario
propio llamado NDR (Network Data Representation).

• RMI (Remote Method Invocation), es la metodología de llamada remota a


procedimientos de Java. No se centra en la definición de interfaces para
compatibilidad binaria de componentes, ni en otros conceptos avanzados, y se basa
en la existencia de un cliente y un servidor que actúan de intermediarios entre los
componentes que se quieren comunicar. Es una tecnología bastante simple que es
fácil de utilizar para aplicaciones básicas.
• CORBA (Common Object Request Broker Architecture). Se trata de una serie de
convenciones que describen cómo deben comunicarse los distintos componentes,
cómo deben transferir los datos de las llamadas y sus resultados o cómo se describen
las interfaces de programación de los componentes para que los demás sepan cómo
utilizarlos. Fue desarrollado por el OMG (Object Management Group) en la segunda
mitad de la década de los '90 y es el modelo que más éxito ha tenido en el mundo
UNIX. Su método de empaquetado y transmisión de datos a través de la red se llama
CDR (Common Data representation). Existen diversas implementaciones de distintos
fabricantes.

Estos modelos son buenos y muy eficientes, cumpliendo bien su trabajo pero tienen
algunas limitaciones importantes siendo las principales las siguientes:

· Es difícil la comunicación entre los distintos modelos


· Están ligados a plataformas de desarrollo específicas, lo que dificulta la
comunicación entre ellas
· Su utilización a través de Internet se complica debido a cuestiones de seguridad
de las que enseguida hablaremos.
· Existen en el mercado puentes CORBA/DCOM que permiten la comunicación
entre componentes COM y componentes CORBA, pero su utilización es difícil y
añaden una nueva capa de complejidad a las aplicaciones además de disminuir
su rendimiento.
SOAP

Las expectativas actuales respecto a los componentes han aumentado. Al igual que
podemos usar un navegador web para acceder a cualquier página
independientemente del sistema operativo del servidor en que resida, ¿por qué no
podríamos invocar métodos de componentes a través de la red independientemente
de dónde se encuentren, del lenguaje en el que estén escritos y de la plataforma de
computación en la que se ejecuten?.

Esto es precisamente lo que ofrecen los Servicios Web. Gracias a ellos se derriban la
antiguas divisiones resultantes de los modelos de componentes descritos, y la
integración de las aplicaciones, la ubicuidad de sus componentes y su reutilización a
través de la red se convierten en una realidad.

La tecnología que está detrás de todo ello se llama SOAP (jabón en inglés). Este
acrónimo (Simple Object Access Protocol) describe un concepto tecnológico basado en
la sencillez y la flexibilidad que hace uso de tecnologías y estándares comunes
para conseguir las promesas de la ubicuidad de los servicios, la transparencia de los
datos y la independencia de la plataforma que según hemos visto, se hacen
necesarios en las aplicaciones actuales.

Breve historia de SOAP

SOAP empezó como un protocolo de invocación remota basado en XML diseñado por
Dave Winer de UserLand, llamado XML-RPC. A partir de éste se obtuvo en Septiembre
de 1999 la versión 1.0 de SOAP, en la que participo activamente Microsoft y el
archiconocido experto en programación Don Box.

Esta primera versión fue más o menos despreciada por los principales fabricantes de
software que en esa época tenían en marcha un proyecto más ambicioso llamado
ebXML. Esto puso en peligro en su nacimiento la existencia de SOAP ya que si los
grandes no lo apoyaban poco se podía hacer. Por fortuna uno de estos grandes
fabricantes, IBM, decidió apoyarlo y en la actualidad no sólo acepta SOAP sino que es
uno de lo motores detrás de su desarrollo (dos importantes personas de IBM y su filial
Lotus, David Ehnebuske y Noah Mendelsohn, son autores de la especificación 1.1 de
SOAP). Sun Microsystems también anunció oficialmente en Junio de 2000 que
soportaba el estándar. El hecho de que los tres gigantes del software (Microsoft, IBM
y Sun) apoyen SOAP ha hecho que muchos fabricantes de Middleware y puentes
CORBA-DCOM (como Roguewave o IONA) ofrezcan productos para SOAP, así como
otras muchas pequeñas empresas de software.

El paso definitivo para asegurar el éxito de SOAP y los servicios web es su envío al
W3C (World Wide Web Consortium) para proponerlo como estándar. La última versión
de la especificación se puede encontrar en www.w3.org/TR/SOAP/.

Este soporte mayoritario hace que su éxito y pervivencia estén asegurados y hoy
todas las herramientas de desarrollo del mercado ofrecen en menor o mayor medida
soporte para SOAP. Por supuesto .NET y Visual Studio son los entornos más
avanzados en la adopción de SOAP.

La base tecnológica de SOAP

Lo interesante de SOAP es que utiliza para su implementación tecnologías y


estándares muy conocidos y accesibles como son XML o el protocolo HTTP.

· Dado que los mensajes entre componentes y los datos de los parámetros para
llamadas a métodos remotos se envían en formato XML basado en texto plano,
SOAP se puede utilizar para comunicarse con cualquier plataforma de
computación, consiguiendo la ansiada ubicuidad de los componentes.
· El uso de HTTP como protocolo principal de comunicación hace que cualquier
servidor web del mercado pueda actuar como servidor SOAP, reduciendo la
cantidad de software a desarrollar y haciendo la tecnología disponible
inmediatamente. Además en la mayoría de los casos se puede hacer uso de
SOAP a través de los cortafuegos que defienden las redes, ya que no suelen
tener bloqueadas las peticiones a través del puerto 80, el puerto por defecto de
HTTP (de ahí la ubicuidad, aunque se pueden usar otros puertos distintos al 80,
por supuesto).

La seguridad se puede conseguir a través de los métodos habituales en los servidores


web y por tanto se dispone de autenticación de usuarios y cifrado de información de
forma transparente al programador, usando protocolos y técnicas como IPSec o SSL,
ampliamente conocidos y usados en el mundo web.

Por otra parte la escalabilidad se obtiene a través del propio servidor web o incluso
del sistema operativo, ya que la mayoría de ellos (por ejemplo IIS) poseen
capacidades de ampliación mediante clusters de servidores, enrutadores que
discriminan las peticiones y otras técnicas para crear Web Farms, o conjuntos de
servidores web que trabajan como si fueran uno solo para así poder atender a más
clientes simultáneamente.
Nota:
Existen ampliaciones al protocolo SOAP base que definen protocolos y
convenciones para tareas específicas como las mecionadas de seguridad,
enrutado de mensajes, los eventos y muchas otras cuestiones
avanzadas. En .NET se implementan mediante los concidos Web Services
Enhancements (WSE) actualmente por su versión 3.0, y en un futuro
inmediato con Windows Communication Foundation, la nueva plataforma
de servicios de comunicaciones de Windows. El estudio de éstos se sale
del ámbito de este curso.

Como vemos, las tecnologías utilizadas son conocidas y la especificación SOAP se


refiere más bien a la manera de usarlas. De este modo las áreas cubiertas por la
especificación se refieren a cómo se codifican los mensajes XML que contienen las
llamadas a procedimientos y sus respuestas, y a la manera en que HTTP debe
intercambiar estos mensajes. Si nos referimos a la esencia del estándar, SOAP trata
de sustituir a los diferentes formatos propietarios de empaquetamiento de datos que
utilizan otras tecnologías (como DCOM o CORBA con NDR y CDR respectivamente),
así como los protocolos propietarios empleados para transmitir estos datos
empaquetados.

HTTP es el único protocolo definido en el estándar para SOAP pero éste es lo


suficientemente abierto como para permitir que se empleen otros protocolos distintos
para transmitir mensajes SOAP. Por citar unos pocos, se podría utilizar SMTP (correo
electrónico), MSMQ (Microsoft Messaging Queue) para enviar de manera asíncrona las
llamadas a procedimientos con SOAP, etc...

Descubrimiento de servicios: WSDL y UDDI

Otro de los estándares que se definen en SOAP es WSDL (Web Service Definition
Language). Se trata de un formato estándar para describir las interfaces de los
servicios web. WSDL describe qué métodos están disponibles a través de un servicio
Web y cuáles son los parámetros y valores devueltos por éstos. Antes de usar un
componente que actúa como servicio web se debe leer su archivo WSDL para
averiguar cómo utilizarlo.

Nota:
Para aquellos programadores que conocen otras arquitecturas podemos
decir que WSDL es el equivalente en XML a los lenguajes IDL (Interface
Description Language) de DCOM y CORBA.

Se ha definido también un formato estándar para publicación de información de


servicios web llamado UDDI (Universal Description Discovery and Integration). Esta
especificación permite la creación de directorios de servicios web, donde se definen
métodos que permiten consultarlos para encontrar fácilmente aquel servicio que se
necesite. Windows Server 2003 incluye gratuitamente un servidor para implementar
directorios UDDI en organizaciones.
Creación de un servicio Web

En el módulo anterior se presentaron los servicios web: qué son, de dónde vienen y a
dónde van. Tras la teoría veremos una sencilla aplicación práctica construyendo, paso
a paso, un servicio web con Visual Studio 2005, y comprobaremos lo extremadamente
simple que es su creación con esta herramienta.

Nuestro primer ejemplo será un servicio web muy simple que se ocupará de devolver
la fecha y la hora actuales del servidor en el que se ejecute.

Proyectos de servicios Web

Para crear un nuevo proyecto que albergue servicios Web sólo tenemos que utilizar el
menú Archivo·Nuevo sitio Web y elegir el tipo "Servicio Web" en lugar del habitual
"Sitio web ASP.NET" tal y como se muestra en la figura:
Figura 6.4.- Nuevo proyecto de servicios Web

Nota:
En realidad tampoco es necesario puesto que se pueden crear archivos
de servicios web dentro de proyectos de aplicaciones Web "normales"
usando el diálogo Agregar nuevo elemento. Sin embargo, lo habitual es
que se guarden en proyectos separados para facilitar su posterior
gestión.

Archivos del servicio Web

Al aceptar se crean para nosotros diversos


archivos en el nuevo proyecto, tal y como se
observa en la figura del lateral. Éstos
constituyen el esqueleto de nuestro servicio.

La extensión asignada a los archivos que


representan un servicio Web en ASP.NET es
'.asmx'. En esta figura el archivo Service.asmx
es el que contiene la definición de nuestro
servicio.

Si hacemos doble-clic sobre Service.asmx se abre su definición en el editor de Visual


Studio. Lo único que contiene es la siguiente línea:

Ésta se parece mucho a la directiva de las páginas ASPX y, al igual que en ese caso,
define dónde se encuentra en código que dotará de funcionalidad al servicio Web. En
este caso y dado que el servicio Web no posee elementos de interfaz de usuario, del
único archivo que nos tenemos que preocupar es del que se indica en el atributo
CodeBehind. Éste contiene la definición de la clase indicada en el atributo Class, y que
en nuestro ejemplo se llama Service.

La clase Service (podría llamarse de cualquier otro modo) está dentro del archivo
Service.vb, el cual se ubica dentro de la carpeta App_Code que como ya hemos
estudiado asegura la compilación dinámica de los archivos de código que contiene.

Si abrimos el archivo Service.vb, veremos que por defecto contiene el siguiente


código:

Aparte de las preceptivas sentencias Imports, vemos que la clase Service hereda de la
clase WebService. Sólo con hacer esto ya obtenemos la funcionalidad necesaria para
gestionar el XML, HTTP y demás tecnologías en las que se basan los servicios Web. su
gestión pasa inadvertida para nosotros.

Los métodos que un servicio Web expone al exterior son métodos normales de una
clase a los que se le ha añadido el atributo WebMethod, tal y como se observa en el
código anterior. Es decir, para que uno de nuestros métodos sea expuesto en un
servicio Web y pueda ser utilizado remotamente desde cualquier otra plataforma o
lenguaje basta con incluir el atributo WebMethod delante y que esté contenido dentro
de una clase que hereda de WebService. Así de sencillo.

Por ejemplo, elimine el método de ejemplo HelloWorld que introduce Visual Studio y
escriba el siguiente código:
Ahora disponemos de un método que devuelve la fecha y la hora actuales en el
servidor en el que se ejecute nuestro servicio Web y que puede ser utilizado desde
cualquier otra aplicación independientemente del lenguaje con el que esté construida
y del sistema operativo en el que trabaje. Enseguida veremos cómo.

Este ejemplo, aunque trivial, puede resultar útil en entornos, como los de gestión, en
los que la sincronización horaria entre clientes y servidores es fundamental para
asegurar la coherencia de facturaciones y otros datos económicos.

Tipos de datos

Vamos a mejorar un poco el ejemplo. Tratar una fecha a partir de una cadena es un
poco tedioso porque hay que analizarla para obtener sus diversas partes (día, mes,
año, hora, minutos y segundos) y además su formato depende del idioma definido en
el servidor, que no tiene porque ser el mismo que tienen los clientes. Lo ideal sería
obtener una representación única de una fecha sin preocuparnos de estos problemas.
Al igual que hemos definido como tipo devuelto una cadena podemos definir
perfectamente una fecha, quedando el código así:

Lo único que hemos cambiado es el tipo a devolver, pero hemos ganado mucho ya
que éste se interpretará correctamente como una fecha en cualquier aplicación que
consuma nuestro servicio.

La especificación estándar de SOAP define representaciones en formato XML para


todos los tipos de datos básicos que nos podemos encontrar en las aplicaciones,
incluyendo cadenas, fechas, booleanos, etc... Cualquier clase (o tipo) que se use
como parámetro o como valor devuelto por un método que se expondrá en un
servicio Web se serializa a XML antes de ser enviada a través de la red tanto en un
sentido como en el otro (cliente-servidor y viceversa).

Serializar a XML un objeto consiste en generar una representación de los datos de


éste en formato XML de modo que posteriormente puedan ser deserializados en el
otro extremo para obtener un objeto equivalente al original. En el caso de tipos
básicos la cosa es fácil aunque se debe seguir una convención para hacerlo. En el caso
de otros objetos más complejos (como los dataSet, por ejemplo) el formato XML
ovtenido es mucho más complejo y no se encuentra estandarizado, si bien al ser XML
y disponer de su esquema es muy fácil utilizarlos desde cualquier lenguaje.
Como resumen de este párrafo hay que quedarse con una idea: sólo se pueden
emplear clases serializables como parámetros o valores devueltos por un método
de un servicio Web.

Nota:
Los objetos de la clase DataSet de ADO.NET son objetos serializables
que están adaptados a la perfección para su uso en un servicio Web. De
hecho es muy habitual que se utilicen como valores devueltos y como
parámetros en métodos de servicios Web. Los DataSet tipados también
se incluyen en esta categoría y de hecho pueden resultar incluso más
sencillos de utilizar cuando el servicio se invoca desde entornos
diferentes a .NET.

Descripción de métodos

El atributo WebMethod no sólo se usa para especificar que un método determinado de


la clase se expondrá para su consumo desde el exterior. Además se usa para
especificar ciertos comportamientos del método. A efectos de lo que nos interesa en
este curso veremos sólo uno de los parámetros de este atributo: Description.

Con Description ofreceremos a los programadores que consuman nuestro servicio


información sobre lo qué hace el método lo que puede ser de gran utilidad. Con ello
dejamos nuestro método finalmente definido como:

Con esto queda definido nuestro sencillo servicio web de ejemplo. Enseguida veremos
cómo utilizarlo.

Parámetros opcionales y/o sobrecarga de métodos Web

Imaginemos que tenemos que crear un servicio Web para una tarea determinada
(para simplificar, digamos, realizar una suma), y dicha tarea debe trabajar con
diferentes tipos de datos o debe poder usarse de varias formas con parámetros
diferentes.

En una clase .NET podemos crear versiones sobrecargadas de un método cada una de
las cuales tomará el tipo de datos pertinente, siendo el propio compilador el que
llamará a la versión sobrecargada que convenga en cada caso.

En el caso de un servicio Web a priori puede parecer que no seremos capaces de


obtener el mismo efecto, ya que no podemos exponer dos métodos Web con el
mismo nombre. Sin embargo existe otro parámetro del atributo WebMethod que
permite conseguir un efecto similar. Se trata de MessageName.

MessageName se utiliza para asignar alias a los métodos Web que definamos, de
forma que podemos identificar de manera única a cada uno de ellos, incluso aunque el
nombre asignado a la función sea el mismo. De este modo, si queremos exponer
como servicio Web una clase que posee un método con varias sobrecargas sólo
tenemos que emplear este atributo asignándole un identificador diferente a cada una.

Por ejemplo:

Las dos funciones con el mismo nombre y diferentes parámetros son perfectamente
válidas en VB.NET. Sin embargo si hubiésemos colocado simplemente la palabra
WebMethod delante de ambas hubiésemos obtenido un error a la hora de compilar ya
que no pueden exponerse dos métodos con el mismo nombre en un servicio Web.
Cambiando el nombre del mensaje de una de ellas se pueden exponer sin problema
ya que a la hora de consumirlas, aunque tengan el mismo nombre, se distinguen
perfectamente por el nombre del mensaje empleado.

Con esto conseguimos el efecto de métodos Web sobrecargados.

Ver vídeo de esta lección (Creación de un servicio Web de datos)


Consumo de un servicio web

En esta lección vamos a estudiar el comportamiento del servicio Web que hemos
creado y aprenderemos a consumirlo desde una página ASP.NET además de a
examinarlos manualmente.

· Lección 3: Consumo de un servicio Web


o Examinando manualmente un servicio Web creado con ASP.NET
o Consumiendo un servicio Web desde ASP.NET 2.0
§ Generación del proxy
§ cambio de ubicación del servicio
§ Credenciales de acceso
Examinando manualmente un servicio Web creado con ASP.NET

La manera más sencilla de examinar un servicio Web creado con ASP.NET es


navegando con Internet Explorer a la URL del archivo asmx del servicio. En nuestro
ejemplo, si navegamos hacia el servicio horario anterior (basta con presionar F5
desde Visual Studio para conseguirlo), obtenemos una página como la de la figura:

Figura 6.5.- Aspecto de la página generada automáticamente por ASP.NET


Tal y como comentamos en la primera lección, para que un cliente pueda consumir un
servicio Web deberá primero leer el "contrato" de uso del servicio, es decir,
averiguará los métodos que éste ofrece y la forma de invocarlos. Como recordará este
contrato está escrito con el lenguaje WSDL (Web Service Description Language). Para
obtener el WSDL correspondiente a nuestro servicio basta con presionar sobre el
enlace "descripción de servicios" que ofrece la página autogenerada. El aspecto del
WSDL es este (parcial):

Figura 6.6.- WSDL de nuestro servicio Web horario.

Este código XML es el que se utiliza para averiguar los métodos disponibles y como
invocarlos. Como es bastante tedioso leerlo, ASP.NET simplemente lo procesa por
nosotros y genera una bonita página Web para informarnos del contenido del servicio
de una forma más agradable para los humanos.

Lo interesante de esta página es que nos permite probar manualmente los métodos
del servicio Web siempre y cuando utilicen parámetros y valores devueltos sencillos.

Nota:
Esta página autogenerada sólo se muestra si se accede al servicio Web
en modo local. Si se intenta el acceso desde un equipo remoto no se
tendrá acceso por cuestiones de seguridad, aunque es posible habilitarlo
en casos que se haga necesario (para depuración remota, por ejemplo).
Sin embargo el WSDL (ruta al servicio Web seguido de ?WSDL) sí es
accesible siempre ya que es necesario que otras máquinas lo puedan
leer para hacer uso del servicio.

Si presionamos el método Hora obtendremos una página de pruebas que nos


permitirá invocarlo. Además dicha página nos ofrece ejemplos de mensajes SOAP de
solicitud y respuesta para acceder al método:
Figura 6.7.- Página de prueba delmétodo horario. Se ha resaltado una de las
solicitudes de ejemplo facilitadas.

Como vemos las llamadas a la función se realizan con XML. ¿Y el resultado?.


Obviamente también será XML. Si presionamos sobre el botón Invocar obtendremos
el resultado en una ventana aparte:

En este caso la invocación, dado que la hacemos desde el navegador, se realiza


mediante un POST HTTP 8es decir, como si enviásemos un formulario). El XML
devuelto ofrece únicamente el resultado de invocar el método y al tratarse de una
fecha viene codificada de manera independiente al idioma o al sistema operativo, tal y
como define el estándar SOAP.

Cualquier cliente de SOAP sabe interpretar este valor como una fecha y por lo tanto
acceder a sus elementos es una tarea muy sencilla pues se convierte, en el caso de
.NET, en un objeto DateTime normal y corriente tras llamar a la función remota.

Ver vídeo de esta lección (Consumo de un servicio Web - Examinando


manualmente un servicio web)
Consumo de servicios Web con ASP.NET

Si crear servicios web le ha parecido fácil enseguida comprobará que utilizarlos es


incluso más fácil todavía con ASP.NET.

Crear un proxy para utilizar el servicio

Cree un nuevo proyecto de aplicación Web y en la página Web por defecto agregue
una etiqueta Label1. Este etiqueta la usaremos para mostrar la hora en el servidor
obtenida mediante el uso del servicio Web creado en el apartado anterior.

Para poder hacer uso del servicio Web debemos añadir una referencia al mismo desde
nuestro proyecto. Esto se consigue presionando con el botón secundario sobre el nodo
del proyecto en el Explorador de soluciones y eligiendo la opción Agregar referencia
Web:
Figura 6.8.- Agregar referencia web

Al hacer esto se abre un diálogo que nos permite escribir la dirección del WSDL de
algún servicio Web concreto (si nos la sabemos) o buscar servicios Web en diversos
lugares.

Figura 6.9.- Buscar servicios web y agregarlos

Podemos buscar servicios en la misma solución de Visual Studio, buscar todos los
servicios que haya instalados en la máquina local o examinar directorios UDDI para
conocer los servicios que contienen.
En nuestro caso agregaremos una referencia al servicio Web de hora que creamos en
el apartado anterior escribiendo la URL en la línea de dirección URL.

Figura 6.10.- Agregar el servicio Web de hora.

Al seleccionarlo se muestran sus métodos y debemos asignarle un nombre con el que


accederemos a su funcionalidad desde el código. Por defecto el nombre que Visual
Studio le otorga es localhost, pero debemos utilizar algo más descriptivo, por ejemplo
ServicioHora como se ha hecho en la figura.

De manera automática se añade una nueva carpeta


llamada App_WebReferences y dentro de ella una
carpeta por cada referencia a un servicio web que
hayamos añadido. Dentro de esta carpeta hay dos
archivos (.disco y .discomap) que contienen
información sobre cómo acceder al servicio Web y una
copia del archivo de definición del servicio Web
(.wsdl) que indica a ASP.NET cómo utilizarlo.

A partir de ahora disponemos de un nuevo espacio de


nombres llamado como la referencia Web (en nuestro
caso ServicioHora) que contiene una clase con el
nombre de la clase que definimos al crear el servicio
Web (Service en nuestro ejemplo).

Así, para utilizar cualquier método del servicio sólo


debemos instanciar un objeto de esta clase y llamar al método que nos interese. Por
ejemplo, para mostrar la hora en el servidor en la etiqueta que hemos añadido a la
página escribiríamos:
Dim sh As New ServicioHora.Service()

Label1.Text = "Hora en el servidor: " + sh.Hora().ToString()

Por supuesto el método Hora devuelve un objeto de la clase Date de VB.NET y


podemos utilizar cualquiera de sus métodos, como por ejemplo ToLongDateString
para obtener la expresión larga de la fecha, u operar con él para ver la diferencia con
cualquier otra fecha, etc...

Es decir, si alguien añade la referencia Web por nosotros y no nos dice que estamos
usando un servicio Web desde nuestro código no habrá diferencia a la hora de usar
los objetos expuestos a través del servicio, si bien éstos pueden estar ejecutándose
en otro servidor e incluso en otro país a través de Internet.

A la clase auto-generada que representa en local a los objetos expuestos por el


servicio web se la denomina proxy.

Nota:
Hacer uso de este servicio Web desde una aplicación Windows es igual
de fácil que desde una página ASP.NET. También podemos permitir que
programadores de otras plataformas hagan uso de nuestro servicio.

Cambio de ubicación del servicio

Lo más habitual es que cuando estamos desarrollando el servicio y el cliente se


encuentren en la misma máquina. Incluso en caso de no ser así seguramente el
servicio que usemos para desarrollo no sea el mismo que se utiliza luego en
producción, cuando la aplicación está terminada y usándose en el entorno real.
Debido a ello la referencia que hemos añadido al servicio Web no será válida al poner
la aplicación en producción y deberemos hacer que apunte a la referencia web
correcta.

La propiedad Url de la clase proxy que se crea para acceder al servicio web nos
permite establecer en tiempo de ejecución la dirección en la que está ubicado el
WSDL del servicio que realmente usaremos. Éste obviamente debe ser idéntico (su
WSDL debe ser idéntica) al servicio web original que usamos en el desarrollo.

Por ejemplo si a la hora de poner en producción la aplicación de ejemplo el servicio


Web reside en un servidor cuya URL es www.serviciosweb.com escribiríamos:

Dim sh As New ServicioHora.Service()

sh.Url = "http://www.serviciosweb.com/servicioHora.asmx"

....

Por supuesto lo habitual es almacenar esta dirección en el archivo web.config para


recuperarla en tiempo de ejecución y poder variarla fácilmente sin tocar el código.
Credenciales de acceso

Podemos proteger el acceso a un servicio Web usando los métodos de autenticación u


autorización proporcionados por Internet Information Server. Si deshabilitamos la
autenticación anónima y marcamos la autenticación de Windows, para poder acceder
al servicio deberemos facilitar credenciales de acceso validas. Éstas se corresponden
con el nombre de usuario y la contraseña de un usuario del sistema. El código
necesario para hacerlo es el siguiente:

Dim sh As New ServicioHora.Service()


sh.Credentials = New System.Net.NetworkCredential("usuario",
"clavesecreta")

....

La propiedad Credentials se usa para enviar las credenciales de cualquier usuario al


servidor.

Se pueden usar las credenciales del usuario actualmente autenticado en la aplicación


(sin tener que escribir nombre de usuario y contraseña alguno) usando la propiedad
UseDefaultCredentials:

Dim sh As New ServicioHora.Service()


sh..UseDefaultCredentials = True

....

Con esto obtendremos una barrera de protección básica para nuestro servicio Web.

Nota:
Existen una serie de especificaciones adicionales para servicios Web
denominadas Web Service Enhancenments (WSE) que añaden funciones
especiales para autenticación, privacidad, cifrado, enrutado, y otras
muchas tareas avanzadas. Estos WSE siguen las especificaciones
conocidas como WS-*, que son un estándar de la W3C y la nuev
aplataforma unificada de mensajería de Microsoft, Windows
Communication Foundation, también las implementa para sus
servicios web. El estudio de WSE se sale del ámbito de este curso.

Ver vídeo de esta lección (Consumo de un servicio Web - Orígenes de


datos de objetos)
Aplicación de ejemplo MSDN Video

A continuación vamos a desarrollar una aplicación que nos permita explorar las
principales características de Visual Studio 2005. Puede descargar el código fuente en
su máquina y explorarlo o modificarlo para conocer de forma práctica cómo usar
ASP.NET para desarrollar aplicaciones reales. También puede explorar los videos
donde construimos esta
aplicación paso a paso.

La aplicación

MSDN Video es una aplicación


de ejemplo empresarial
desarrollada en España por la
comunidad de desarrollo y
Microsoft. Puede utilizarse para
comprobar las mejores prácticas
en la construcción aplicaciones
distribuidas, escalables y con
distintos tipos de clientes
(Windows, Web y móviles).

La aplicación que desarrollaremos en este tutorial es una versión reducida de MSDN


Video donde podrá ver Visual Studio 2005 en acción en un escenario sencillo.

Código fuente de MSDN Video


Puede descargar el código fuente de MSDN Video y visionarlo o editarlo en su máquina.

Nota:
MSDN Video utiliza una base de datos para almacenar su estado. Si
desea ejecutar la aplicación en su máquina necesitará SQL Server 2005
Express instalado.
Videos explicativos
Si desea ver cómo se hizo esta aplicación puede visionar estos videos donde
desarrollamos la aplicación paso a paso:
· Video 1. Introducción MSDN Video Web y Modelo de Datos
· Video 2. El Esqueleto de Presentación MSDN Video Web
· Video 3. Entidades de Negocio y Acceso a Datos de MSDN Video Web
· Video 4. Funcionalidad y Enlace de Datos de MSDN Video Web
· Video 5. Seguridad en MSDN Video Web
· Video 6. Transacciones, Temas y Pieles y Conclusión de MSDN Video Web

MSDN Video empresarial


Puede descargar la versión empresarial de MSDN Video y ver cómo podemos convertir la
aplicación que hemos desarrollado aquí en un sistema distribuido completo, con acceso a
través de servicios web desde clientes Windows, Web y dispositivos móviles.
Web de MSDN Video
Código fuente, tutoriales y videos explicativos de la versión completa de MSDN Video.
Desarrolla con MSDN
Puedes colaborar con MSDN Video, te proporcionamos materiales de formación, videos y
eventos para que aprendas las tecnologías utilizadas en la aplicación.

Tomado de:
http://www.desarrollaconmsdn.com/msdn/Cursos/Curso_Desarrollo_web_con_Visual_Stud
io_2005/index.html

You might also like