9. La aplicación [SimuPaie] – versión 5 – ASP.NET / servicio web
Lecturas recomendadas: referencia [2], Introducción a C# 2008, capítulo 10 «Servicios web»
9.1. La nueva arquitectura de la aplicación
La arquitectura en capas de la aplicación Pam es actualmente la siguiente:
![]() |
La vamos a modificar de la siguiente manera:
![]() |
Mientras que en la arquitectura anterior, las capas [web], [metier] y [dao] se ejecutaban en una misma máquina virtual.NET, en la nueva arquitectura, la capa [web] se ejecutará en una máquina virtual diferente a la de las capas [metier] y [dao]. Esto sucederá, en particular, si la capa [web] se encuentra en una máquina M1 y las capas [metier] y [dao] en una máquina M2. Aquí tenemos una arquitectura cliente/servidor:
- el servidor está compuesto por las capas [metier] y [dao]. Como se trata de un servicio web, necesita el servidor web n.º 2 para ejecutarse.
- El cliente está formado por la capa [web]. Para ejecutarse, necesita el servidor web n.º 1.
- El cliente y el servidor se comunican a través de la red TCP/IP con el protocolo HTTP / SOAP. Para ello, se deben agregar dos nuevas capas a la arquitectura:
- la capa [S], que será un servicio web. El servicio web recibe las solicitudes de los clientes remotos y utiliza las capas [metier] y [dao] para atenderlas. Existen muchas formas de construir un servicio TCP/IP. La ventaja del servicio web es doble:
- utiliza el protocolo HTTP, que los cortafuegos de las empresas y administraciones permiten pasar
- utiliza un subprotocolo HTTP / SOAP estándar, implementado por numerosas plataformas de desarrollo: .NET, Java, PHP, Flex, ... De este modo, un servicio web puede ser «consumido» (ese es el término habitual) por clientes .Net, Java, PHP, Flex, etc.
- La capa [C] será el cliente del servicio web remoto. Su función será comunicarse con el servicio web [S].
- la capa [S], que será un servicio web. El servicio web recibe las solicitudes de los clientes remotos y utiliza las capas [metier] y [dao] para atenderlas. Existen muchas formas de construir un servicio TCP/IP. La ventaja del servicio web es doble:
Esta nueva arquitectura se puede derivar de las anteriores sin demasiado esfuerzo:
- las capas [metier] y [dao] permanecen sin cambios
- la capa [web] evoluciona ligeramente, principalmente para hacer referencia a entidades como Employe y FeuilleSalaire, que ahora forman parte de la capa de cliente [C]. Estas entidades son análogas a las de las capas [metier] o [dao], pero pertenecen a espacios de nombres diferentes.
- La capa de servidor [S] es una clase que implementa la interfaz IPamMetier de la capa [metier]. Esta implementación se limita a invocar los métodos correspondientes de la capa [metier]. Los métodos implementados por la capa de servidor [S] quedarán «expuestos» a los clientes remotos, quienes podrán invocarlos.
- La capa de cliente [C] será generada por Visual Studio.
Los principios de la nueva arquitectura son los siguientes:
- la capa [web] sigue comunicándose con la capa [metier] como si esta fuera local. Para ello, la capa de cliente [C] implementa lainterfaz IPamMetier de la capa [metier] real y se presenta ante la capa [web] como una capa [metier] local. Aparte del problema de los espacios de nombres mencionado anteriormente, la capa [web] no cambia. Esa es la ventaja de haber trabajado por capas. Si se hubiera construido una aplicación de una sola capa, habría que rediseñarla muy a fondo.
- La capa de cliente [C] transmite, de manera transparente para la capa [web], las solicitudes de esta última al servicio web remoto [S]. Se encarga de todo el aspecto de la «comunicación de red». Recibe una respuesta del servicio web remoto, la formatea y se la entrega a la capa [web] en el formato que esta espera.
- Del lado del servidor, el servicio web [S] recibe comandos de sus clientes remotos. Los formatea para invocar los métodos de la interfaz IPamMetier de la capa [metier]. Cuando recibe la respuesta de la capa [metier], la formatea para transmitirla a través de la red al cliente [C]. Las capas [metier] y [dao] no necesitan modificarse.
9.2. El proyecto de Visual Web Developer del servicio web
Creamos un nuevo proyecto con Visual Web Developer:
![]() |
- en [1], elegimos un proyecto web en C#
- en [2], elegimos «Aplicación de servicio web ASP.NET»
- en [3], le damos un nombre al proyecto web
- en [4], indicamos una ubicación para este proyecto
![]() |
- en [1], el proyecto generado. Se trata de un proyecto web clásico con las siguientes particularidades:
- se ha indicado que el proyecto es de tipo «servicio web». Un servicio web no envía páginas web HTML a sus clientes, sino datos en formato XML. Por lo tanto, la página [Default.aspx] que normalmente se genera no se ha generado.
- En [2], se generó un archivo [Service1.asmx] con el siguiente contenido:
<%@ WebService Language="C#" CodeBehind="Service1.asmx.cs" Class="pam_v5_webservice.Service1" %>
- (continuación)
- - la etiqueta WebService indica que [Service.asmx] es un servicio web
- - el atributo CodeBehind indica la ubicación del código fuente de este servicio web
- - el atributo Class indica el nombre de la clase que implementa el servicio web en el código fuente
El código fuente [Service.asmx.cs] del servicio web generado por defecto es el siguiente:
using System.Web.Services;
namespace pam_v5_webservice
{
/// <summary>
/// Descripción resumida de Servicio1
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
// Para permitir que se llame a este servicio web desde un script utilizando ASP.NET AJAX, elimine los símbolos de comentario de la siguiente línea.
// [System.Web.Script.Services.ScriptService]
public class Service1 : System.Web.Services.WebService
{
[WebMethod]
public string HelloWorld()
{
return "Hello World";
}
}
}
- línea 8: la anotación WebService, que hace que la clase Service1 de la línea 13 se exponga como un servicio web. Un servicio web pertenece a un espacio de nombres para evitar que dos servicios web en todo el mundo tengan el mismo nombre. Más adelante tendremos que cambiar este espacio de nombres.
- línea 13: la clase Service1 deriva de la clase WebService del marco .NET.
- línea 16: la anotación WebMethod hace que el método así anotado quede expuesto a los clientes remotos, quienes podrán llamarlo.
- Líneas 17-20: el método HelloWorld es un método de demostración. Lo eliminaremos más adelante. Nos permite realizar las primeras pruebas y familiarizarnos con las herramientas de Visual Studio, así como con algunos aspectos que es importante conocer sobre los servicios web.
![]() |
- En [1], ejecutamos el servicio web [Service.asmx]
![]() |
- VS Web Developer ha iniciado su servidor web integrado y lo tiene escuchando en un puerto aleatorio, en este caso el 1599. A continuación, se solicitó la URL [2] al servidor web. Se trata de una página de prueba del servicio web.
- En [3], un enlace que permite visualizar el archivo de descripción del servicio web. Este archivo, denominado WSDL (WebService Description Language) debido a su extensión (.wsdl), es un archivo XML que describe los métodos expuestos por el servicio web. A partir de este archivo WSDL, los clientes pueden conocer:
- el espacio de nombres del servicio web
- la lista de métodos expuestos por el servicio web
- los parámetros que espera cada uno de ellos
- la respuesta que devuelve cada uno de ellos
- en [4], el único método expuesto por el servicio web .
![]() |
- en [5], el contenido del archivo WSDL obtenido a través del enlace [3]. Cabe destacar el archivo URL y [6]. Es necesario que los clientes del servicio web conozcan estos archivos.
![]() |
- en [7], la página obtenida al seguir el enlace [4] permite invocar el método [HelloWorld] del servicio web
- en [8], el resultado obtenido: una respuesta XML. Cabe destacar el URL [9] del método.
El análisis de las páginas anteriores permite comprender cómo se invoca un método de un servicio web y qué tipo de respuesta devuelve. Esto permite escribir clientes HTTP capaces de interactuar con el servicio web. La mayoría de los IDE actuales permiten la generación automática de este cliente HTTP, lo que evita que el desarrollador tenga que escribirlo. Este es el caso, en particular, de Visual Studio Express.
Antes de continuar con este proyecto, cambiaremos el espacio de nombres utilizado por defecto al generar las clases:
![]() |
Al seleccionar las propiedades del proyecto (clic derecho en el proyecto / Propiedades), vemos en [1] el nombre del proyecto y en [2] su espacio de nombres por defecto.
Una vez hecho esto,
- en [Service1.asmx.cs], cambiamos el espacio de nombres de la clase:
using System.Web.Services;
namespace pam_v5
{
...
public class Service1 : System.Web.Services.WebService
{
...
}
}
- en [Service.asmx], también cambiamos el espacio de nombres utilizado para la clase [Service1] (clic derecho / mostrar el código):
<%@ WebService Language="C#" CodeBehind="Service1.asmx.cs" Class="pam_v5.Service1" %>
Volvamos a la arquitectura de nuestra aplicación:
![]() |
- La capa [S] es el servicio web. Se limita a exponer los métodos de la capa [metier] a clientes remotos. Es esta capa la que estamos construyendo.
- La capa [C] es el cliente HTTP del servicio web. Es esta capa la que los IDE saben generar automáticamente.
- La capa [web] ve a la capa [C] como una capa [metier] local si nos aseguramos de que la capa [C] implemente lainterfaz de la capa remota [metier].
A continuación vemos que nuestro servicio web va a:
- exponer los métodos de la capa [metier]
- interactuar con esta última, la cual a su vez interactuará con la capa [dao].
Por lo tanto, el proyecto debe utilizar los DLL de las capas [metier] y [dao]. Se desarrolla de la siguiente manera:
![]() |
- a [1], se agregan referencias al proyecto
- en [2], se seleccionan los DLL habituales de la carpeta [lib]. Se debe asegurar que todos tengan su propiedad «Copia local» establecida en True. Los DLL seleccionados son aquellos que implementan las capas [metier] y [dao] con un soporte NHibernate.
Una aplicación web del tipo «servicio web ASP.NET» puede tener una clase de aplicación global «Global.asax», al igual que una aplicación clásica de «sitio web ASP.NET». Ya hemos visto la ventaja de contar con dicha clase:
- se instancia al iniciar la aplicación y permanece en memoria
- de este modo, puede almacenar datos compartidos por todos los clientes y que son de solo lectura. En nuestra aplicación, almacenará, al igual que en las anteriores, la lista simplificada de empleados. Esto evitará tener que buscar esta lista en la base de datos cuando un cliente la solicite.
![]() |
- en [1], haz clic con el botón derecho del ratón en el proyecto
- en [2], selecciona la opción [Ajouter un nouvel élément]
- en [3], seleccione [Classe d'application globale]
- en [4], se ha agregado el archivo [Global.asax] al proyecto
El contenido del archivo [Global.asax] es el siguiente:
<%@ Application Codebehind="Global.asax.cs" Inherits="pam_v5.Global" Language="C#" %>
El contenido del archivo [Global.asax.cs] es el siguiente:
using System;
namespace pam_v5
{
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
}
...
}
}
¿Qué debemos hacer en el método Application_Start? Exactamente lo mismo que en las aplicaciones web anteriores. Regresemos a la arquitectura de la aplicación y coloquemos allí la clase [Global]:
![]() |
En el diagrama anterior,
- la clase [Global] se instancia al iniciar el servicio web. Permanece en memoria mientras el servicio web esté activo.
- La clase [Global] instancia las capas [metier] y [dao] en su método [Application_Start]
- Para mejorar el rendimiento, la clase [Global] almacena la lista simplificada de empleados en un campo interno. A partir de este campo, generará la lista de empleados.
- El servicio web, por su parte, se instancia con cada solicitud de un cliente. Desaparece después de atenderla. No se comunicará directamente con la capa [metier], sino con la clase [Global]. Esta implementará la interfaz de la capa [metier].
La clase [Global] es similar a la que ya se creó para las aplicaciones anteriores:
using System;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;
namespace pam_v5
{
public class Global : System.Web.HttpApplication
{
// --- datos estáticos de la aplicación ---
public static Employe[] Employes;
public static IPamMetier PamMetier = null;
protected void Application_Start(object sender, EventArgs e)
{
// instanciación de la capa [metier]
PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
// se recupera la tabla simplificada de empleados
Employes = PamMetier.GetAllIdentitesEmployes();
}
// lista simplificada de empleados
static public Employe[] GetAllIdentitesEmployes()
{
return Employes;
}
// salario de un empleado
static public FeuilleSalaire GetSalaire(string SS, double heuresTravaillées, int joursTravailles)
{
return PamMetier.GetSalaire(SS, heuresTravaillées, joursTravailles);
}
}
}
La clase [Global] implementa la interfaz [IPamMetier], aunque esto no se indica explícitamente en la declaración:
public class Global : System.Web.HttpApplication, IPamMetier
De hecho, los métodos GetAllIdentitesEmployes (línea 24) y GetSalaire (línea 30) son estáticos, mientras que los métodos de la interfaz IPamMetier no lo son. Por lo tanto, la clase Global no puede implementar la interfaz IPamMetier. Por otra parte, no es posible declarar los métodos GetAllIdentitesEmployes y GetSalaire como no estáticos. De hecho, se accede a ellos a través del nombre de la clase y no a través de una instancia de la misma.
- línea 15: el método Application_Start es similar al de las clases [Global] analizadas en versiones anteriores. Instancia la capa [metier] (línea 18) y luego inicializa (línea 20) la matriz de empleados de la línea 12.
- línea 24: el método GetAllIdentitesEmployes simplemente devuelve la matriz de empleados de la línea 12. De ahí la importancia de haberla almacenado desde el inicio de la aplicación.
- línea 30: el método GetSalaire invoca al método del mismo nombre de la capa [metier].
Para instanciar la capa [metier] (línea 18), la clase [Global] utiliza el marco Spring. Este se configura mediante el archivo [Web.config], que es idéntico al del proyecto anterior: configura Spring y NHibernate para instanciar las capas [metier] y [dao] del servicio web.
Volvamos a la arquitectura de nuestra aplicación cliente/servidor:
![]() |
Del lado del servidor, solo queda escribir el servicio web [S] propiamente dicho. Si volvemos a la arquitectura de la aplicación:
![]() |
vemos que, del lado del servidor, todas las capas que preceden a la capa [metier] implementan su interfaz IPamMetier. No es obligatorio, pero es un enfoque que parece lógico. Este razonamiento se puede aplicar del lado del cliente, al cliente [C] del servicio web [S]. De este modo, todas las capas que separan la capa [web] de la capa [metier] implementan entonces la interfaz IPamMetier. Así, se puede decir que hemos vuelto a una aplicación de tres capas:
- la capa de presentación [web] [1]
- la capa [metier] [2]
- la capa de acceso a datos [3]
La implementación del servicio web [Service1.asmx.cs] podría ser la siguiente:
using System.Web.Services;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
using Pam.Metier.Service;
namespace pam_v5
{
[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class Service1 : System.Web.Services.WebService, IPamMetier
{
// lista de todas las identidades de los empleados
[WebMethod]
public Employe[] GetAllIdentitesEmployes()
{
return Global.GetAllIdentitesEmployes();
}
// ------- el cálculo del salario
[WebMethod]
public FeuilleSalaire GetSalaire(string ss, double heuresTravaillees, int joursTravailles)
{
return Global.GetSalaire(ss, heuresTravaillees, joursTravailles);
}
}
}
- línea 8: la clase está anotada con el atributo [WebService] y le damos un nombre al espacio de nombres del servicio web
- línea 11: la clase [Service1] hereda de la clase [WebService] e implementa la interfaz [IPamMetier]
- líneas 15 y 22: cada método de la clase está anotado con el atributo [WebMethod] para que quede expuesto a los clientes remotos. Por defecto, todos los métodos públicos de un servicio web quedan expuestos. Por lo tanto, los atributos de las líneas 15 y 22 son opcionales en este caso. Para implementar la interfaz [IPamMetier], cada método simplemente llama al método del mismo nombre de la clase [Global].
Ya estamos listos para ejecutar el servicio web:
![]() |
- en [1], el proyecto se regenera
- en [2], se selecciona el servicio web [Service1.asmx] y se muestra en el navegador [3]
- en [4], la página web mostrada. En ella se presentan los métodos del servicio web.
![]() |
- en [4], seguimos el enlace [GetAllIdentitesEmployes] y llegamos a [5], la página de prueba de este método.
- En [6], el URL del método
- En [7], el botón [Appeler] que permite probar el método. Este no requiere ningún parámetro.
- en [8], el resultado XML devuelto por el servicio web. En este, solo las propiedades SS, Nom, Prenom de los objetos Employe son relevantes, ya que el método [GetAllIdentitesEmployes] solo solicita estas propiedades. Sin embargo, este método devuelve una matriz de objetos Employe. En [8] se observa que las propiedades numéricas Id y Version se incluyen en el flujo XML devuelto, pero no las propiedades con un valor null: Adresse, Ville, CodePostal, Indemnites.
Tenemos un servicio web activo. Ahora vamos a desarrollar un cliente en C# para él. Para ello, necesitaremos el URI del archivo WSDL del servicio web. Lo obtenemos en la página que se muestra inicialmente al ejecutar [Service.asmx]:
![]() |
- en [1], el URI del servicio web
- en [2], el enlace que lleva a su archivo WSDL
- en [3], el valor de este enlace
9.3. El proyecto en C# de un cliente del servicio web NUnit
Creamos un proyecto en C# (con Visual C# y no con Visual Web Developer) para el cliente del servicio web. Será un cliente de prueba NUnit. Por lo tanto, el proyecto será de tipo «Biblioteca de clases».
![]() |
- En [1], creamos un proyecto en C# de tipo «Biblioteca de clases»
- en [2], le damos un nombre al proyecto
- en [3], el proyecto. Eliminamos [Class1.cs].
- en [4], el nuevo proyecto.
![]() |
- En las propiedades del proyecto, en la pestaña [Application] [5], configuramos el espacio de nombres del proyecto. Cada clase generada por el IDE se creará en este espacio.
Guardamos nuestro nuevo proyecto en la ubicación que nos convenga:
Una vez hecho esto, generamos el cliente del servicio web remoto. Para entender lo que vamos a hacer, debemos volver a la arquitectura cliente/servidor que estamos construyendo:
![]() |
El IDE generará la capa de cliente [C] a partir del URI del archivo WSDL del servicio web [S]. Recordemos que el URI de este archivo ya se había anotado anteriormente. Procedemos desde el de la siguiente manera:
![]() |
- a [1], hacer clic con el botón derecho en la rama References y agregar una referencia de servicio
- en [2], indique el URL del archivo WSDL del servicio web anotado anteriormente. Este debe iniciarse previamente si aún no lo está.
- En [3], solicitar el descubrimiento del servicio web a través de su archivo WSDL
- en [4], el servicio web detectado
- en [5], los métodos expuestos por el servicio web.
- en [6], el espacio de nombres en el que se desea colocar las clases e interfaces del cliente que se va a generar.
- Se confirma el asistente
![]() |
- en [1], el e e del cliente generado. Hacemos doble clic en él para acceder a su contenido.
- En [2], en el explorador de objetos, se muestran las clases e interfaces del espacio de nombres Client.WsPam. Este es el espacio de nombres del cliente generado.
- En [3], la clase que implementa el cliente del servicio web.
- En [4], se muestran los métodos implementados por el cliente [Service1SoapClient]. Aquí se encuentran los dos métodos del servicio web remoto [5] y [6].
- En [2], se encuentran las representaciones de las entidades de las capas:
- [metier]: FeuilleSalaire, ElementsSalaire
- [dao]: Employe, Cotisations, Indemnites
A continuación, hay que recordar que estas imágenes de las entidades remotas se encuentran del lado del cliente y en el espacio de nombres PamV5Client.WsPam.
Analicemos los métodos y propiedades que expone una de ellas:
![]() |
- en [1], se selecciona la clase local [Employe]
- en [2], encontramos las propiedades de la entidad remota [Employe], así como los campos privados utilizados para las necesidades específicas de la entidad local.
Volvamos a nuestra aplicación en C#. Le agregamos una clase de prueba NUnit:
![]() |
- En [1], se ha agregado la clase [NUnit]. La clase [NUnit] necesitará el marco NUnit y, por lo tanto, una referencia a DLL de este. Suponemos aquí que el marco NUnit se ha instalado en la computadora (http://nunit.org/).
- En [2], se agrega una referencia al proyecto
- en la pestaña [3]. En NET, que reúne los DLL registrados en la computadora, seleccionamos [4], la DLL y la [nunit.framework], versión mínima 2.4.6.
Además, utilizaremos Spring para instanciar el cliente local [C] del servicio web [S]:
![]() |
La referencia a DLL de Spring se puede agregar tal como se hizo con el marco NUnit, siempre y cuando los DLL se hayan registrado previamente en la computadora (http://www.springframework.net/download.html).
Procedemos de manera diferente. Utilizamos la carpeta [lib] de los proyectos anteriores, que contenía los DLL necesarios para Spring, y agregamos la referencia de Spring al proyecto:
![]() |
Volvamos a la arquitectura del cliente que estamos desarrollando:
![]() |
Arriba, vemos que el cliente de prueba [1] se conecta con una capa [metier] [2] extendida. Esta presenta los mismos métodos que la capa remota [metier]. Por lo tanto, podemos utilizar la clase de prueba que ya vimos al probar la capa [metier] en el proyecto C# [pam-metier-dao-nhibernate]:
using NUnit.Framework;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;
namespace Pam.Metier.Tests {
[TestFixture()]
public class NunitTestPamMetier : AssertionHelper {
// la capa [metier] para probar
private IPamMetier pamMetier;
// constructor
public NunitTestPamMetier() {
// instanciación de la capa [dao]
pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
[Test]
public void GetAllIdentitesEmployes() {
// verificación del número de empleados
Expect(2, EqualTo(pamMetier.GetAllIdentitesEmployes().Length));
}
[Test]
public void GetSalaire1() {
// cálculo de una nómina
FeuilleSalaire feuilleSalaire = pamMetier.GetSalaire("254104940426058", 150, 20);
// verificaciones
Expect(368.77, EqualTo(feuilleSalaire.ElementsSalaire.SalaireNet).Within(1E-06));
// nómina de un empleado inexistente
bool erreur = false;
try {
feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
} catch (PamException) {
erreur = true;
}
Expect(erreur, True);
}
}
}
Hay que hacer algunas modificaciones:
- En la línea 18, se instancia la capa [metier] con el marco Spring. La clase no es la misma en ambos casos. Aquí, la capa local [metier] es una instancia de la clase [PamV5Client.WsPam.Service1SoapClient], la clase generada por IDE. Por lo tanto, Spring está configurado de la siguiente manera en el archivo [app.config] del proyecto C#:
<?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 id="pammetier" type="PamV5Client.WsPam.Service1SoapClient, pam-v5-client-csharp-webservice"/>
</objects>
</spring>
<system.serviceModel>
...
- En la línea 16 anterior, el objeto [pammetier] es una instancia de la clase [PamV5Client.WsPam.Service1SoapClient], que se encuentra en el ensamblado (assembly) [pam-v5-client-csharp-webservice]. Para obtener esta primera información, basta con volver a la definición de la clase [Service1SoapClient] en el explorador de objetos (párrafo 9.3):
![]() |
- en [2], la clase de implementación de la capa local [metier] y en [1] su espacio de nombres
- en [3]; en las propiedades del proyecto, el nombre del ensamblado, la segunda información necesaria para la configuración del objeto Spring [pammetier].
Volvamos al código de instanciación de la capa local [metier] en [NUnit.cs]:
// la capa [metier] a probar
private IPamMetier pamMetier;
// constructor
public NunitTestPamMetier() {
// instanciación de la capa [dao]
pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
En la línea 7, la capa remota [metier] era de tipo IPamMetier. Aquí, la capa [metier] es de tipo [Service1SoapClient]:
public class Service1SoapClient : System.ServiceModel.ClientBase<Service1Soap>
Vemos que la clase Service1SoapClient no implementa la interfaz IPamMetier, aunque exponga métodos con el mismo nombre. Por lo tanto, debemos escribir la instanciación de la capa local [metier] de la siguiente manera:
// la capa [metier] a probar
private Service1SoapClient pamMetier;
// constructor
public NunitTestPamMetier() {
// instanciación de la capa [metier]
pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as Service1SoapClient;
}
Otra modificación que hay que realizar:
[Test]
public void GetSalaire1() {
...
try {
feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
} catch (PamException) {
erreur = true;
}
Expect(erreur, True);
}
El código anterior utiliza, en la línea 6, el tipo PamException, que no existe en el lado del cliente. Lo reemplazaremos por su clase padre, el tipo Exception.
[Test]
public void GetSalaire1() {
...
try {
feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
} catch (Exception) {
erreur = true;
}
Expect(erreur, True);
}
Por último, los espacios de nombres importados ya no son los mismos:
using System;
using PamV5Client.WsPam;
using NUnit.Framework;
using Spring.Context.Support;
Una vez hecho esto, se puede generar el proyecto de tipo «Biblioteca de clases». Se crea el siguiente DLL:
![]() |
- en [1], la carpeta [bin/Release] del proyecto C#
- en [2], el archivo DLL del proyecto.
A continuación, la prueba NUnit se ejecuta mediante el marco NUnit (la base MySQL dbpam_nhibernate debe estar activa para la prueba):
- en [3] y [4], se carga DLL [2] en la aplicación de prueba NUnit
![]() |
- en [5], se selecciona y ejecuta la clase de prueba [6]
- en [7], los resultados de una prueba exitosa
Ahora contamos con un servicio web operativo.






























