12. Servicios web
12.1. Introduction
En el capítulo anterior hemos presentado varias aplicaciones cliente-servidor TCP/IP. Dado que los clientes y el servidor intercambian líneas de texto, pueden escribirse en cualquier lenguaje. El cliente solo debe conocer el protocolo de comunicación que espera el servidor.
Los servicios web también son aplicaciones de servidor TCP/IP. Presentan las siguientes características:
- Están alojados en servidores web y el protocolo de intercambio cliente-servidor es HTTP (HyperText Transport Protocol), un protocolo que se superpone a TCP-IP.
- El servicio web tiene un protocolo de comunicación estándar independientemente del servicio que preste. Un servicio web ofrece diversos servicios: S1, S2, …, Sn. Cada uno de ellos espera los parámetros proporcionados por el cliente y le devuelve un resultado. Para cada servicio, el cliente necesita saber:
- el nombre exacto del servicio; si
- la lista de parámetros que debe proporcionar y su tipo
- el tipo de resultado que devuelve el servicio
Una vez conocidos estos elementos, el diálogo cliente-servidor sigue el mismo formato, independientemente del servicio web al que se acceda. De este modo, la comunicación de los clientes queda estandarizada.
- Por motivos de seguridad frente a los ataques procedentes de Internet, muchas organizaciones disponen de redes privadas y solo abren a Internet determinados puertos de sus servidores: fundamentalmente el puerto 80 del servicio web. Todos los demás puertos están bloqueados. Por ello, las aplicaciones cliente-servidor, tal y como se presentan en el capítulo anterior, se desarrollan dentro de la red privada (intranet) y, por lo general, no son accesibles desde el exterior. Alojar un servicio en un servidor web lo hace accesible a toda la comunidad de Internet.
- El servicio web puede modelarse como un objeto remoto. Los servicios ofrecidos se convierten entonces en métodos de dicho objeto. Un cliente puede acceder a este objeto remoto como si fuera local. Esto oculta toda la parte de comunicación de red y permite desarrollar un cliente independiente de esta capa. Si esta llegara a cambiar, no sería necesario modificar el cliente.
- Al igual que en las aplicaciones cliente-servidor TCP/IP presentadas en el capítulo anterior, tanto el cliente como el servidor pueden estar escritos en cualquier lenguaje. Intercambian líneas de texto. Estas constan de dos partes:
- las cabeceras necesarias para el protocolo HTTP
- el cuerpo del mensaje. En el caso de una respuesta del servidor al cliente, este tiene el formato XML (eXtensible Markup Language). En el caso de una solicitud del cliente al servidor, el cuerpo del mensaje puede adoptar varias formas, entre ellas XML. La solicitud XML del cliente puede tener un formato específico denominado SOAP (Simple Object Access Protocol). En este caso, la respuesta del servidor también sigue el formato SOAP.
La arquitectura de una aplicación cliente/servidor basada en un servicio web es la siguiente:
![]() |
Se trata de una extensión de la arquitectura de tres capas a la que se añaden clases de comunicación de red especializadas. Ya hemos visto una arquitectura similar con la aplicación gráfica de cliente Windows/servidor TCP de la Agencia Tributaria en el apartado 11.9.1.
Aclaremos estos conceptos generales con un primer ejemplo.
12.2. Un primer servicio web con Visual Web Developer
Vamos a crear una primera aplicación cliente/servidor con la siguiente arquitectura simplificada:
![]() |
12.2.1. La parte del servidor
Ya hemos señalado que un servicio web está alojado en un servidor web. La creación de un servicio web se enmarca en el ámbito general de la programación web del lado del servidor. Anteriormente hemos tenido ocasión de crear clientes web, lo cual también es programación web, pero en este caso del lado del cliente. El término «programación web» suele referirse más a la programación del lado del servidor que a la del lado del cliente. Para desarrollar servicios web o, en general, aplicaciones web, Visual C# no es la herramienta adecuada. Vamos a utilizar Visual Developer, una de las versiones Express de Visual Studio 2008 que se pueden descargar en la dirección [2]: [1]: [http://msdn.microsoft.com/fr-fr/express/future/bb421473(en-us).aspx] (mayo de 2008):
![]() |
- [1]: la dirección de descarga
- [2]: la pestaña de descargas
- [3]: descargar Visual Developer 2008
Para crear un primer servicio web, se puede proceder de la siguiente manera, tras iniciar Visual Developer:
![]() |
- [1]: seleccionar la opción «Archivo / Nuevo sitio web»
- [2]: elegir una aplicación de tipo ASP.NET «Servicio web»
- [3]: seleccionar el lenguaje de desarrollo: C#
- [4]: indicar la carpeta donde crear el proyecto
![]() |
- [5]: el proyecto creado en Visual Web Developer
- [6]: la carpeta del proyecto en el disco
Una aplicación web se estructura de la siguiente manera en Web Developer:
- una carpeta raíz en la que se encuentran los documentos del sitio web (páginas web estáticas HTML, imágenes, páginas web dinámicas .aspx, servicios web .asmx, etc.). En ella también se encuentra el archivo [web.config], que es el archivo de configuración de la aplicación web. Desempeña la misma función que el archivo [App.config] de las aplicaciones de Windows y tiene la misma estructura.
- una carpeta [App_Code] en la que se encuentran las clases e interfaces del sitio web destinadas a ser compiladas.
- una carpeta [App_Data] en la que se almacenarán los datos utilizados por las clases de [App_Code]. Por ejemplo, en ella se puede encontrar una base de datos SQL Server *.mdf.
[Service.asmx] es el servicio web cuya creación hemos solicitado. Solo contiene la siguiente línea:
<%@ WebService Language="C#" CodeBehind="~/App_Code/Service.cs" Class="Service" %>
El código fuente anterior está destinado al servidor web que alojará la aplicación. En modo de producción, este servidor suele ser IIS (Internet Information Server), el servidor web de Microsoft. Visual Web Developer incorpora un servidor web ligero que se utiliza en modo de desarrollo. La directiva anterior indica al servidor web:
- [Service.asmx] es un servicio web (directiva WebService)
- escrito en C# (atributo Language)
- que el código C# del servicio web se encuentra en el archivo [~/App_Code/Service.cs] (atributo CodeBehind). Ahí es donde el servidor web lo buscará para compilarlo.
- que la clase que implementa el servicio web se llama Service (atributo Class)
El código C# [Service.cs] del servicio web generado por Visual Developer es el siguiente:
using System.Web.Services;
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// Para permitir que este servicio web se llame desde un script, utilizando ASP.NET AJAX, descomenta la siguiente línea.
// [System.Web.Script.Services.ScriptService]
public class Service : System.Web.Services.WebService
{
public Service () {
//Descomenta la siguiente línea si utilizas componentes diseñados
//InitializeComponent();
}
[WebMethod]
public string HelloWorld() {
return "Hello World";
}
}
La clase Service se asemeja a una clase de C# clásica, aunque hay algunos aspectos que cabe destacar:
- línea 7: la clase deriva de la clase WebService definida en el espacio de nombres System.Web.Services. Esta herencia no siempre es obligatoria. En este ejemplo concreto, por ejemplo, se podría prescindir de ella.
- línea 3: la propia clase va precedida de un atributo [WebService(Namespace="http://tempuri.org/")] destinado a asignar un espacio de nombres al servicio web. Un proveedor de clases asigna un espacio de nombres a sus clases para dotarlas de un nombre único y evitar así conflictos con clases de otros proveedores que pudieran tener el mismo nombre. En el caso de los servicios web, ocurre lo mismo. Cada servicio web debe poder identificarse mediante un nombre único, en este caso http://tempuri.org/. Este nombre puede ser cualquiera. No tiene por qué tener necesariamente la forma de una URI HTTP.
- Línea 15: el método HelloWorld va precedido de un atributo [WebMethod] que indica al compilador que el método debe hacerse visible para los clientes remotos del servicio web. Un método que no vaya precedido de este atributo no es visible para los clientes del servicio web. Podría tratarse de un método interno utilizado por otros métodos, pero que no está destinado a ser publicado.
- Línea 9: el constructor del servicio web. No es necesario en nuestra aplicación.
La clase [Service.cs] generada se transforma de la siguiente manera:
using System.Web.Services;
[WebService(Namespace = "http://st.istia.univ-angers.fr")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
[WebMethod]
public string DisBonjourALaDame(string nomDeLaDame) {
return string.Format("Bonjour Mme {0}", nomDeLaDame);
}
}
El archivo de configuración [web.config] generado para la aplicación web es el siguiente:
<?xml version="1.0"?>
<!--
Note: As an alternative to hand editing this file you can use the
web admin tool to configure settings for your application. Use
the Website->Asp.Net Configuration option in Visual Studio.
A full list of settings and comments can be found in
machine.config.comments usually located in
\Windows\Microsoft.Net\Framework\v2.x\Config
-->
<configuration>
<configSections>
<sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
...
</sectionGroup>
</configSections>
<appSettings/>
<connectionStrings/>
...
</configuration>
El archivo tiene 140 líneas. Es complejo y no lo comentaremos. Lo mantendremos tal cual. En él aparecen las etiquetas <configuration>, <configSections>, <sectionGroup>, <appSettings> y <connectionString>, que ya hemos visto en el archivo [App.config] de las aplicaciones de Windows.
Tenemos un servicio web operativo que se puede ejecutar:
![]() |
- [1,2]: hacemos clic con el botón derecho en [Service.asmx] y solicitamos ver la página en un navegador
- [3]: Visual Web Developer inicia su servidor web integrado y coloca el icono de este en la esquina inferior derecha de la barra de tareas. El servidor web se inicia en un puerto aleatorio, en este caso el 1906. La URI que se muestra, /WsHello, es el nombre del sitio web [4].
Visual Web Developer también ha iniciado un navegador para mostrar la página solicitada, es decir, [Service.asmx]:
![]() |
- en [1], la URI de la página. Aparece la URI del sitio [http://localhost:1906/WsHello] seguida de la de la página /Service.asmx.
- En [2], el sufijo .asmx indica al servidor web que no se trata de una página web normal (sufijo .aspx) que genera una página HTML, sino de la página de un servicio web. A continuación, genera automáticamente una página web que muestra un enlace para cada uno de los métodos del servicio web con el atributo [WebMethod]. Estos enlaces permiten probar los métodos.
Al hacer clic en el enlace [2] anterior, se nos redirige a la siguiente página:
![]() |
- en [1], cabe destacar la URI [http://localhost:1906/WsHello/Service.asmx?op=DisBonjourALaDame] de la nueva página. Es la URI del servicio web con un parámetro op=M, donde M es el nombre de uno de los métodos del servicio web.
- Recordemos la firma del método [DisBonjourALaDame]:
public string DisBonjourALaDame(string nomDeLaDame) ;
El método admite un parámetro de tipo string y devuelve un resultado de tipo string. La página nos permite ejecutar el método [DisBonjourALaDame]: en [2] introducimos el valor del parámetro nomDeLaDame y en [3] solicitamos la ejecución del método. Obtenemos el siguiente resultado:
![]() |
- En [1], cabe señalar que la URI de la respuesta no es idéntica a la de la solicitud. Ha cambiado.
- En [2], la respuesta del servidor web. Cabe destacar lo siguiente:
- se trata de una respuesta XML y no de HTML
- el resultado del método [DisBonjourALaDame] está encapsulado en una etiqueta <string> que representa su tipo.
- la etiqueta <string> tiene un atributo xmlns (espacio de nombres XML), que es el espacio de nombres que le hemos asignado a nuestro servicio web (línea 1 a continuación).
[WebService(Namespace = "http://st.istia.univ-angers.fr")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
Para saber cómo ha realizado la solicitud el navegador web, hay que fijarse en el código HTML del formulario de prueba:
- línea 11: los valores del formulario (etiqueta «form») se enviarán (atributo «method») a la URL [ http://localhost:1906/WsHello/Service.asmx/DisBonjourALaDame] (atributo «action»).
- línea 19: el campo de entrada se llama nomDeLaDame (atributo «name»).
Al solicitar la ejecución del servicio web [/Service.asmx], hemos podido probar sus métodos y adquirir un conocimiento básico de las comunicaciones entre el cliente y el servidor.
12.2.2. La parte del cliente
![]() |
Es posible implementar el cliente del servicio web remoto anterior con un cliente TCP/IP básico. A continuación se muestra, a modo de ejemplo, el diálogo cliente/servidor realizado con un cliente putty conectado al servicio web remoto (localhost,1906):
- líneas 1-5: mensajes enviados por el cliente putty
- línea 1: comando POST
- líneas 6-10: respuesta del servidor. Significa que el cliente puede enviar los valores de POST.
- línea 11: los valores enviados en formato param1=val1¶m2=val2&... Algunos caracteres deben ser caracteres válidos en una URL. Esto es lo que antes se denominaba una URL codificada. En este caso, el formulario solo tiene un único parámetro denominado nomDeLaDame. El valor enviado tiene un total de 23 caracteres. Este tamaño debe declararse en el encabezado HTTP de la línea 4.
- líneas 12-22: la respuesta del servidor
- línea 22: el resultado del método web [DisBonjourALaDame].
Con Visual C#, es posible generar, mediante un asistente, el cliente de un servicio web remoto. Eso es lo que vamos a ver ahora.
![]() |
La capa [1] anterior se implementa mediante un proyecto de Visual Studio C# de tipo «Aplicación de Windows» denominado ClientWsHello:
![]() |
- en [1], el proyecto ClientWsHello en Visual C#
- en [2], el espacio de nombres predeterminado del proyecto será Client (clic con el botón derecho del ratón sobre el proyecto / Propiedades / Aplicación). Este espacio de nombres servirá para construir el espacio de nombres del cliente que se va a generar.
- En [3], haz clic con el botón derecho del ratón sobre el proyecto para añadirle una referencia a un servicio web remoto
![]() |
- En [4], introduce la URI del servicio web creado anteriormente
- En [4b], conecte Visual C# al servicio web indicado en [4]. Visual C# recuperará la descripción del servicio web y, gracias a ella, podrá generar un cliente.
- En [5], una vez recuperada la descripción del servicio web, Visual C# puede mostrar sus métodos públicos
- En [6], indique un espacio de nombres para el cliente que se va a generar. Este se añadirá al espacio de nombres definido en [2]. De este modo, el espacio de nombres del cliente será Client.WsHello.
- En [6b], confirme el asistente.
- En [7], la referencia al servicio web WsHello aparece en el proyecto. Además, se ha creado un archivo de configuración [app.config].
- En [8], visualice todos los archivos del proyecto.
- En [9], la referencia al servicio web WsHello contiene varios archivos que no vamos a detallar. Sin embargo, echaremos un vistazo al archivo [Reference.cs], que es el código C# del cliente generado:
namespace Client.WsHello {
...
public partial class ServiceSoapClient : System.ServiceModel.ClientBase<Client.WsHello.ServiceSoap>, Client.WsHello.ServiceSoap {
public ServiceSoapClient() {
}
...
public string DisBonjourALaDame(string nomDeLaDame) {
Client.WsHello.DisBonjourALaDameRequest inValue = new Client.WsHello.DisBonjourALaDameRequest();
inValue.Body = new Client.WsHello.DisBonjourALaDameRequestBody();
inValue.Body.nomDeLaDame = nomDeLaDame;
Client.WsHello.DisBonjourALaDameResponse retVal = ((Client.WsHello.ServiceSoap)(this)).DisBonjourALaDame(inValue);
return retVal.Body.DisBonjourALaDameResult;
}
}
}
- línea 1: el espacio de nombres del cliente generado es Client.WsHello. Si se desea cambiar este espacio de nombres, es aquí donde hay que hacerlo.
- Línea 3: la clase ServiceSoapClient es la clase del cliente generado. Se trata de una clase proxy, en el sentido de que ocultará a la aplicación de Windows el hecho de que se está utilizando un servicio web remoto. La aplicación de Windows utilizará la clase remota WsHello a través de la clase local Client.WsHello.ServiceSoapClient. Para crear una instancia del cliente, se utilizará el constructor de la línea 5:
- línea 8: el método DisBonjourALaDame es el equivalente, en el lado del cliente, del método DisBonjourALaDame del servicio web. La aplicación de Windows utilizará el método remoto DisBonjourALaDame a través del método local Client.WsHello.ServiceSoapClient.DisBonjourALaDame de la siguiente forma:
El archivo [app.config] generado es el siguiente:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
....
</bindings>
<client>
<endpoint address="http://localhost:1906/WsHello/Service.asmx"... />
</client>
</system.serviceModel>
</configuration>
De este archivo, solo tomaremos la línea 8, que contiene la URI del servicio web. Si esta cambia, no es necesario recompilar el cliente de Windows. Basta con cambiar la URI del archivo [app.config].
Volvamos a la arquitectura de la aplicación de Windows que queremos crear:
![]() |
Hemos creado la capa [client] del servicio web. La capa [ui] será la siguiente:
![]() |
n.º | tipo | nombre | función |
1 | TextBox | textBoxNomDame | nombre de la mujer |
2 | Button | buttonSalutations | para conectarse al servicio web remoto WsHello y consultar el método DisBonjourALaDame. |
3 | Etiqueta | labelBonjour | el resultado devuelto por el servicio web |
El código del formulario [Form1.cs] es el siguiente:
using System;
using System.Windows.Forms;
using Client.WsHello;
namespace ClientSalutations {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void buttonSalutations_Click(object sender, EventArgs e) {
// reloj de arena
Cursor=Cursors.WaitCursor;
// consulta al servicio web
labelBonjour.Text = new ServiceSoapClient().DisBonjourALaDame(textBoxNomDame.Text.Trim());
// cursor normal
Cursor = Cursors.Arrow;
}
}
}
- Línea 15: se instancia el cliente del servicio web. Es de tipo Client.WsHello.ServiceSoapClient. El espacio de nombres Client.WsHello se declara en la línea 3. Se invoca el método local ServiceSoapClient().DisBonjourALaDame. Sabemos que este, a su vez, consulta el método remoto del mismo nombre del servicio web.
12.3. Un servicio web de operaciones aritméticas
Vamos a crear una segunda aplicación cliente/servidor que, de nuevo, tenga la siguiente arquitectura simplificada:
![]() |
El servicio web anterior ofrecía un único método. Consideremos un servicio web que ofrecerá las cuatro operaciones aritméticas:
- sumar(a,b), que devolverá a+b
- restar(a,b), que devolverá a-b
- multiplicar(a,b), que devolverá a*b
- dividir(a,b), que devolverá a/b
y al que se accederá a través de la siguiente interfaz gráfica:
![]() |
- en [1], la operación a realizar
- en [2,3]: los operandos
- en [4], el botón para llamar al servicio web
- en [5], el resultado devuelto por el servicio web
12.3.1. La parte del servidor
Creamos un proyecto de tipo servicio web con Visual Web Developer:
![]() |
- en [1], la aplicación web WsOperations generada
- En [2], la aplicación web WsOperations se ha rediseñado de la siguiente manera:
- la página web [Service.asmx] ha pasado a llamarse [Operations.asmx]
- la clase [Service.cs] ha pasado a llamarse [Operations.cs]
- el archivo [web.config] se ha eliminado para demostrar que no es imprescindible.
La página web [Service.asmx] contiene la siguiente línea:
<%@ WebService Language="C#" CodeBehind="~/App_Code/Operations.cs" Class="Operations" %>
El servicio web lo presta la siguiente clase [Operations.cs]:
using System.Web.Services;
[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Operations : System.Web.Services.WebService
{
[WebMethod]
public double Ajouter(double a, double b)
{
return a + b;
}
[WebMethod]
public double Soustraire(double a, double b)
{
return a - b;
}
[WebMethod]
public double Multiplier(double a, double b)
{
return a * b;
}
[WebMethod]
public double Diviser(double a, double b)
{
return a / b;
}
}
Para publicar el servicio web, seguimos los pasos indicados en [3]. De este modo, obtenemos la página de prueba de los cuatro métodos del servicio web WsOperations:

Se invita al lector a probar los cuatro métodos.
12.3.2. La parte del cliente
![]() |
Con Visual C# creamos una aplicación de Windows ClientWsOperations:
![]() |
- en [1], el proyecto ClientWsOperations en Visual C#
- en [2], el espacio de nombres predeterminado del proyecto será Client (clic con el botón derecho del ratón sobre el proyecto / Propiedades / Aplicación). Este espacio de nombres servirá para construir el espacio de nombres del cliente que se va a generar.
- En [3], haz clic con el botón derecho del ratón sobre el proyecto para añadirle una referencia a un servicio web existente
![]() |
- En [4], introduce la URI del servicio web creado anteriormente. Para ello, debes fijarte en lo que aparece en el campo de dirección del navegador que muestra la página de prueba del servicio web.
- En [4b], conecta Visual C# al servicio web indicado por [4]. Visual C# recuperará la descripción del servicio web y, gracias a ella, podrá generar un cliente.
- En [5], una vez recuperada la descripción del servicio web, Visual C# puede mostrar sus métodos públicos
- En [6], indicar un espacio de nombres para el cliente que se va a generar. Este se añadirá al espacio de nombres definido en [2]. De este modo, el espacio de nombres del cliente será Client.WsOperations.
- En [6b], confirme el asistente.
- En [7], la referencia al servicio web WsOperations aparece en el proyecto. Además, se ha creado un archivo de configuración [app.config].
Cabe recordar que el cliente generado es de tipo Client.WsOperations.OperationsSoapClient, donde
- Client.WsOperations es el espacio de nombres del cliente del servicio web
- Operations es la clase del servicio web remoto.
Aunque existe una forma lógica de construir este nombre, a menudo resulta más sencillo encontrarlo en el archivo [Reference.cs], que es un archivo oculto por defecto. Su contenido es el siguiente:
namespace Client.WsOperations {
...
public partial class OperationsSoapClient : System.ServiceModel.ClientBase<Client.WsOperations.OperationsSoap>, Client.WsOperations.OperationsSoap {
public OperationsSoapClient() {
}
...
public double Ajouter(double a, double b) {
...
}
public double Soustraire(double a, double b) {
...
}
public double Multiplier(double a, double b) {
...
}
public double Diviser(double a, double b) {
...
}
}
}
Se accederá a los métodos Ajouter, Soustraire, Multiplier, Diviser del servicio web remoto se accederán a través de los métodos proxy del mismo nombre (líneas 8, 12, 16, 20) del cliente de tipo Client.WsOperations.OperationsSoapClient (línea 3).
Ahora solo nos queda crear la interfaz gráfica:
![]() |
n.º | tipo | nombre | función |
1 | ComboBox | comboBoxOperations | lista de operaciones aritméticas |
2 | TextBox | textBoxA | número a |
3 | TextBox | textBoxB | número b |
4 | Botón | buttonExécuter | consulta el servicio web remoto |
5 | Etiqueta | labelRésultat | el resultado de la operación |
El código de [Form1.cs] es el siguiente:
using System;
using System.Windows.Forms;
using Client.WsOperations;
namespace ClientWsOperations {
public partial class Form1 : Form {
// tabla de operaciones
private string[] opérations = { "Ajouter", "Soustraire", "Multiplier", "Diviser" };
// servicio web al que dirigirse
private OperationsSoapClient opérateur = new OperationsSoapClient();
// constructor
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
// autocompletado del menú desplegable de operaciones
comboBoxOperations.Items.AddRange(opérations);
comboBoxOperations.SelectedIndex = 0;
}
private void buttonExécuter_Click(object sender, EventArgs e) {
// verificación de los parámetros a y b de la operación
textBoxMessage.Text = "";
bool erreur = false;
Double a = 0;
if (!Double.TryParse(textBoxA.Text, out a)) {
textBoxMessage.Text += "Nombre a erroné...";
}
Double b = 0;
if (!Double.TryParse(textBoxB.Text, out b)) {
textBoxMessage.Text += String.Format("{0}Nombre b erroné...", Environment.NewLine);
}
if (erreur) {
return;
}
// ejecución de la operación
Double c=0;
try {
switch (comboBoxOperations.SelectedItem.ToString()) {
case "Ajouter":
c=opérateur.Ajouter(a, b);
break;
case "Soustraire":
c=opérateur.Soustraire(a, b);
break;
case "Multiplier":
c=opérateur.Multiplier(a, b);
break;
case "Diviser":
c=opérateur.Diviser(a, b);
break;
}
// visualización del resultado
labelRésultat.Text = c.ToString();
} catch (Exception ex) {
textBoxMessage.Text = ex.Message;
}
}
}
}
- línea 3: el espacio de nombres del cliente del servicio web remoto
- línea 10: el cliente del servicio web remoto se instancia al mismo tiempo que el formulario
- líneas 17-21: el menú desplegable de operaciones se rellena durante la carga inicial del formulario
- línea 23: ejecución de la operación solicitada por el usuario
- líneas 25-37: se comprueba que los valores introducidos a y b sean números reales
- líneas 41-54: un switch para ejecutar la operación remota solicitada por el usuario
- líneas 43, 46, 49 y 52: se consulta al cliente local. De forma transparente, este último consulta al servicio web remoto.
12.4. Un servicio web de cálculo de impuestos
Retomamos la ya conocida aplicación de cálculo de impuestos. La última vez que trabajamos con ella, la convertimos en un servidor TCP remoto al que se podía acceder a través de Internet. Ahora la convertimos en un servicio web.
La arquitectura de la versión 8 era la siguiente:
![]() |
La arquitectura de la versión 9 será similar:
![]() |
Se trata de una arquitectura similar a la de la versión 8 analizada en el apartado 11.9.1, pero en la que el servidor y el cliente TCP se sustituyen por un servicio web y su cliente proxy. Vamos a retomar íntegramente las capas [ui], [metier] y [dao] de la versión 8.
12.4.1. La parte del servidor
Creamos un proyecto de tipo servicio web con Visual Web Developer:
![]() |
- en [1], la aplicación web WsImpot generada
- en [2], la aplicación web WsImpot rediseñada de la siguiente manera:
- la página web [Service.asmx] ha pasado a llamarse [ServiceImpot.asmx]
- la clase [Service.cs] ha pasado a llamarse [ServiceImpot .cs]
La página web [ServiceImpot.asmx] contiene la siguiente línea:
<%@ WebService Language="C#" CodeBehind="~/App_Code/ServiceImpot.cs" Class="ServiceImpot" %>
El servicio web lo presta la siguiente clase: [ServiceImpot.cs]:
using System.Web.Services;
[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class ServiceImpot : System.Web.Services.WebService
{
[WebMethod]
public int CalculerImpot(bool marié, int nbEnfants, int salaire)
{
return 0;
}
}
El servicio web solo expondrá el método CalculerImpot de la línea 9.
Volvamos a la arquitectura cliente/servidor de la versión 8:
![]() |
El proyecto de Visual Studio del servidor [1] era el siguiente:
![]() |
- En [1], el proyecto. En él se encontraban los siguientes elementos:
- [ServeurImpot.cs]: el servidor TCP/IP de cálculo de impuestos en forma de aplicación de consola.
- [dbimpots.sdf]: la base de datos SQL Server Compact de la versión 7 descrita en el apartado 9.8.5.
- [App.config]: el archivo de configuración de la aplicación.
- En [2], la carpeta [lib] contiene los archivos DLL necesarios para el proyecto:
- [ImpotsV7-dao]: la capa [dao] de la versión 7
- [ImpotsV7-metier]: la capa [metier] de la versión 7
- [antlr.runtime, CommonLogging, Spring.Core] para Spring
- en [3], las referencias del proyecto
Las capas [metier] y [dao] de esta versión ya existen: son las que se utilizan en las versiones 7 y 8. Se presentan en forma de DLL, que integramos en el proyecto de la siguiente manera:
![]() |
- en [1], la carpeta [lib] del servidor de la versión 8 se ha copiado en el proyecto del servicio web de la versión 9.
- En [2], modificamos las propiedades de la página para añadir los archivos DLL de la carpeta [lib] y [4] a las referencias del proyecto [3].
Tras esta operación, disponemos de todas las capas necesarias para el servidor [1] que se muestra a continuación:
![]() |
Si los elementos del servidor [1], [serveur], [metier], [dao], [entites] y [spring] están todos presentes en el proyecto de Visual Studio, nos falta el elemento que los instanciará al iniciar la aplicación web. En la versión 8, una clase principal que tenía el método estático [Main] se encargaba de instanciar las capas con la ayuda de Spring. En una aplicación web, la clase capaz de realizar una tarea similar es la clase asociada al archivo [Global.asax] :
![]() |
- en [1], se añade un nuevo elemento al proyecto web
- en [2], se selecciona el tipo «Global Application Class»
- en [3], el nombre propuesto por defecto para este elemento
- en [4], se confirma la incorporación
- en [5], el nuevo elemento se ha integrado en el proyecto
Veamos el contenido del archivo [Global.asax]:
<%@ Application Language="C#" %>
<script runat="server">
void Application_Start(object sender, EventArgs e)
{
// Código que se ejecuta al iniciar la aplicación
}
void Application_End(object sender, EventArgs e)
{
// Código que se ejecuta al cerrar la aplicación
}
void Application_Error(object sender, EventArgs e)
{
// Código que se ejecuta cuando se produce un error no gestionado
}
void Session_Start(object sender, EventArgs e)
{
// Código que se ejecuta al iniciar una nueva sesión
}
void Session_End(object sender, EventArgs e)
{
// Código que se ejecuta al finalizar una sesión.
}
</script>
El archivo es una mezcla de etiquetas destinadas al servidor web (líneas 1, 3 y 30) y código C#. Este método era el único que se utilizaba con ASP, el antecesor de ASP.NET, que es la tecnología actual de Microsoft para la programación web. Con ASP.NET, este método sigue siendo utilizable, pero no es el método predeterminado. El método predeterminado es el denominado «CodeBehind», que hemos encontrado en las páginas de los servicios web, por ejemplo, aquí en [ServiceImpot.asmx]:
<%@ WebService Language="C#" CodeBehind="~/App_Code/ServiceImpot.cs" Class="ServiceImpot" %>
El atributo CodeBehind especifica dónde se encuentra el código fuente de la página [ServiceImpot.asmx]. Sin este atributo, el código fuente estaría en la página [ServiceImpot.asmx] con una sintaxis similar a la que se encuentra en [Global.asax]. No conservaremos el archivo [Global.asax] tal y como se generó, pero su código nos permite comprender para qué sirve:
- la clase asociada a Global.asax se instancia al iniciar la aplicación. Su ciclo de vida es el de toda la aplicación. En concreto, solo desaparece cuando se detiene el servidor web.
- A continuación, se ejecuta el método Application_Start. Es la única vez que se ejecutará. Por eso se utiliza para instanciar objetos compartidos entre todos los usuarios. Estos objetos se colocan:
- o bien en los campos estáticos de la clase asociada a Global.asax. Dado que esta clase está presente de forma permanente, cualquier usuario puede consultar la información que contiene en cualquier consulta.
- o bien en el contenedor Application. Este contenedor también se crea al iniciar la aplicación y su vida útil es la misma que la de la aplicación.
- Para introducir un dato en este contenedor, se escribe Application["clé"]=valor;
- para recuperarlo, se escribe T valor=(T)Application["clé"]; donde T es el tipo de valeur.
- El método Session_Start se ejecuta cada vez que un nuevo usuario realiza una solicitud. ¿Cómo se reconoce a un nuevo usuario? Cada usuario (normalmente un navegador) recibe, al finalizar su primera solicitud, un token de sesión que es una cadena de caracteres única para cada usuario. A continuación, el usuario reenvía el token de sesión que ha recibido con cada nueva solicitud que realiza. Esto permite al servidor web reconocerlo. A lo largo de las diferentes solicitudes de un mismo usuario, los datos que le son propios pueden almacenarse en el contenedor Session:
- para introducir un dato en este contenedor, se escribe Session["clé"]=valor;
- para recuperarlo, se escribe T valor=(T)Session["clé"]; donde T es el tipo de valeur.
La duración de una sesión está limitada por defecto a 20 minutos de inactividad del usuario (c.a.d, es decir, que no ha reenviado su token de sesión desde hace 20 minutos).
- El método Application_Error se ejecuta cuando una excepción no gestionada por la aplicación web llega hasta el servidor web.
- Los demás métodos se utilizan con menos frecuencia.
Tras estas consideraciones generales, ¿para qué nos puede servir Global.asax? Vamos a utilizar su método Application_Start para inicializar las capas [metier], [dao] y [entites] contenidas en DLL y [ImpotsV7-metier, ImpotsV7-dao]. Utilizaremos Spring para instanciarlas. Las referencias de las capas así creadas se almacenarán a continuación en campos estáticos de la clase asociada a Global.asax.
En primer lugar, trasladamos el código C# de Global.asax a una clase independiente. El proyecto queda de la siguiente manera:
![]() |
En [1], el archivo [Global.asax] se asociará a la clase [Global.cs] [2] con la siguiente línea única:
<%@ Application Language="C#" Inherits="WsImpot.Global"%>
El atributo Inherits="WsImpot.Global" indica que la clase asociada a Global.asax hereda de la clase WsImpot.Global. Esta clase se define en [Global.cs] de la siguiente manera:
using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
public class Global : System.Web.HttpApplication
{
// Capa de negocio
public static IImpotMetier Metier;
// Método que se ejecuta al iniciar la aplicación
private void Application_Start(object sender, EventArgs e)
{
// instancias de las capas [metier] y [dao]
Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
}
}
}
- línea 4: el espacio de nombres de la clase
- línea 6: la clase Global. Se le puede dar el nombre que se desee. Lo importante es que derive de la clase System.Web.HttpApplication.
- línea 9: un campo estático público que contendrá una referencia a la capa [metier].
- línea 12: el método Application_Start, que se ejecutará al iniciar la aplicación.
- línea 15: se utiliza Spring para procesar el archivo [web.config], en el que encontrará los objetos que debe instanciar para crear las capas [metier] y [dao]. No hay ninguna diferencia entre el uso de Spring con [App.config] en una aplicación de Windows y el de Spring con [web.config] en una aplicación web. Además, [web.config] y [App.config] tienen la misma estructura. La línea 15 almacena la referencia de la capa [metier] en el campo estático de la línea 9, de modo que dicha referencia esté disponible para todas las consultas de todos los usuarios.
El archivo [web.config] tendrá el siguiente aspecto:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="dao" type="Dao.DataBaseImpot, ImpotsV7-dao">
<constructor-arg index="0" value="MySql.Data.MySqlClient"/>
<constructor-arg index="1" value="Server=localhost;Database=bdimpots;Uid=admimpots;Pwd=mdpimpots;"/>
<constructor-arg index="2" value="select limite, coeffr, coeffn from tranches"/>
</object>
<object name="metier" type="Metier.ImpotMetier, ImpotsV7-metier">
<constructor-arg index="0" ref="dao"/>
</object>
</objects>
</spring>
</configuration>
Este es el archivo [App.config] utilizado en la versión 7 de la aplicación y analizado en el apartado 9.8.4.
- Líneas 16-20: definen una capa [dao] que funciona con una base de datos MySQL5. Esta base de datos se ha descrito en el apartado 9.8.1.
- líneas 21-23: definen la capa [metier]
Volvamos al rompecabezas del servidor:
![]() |
Al iniciar la aplicación, se instanciaron las capas [metier] y [dao]. La vida útil de las capas es la misma que la de la propia aplicación. ¿Cuándo se instancia el servicio web? De hecho, cada vez que se le envía una solicitud. Al finalizar la solicitud, se elimina el objeto que la ha atendido. Por lo tanto, a primera vista, un servicio web no tiene estado. No puede almacenar información entre dos solicitudes en campos que le pertenezcan. Sí puede almacenarla en la sesión del usuario. Para ello, los métodos que expone deben etiquetarse con un atributo especial:
[WebMethod(EnableSession=true)]
public int CalculerImpot(bool marié, int nbEnfants, int salaire)
....
En el ejemplo anterior, la línea 1 permite que el método CalculerImpot tenga acceso al contenedor Session del que hemos hablado anteriormente. No tendremos que utilizar este atributo en nuestra aplicación. Por lo tanto, el servicio web WsImpot se instanciará en cada solicitud y será sin estado.
Ahora podemos escribir el código [ServiceImpot.cs] que implementa el servicio web:
using System.Web.Services;
using WsImpot;
[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class ServiceImpot : System.Web.Services.WebService
{
[WebMethod]
public int CalculerImpot(bool marié, int nbEnfants, int salaire)
{
return Global.Metier.CalculerImpot(marié, nbEnfants, salaire);
}
}
- línea 10: el único método del servicio web
- línea 12: se utiliza el método CalculerImpot de la capa [metier]. En el campo estático Metier de la clase Global se encuentra una referencia a esta capa. Esta pertenece al espacio de nombres WsImpot (línea 2).
Ya estamos listos para iniciar el servicio web. Antes hay que iniciar SGBD y MySQL5 para que se pueda acceder a la base de datos bdimpots. Una vez hecho esto, iniciamos [1], el servicio web:
![]() |
A continuación, el navegador muestra la página [2]. Seguimos el enlace:
![]() |
Asignamos un valor a cada uno de los tres parámetros del método CalculerImpot y solicitamos la ejecución del método. Obtenemos el siguiente resultado, que es correcto:

12.4.2. Un cliente gráfico de Windows para el servicio web remoto
Ahora que ya hemos escrito el servicio web, pasamos al cliente. Repasemos la arquitectura de la aplicación cliente/servidor:
![]() |
Tenemos que escribir el cliente [2]. La interfaz gráfica será idéntica a la de la versión 8:
![]() |
Para desarrollar la parte [client] de la versión 9, partiremos de la parte [client] de la versión 8 y, a continuación, realizaremos las modificaciones necesarias. Duplicamos el proyecto de Visual Studio analizado en el apartado 11.9.4.1, lo renombramos como ClientWsImpot y lo abrimos en Visual Studio:
![]() |
La solución de Visual Studio de la versión 8 constaba de dos proyectos:
- el proyecto [metier] [1], que era un cliente TCP del servidor TCP de cálculo de impuestos
- el proyecto [ui] [2] de la interfaz gráfica.
Los cambios que hay que realizar son los siguientes:
- el proyecto [metier] debe ser a partir de ahora el cliente de un servicio web
- el proyecto [ui] debe hacer referencia al DLL de la nueva capa [metier]
- la configuración de la capa [metier] en [App.config] debe modificarse.
12.4.2.1. La nueva capa [metier]
![]() |
- en [1], IImpotMetier es la interfaz de la capa [metier] y ImpotMetierTcp es su implementación mediante un cliente TCP
- en [2], eliminamos la implementación ImpotMetierTcp. Debemos crear otra implementación de la interfaz IImpotMetier que será cliente de un servicio web.
- En [3], denominamos Client al espacio de nombres por defecto del proyecto [metier]. El archivo DLL que se generará se llamará [ImpotsV9-metier.dll].
![]() |
- en [4], creamos una referencia al servicio web WsImpot.
- En [5], lo configuramos y lo validamos.
- En [6], se ha creado la referencia al servicio web WsImpot y se ha generado un archivo [app.config].
En el archivo oculto [Reference.cs]:
- el espacio de nombres es Client.WsImpot
- la clase cliente se llama ServiceImpotSoapClient
- tiene un único método de firma:
public int CalculerImpot(bool marié, int nbEnfants, int salaire) ;
Ahora nos queda implementar la interfaz IImpotMetier:
namespace Metier {
public interface IImpotMetier {
int CalculerImpot(bool marié, int nbEnfants, int salaire);
}
}
La implementamos con la siguiente clase ImpotMetierWs:
using System.Net.Sockets;
using System.IO;
using Client.WsImpot;
namespace Metier {
public class ImpotMetierWs : IImpotMetier {
// cliente del servicio web remoto
private ServiceImpotSoapClient client = new ServiceImpotSoapClient();
// cálculo del impuesto
public int CalculerImpot(bool marié, int nbEnfants, int salaire) {
return client.CalculerImpot(marié, nbEnfants, salaire);
}
}
}
- línea 6: la clase ImpotMetierWs implementa la interfaz IImpotMetier.
- línea 9: al crear una instancia de ImpotMetierWs, el campo client se inicializa con una instancia de un cliente del servicio web de cálculo de impuestos.
- línea 12: el único método de la interfaz IImpotMetier que hay que implementar.
- línea 13: se utiliza el método CalculerImpot del cliente del servicio web remoto de cálculo de impuestos. En definitiva, se consultará el método CalculerImpot del servicio web remoto.
Se puede generar el DLL del proyecto:
![]() |
- en [1], el proyecto [client] en su estado final
- en [2], generación del archivo DLL del proyecto
- en [3], el archivo DLL y ImpotsV9-metier.dll se encuentran en la carpeta /bin/Release del proyecto.
12.4.2.2. La nueva capa [ui]
![]() |
Se ha escrito la capa [client] del cliente. Nos queda por escribir la capa [ui]. Volvamos al proyecto de Visual Studio:
![]() |
- en [1], el proyecto [ui] procedente de la versión 8
- en [2], el DLL ImpotsV8-metier de la antigua capa [metier] se sustituye por el DLL ImpotsV9-metier de la nueva capa
- en [3], se añaden DLL y ImpotsV9-metier a las referencias del proyecto.
El segundo cambio se produce en [App.config]. Hay que tener en cuenta que Spring utiliza este archivo para instanciar la capa [metier]. Dado que esta ha cambiado, la configuración de [App.config] debe modificarse. Por otra parte, [App.config] debe tener la configuración que permita conectarse al servicio web remoto de cálculo de impuestos. Esta configuración se generó en el archivo [app.config] del proyecto [metier] cuando se añadió en él la referencia al servicio web remoto.
Por lo tanto, el archivo [App.config] queda así:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="metier" type="Metier.ImpotMetierWs, ImpotsV9-metier">
</object>
</objects>
</spring>
<!-- servicio web -->
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="ServiceImpotSoap" closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false"
bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
</client>
</system.serviceModel>
</configuration>
- líneas 15-18: Spring solo instancia un objeto, la capa [metier]
- línea 16: la capa [metier] es instanciada por la clase [Metier.ImpotMetierWs], que se encuentra en DLL ImpotsV9-metier.
- Líneas 22-46: la configuración del cliente del servicio web remoto. Se trata de copiar y pegar el contenido del archivo [app.config] del proyecto [metier].
Ya estamos listos. Ejecutamos la aplicación con Ctrl+F5 (el servicio web debe estar en marcha, los archivos SGBD y MySQL5 deben estar en marcha, y el puerto de la línea 42 anterior debe ser el correcto):
![]() |
12.5. Un cliente web para el servicio web de cálculo de impuestos
Repasemos la arquitectura de la aplicación cliente/servidor que acabamos de escribir:
![]() |
La capa [ui] anterior se implementó mediante un cliente gráfico de Windows. Ahora la implementamos con una interfaz web:
![]() |
Se trata de un cambio importante para los usuarios. Actualmente, nuestra aplicación cliente/servidor, versión 9, puede atender a varios clientes simultáneamente. Esto supone una mejora con respecto a la versión 8, en la que solo atendía a un cliente a la vez. La limitación es que los usuarios que deseen utilizar el servicio web de cálculo de impuestos deben tener instalado en su ordenador el cliente de Windows que hemos desarrollado. En esta nueva versión, que denominaremos versión 10, los usuarios podrán acceder, a través de su navegador, al servicio web de cálculo de impuestos.
En la arquitectura anterior:
- el lado del servidor no cambia. Sigue siendo igual que en la versión 9.
- En el lado del cliente, la capa [client du service web] no cambia. Se ha encapsulado en DLL y [ImpotsV9-metier]. Vamos a reutilizar esta DLL.
- En definitiva, el único cambio consiste en sustituir una interfaz gráfica de Windows por una interfaz web.
Vamos a abordar nuevos conceptos de programación web del lado del servidor. Dado que el objetivo de este documento no es enseñar programación web, intentaremos explicar el procedimiento que se va a seguir, pero sin entrar en detalles. Por lo tanto, esta sección tendrá un toque un poco «mágico». No obstante, nos parece interesante seguir este procedimiento para mostrar un nuevo ejemplo de arquitectura multicapa en la que se modifica una de las capas.
La arquitectura de la versión 10 es, por tanto, la siguiente:
![]() |
Ya disponemos de todas las capas, excepto la de la capa [web]. Para comprender mejor lo que se va a hacer, necesitamos ser más precisos sobre la arquitectura del cliente. Será la siguiente:
![]() |
- El usuario web tiene en su navegador un formulario web
- este formulario se envía al servidor web 1, que lo procesa mediante la capa [web]
- la capa [web] necesitará los servicios del cliente del servicio web remoto, encapsulados en [ImpotsV9-metier.dll].
- El cliente del servicio web remoto se comunicará con el servidor web 2, que aloja el servicio web remoto.
- La respuesta del servicio web remoto se transmitirá hasta la capa web del cliente, que la maquetará en una página que enviará al usuario.
Por lo tanto, nuestra tarea aquí es:
- crear el formulario web que verá el usuario en su navegador
- escribir la aplicación web que procesará la solicitud del usuario y le enviará una respuesta en forma de una nueva página web. Esta será, de hecho, la misma que el formulario, al que se habrá añadido el importe del impuesto a pagar
- escribir el «glue» que hace que todo esto funcione en conjunto.
Todo esto se hará con ayuda de una nueva página web creada con Visual Web Developer:
![]() |
- [1]: selecciona la opción Archivo / Nuevo sitio web
- [2]: elegir un tipo de aplicación «ASP.NET Web Site»
- [3]: elegir el lenguaje de desarrollo: C#
- [4]: indicar la carpeta donde crear el proyecto
![]() |
- [5]: el proyecto creado en Visual Web Developer
- [Default.aspx] es una página web denominada «página por defecto». Es la que se mostrará si se solicita la URL http://.../ClientAspImpot sin especificar ningún documento. Esta página es la que contendrá el formulario de cálculo de impuestos que el usuario verá en su navegador.
- [Default.aspx.cs] es la clase asociada a la página, la que generará el formulario que se enviará al usuario y lo procesará una vez que este lo haya rellenado y validado.
- [web.config] es el archivo de configuración de la aplicación. A diferencia de las veces anteriores, este lo vamos a conservar.
Si volvemos a la arquitectura que debemos construir:
![]() |
- [1] será implementado por [Default.aspx]
- [2] se implementará mediante [Default.aspx.cs]
- [3] se implementará mediante DLL y [ImpotV9-metier]
Empecemos por implementar la capa [3]. Hay varios pasos:
![]() |
- En [1], la carpeta [lib] del cliente gráfico de Windows versión 9 se copia en la carpeta del proyecto web [ClientAspWsImpot]. Esto se hace con el Explorador de Windows. Para que esta carpeta aparezca en la solución de Web Developer, hay que actualizar la solución con el botón [2].
- A continuación, hay que añadirlas a las referencias del proyecto [3,4,5]. Las DLL a las que se hace referencia se copian automáticamente en la carpeta /bin del proyecto [6].
Ahora disponemos de los DLL necesarios para el funcionamiento de Spring y la capa de cliente del servicio web remoto también está implementada. Aunque el código de este último está presente, aún hay que configurarlo. En la versión 9, se configuraba mediante el siguiente archivo [App.config]:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="metier" type="Metier.ImpotMetierWs, ImpotsV9-metier">
</object>
</objects>
</spring>
<!-- servicio web -->
<system.serviceModel>
<bindings>
<basicHttpBinding>
...
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
</client>
</system.serviceModel>
</configuration>
Retomamos esta configuración tal cual y la integramos en el archivo [web.config] de la siguiente manera:
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="system.web.extensions"...>
...
</sectionGroup>
<!-- Inicio de la sección Spring -->
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
<!-- fin de la sección Spring -->
</configSections>
<!-- Inicio de la configuración de Spring -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="metier" type="Metier.ImpotMetierWs, ImpotsV9-metier">
</object>
</objects>
</spring>
<!-- fin de la configuración de Spring -->
<!-- Inicio de la configuración del cliente del servicio web remoto -->
<system.serviceModel>
<bindings>
<basicHttpBinding>
...
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
</client>
</system.serviceModel>
<!-- fin de la configuración del cliente del servicio web remoto -->
<!-- otras configuraciones ya presentes en el web.config generado -->
...
</configuration>
Cabe señalar que la línea 37 hace referencia al puerto del servicio web remoto. Este puerto puede variar, ya que Visual Developer inicia el servicio web en un puerto aleatorio.
Volvamos a la arquitectura del cliente web que debemos crear:
![]() |
- [1] se implementará mediante [Default.aspx]
- [2] se implementará mediante [Default.aspx.cs]
- [3] ha sido implementada por DLL y [ImpotV9-metier]
Acabamos de implementar la capa [3]. Pasamos a la interfaz web [1], implementada por la página [Default.aspx]. Hacemos doble clic en la página [Default.aspx] para pasar al modo de diseño.
![]() |
Hay dos formas de crear una página web:
- gráficamente, como en [2]. Para ello, hay que seleccionar el modo [Design] en [1]. Esta barra de botones se encuentra en la parte inferior de la barra de estado del editor de la página web.
- con un lenguaje de etiquetas, como en [3]. En ese caso, hay que seleccionar el modo [Source] en [1].
Los modos [Design] y [Source] son bidireccionales: una modificación realizada en el modo [Design] se traduce en una modificación en el modo [Source] y viceversa. Recordemos que el formulario web que debe mostrarse en el navegador es el siguiente:
![]() |
- en [1], el formulario que se muestra en un navegador
- en [2], los componentes utilizados para crearlo
- en [3], la página de diseño del formulario. Incluye los siguientes elementos:
- línea A, dos botones de opción denominados RadioButtonOui y RadioButtonNon
- línea B, un campo de entrada denominado TextBoxEnfants y una etiqueta denominada LabelErreurEnfants
- línea C, un campo de entrada denominado TextBoxSalaire y una etiqueta denominada LabelErreurSalaire
- línea D, una etiqueta denominada LabelImpot
- línea E, dos botones denominados ButtonCalculer y ButtonEffacer
Una vez colocado un componente en el área de diseño, se puede acceder a sus propiedades:
![]() |
- en [1], acceso a las propiedades de un componente
- en [2], la ficha de propiedades del componente [LabelErreurEnfants ]
- en [3], (ID) es el nombre del componente
- en [4], hemos asignado el color rojo a los caracteres de la etiqueta.
No basta con colocar componentes en el formulario y establecer sus propiedades. También hay que organizar su disposición. En una interfaz gráfica de Windows, esta disposición es absoluta: se arrastra el componente hasta donde se quiere que esté. En una página web, es diferente, más complejo, pero también más potente. Este aspecto no se tratará aquí.
El código fuente [Default.aspx] generado por este diseño es el siguiente:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Calculer votre impôt</title>
</head>
<body bgcolor="#ffff99">
<h2>
Calculer votre impôt</h2>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager2" runat="server" EnablePartialRendering="true" />
<asp:UpdatePanel runat="server" ID="UpdatePanelPam">
<ContentTemplate>
<div>
</div>
<table>
<tr>
<td>
Etes-vous marié(e)
</td>
<td>
<asp:RadioButton ID="RadioButtonOui" runat="server" GroupName="statut" Text="Oui" />
<asp:RadioButton ID="RadioButtonNon" runat="server" GroupName="statut" Text="Non"
Checked="True" />
</td>
</tr>
<tr>
<td>
Nombre d'subelementos
</td>
<td>
<asp:TextBox ID="TextBoxEnfants" runat="server" Columns="3"></asp:TextBox>
</td>
<td>
<asp:Label ID="LabelErreurEnfants" runat="server" ForeColor="#FF3300"></asp:Label>
</td>
</tr>
<tr>
<td>
Salaire annuel
</td>
<td>
<asp:TextBox ID="TextBoxSalaire" runat="server" Columns="8"></asp:TextBox>
</td>
<td>
<asp:Label ID="LabelErreurSalaire" runat="server" ForeColor="#FF3300"></asp:Label>
</td>
</tr>
<tr>
<td>
Impôt à payer
</td>
<td>
<asp:Label ID="LabelImpot" runat="server" BackColor="#99CCFF"></asp:Label>
</td>
</tr>
</table>
<br />
<table>
<tr>
<td>
<asp:Button ID="ButtonCalculer" runat="server" Text="Calculer" OnClick="ButtonCalculer_Click" />
</td>
<td>
<asp:Button ID="ButtonEffacer" runat="server" Text="Effacer" OnClick="ButtonEffacer_Click" />
</td>
<td>
</td>
</tr>
</table>
</div>
</ContentTemplate>
</asp:UpdatePanel>
</form>
</body>
</html>
Se reconocen los componentes del formulario en las líneas 23, 24, 33, 36, 44, 47, 55, 63 y 66. El resto es, básicamente, formato.
Volvamos a la arquitectura que debemos construir:
![]() |
- [1] ha sido implementada por [Default.aspx]
- [2] va a ser implementada por [Default.aspx.cs]
- [3] ha sido implementada por DLL y [ImpotV9-metier]
Las capas [1] y [3] ya están implementadas. Nos queda por escribir la capa [2], que es la que genera el formulario, lo envía al usuario, lo procesa cuando este lo devuelve cumplimentado, utiliza la capa [3] para calcular el impuesto, genera la página web de respuesta para el usuario y se la envía. El código [Default.aspx.cs] es el que realiza todo este trabajo:
using System;
using WsImpot;
public partial class _Default : System.Web.UI.Page
{
protected void ButtonCalculer_Click(object sender, EventArgs e)
{
...
}
protected void ButtonEffacer_Click(object sender, EventArgs e)
{
...
}
}
Se trata de un código muy similar al de un formulario clásico de Windows. Esta es la principal ventaja de la tecnología ASP.NET: no hay ninguna ruptura entre el modelo de programación de Windows y el de la programación web ASP.NET. Solo hay que recordar siempre el siguiente esquema:
![]() |
Cuando, en [1], el usuario haga clic en el botón [Calculer], se ejecutará el procedimiento ButtonCalculer_Click de la línea 6 de [Default.aspx.cs]. Pero mientras tanto:
- los valores del formulario rellenado se transmitirán del navegador al servidor web a través del protocolo HTTP
- el servidor ASP.NET analizará la solicitud y la transferirá a la página [Default.aspx]
- se instanciará la página [Default.aspx].
- sus componentes (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire, LabelErreurEnfants, LabelErreurSalaire, LabelImpot) se inicializarán con el valor que tenían cuando el formulario se envió inicialmente al navegador mediante un mecanismo denominado «ViewState».
- Los valores enviados se asignarán a sus componentes (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire). Así, si el usuario ha introducido 2 como número de hijos, tendremos TextBoxEnfants.Text="2".
- Si la página [Default.aspx] tiene un método [Page_Load], este se ejecutará
- el método [ButtonCalculer_Click] de la línea 6 se ejecutará si se ha pulsado el botón [Calculer]
- el método [ButtonEffacer_Click] de la línea 10 se ejecutará si se ha pulsado el botón [Effacer]
Entre el momento en que el usuario genera un evento en su navegador y el momento en que se procesa en [Default.aspx.cs], existe una gran complejidad. Esta complejidad queda oculta y podemos actuar como si no existiera a la hora de escribir los controladores de eventos de la página web. Pero nunca hay que olvidar que existe la red entre el evento y su controlador y que, por lo tanto, no se trata de gestionar eventos del ratón como Mouse_Move que provocarían costosas idas y venidas entre el cliente y el servidor...
El código de los controladores de los clics en los botones [Calculer] y [Effacer] es el que se habría escrito para una aplicación clásica de Windows:
protected void ButtonCalculer_Click(object sender, EventArgs e)
{
// verificación de datos
int nbEnfants;
bool erreur = false;
if (!int.TryParse(TextBoxEnfants.Text.Trim(), out nbEnfants) || nbEnfants < 0)
{
LabelErreurEnfants.Text = "Valeur incorrecte...";
erreur = true;
}
int salaire;
if (!int.TryParse(TextBoxSalaire.Text.Trim(), out salaire) || salaire < 0)
{
LabelErreurSalaire.Text = "Valeur incorrecte...";
erreur = true;
}
// ¿Error?
if (erreur) return;
// se eliminan los posibles errores
LabelErreurEnfants.Text = "";
LabelErreurSalaire.Text = "";
// estado civil
bool marié = RadioButtonOui.Checked;
// cálculo del impuesto
try
{
LabelImpot.Text = String.Format("{0} euros",Global.Metier.CalculerImpot(marié, nbEnfants, salaire));
}
catch (Exception ex)
{
LabelImpot.Text = ex.Message;
}
}
- Para entender este código, hay que saber
- que, al inicio de su ejecución, el formulario [Default.aspx] se encuentra tal y como lo ha rellenado el usuario. Así, los campos (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire) contienen los valores introducidos por el usuario.
- De modo que, una vez finalizada su ejecución, se devolverá al usuario la misma página [Default.aspx]. Esto se realiza de forma automática.
Por lo tanto, el procedimiento ButtonCalculer_Click debe, a partir de los valores actuales de los campos (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire) establecer el valor de todos los campos (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire, LabelErreurEnfants, LabelErreurSalaire, LabelImpot) de la nueva página [Default.aspx] que se va a devolver al usuario.
Este código no presenta ninguna dificultad especial. Solo merece la pena explicar la línea 27. En ella se utiliza el método CalculerImpot de un campo Global.Metier que aún no hemos visto. Volveremos sobre ello más adelante.
El método ButtonEffacer_Click es el siguiente:
protected void ButtonEffacer_Click(object sender, EventArgs e)
{
// borrado del formulario
TextBoxEnfants.Text = "";
TextBoxSalaire.Text = "";
LabelImpot.Text = "";
LabelErreurEnfants.Text = "";
LabelErreurSalaire.Text = "";
}
Volvamos a la arquitectura que debemos construir:
![]() |
- [1] ha sido implementado por [Default.aspx]
- [2] ha sido implementada por [Default.aspx.cs]
- [3] ha sido implementada por DLL [ImpotV9-metier]
Ahora solo nos queda «unir» estas tres capas. Básicamente, se trata de:
- instanciar la capa [3] al iniciar la aplicación
- de colocar una referencia en un lugar donde la página web [Default.aspx.cs] pueda recuperarla cada vez que se instancie y se le solicite que calcule el impuesto.
No se trata de un problema nuevo. Ya se ha planteado en la construcción del servicio web remoto y se ha analizado en el apartado 12.4.1. Sabemos que la solución consiste en:
- en crear un archivo [Global.asax] asociado a una clase [Global.cs]
- instanciar la capa [3] en el método Application_Start de [Global.cs]
- a introducir la referencia de la capa [3] en un campo estático de la clase [Global.cs], ya que el ciclo de vida de esta clase es el mismo que el de la aplicación.
Por lo tanto, nuestro proyecto web evoluciona de la siguiente manera:
![]() |
- a [1], el archivo [Global.asax].
- a [2], el código [Global.cs] asociado. La carpeta [App_Code] en la que se encuentra este archivo no está presente por defecto en la solución web. Utiliza [3] para crearla.
El archivo Global.asax es el siguiente:
<%@ Application Language="C#" Inherits="WsImpot.Global"%>
El código [Global.cs] es el siguiente:
using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
public class Global : System.Web.HttpApplication
{
// capa de negocio
public static IImpotMetier Metier;
// método ejecutado al iniciar la aplicación
private void Application_Start(object sender, EventArgs e)
{
// instancias de las capas [metier] y [dao]
Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
}
}
}
- línea 6: la clase se llama Global y forma parte del espacio de nombres WsImpot (línea 4). Por lo tanto, su nombre completo es WsImpot.Global y es este nombre el que hay que introducir en el atributo Inherits de Global.asax.
- línea 6: sabemos que la clase asociada a Global.asax debe derivarse obligatoriamente de la clase System.Web.HttpApplication.
- línea 12: el método Application_Start se ejecuta al iniciar la aplicación web.
- línea 15: se instancia la capa [metier] (capa [3] de la aplicación en desarrollo) mediante Spring y la siguiente configuración en [web.config]:
<!-- objetos Spring -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="metier" type="Metier.ImpotMetierWS, ImpotsV9-metier">
</object>
</objects>
</spring>
La clase [Metier.ImpotMetierWS] de la línea (g) anterior se encuentra en [ImpotsV9-metier.dll].
La referencia de la capa [metier] creada se introduce en el campo estático de la línea 9. Este campo es el que se utiliza en la línea 27 del procedimiento ButtonCalculer_Click:
LabelImpot.Text = String.Format("{0} euros",Global.Metier.CalculerImpot(marié, nbEnfants, salaire));
Ya estamos listos para realizar una prueba. Hay que iniciar el servicio web remoto SGBD MySQL5 y anotar el puerto en el que opera:
![]() |
Una vez hecho esto, hay que comprobar que, en el archivo [web.config] del cliente web, el puerto del servicio web remoto sea el correcto:
![]() |
Una vez hecho esto, el cliente web del servicio web remoto se puede iniciar pulsando Ctrl+F5:
![]() |
12.6. Un cliente de consola Java para el servicio web de cálculo de impuestos
Para demostrar que se puede acceder a los servicios web desde clientes escritos en cualquier lenguaje, vamos a crear un cliente de consola Java básico. La arquitectura de la aplicación cliente/servidor será la siguiente:
![]() |
- el cliente [1] estará escrito en Java
- el servidor [2] está escrito en C#
En primer lugar, vamos a modificar un detalle de nuestro servicio web de cálculo de impuestos. Su definición actual en [ServiceImpot.cs] es la siguiente:
...
public class ServiceImpot : System.Web.Services.WebService
{
[WebMethod]
public int CalculerImpot(bool marié, int nbEnfants, int salaire)
{
return Global.Metier.CalculerImpot(marié, nbEnfants, salaire);
}
}
Las pruebas han demostrado que el acento del parámetro marié de las líneas 6 y 8 podría suponer un problema de interoperabilidad entre Java y C#. Adoptaremos la siguiente nueva definición:
...
public class ServiceImpot : System.Web.Services.WebService
{
[WebMethod]
public int CalculerImpot(bool marie, int nbEnfants, int salaire)
{
return Global.Metier.CalculerImpot(marie, nbEnfants, salaire);
}
}
Este servicio se incluirá en un nuevo proyecto de Web Developer denominado WsImpotsSansAccents. La URL del servicio web será entonces [/WsImpotSansAccents/ServiceImpot.asmx].

Para escribir el cliente Java, utilizaremos NetBeans:
![]() |
- En [1], crear un nuevo proyecto
- en [2,3], selecciona un proyecto Java de tipo «Aplicación Java».
- en [4], pasar al siguiente paso
- en [5], asignar un nombre al proyecto
- en [6], indicar la carpeta en la que se creará una subcarpeta con el nombre del proyecto
- en [7], asigna un nombre a la clase que contendrá el método main que se ejecutará al iniciar la aplicación
- en [8], finalizar el asistente
![]() |
- en [9]: el proyecto Java generado
- en [10]: haz clic con el botón derecho del ratón sobre el proyecto para generar el cliente del servicio web de cálculo de impuestos
![]() |
- en [11], la URL del archivo que describe el servicio web de cálculo de impuestos:
http://localhost:1089/WsImpotSansAccents/ServiceImpot.asmx?WSDL
Esta URL corresponde al servicio [ServiceImpot.asmx], al que se añade el parámetro ?WSDL. El documento ubicado en esta URL describe en lenguaje XML las funciones del servicio [15]. Se trata de un elemento estándar de un servicio web.
- en [12], el paquete (equivalente al espacio de nombres de C#) en el que se colocarán las clases que se van a generar
- en [13], dejar el valor por defecto
- en [14], finaliza el asistente
![]() |
- En [16], el servicio web importado se ha integrado en el proyecto Java. Es compatible con dos protocolos de comunicación: Soap y Soap12.
- En [17], la clase [Main] en la que vamos a utilizar el cliente generado
![]() |
- en [18], vamos a insertar código en el método [main]. Coloca el cursor en el lugar donde se debe insertar el código, haz clic con el botón derecho y selecciona la opción [19]
- en [20], indique que desea generar el código de llamada a la función CalculerImpot del servicio remoto de cálculo de impuestos y, a continuación, haga clic en Aceptar.
El código generado en [Main] es el siguiente:
El código generado muestra cómo llamar a la función CalculerImpot del servicio remoto de cálculo de impuestos. Si lo comparamos con lo visto en C#, la variable port de la línea 7 es el equivalente al cliente utilizado en C#. No comentaremos más este código. Lo reestructuramos de la siguiente manera:
- línea 1: importamos la clase ServiceImpot, que representa el cliente generado por el asistente.
- Línea 6: llamamos al método remoto CalculerImpot siguiendo el procedimiento indicado en el código generado en main.
Los resultados obtenidos en la consola al ejecutar (F6) son los siguientes:



































































