9. A aplicação [SimuPaie] – versão 5 – ASP.NET / serviço web
Leituras recomendadas: referência [2], Introdução ao C# 2008, capítulo 10 «Serviços Web»
9.1. A nova arquitetura da aplicação
A arquitetura em camadas da aplicação Pam é atualmente a seguinte:
![]() |
Vamos desenvolvê-la da seguinte forma:
![]() |
Enquanto que na arquitetura anterior as camadas [web], [metier] e [dao] eram executadas na mesma máquina virtual.NET, na nova arquitetura, a camada [web] será executada numa máquina virtual diferente daquela onde se encontram as camadas [metier] e [dao]. Este será o caso, nomeadamente, se a camada [web] estiver numa máquina M1 e as camadas [metier] e [dao] estiverem numa máquina M2. Estamos perante uma arquitetura cliente/servidor:
- o servidor é constituído pelas camadas [metier] e [dao]. Por se tratar de um serviço web, necessita do servidor web n.º 2 para funcionar.
- O cliente é constituído pela camada [web]. Para funcionar, necessita do servidor web n.º 1.
- O cliente e o servidor comunicam através da rede TCP/IP com o protocolo HTTP / SOAP. Para tal, devem ser adicionadas duas novas camadas à arquitetura:
- a camada [S], que será um serviço web. O serviço web recebe os pedidos dos clientes remotos e utiliza as camadas [metier] e [dao] para os satisfazer. Existem inúmeras formas de construir um serviço TCP/IP. A vantagem do serviço web é dupla:
- utiliza o protocolo HTTP, que é permitido pelos firewalls das empresas e administrações
- utiliza um subprotocolo HTTP / SOAP padrão, implementado por várias plataformas de desenvolvimento: .Net, Java, PHP, Flex, ... Assim, um serviço web pode ser «consumido» (este é o termo habitual) por clientes .Net, Java, PHP, Flex, etc.
- A camada [C], que será o cliente do serviço web remoto. A sua função será comunicar com o serviço web [S].
- a camada [S], que será um serviço web. O serviço web recebe os pedidos dos clientes remotos e utiliza as camadas [metier] e [dao] para os satisfazer. Existem inúmeras formas de construir um serviço TCP/IP. A vantagem do serviço web é dupla:
Esta nova arquitetura pode ser derivada das anteriores sem grande esforço:
- as camadas [metier] e [dao] permanecem inalteradas
- a camada [web] sofre uma ligeira alteração, essencialmente para referenciar entidades como Employe e FeuilleSalaire, que passaram a ser entidades da camada cliente [C]. Estas entidades são análogas às das camadas [metier] ou [dao], mas pertencem a espaços de nomes diferentes.
- A camada de servidor [S] é uma classe que implementa a interface IPamMetier da camada [metier]. Esta implementação limita-se a invocar os métodos correspondentes da camada [metier]. Os métodos implementados pela camada de servidor [S] serão «expostos» aos clientes remotos, que poderão chamá-los.
- A camada cliente [C] será gerada pelo Visual Studio.
Os princípios da nova arquitetura são os seguintes:
- a camada [web] continua a comunicar com a camada [metier] como se esta fosse local. Para tal, a camada cliente [C] implementa ainterface IPamMetier da camada [metier] real e apresenta-se à camada [web] como uma camada [metier] local. Para além do problema dos espaços de nomes mencionado anteriormente, a camada [web] não sofre alterações. Esta é a vantagem de se ter trabalhado em camadas. Se tivéssemos construído uma aplicação de camada única, seria necessário reformulá-la profundamente.
- A camada cliente [C] transmite, de forma transparente para a camada [web], as solicitações desta última ao serviço web remoto [S]. Esta camada encarrega-se de todo o aspeto da «comunicação de rede». Recebe uma resposta do serviço web remoto, que formata para a entregar à camada [web] no formato que esta espera.
- Do lado do servidor, o serviço web [S] recebe comandos dos seus clientes remotos. Ele formata esses comandos de modo a chamar os métodos da interface IPamMetier da camada [metier]. Quando recebe a resposta da camada [metier], formata-a para a transmitir através da rede ao cliente [C]. As camadas [metier] e [dao] não precisam de ser alteradas.
9.2. O projeto do Visual Web Developer do serviço web
Criamos um novo projeto com o Visual Web Developer:
![]() |
- em [1], selecionamos um projeto web em C#
- em [2], selecionamos «Aplicação de serviço web ASP.NET»
- em [3], atribuímos um nome ao projeto web
- em [4], indicamos um local para este projeto
![]() |
- em [1], o projeto gerado. Trata-se de um projeto web clássico, com as seguintes particularidades:
- foi indicado que o projeto era do tipo «serviço web». Um serviço web não envia páginas web HTML aos seus clientes, mas sim dados no formato XML. Por isso, a página [Default.aspx], normalmente gerada, não foi criada.
- Em [2], foi gerado um ficheiro [Service1.asmx] com o seguinte conteúdo:
<%@ WebService Language="C#" CodeBehind="Service1.asmx.cs" Class="pam_v5_webservice.Service1" %>
- (continuação)
- - a baliza WebService indica que [Service.asmx] é um serviço web
- - o atributo CodeBehind indica a localização do código-fonte deste serviço web
- - o atributo Class indica o nome da classe que implementa o serviço web no código-fonte
O código-fonte [Service.asmx.cs] do serviço web gerado por predefinição é o seguinte:
using System.Web.Services;
namespace pam_v5_webservice
{
/// <summary>
/// Descrição resumida do Serviço1
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
// Para permitir a chamada deste serviço Web a partir de um script utilizando ASP.NET AJAX, remova os símbolos de comentário da linha seguinte.
// [System.Web.Script.Services.ScriptService]
public class Service1 : System.Web.Services.WebService
{
[WebMethod]
public string HelloWorld()
{
return "Hello World";
}
}
}
- linha 8: a anotação WebService, que faz com que a classe Service1 da linha 13 venha a ser exposta como um serviço web. Um serviço web pertence a um espaço de nomes para evitar que dois serviços web em todo o mundo tenham o mesmo nome. Teremos de alterar este espaço de nomes posteriormente.
- linha 13: a classe Service1 deriva da classe WebService do framework .NET.
- linha 16: a anotação WebMethod faz com que o método assim anotado seja exposto aos clientes remotos, que poderão, assim, chamá-lo.
- linhas 17-20: o método HelloWorld é um método de demonstração. Iremos eliminá-lo mais tarde. Permite-nos realizar os primeiros testes e familiarizar-nos com as ferramentas do Visual Studio, bem como com alguns aspetos importantes a saber sobre os serviços web.
![]() |
- No [1], executamos o serviço web [Service.asmx]
![]() |
- VS O Web Developer iniciou o seu servidor web integrado e configurou-o para escutar numa porta aleatória, neste caso a 1599. A URL [2] foi então solicitada ao servidor web. Trata-se da URL de uma página de teste do serviço web.
- Em [3], um link que permite visualizar o ficheiro de descrição do serviço web. Este ficheiro, denominado WSDL (WebService Description Language) devido à sua extensão (.wsdl), é um ficheiro XML que descreve os métodos expostos pelo serviço web. É a partir deste ficheiro WSDL que os clientes podem conhecer:
- o espaço de nomes do serviço web
- a lista de métodos expostos pelo serviço web
- os parâmetros esperados por cada um deles
- a resposta devolvida por cada um deles
- no [4], o único método disponibilizado pelo serviço web .
![]() |
- em [5], o conteúdo do ficheiro WSDL obtido através do link [3]. Deve-se ter em conta o URL e o [6]. O conhecimento destes é necessário para os clientes do serviço web.
![]() |
- em [7], a página obtida ao seguir o link [4] permite chamar o método [HelloWorld] do serviço web
- em [8], o resultado obtido: uma resposta XML. De notar o URL [9] do método.
A análise das páginas anteriores permite compreender como é chamado um método de um serviço web e que tipo de resposta este devolve. Isto permite escrever clientes HTTP capazes de interagir com o serviço web. A maioria dos IDE atuais permite a geração automática deste cliente HTTP, poupando assim ao programador o trabalho de o escrever. É o caso, nomeadamente, do Visual Studio Express.
Antes de prosseguirmos com este projeto, vamos alterar o espaço de nomes utilizado por predefinição durante a geração das classes:
![]() |
Quando selecionamos as propriedades do projeto (clique com o botão direito do rato no projeto / Propriedades), temos em [1] o nome do projeto e em [2] o seu espaço de nomes por predefinição.
Feito isto,
- em [Service1.asmx.cs], alteramos o espaço de nomes da classe:
using System.Web.Services;
namespace pam_v5
{
...
public class Service1 : System.Web.Services.WebService
{
...
}
}
- em [Service.asmx], alteramos também o espaço de nomes utilizado para a classe [Service1] (clique com o botão direito do rato / exibir código):
<%@ WebService Language="C#" CodeBehind="Service1.asmx.cs" Class="pam_v5.Service1" %>
Voltemos à arquitetura da nossa aplicação:
![]() |
- a camada [S] é o serviço web. Limita-se a expor os métodos da camada [metier] a clientes remotos. É esta camada que estamos a construir.
- A camada [C] é o cliente HTTP do serviço web. É esta camada que os IDE sabem gerar automaticamente.
- A camada [web] vê a camada [C] como uma camada [metier] local, se fizermos com que a camada [C] implemente ainterface da camada remota [metier].
Vemos abaixo que o nosso serviço web irá:
- expor os métodos da camada [metier]
- comunicar-se com esta última, que, por sua vez, irá comunicar-se com a camada [dao].
O projeto deve, portanto, utilizar os DLL das camadas [metier] e [dao]. A sua evolução é a seguinte:
![]() |
- para [1], adicionam-se referências ao projeto
- em [2], selecionam-se as DLL habituais da pasta [lib]. Deve-se certificar-se de que todas elas têm a propriedade «Cópia local» definida como True. Os DLL selecionados são aqueles que implementam as camadas [metier] e [dao] com suporte NHibernate.
Uma aplicação web do tipo «serviço Web ASP.NET» pode ter uma classe de aplicação global «Global.asax», tal como uma aplicação «site Web ASP.NET» clássica. Já vimos a utilidade de uma classe deste tipo:
- é instanciada no arranque da aplicação e permanece na memória
- pode, assim, armazenar dados partilhados por todos os clientes e que são de leitura única. Na nossa aplicação, irá armazenar, tal como nas anteriores, a lista simplificada de funcionários. Isto evitará ter de ir buscar essa lista à base de dados quando um cliente a solicitar.
![]() |
- em [1], clique com o botão direito do rato no projeto
- em [2], selecione a opção [Ajouter un nouvel élément]
- em [3], selecione [Classe d'application globale]
- em [4], o ficheiro [Global.asax] foi adicionado ao projeto
O conteúdo do ficheiro [Global.asax] é o seguinte:
<%@ Application Codebehind="Global.asax.cs" Inherits="pam_v5.Global" Language="C#" %>
O conteúdo do ficheiro [Global.asax.cs] é o seguinte:
using System;
namespace pam_v5
{
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
}
...
}
}
O que devemos fazer no método Application_Start? Exatamente o mesmo que nas aplicações web anteriores. Voltemos à arquitetura da aplicação e insiramos aí a classe [Global]:
![]() |
No esquema acima,
- a classe [Global] é instanciada quando o serviço web é iniciado. Permanece na memória enquanto o serviço web estiver ativo.
- A classe [Global] instancia as camadas [metier] e [dao] no seu método [Application_Start]
- Para melhorar o desempenho, a classe [Global] armazena a lista simplificada de funcionários num campo interno. Ela irá fornecer a lista de funcionários a partir desse campo.
- O serviço web, por sua vez, é instanciado a cada pedido de um cliente. Desaparece após ter atendido a esse pedido. Não se dirigirá diretamente à camada [metier], mas sim à classe [Global]. Esta implementará a interface da camada [metier].
A classe [Global] é análoga à que já foi criada para as aplicações 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
{
// --- dados estáticos da aplicação ---
public static Employe[] Employes;
public static IPamMetier PamMetier = null;
protected void Application_Start(object sender, EventArgs e)
{
// instanciação da camada [metier]
PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
// recupera-se a tabela simplificada dos funcionários
Employes = PamMetier.GetAllIdentitesEmployes();
}
// lista simplificada de funcionários
static public Employe[] GetAllIdentitesEmployes()
{
return Employes;
}
// salário de um funcionário
static public FeuilleSalaire GetSalaire(string SS, double heuresTravaillées, int joursTravailles)
{
return PamMetier.GetSalaire(SS, heuresTravaillées, joursTravailles);
}
}
}
A classe [Global] implementa a interface [IPamMetier], mas isso não é explicitamente indicado na declaração:
public class Global : System.Web.HttpApplication, IPamMetier
Com efeito, os métodos GetAllIdentitesEmployes (linha 24) e GetSalaire (linha 30) são estáticos, ao passo que os métodos da interface IPamMetier não o são. Por isso, a classe Global não pode implementar a interface IPamMetier. Além disso, não é possível declarar os métodos GetAllIdentitesEmployes e GetSalaire como não estáticos. Com efeito, estes são acedidos através do nome da classe e não através de uma instância da mesma.
- linha 15: o método Application_Start é análogo ao das classes [Global] analisadas nas versões anteriores. Este método instancia a camada [metier] (linha 18) e, em seguida, inicializa (linha 20) a matriz dos funcionários da linha 12.
- linha 24: o método GetAllIdentitesEmployes limita-se a devolver o tabuleiro de funcionários da linha 12. É por isso que é importante tê-lo memorizado logo no início da aplicação.
- linha 30: o método GetSalaire invoca o método com o mesmo nome da camada [metier].
Para instanciar a camada [metier] (linha 18), a classe [Global] utiliza o framework Spring. Este framework é configurado pelo ficheiro [Web.config], que é idêntico ao do projeto anterior: configura o Spring e o NHibernate para instanciar as camadas [metier] e [dao] do serviço web.
Voltemos à arquitetura da nossa aplicação cliente/servidor:
![]() |
Do lado do servidor, resta apenas escrever o próprio serviço web [S]. Se voltarmos à arquitetura da aplicação:
![]() |
vemos que, no lado do servidor, todas as camadas que precedem a camada [metier] implementam a interface desta última, IPamMetier. Não é obrigatório, mas é uma abordagem que parece lógica. Este raciocínio pode ser aplicado do lado do cliente, ao cliente [C] do serviço web [S]. Assim, todas as camadas que separam a camada [web] da camada [metier] implementam, por sua vez, a interface IPamMetier. Pode-se dizer, portanto, que voltámos a uma aplicação de três camadas:
- a camada de apresentação [web] [1]
- a camada [metier] [2]
- a camada de acesso aos dados [3]
A implementação do serviço web [Service1.asmx.cs] poderia ser a seguinte:
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 as identidades dos funcionários
[WebMethod]
public Employe[] GetAllIdentitesEmployes()
{
return Global.GetAllIdentitesEmployes();
}
// ------- o cálculo do salário
[WebMethod]
public FeuilleSalaire GetSalaire(string ss, double heuresTravaillees, int joursTravailles)
{
return Global.GetSalaire(ss, heuresTravaillees, joursTravailles);
}
}
}
- linha 8: a classe é anotada com o atributo [WebService] e atribuímos um nome ao espaço de nomes do serviço web
- linha 11: a classe [Service1] herda da classe [WebService] e implementa a interface [IPamMetier]
- linhas 15 e 22: cada método da classe é anotado com o atributo [WebMethod] para ser exposto aos clientes remotos. Por predefinição, todos os métodos públicos de um serviço web são expostos. Os atributos das linhas 15 e 22 são, portanto, opcionais neste caso. Para implementar a interface [IPamMetier], cada método limita-se a chamar o método com o mesmo nome da classe [Global].
Estamos prontos para a execução do serviço web:
![]() |
- em [1], o projeto é regenerado
- em [2], seleciona-se o serviço web [Service1.asmx] e exibe-se no navegador [3]
- em [4], a página web apresentada. Esta apresenta os métodos do serviço web.
![]() |
- em [4], seguimos a ligação [GetAllIdentitesEmployes] e obtemos, em [5], a página de teste deste método.
- em [6], o URL do método
- em [7], o botão [Appeler] que permite testar o método. Este não requer nenhum parâmetro.
- em [8], o resultado XML devolvido pelo serviço web. Neste, apenas as propriedades SS, Nom, Prenom dos objetos Employe são relevantes, uma vez que o método [GetAllIdentitesEmployes] apenas solicita essas propriedades. No entanto, este método devolve um array de objetos Employe. Vê-se em [8] que as propriedades numéricas Id e Version estão no fluxo XML devolvido, mas não as propriedades com um valor null: Adresse, Ville, CodePostal, Indemnites.
Temos um serviço web ativo. Vamos agora criar um cliente em C# para este serviço. Para tal, precisaremos do URI do ficheiro WSDL do serviço web. Obtemo-lo na página inicialmente apresentada ao executar o [Service.asmx]:
![]() |
- no [1], o URI do serviço web
- em [2], o link que remete para o seu ficheiro WSDL
- em [3], o valor desse link
9.3. O projeto C# de um cliente NUnit do serviço web
Criamos um projeto C# (com o Visual C# e não com o Visual Web Developer) para o cliente do serviço web. Será um cliente de teste NUnit. Assim, o projeto será do tipo «Biblioteca de classes».
![]() |
- no [1], criamos um projeto C# do tipo «Biblioteca de classes»
- em [2], atribuímos um nome ao projeto
- em [3], o projeto. Eliminamos [Class1.cs].
- em [4], o novo projeto.
![]() |
- nas propriedades do projeto, no separador [Application] [5], definimos o espaço de nomes do projeto. Cada classe gerada pelo IDE será criada neste espaço.
Guardamos o nosso novo projeto num local à nossa escolha:
Feito isto, geramos o cliente do serviço web remoto. Para compreender o que vamos fazer, é necessário voltar à arquitetura cliente/servidor que estamos a construir:
![]() |
O IDE irá gerar a camada cliente [C] a partir do URI do ficheiro WSDL do serviço web [S]. Recorde-se que o URI deste ficheiro foi anteriormente registado. Procedemos a partir do da seguinte forma:
![]() |
- para [1], clicar com o botão direito do rato no ramo References e adicionar uma referência de serviço
- em [2], indicar o URL do ficheiro WSDL do serviço web anteriormente indicado. Este deve ser iniciado previamente, caso ainda não o esteja.
- em [3], solicitar a descoberta do serviço web através do seu ficheiro WSDL
- no [4], o serviço web detetado
- em [5], os métodos expostos pelo serviço web.
- em [6], o espaço de nomes no qual se pretende colocar as classes e interfaces do cliente que vai ser gerado.
- confirmamos o assistente
![]() |
- em [1], o « » do cliente gerado. Clica-se duas vezes nele para aceder ao seu conteúdo.
- Em [2], no explorador de objetos, são apresentadas as classes e interfaces do espaço de nomes Client.WsPam. Este é o espaço de nomes do cliente gerado.
- Em [3], a classe que implementa o cliente do serviço web.
- Em [4], os métodos implementados pelo cliente [Service1SoapClient]. Aqui encontram-se os dois métodos do serviço web remoto [5] e [6].
- Em [2], encontram-se as imagens das entidades das camadas:
- [metier]: FeuilleSalaire, ElementsSalaire
- [dao]: Employe, Cotisations, Indemnites
A seguir, é importante ter em conta que estas imagens das entidades remotas encontram-se no lado do cliente e no espaço de nomes PamV5Client.WsPam.
Analisemos os métodos e propriedades expostos por uma delas:
![]() |
- em [1], seleciona-se a classe local [Employe]
- em [2], encontram-se as propriedades da entidade remota [Employe], bem como campos privados utilizados para as necessidades específicas da entidade local.
Voltemos à nossa aplicação C#. Adicionamos-lhe uma classe de teste NUnit:
![]() |
- Na classe [1], foi adicionada a classe [NUnit]. A classe [NUnit] vai precisar do framework NUnit e, por conseguinte, de uma referência à classe DLL deste. Partimos do princípio de que o framework NUnit foi instalado no computador (http://nunit.org/).
- No [2], adiciona-se uma referência ao projeto
- no separador [3] .NET, que reúne os DLL registados no computador, selecionamos [4], o DLL [nunit.framework], versão mínima 2.4.6.
Além disso, vamos utilizar o Spring para instanciar o cliente local [C] do serviço web [S]:
![]() |
A referência ao DLL do Spring pode ser adicionada tal como foi feito com o framework NUnit, caso os DLL tenham sido previamente registados no computador (http://www.springframework.net/download.html).
Procedemos de forma diferente. Utilizamos a pasta [lib] dos projetos anteriores, que continha os DLL necessários ao Spring, e adicionamos a referência do Spring ao projeto:
![]() |
Voltemos à arquitetura do cliente que está a ser desenvolvido:
![]() |
Acima, vemos que o cliente de teste [1] interage com uma camada [metier] [2] estendida. Esta apresenta os mesmos métodos que a camada remota [metier]. Podemos, portanto, utilizar a classe de teste já apresentada durante o teste da camada [metier] no projeto 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 {
// a camada [metier] a testar
private IPamMetier pamMetier;
// construtor
public NunitTestPamMetier() {
// instanciação da camada [dao]
pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
[Test]
public void GetAllIdentitesEmployes() {
// verificação do número de funcionários
Expect(2, EqualTo(pamMetier.GetAllIdentitesEmployes().Length));
}
[Test]
public void GetSalaire1() {
// cálculo de uma folha de pagamentos
FeuilleSalaire feuilleSalaire = pamMetier.GetSalaire("254104940426058", 150, 20);
// verificações
Expect(368.77, EqualTo(feuilleSalaire.ElementsSalaire.SalaireNet).Within(1E-06));
// folha de pagamento de um funcionário inexistente
bool erreur = false;
try {
feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
} catch (PamException) {
erreur = true;
}
Expect(erreur, True);
}
}
}
Há algumas alterações a fazer:
- na linha 18, instanciamos a camada [metier] com o framework Spring. A classe não é a mesma nos dois casos. Aqui, a camada local [metier] é uma instância da classe [PamV5Client.WsPam.Service1SoapClient], a classe gerada pelo IDE. Assim, o Spring está configurado da seguinte forma no ficheiro [app.config] do projeto 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>
...
- na linha 16 acima, o objeto [pammetier] é uma instância da classe [PamV5Client.WsPam.Service1SoapClient], que se encontra no assembly [pam-v5-client-csharp-webservice]. Para obter esta informação, basta voltar à definição da classe [Service1SoapClient] no explorador de objetos (parágrafo 9.3):
![]() |
- em [2], a classe de implementação da camada local [metier] e em [1] o seu espaço de nomes
- em [3]; nas propriedades do projeto, o nome do assembly, a segunda informação necessária para a configuração do objeto Spring [pammetier].
Voltemos ao código de instanciação da camada local [metier] em [NUnit.cs]:
// a camada [metier] a testar
private IPamMetier pamMetier;
// construtor
public NunitTestPamMetier() {
// instanciação da camada [dao]
pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
Na linha 7, a camada remota [metier] era do tipo IPamMetier. Aqui, a camada [metier] é do tipo [Service1SoapClient]:
public class Service1SoapClient : System.ServiceModel.ClientBase<Service1Soap>
Verificamos que a classe Service1SoapClient não implementa a interface IPamMetier, apesar de expor métodos com o mesmo nome. Por isso, temos de escrever a instanciação da camada local [metier] da seguinte forma:
// a camada [metier] a ser testada
private Service1SoapClient pamMetier;
// construtor
public NunitTestPamMetier() {
// instanciação da camada [metier]
pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as Service1SoapClient;
}
Outra alteração a efetuar:
[Test]
public void GetSalaire1() {
...
try {
feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
} catch (PamException) {
erreur = true;
}
Expect(erreur, True);
}
O código acima utiliza, na linha 6, o tipo PamException, que não existe no lado do cliente. Substituí-lo-emos pela sua classe pai, o tipo Exception.
[Test]
public void GetSalaire1() {
...
try {
feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
} catch (Exception) {
erreur = true;
}
Expect(erreur, True);
}
Por fim, os espaços de nomes importados já não são os mesmos:
using System;
using PamV5Client.WsPam;
using NUnit.Framework;
using Spring.Context.Support;
Feito isto, o projeto do tipo «Biblioteca de classes» pode ser gerado. É criada a seguinte DLL:
![]() |
- em [1], a pasta [bin/Release] do projeto C#
- em [2], o ficheiro DLL do projeto.
O teste NUnit é, em seguida, executado pelo framework NUnit (a base MySQL dbpam_nhibernate deve estar ativa para o teste):
- em [3] e [4], a DLL [2] é carregada na aplicação de teste NUnit
![]() |
- no [5], a classe de teste é selecionada e executada [6]
- em [7], os resultados de um teste bem-sucedido
Temos agora um serviço web operacional.






























