Skip to content

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].

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.