Skip to content

12. Serviços Web

12.1. Introdução

No capítulo anterior, apresentámos várias aplicações cliente-servidor TCP/IP. Uma vez que o cliente e o servidor trocam linhas de texto, estas podem ser escritas em qualquer linguagem. O cliente precisa apenas de conhecer o protocolo de diálogo esperado pelo servidor.

Os serviços Web também são aplicações servidor TCP/IP. Apresentam as seguintes características:

  • São hospedados por servidores Web, e o protocolo de troca cliente-servidor é o HTTP (HyperText Transport Protocol), um protocolo acima do TCP-IP.
  • O serviço Web possui um protocolo de diálogo padrão, independentemente do serviço prestado. Um serviço Web oferece vários serviços S1, S2, .., Sn. Cada um deles espera parâmetros fornecidos pelo cliente e devolve um resultado ao cliente. Para cada serviço, o cliente precisa de saber:
    • o nome exato do departamento, se
    • a lista de parâmetros a fornecer e o seu tipo
    • o tipo de resultado devolvido pelo serviço

Uma vez conhecidos estes elementos, o diálogo cliente-servidor segue o mesmo formato, independentemente do serviço Web que está a ser consultado. Desta forma, a programação do cliente é padronizada.

  • Por motivos de segurança contra ataques da Internet, muitas organizações dispõem de redes privadas e apenas abrem determinadas portas nos seus servidores para a Internet: essencialmente a porta 80 do serviço web. Todas as outras portas estão bloqueadas. As aplicações cliente-servidor, como as apresentadas no capítulo anterior, são desenvolvidas dentro da rede privada (intranet) e, geralmente, não são acessíveis a partir do exterior. Alojamento de um serviço num servidor web torna-o acessível a toda a comunidade da Internet.
  • O serviço Web pode ser modelado como um objeto remoto. Os serviços oferecidos tornam-se então métodos desse objeto. Um cliente pode aceder a este objeto remoto como se fosse local. Isto oculta toda a camada de comunicação de rede e permite-lhe construir um cliente independente desta camada. Se a camada de rede mudar, o cliente não precisa de ser modificado.
  • Tal como nas aplicações cliente-servidor TCP/IP apresentadas no capítulo anterior, o cliente e o servidor podem ser escritos em qualquer linguagem. Eles trocam linhas de texto. Estas consistem em duas partes:
    • cabeçalhos necessários para o protocolo HTTP
    • o corpo da mensagem. No caso de uma resposta do servidor para o cliente, esta está no formato XML (eXtensible Markup Language). No caso de um pedido do cliente para o servidor, o corpo da mensagem pode assumir várias formas, incluindo XML. O pedido XML do cliente pode ter um formato especial chamado SOAP (Simple Object Access Protocol). Neste caso, a resposta do servidor também segue o formato SOAP.

A arquitetura de uma aplicação cliente/servidor baseada num serviço web é a seguinte:

Trata-se de uma extensão da arquitetura de 3 camadas, à qual são adicionadas classes especializadas de comunicação de rede. Já nos deparámos com uma arquitetura semelhante no caso da aplicação gráfica cliente/servidor do Windows Tcp d'impôts, no parágrafo 11.9.1.

Vamos explicar estas noções gerais com um primeiro exemplo.

12.2. Um primeiro serviço Web com o Visual Web Developer

Vamos construir uma primeira aplicação cliente/servidor com a seguinte arquitetura simplificada:

12.2.1. O lado do servidor

Já referimos que um serviço web é alojado por um servidor web. A criação de um serviço web insere-se no âmbito geral da programação web do lado do servidor. Já tivemos oportunidade de criar clientes web, o que também é programação web, mas desta vez do lado do cliente. O termo «programação web» refere-se, na maioria das vezes, à programação do lado do servidor e não à programação do lado do cliente. Para desenvolver serviços web ou, de forma mais geral, aplicações web, o Visual C# não é a ferramenta adequada. Iremos utilizar o Visual Developer, uma das versões Express do Visual Studio 2008 disponível para download [2] em [1]: [http://msdn.microsoft.com/en-fr/express/future/bb421473(en-us).aspx] (maio de 2008):

  • [1]: endereço de download
  • [2]: separador «Downloads»
  • [3] : descarregar o Visual Developer 2008

Para criar um serviço web inicial, proceda da seguinte forma após iniciar o Visual Developer:

  • [1]: selecione a opção Ficheiro / Novo Site Web
  • [2]: escolha um Serviço Web ASP.NET
  • [3]: escolha a linguagem de desenvolvimento: C#
  • [4]: indique a pasta na qual criar o projeto
  • [5]: o projeto criado no Visual Web Developer
  • [6]: a pasta do projeto no disco

Uma aplicação web está estruturada da seguinte forma no Web Developer:

  • uma raiz contendo os documentos do site (páginas web estáticas HTML, imagens, páginas web dinâmicas .aspx, serviços web .asmx, ...). Também está incluído o ficheiro [web.config], que é o ficheiro de configuração da aplicação web. Desempenha o mesmo papel que o ficheiro [App.config] para aplicações Windows e está estruturado da mesma forma.
  • uma pasta [App_Code] contendo classes e interfaces do site para compilação.
  • uma pasta [App_Data] na qual se colocam os dados utilizados pelas classes [App_Code]. Por exemplo, esta pode conter uma base de dados SQL Server *.mdf.

[Service.asmx] é o serviço web cuja criação solicitámos. Contém apenas a seguinte linha:


<%@ WebService Language="C#" CodeBehind="~/App_Code/Service.cs" Class="Service" %>

O código-fonte acima destina-se ao servidor Web que irá hospedar a aplicação. No modo de produção, este servidor é normalmente o IIS (Internet Information Server), o servidor Web da Microsoft. O Visual Web Developer incorpora um servidor Web leve para utilização no modo de desenvolvimento. A diretiva anterior indica ao servidor Web:

  • [Service.asmx] é um serviço Web (diretiva WebService)
  • escrito em C# (atributo Language)
  • que o código C# para o serviço Web se encontra em [~/App_Code/Service.cs] (atributo CodeBehind). É aqui que o servidor Web irá para o compilar.
  • que a classe que implementa o serviço web se chama Service (atributo Class)

O código C# [Service.cs] do serviço web gerado pelo Visual Developer é o seguinte:


using System.Web.Services;
 
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
// [System.Web.Script.Services.ScriptService]
public class Service : System.Web.Services.WebService
{
    public Service () {
 
        //Uncomment the following line if using designed components 
         //InitializeComponent(); 
    }
 
    [WebMethod]
    public string HelloWorld() {
        return "Hello World";
    }
 
}

A classe Service assemelha-se a uma classe clássica do C#, mas com alguns pontos a ter em conta:

  • linha 7: a classe deriva da WebService definida em System.Web.Services. Esta herança nem sempre é obrigatória. Neste exemplo em particular, poderia ser dispensada.
  • linha 3: a própria classe é precedida por um atributo [WebService(Namespace="http://tempuri.org/")] destinado a atribuir um namespace ao serviço web. Um fornecedor de classes atribui um namespace às suas classes para lhes dar um nome único e evitar conflitos com classes de outros fornecedores que possam ter o mesmo nome. O mesmo se aplica aos serviços Web. Cada serviço Web deve ser identificado por um nome único, neste caso http://tempuri.org/. Este nome pode ser qualquer coisa. Não tem de ser um Uri Http.
  • linha 15: o método HelloWorld é precedido por um [WebMethod] que indica ao compilador que o método deve ser tornado visível para clientes remotos do serviço web. Um método não precedido por este atributo não é visível para os clientes do serviço web. Este pode ser um método interno utilizado por outros métodos, mas não destinado a publicação.
  • linha 9: construtor do serviço web. É inútil na nossa aplicação.

A classe [Service.cs] gerada é transformada da seguinte forma:


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);
    }
 
}

O ficheiro de configuração [web.config] gerado para a aplicação web é o seguinte:


<?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>

O ficheiro tem 140 linhas. É complexo e não vamos comentá-lo. Vamos mantê-lo tal como está. Acima estão os elementos <configuration>, <configSections>, <sectionGroup>, <appSettings> e <connectionString> que encontramos no ficheiro [App.config] das aplicações do Windows.

Temos um serviço web operacional que pode ser executado:

  • [1,2]: clique com o botão direito do rato em [Service.asmx] e peça para visualizar a página num navegador
  • [3]: O Visual Web Developer inicia o seu servidor web integrado e coloca o seu ícone no canto inferior direito da barra de tarefas. O servidor web é iniciado numa porta aleatória, neste caso 1906. O URI apresentado /WsHello é o nome do site [4].

O Visual Web Developer também iniciou um navegador para exibir a página solicitada, nomeadamente [Service.asmx] :

  • em [1], o URI da página. Encontramos o URI do site [http://localhost:1906/WsHello] seguido do da página /Service.asmx.
  • em [2], a extensão .asmx indicou ao servidor web que não se tratava de uma página web normal (extensão .aspx) que gera uma página HTML, mas sim de uma página de serviço web. Em seguida, gera automaticamente uma página web com um link para cada método do serviço web com o atributo [WebMethod]. Estes links permitem-lhe testar os métodos.

Clique no link [2] acima para aceder à página seguinte:

  • em [1], repare no URI [http://localhost:1906/WsHello/Service.asmx?op=DisBonjourALaDame] da nova página. Este é o URI do serviço web, com um parâmetro op=M, em que M é o nome de um dos métodos do serviço web.
  • Lembre-se da assinatura do método [DisBonjourALaDame]:

    public string DisBonjourALaDame(string nomDeLaDame) ;

O método aceita um parâmetro do tipo string e também devolve uma string. A página permite-nos executar o método [DisBonjourALaDame]: em [2] definimos o valor do parâmetro nomDeLaDame e em [3], solicitamos a execução do método. Obtemos o seguinte resultado:

  • em [1], repare que o URI na resposta não é idêntico ao da solicitação. Ele mudou.
  • em [2], a resposta do servidor web. Tenha em atenção os seguintes pontos:
    • é uma resposta em XML, não em HTML
    • o resultado do método [DisBonjourALaDame] está encapsulado numa tag <string> que representa o seu tipo.
    • A tag <string> tem um atributo xmlns (espaço de nomes XML), que é o espaço de nomes que atribuímos ao nosso serviço web (linha 1 abaixo).

[WebService(Namespace = "http://st.istia.univ-angers.fr")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService

Para saber como o navegador da Web efetuou o seu pedido, consulte o código HTML no formulário de teste:

...
<span>
  <p class="intro">Click <a href="Service.asmx">here</a> for a complete list of operations.</p>
  <h2>DisBonjourALaDame</h2>
  <p class="intro"></p>

  <h3>Test</h3>

         To test the operation using the HTTP POST protocol, click the 'Invoke' button.

   <form action='http://localhost:1906/WsHello/Service.asmx/DisBonjourALaDame' method="POST">                                      
      <table>
              <tr>
                    <td>Parameter</td>
                    <td>Value</td>
                </tr>
                <tr>
               <td>nomDeLaDame:</td>
               <td><input type="text" size="50" name="nomDeLaDame"></td>
           </tr>
                <tr>
                   <td></td>
                  <td align="right"> <input type="submit" value="Invoke" class="button"></td>
           </tr>
      </table>
    </form>
<span>
...
  • linha 11: os valores do formulário (tag form) serão enviados (atributo method) para a URL [ http://localhost:1906/WsHello/Service.asmx/DisBonjourALaDame] (atributo action).
  • linha 19: o campo de entrada chama-se nomDeLaDame (atributo name).

A solicitação da execução do serviço web [/Service.asmx] permitiu-nos testar os seus métodos e adquirir uma compreensão básica das interações cliente-servidor.

12.2.2. A secção do cliente

É possível implementar o cliente do serviço web remoto acima com um cliente TCP/IP básico. Por exemplo, eis o diálogo cliente/servidor criado com um cliente PuTTY ligado ao serviço web remoto (localhost,1906):

POST /WsHello/Service.asmx/DisBonjourALaDame HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 23

HTTP/1.1 100 Continue
Server: ASP.NET Development Server/9.0.0.0
Date: Sat, 10 May 2008 08:36:41 GMT
Content-Length: 0

nomDeLaDame=Carla+Bruni
HTTP/1.1 200 OK
Server: ASP.NET Development Server/9.0.0.0
Date: Sat, 10 May 2008 08:36:47 GMT
X-AspNet-Version: 2.0.50727
Cache-Control: private, max-age=0
Content-Type: text/xml; charset=utf-8
Content-Length: 119
Connection: Close

<?xml version="1.0" encoding="utf-8"?>
<string xmlns="http://st.istia.univ-angers.fr">Bonjour Mme Carla Bruni</string>
  • linhas 1-5: mensagens enviadas pelo cliente putty
  • linha 1: comando POST
  • linhas 6-10: resposta do servidor. Isto significa que o cliente pode enviar os valores POST.
  • linha 11: valores enviados no formulário param1=val1&param2=val2& .... Alguns caracteres devem estar entre os caracteres permitidos numa URL. É o que anteriormente chamávamos de URL codificada. Aqui, o formulário tem um único parâmetro chamado nomDeLaDame. O valor enviado tem um total de 23 caracteres. Este tamanho deve ser declarado no cabeçalho HTTP na linha 4.
  • linhas 12-22: resposta do servidor
  • linha 22: o resultado do método web [DisBonjourALaDame].

Com o Visual C#, pode utilizar um assistente para gerar o cliente de um serviço Web remoto. É isso que vamos ver agora.

A camada [1] acima é implementada por um projeto Visual Studio C# do tipo aplicação Windows e denominado ClientWsHello :

  • em [1], o ClientWsHello no Visual C#
  • em [2], o namespace padrão do projeto será Customer (clique com o botão direito do rato no projeto / Propriedades / Aplicação). Este namespace será utilizado para construir o namespace do cliente que será gerado.
  • em [3], clique com o botão direito do rato no projeto para adicionar uma referência a um serviço web remoto
  • em [4], defina o Uri do serviço web criado anteriormente
  • em [4b], ligue o Visual C# ao serviço web indicado em [4]. O Visual C# irá recuperar a descrição do serviço web e utilizá-la para gerar um cliente.
  • em [5], assim que a descrição do serviço web for recuperada, o Visual C# pode exibir os seus métodos públicos
  • em [6], especifique um namespace para o cliente a ser gerado. Este será adicionado ao namespace definido em [2]. Desta forma, o namespace do cliente será Client.WsHello.
  • em [6b] para validar o assistente.
  • em [7], a referência ao serviço web WsHello aparece no projeto. Além disso, foi criado um ficheiro de configuração [app.config].
  • em [8], visualize todos os ficheiros do projeto.
  • em [9], a referência ao serviço web WsHello contém vários ficheiros que não iremos abordar aqui. No entanto, iremos dar uma vista de olhos ao ficheiro [Reference.cs], que é o código C# para o cliente gerado:

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;
        }
    }
}
  • linha 1: o namespace do cliente gerado é Client.WsHello. Se quiser alterar este namespace, é aqui que deve fazê-lo.
  • linha 3: a classe ServiceSoapClient é a classe cliente gerada. É uma classe proxy, no sentido de que irá ocultar da aplicação Windows o facto de estar a ser utilizado um serviço web remoto. A aplicação Windows utilizará o WsHello através da classe local Client.WsHello.ServiceSoapClient. Para criar uma instância do cliente, utilize o construtor na linha 5:
Client.WsHello.ServiceSoapClient client=new Client.WsHello.ServiceSoapClient();
  • linha 8: o método DisBonjourALaDame é a contraparte do lado do cliente do serviço web DisBonjourALaDame. A aplicação Windows utilizará o método remoto DisBonjourALaDame através do método local Client.WsHello.ServiceSoapClient.DisBonjourALaDame da seguinte forma:
string bonjour=client.DisBonjourALaDame("Carla Bruni");

O ficheiro [app.config] gerado é o seguinte:


<?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>

Deste ficheiro, iremos reter apenas a linha 8, que contém o URI do serviço web. Se este URI for alterado, não é necessário recompilar o cliente Windows. Basta alterar o URI no ficheiro [app.config].

Voltemos à arquitetura da aplicação Windows que pretendemos construir:

Já construímos a camada [client] do serviço web. A camada [ui] será a seguinte:

n.º
tipo
nome
função
1
Caixa de Texto
textBoxNomDame
nome da senhora
2
Botão
botãoSaudações
para se ligar ao serviço web remoto WsHello e consultar o método DisBonjourALaDame.
3
Rótulo
labelBonjour
o resultado devolvido pelo serviço web

O código do formulário [Form1.cs] é o seguinte:


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) {
             // hourglass
            Cursor=Cursors.WaitCursor;
             // web query service
            labelBonjour.Text = new ServiceSoapClient().DisBonjourALaDame(textBoxNomDame.Text.Trim());
             // normal slider
            Cursor = Cursors.Arrow;
        }
    }
}
  • linha 15: o cliente do serviço web é instanciado. É do tipo Client.WsHello.ServiceSoapClient. O namespace Client.WsHello é declarado na linha 3. É chamado o método local ServiceSoapClient().DisBonjourALaDame. Sabemos que este invoca o método remoto com o mesmo nome no serviço web.

12.3. Um serviço de operações aritméticas na Web

Vamos construir uma segunda aplicação cliente/servidor com a seguinte arquitetura simplificada:

O serviço Web anterior oferecia um único método. Consideramos um serviço Web que irá oferecer 4 operações aritméticas:

  1. ajouter(a,b) que resultará em a+b
  2. subtrair(a,b) que resultará em a-b
  3. multiplier(a,b) que retornará a*b
  4. diviser(a,b) que retornará a/b

que será consultado pela seguinte interface gráfica:

  • em [1], a operação a ser realizada
  • em [2,3]: os operandos
  • em [4], o botão de chamada do serviço web
  • em [5], o resultado do serviço web

12.3.1. O lado do servidor

Criamos um projeto de serviço web com o Visual Web Developer:

  • em [1], a aplicação web WsOperations gerada
  • em [2], a aplicação web WsOperations foi redesenhada da seguinte forma:
  • a página web [Service.asmx] foi renomeada para [Operations.asmx]
  • a classe [Service.cs] foi renomeada para [Operations.cs]
  • o ficheiro [web.config] foi removido para mostrar que não é essencial.

A página web [Service.asmx] contém a seguinte linha:


<%@ WebService Language="C#" CodeBehind="~/App_Code/Operations.cs" Class="Operations" %>

O serviço web é fornecido pela seguinte classe [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 colocar o serviço web online, procedemos conforme descrito em [3]. Em seguida, obtemos a página de teste para os 4 métodos do serviço web WsOperations :

Image

Convidamos os leitores a experimentarem os 4 métodos.

12.3.2. A secção do cliente

Com o Visual C#, criamos uma aplicação Windows chamada ClientWsOperations:

  • em [1], o ClientWsOperations no Visual C#
  • em [2], o namespace padrão do projeto será Customer (clique com o botão direito do rato no projeto / Propriedades / Aplicação). Este namespace será utilizado para construir o namespace do cliente que será gerado.
  • em [3], clique com o botão direito do rato no projeto para adicionar uma referência a um serviço web existente
  • em [4], defina o Uri do serviço web criado anteriormente. Para tal, observe o que é apresentado no campo de endereço do navegador que exibe a página de teste do serviço web.
  • em [4b], ligue o Visual C# ao serviço web designado por [4]. O Visual C# irá recuperar a descrição do serviço web e utilizá-la para gerar um cliente.
  • em [5], assim que a descrição do serviço web for recuperada, o Visual C# pode exibir os seus métodos públicos
  • em [6], especifique um namespace para o cliente a ser gerado. Este será adicionado ao namespace definido em [2]. Desta forma, o namespace do cliente será Client.WsOperations.
  • em [6b] para validar o assistente.
  • em [7], a referência ao serviço web WsOperations aparece no projeto. Além disso, foi criado um ficheiro de configuração [app.config].

Lembre-se de que o cliente gerado é do tipo Client.WsOperations.OperationsSoapClient, onde

  • Client.WsOperations é o namespace do cliente do serviço web
  • Operations é a classe do serviço web remoto.

Embora exista uma forma lógica de construir este nome, é frequentemente mais fácil encontrá-lo no ficheiro [Reference.cs], que é um ficheiro oculto por predefinição. O seu conteúdo é o seguinte:


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) {
            ...
        }
    }
}

Os métodos Add, Subtract, Multiply e Divide do serviço web remoto serão acedidos através dos métodos proxy com o mesmo nome (linhas 8, 12, 16, 20) do cliente do tipo Client.WsOperations.OperationsSoapClient (linha 3).

Resta apenas construir a interface gráfica:

n.º
tipo
nome
função
1
ComboBox
comboBoxOperations
lista de operações aritméticas
2
Caixa de Texto
textBoxA
número a
3
Caixa de Texto
caixa de texto B
número b
4
Botão
botãoExecutar
consulta o serviço web remoto
5
Rótulo
labelResult
o resultado da operação

O código para [Form1.cs] é o seguinte:


using System;
using System.Windows.Forms;
using Client.WsOperations;
 
namespace ClientWsOperations {
    public partial class Form1 : Form {
         // operations table
        private string[] opérations = { "Ajouter", "Soustraire", "Multiplier", "Diviser" };
         // department web to contact
        private OperationsSoapClient opérateur = new OperationsSoapClient();
 
         // manufacturer
        public Form1() {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e) {
             // combo filling of operations
            comboBoxOperations.Items.AddRange(opérations);
            comboBoxOperations.SelectedIndex = 0;
        }
 
        private void buttonExécuter_Click(object sender, EventArgs e) {
            // checking operation parameters a and b
            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;
            }
             // operation execution
            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;
                }
                 // result display
                labelRésultat.Text = c.ToString();
            } catch (Exception ex) {
                textBoxMessage.Text = ex.Message;
            }
        }
    }
}
  • linha 3: namespace do cliente do serviço web remoto
  • linha 10: o cliente do serviço web remoto é instanciado ao mesmo tempo que o formulário
  • linhas 17-21: o combo de operações é preenchido quando o formulário é carregado pela primeira vez
  • linha 23: execução da operação solicitada pelo utilizador
  • linhas 25-37: verificação de que as entradas a e b são números reais
  • linhas 41-54: um switch para executar a operação remota solicitada pelo utilizador
  • linhas 43, 46, 49, 52: o cliente local é consultado. De forma transparente, este consulta o serviço web remoto.

12.4. Um serviço web de cálculo de impostos

Estamos de volta com a já conhecida aplicação de cálculo de impostos. Da última vez que trabalhámos com ela, transformámo-la num servidor TCP remoto que podia ser chamado pela Internet. Agora, transformámo-la num serviço web.

A arquitetura da versão 8 era a seguinte:

A arquitetura da versão 9 será semelhante:

Esta arquitetura é semelhante à da versão 8, descrita no parágrafo 11.9.1, mas em que o servidor e o cliente TCP são substituídos por uma web de serviços e o seu cliente proxy. Vamos herdar todas as camadas [ui], [metier] e [dao] da versão 8.

12.4.1. O lado do servidor

Criamos um projeto de serviço web com o Visual Web Developer:

  • em [1], a aplicação web WsImpot gerada
  • em [2], a aplicação web WsImpot foi redesenhada da seguinte forma:
    • a página web [Service.asmx] foi renomeada para [ServiceImpot.asmx]
    • a classe [Service.cs] foi renomeada para [ServiceImpot.cs]

A página web [ServiceImpot.asmx] contém a seguinte linha:


<%@ WebService Language="C#" CodeBehind="~/App_Code/ServiceImpot.cs" Class="ServiceImpot" %>

O serviço web é fornecido pela seguinte classe [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;
    }
 
}

O serviço web irá expor apenas o CalculerImpot na linha 9.

Voltemos à arquitetura cliente/servidor da versão 8:

O projeto do Visual Studio no servidor [1] era o seguinte:

  • em [1], o projeto. Incluía os seguintes elementos:
    • [ServeurImpot.cs]: o servidor de cálculo de impostos TCP/IP na forma de uma aplicação de consola.
    • [dbimpots.sdf]: a base de dados compacta do SQL Server da versão 7 descrita no parágrafo 9.8.5.
    • [App.config]: ficheiro de configuração da aplicação.
  • em [2], a pasta [lib] contém a DLL necessária para o projeto:
    • [ImpotsV7-dao]: a camada [dao] na versão 7
    • [ImpotsV7-metier]: a camada [metier] na versão 7
    • [antlr.runtime, CommonLogging, Spring.Core] para Spring
  • em [3], o projeto faz referência a

As camadas [metier] e [dao] desta versão já existem: são as utilizadas nas versões 7 e 8. Estão na forma de DLL, que integramos no projeto da seguinte forma:

  • em [1], a pasta [lib] do servidor da versão 8 foi copiada para o projeto de serviço web da versão 9.
  • em [2] modificamos as propriedades da página para adicionar a DLL da pasta [lib] [4] às referências do projeto [3].

Após esta operação, temos todas as camadas necessárias para o servidor [1] abaixo:

Embora os elementos do servidor [1], [server], [metier], [dao], [entities] e [spring] estejam todos presentes no projeto do Visual Studio, falta-nos o elemento que os instanciará no arranque da aplicação web. Na versão 8, uma classe principal com o método estático [Main] encarregava-se de instanciar as camadas com a ajuda do Spring. Numa aplicação web, a classe capaz de realizar uma tarefa semelhante é a classe associada ao [ Global.asax]:

  • em [1], um novo elemento é adicionado ao projeto web
  • em [2], escolhemos a Classe de Aplicação Global
  • em [3], o nome padrão proposto para este elemento
  • em [4], validamos a adição de
  • em [5], o novo elemento foi integrado no

Vejamos o conteúdo do ficheiro [Global.asax]:


<%@ Application Language="C#" %>
 
<script runat="server">
 
    void Application_Start(object sender, EventArgs e) 
    {
        // Code that runs on application startup
    }
 
    void Application_End(object sender, EventArgs e) 
    {
        //  Code that runs on application shutdown
    }
 
    void Application_Error(object sender, EventArgs e) 
    { 
        // Code that runs when an unhandled error occurs
    }
 
    void Session_Start(object sender, EventArgs e) 
    {
        // Code that runs when a new session is started
    }
 
    void Session_End(object sender, EventArgs e) 
    {
        // Code that runs when a session ends. 
    }
 
</script>

O ficheiro é uma mistura de tags do servidor web (linhas 1, 3, 30) e código C#. Este era o único método utilizado com o ASP, o antecessor do ASP.NET, a tecnologia atual da Microsoft para programação web. Com o ASP.NET, este método ainda pode ser utilizado, mas não é o método padrão. O método padrão é o método «CodeBehind», que já vimos em páginas de serviços web, por exemplo aqui em [ServiceImpot.asmx]:


<%@ WebService Language="C#" CodeBehind="~/App_Code/ServiceImpot.cs" Class="ServiceImpot" %>

O CodeBehind especifica a localização do código-fonte da página [ServiceImpot.asmx]. Sem este atributo, o código-fonte estaria na página [ServiceImpot.asmx] com uma sintaxe semelhante à encontrada em [Global.asax]. Não vamos manter o ficheiro [Global.asax] tal como foi gerado, mas o seu código permite-nos compreender para que serve:

  • a classe associada a Global.asax é instanciada no arranque da aplicação. A sua duração corresponde à da aplicação na sua totalidade. Em termos concretos, só desaparece quando o servidor web é parado.
  • O método Application_Start é executado a seguir. Esta é a única vez que será executado. É, portanto, utilizado para instanciar objetos partilhados por todos os utilizadores. Estes objetos são colocados:
    • nos campos estáticos da classe associada ao Global.asax. Como esta classe está permanentemente presente, qualquer pedido de qualquer utilizador pode ler informações a partir dela.
    • ou no contentor Application. Este contentor também é criado quando a aplicação é iniciada, e a sua duração é a mesma da aplicação.
      • Para colocar dados neste contentor, escrevemos Application["key"]=value;
      • para os recuperar, escrevemos T value=(T)Application["key"]; onde T é o tipo de valor.
  • O método Session_Start é executado sempre que um novo utilizador faz uma solicitação. Como reconhecemos um novo utilizador? Cada utilizador (geralmente um navegador) recebe um token de sessão, que é uma sequência de caracteres única para cada utilizador. Sempre que uma nova solicitação é feita, o utilizador reenvia o token de sessão que recebeu. Isto permite que o servidor web reconheça o utilizador. Ao longo das várias solicitações de um utilizador, os dados específicos desse utilizador podem ser armazenados na Session:
    • para colocar dados neste contentor, escrevemos Session["chave"]=valor;
    • para o recuperar, escrevemos T value=(T)Session["key"]; onde T é o tipo de valor.

Por predefinição, a duração de uma sessão está limitada a 20 minutos de inatividade do utilizador (ou seja, o utilizador não enviou de volta o seu token de sessão durante 20 minutos).

  • O método Application_Error é executado quando uma exceção não tratada pela aplicação web é enviada para o servidor web.
  • Outros métodos são utilizados com menos frequência.

Após estas generalidades, o que podemos fazer por si? Global.asax? Utilizaremos o seu método Application_Start para inicializar as camadas [metier], [dao] e [entites] contidas nas DLL [ImpotsV7-metier, ImpotsV7-dao]. Utilizaremos o Spring para as instanciar. As referências de camadas criadas desta forma serão então armazenadas em campos estáticos na classe associada ao Global.asax.

Na primeira etapa, transferimos o código C# do ficheiro Global.asax para uma classe própria. O projeto está a evoluir da seguinte forma:

Em [1], o ficheiro [Global.asax] será associado à classe [Global.cs] [2] com a seguinte linha única:


<%@ Application Language="C#" Inherits="WsImpot.Global"%>

O Inherits="WsImpot.Global" indica que a classe associada a Global.asax herda de WsImpot.Global. Esta classe é definida em [Global.cs] da seguinte forma:


using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
    public class Global : System.Web.HttpApplication
    {
         // business layer
        public static IImpotMetier Metier;
 
         // method executed at application startup
        private void Application_Start(object sender, EventArgs e)
        {
            // instantiations [metier] and [dao] layers
            Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
        }
    }
}
  • linha 4: namespace da classe
  • linha 6: a classe Global. Pode dar-lhe o nome que quiser. O importante é que ela derive de System.Web.HttpApplication.
  • linha 9: um campo público estático que contém uma referência à camada [metier].
  • linha 12: o método Application_Start, que será executado quando a aplicação for iniciada.
  • linha 15: o Spring é utilizado para explorar o ficheiro [web.config], no qual irá encontrar os objetos a instanciar para criar as camadas [metier] e [dao]. Não há diferença entre utilizar o Spring com [App.config] numa aplicação Windows e utilizar o Spring com [web.config] numa aplicação web. [web.config] e [App.config] também têm a mesma estrutura. A linha 15 armazena a referência da camada [metier] no campo estático da linha 9, para que esta referência esteja disponível para todas as consultas de todos os utilizadores.

O ficheiro [web.config] terá o seguinte aspeto:


<?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 é o ficheiro [App.config] utilizado na versão 7 da aplicação e analisado no parágrafo 9.8.4.

  • linhas 16-20: definem uma camada [dao] que trabalha com uma base de dados MySQL5. Esta base de dados foi descrita no parágrafo 9.8.1.
  • linhas 21-23: definem a camada [metier]

De volta ao quebra-cabeças do servidor:

No arranque da aplicação, as camadas [metier] e [dao] foram instanciadas. O tempo de vida destas camadas é o da própria aplicação. Quando é que o serviço web é instanciado? Sempre que é feita uma solicitação ao mesmo. No final da solicitação, o objeto que a atendeu é eliminado. Assim, à primeira vista, um serviço web é sem estado. Não pode armazenar informações entre duas solicitações em campos que lhe pertençam. Pode armazenar informações na sessão do utilizador. Para tal, os métodos que expõe devem ser marcados com um especial :


    [WebMethod(EnableSession=true)]
    public int CalculerImpot(bool marié, int nbEnfants, int salaire)
....

Acima, a linha 1 autoriza o CalculerImpot a aceder ao contentor Session que mencionámos anteriormente. Não precisaremos de utilizar este atributo na nossa aplicação. O serviço web WsImpot será, portanto, instanciado em cada pedido e será sem estado.

Podemos agora escrever o código [ServiceImpot.cs] que implementa o serviço 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);
    }
 
}
  • linha 10: o método exclusivo do serviço web
  • linha 12: utilizamos o CalculerImpot da camada [metier]. Encontra-se uma referência a esta camada no campo estático da classe Global da Trade. Esta pertence ao WsImpot (linha 2).

Estamos agora prontos para iniciar o serviço web. Primeiro, precisamos de executar o SGBD MySQL5 para que a base de dados bdimpots fique acessível. Depois de feito isto, iniciamos [1] o serviço web:

O navegador exibe então a página [2]. Seguimos o link:

Atribuímos um valor a cada um dos três parâmetros do método CalculerImpot e solicitamos a execução do método. Obtemos o seguinte resultado, que está correto:

Image

12.4.2. Um cliente gráfico do Windows para o serviço web remoto

Agora que o serviço web foi escrito, vamos passar para o cliente. Vamos rever a arquitetura da aplicação cliente/servidor:

Precisamos agora de escrever o cliente [2]. A interface gráfica será idêntica à da versão 8:

Para escrever a parte [cliente] da versão 9, começaremos pela parte [cliente] da versão 8 e, em seguida, faremos as modificações necessárias. Duplicamos o projeto do Visual Studio estudado no parágrafo 11.9.4.1, renomeamo-lo para ClientWsImpot e carregamo-lo no Visual Studio:

A solução do Visual Studio para a versão 8 consistia em 2 projetos:

  • o projeto [metier] [1], que era um cliente TCP do servidor de cálculo de impostos TCP
  • o projeto GUI [ui] [2].

As alterações a efetuar são as seguintes:

  • o projeto [metier] deve agora ser o cliente de um serviço web
  • o projeto [ui] deve referenciar a DLL da nova camada [metier]
  • a configuração da camada [metier] no [App.config] deve ser alterada.

12.4.2.1. A nova camada [metier]

  • em [1], IImpotMetier é a interface da camada [metier] e ImpotMetierTcp a sua implementação por um cliente TCP
  • em [2], removemos o ImpotMetierTcp. Precisamos de criar outra implementação do IImpotMetier que será um cliente de um serviço web.
  • em [3], chamamos Customer ao namespace padrão do projeto [metier]. A DLL que será gerada será chamada [ImpotsV9-metier.dll].
  • em [4], criamos uma referência ao serviço web WsImpot.
  • em [5], configuramos e validamos o mesmo.
  • em [6], a referência ao ficheiro do serviço web WsImpot foi criada e foi gerado um ficheiro [app.config].

No ficheiro oculto [Reference.cs]:

  • O namespace é Client.WsImpot
  • a classe cliente chama-se ServiceImpotSoapClient
  • possui um método de assinatura único:

        public int CalculerImpot(bool marié, int nbEnfants, int salaire) ;

Resta apenas implementar a interface IImpotMetier:


namespace Metier {
    public interface IImpotMetier {
        int CalculerImpot(bool marié, int nbEnfants, int salaire);
    }
}

Implementamo-la com o ImpotMetierWs a seguir:


using System.Net.Sockets;
using System.IO;
using Client.WsImpot;
 
namespace Metier {
    public class ImpotMetierWs : IImpotMetier {
 
         // remote web customer service
        private ServiceImpotSoapClient client = new ServiceImpotSoapClient();
 
         // tAX CALCULATION
        public int CalculerImpot(bool marié, int nbEnfants, int salaire) {
            return client.CalculerImpot(marié, nbEnfants, salaire);
        }
 
    }
}
  • linha 6: a classe ImpotMetierWs implementa a interface IImpotMetier.
  • linha 9: ao criar uma instância de ImpotMetierWs, o campo customer é inicializado com uma instância do cliente do serviço de cálculo de impostos na Web.
  • linha 12: a única interface a implementar é a IImpotMetier.
  • linha 13: utilizamos o CalculerImpot do cliente do serviço remoto de cálculo de impostos na Web. No final, é o método CalculerImpot do serviço remoto na Web que deve ser consultado.

É possível gerar a DLL do projeto:

  • em [1], o projeto [cliente] no seu estado final
  • em [2], geração da DLL do projeto
  • em [3], a DLL ImpotsV9-metier.dll encontra-se na pasta /bin/Release do projeto.

12.4.2.2. A nova camada [ui]

A camada [client] do cliente já foi escrita. Agora precisamos de escrever a camada [ui]. De volta ao projeto do Visual Studio:

  • em [1], o projeto [ui] da versão 8
  • em [2], a DLL ImpotsV8-metier da antiga camada [metier] é substituída pela DLL ImpotsV9-metier da nova camada
  • em [3], a DLL ImpotsV9-metier é adicionada às referências do projeto.

A segunda alteração ocorre em [App.config]. Lembre-se de que este ficheiro é utilizado pelo Spring para instanciar a camada [metier]. Como isto mudou, a configuração de [App.config] deve ser alterada. Por outro lado, [App.config] deve ser configurado para aceder ao serviço remoto de cálculo de impostos na Web. Esta configuração foi gerada no ficheiro [app.config] do projeto [metier] quando a referência ao serviço remoto na Web foi adicionada.

O ficheiro [App.config] passa assim a ter o seguinte aspeto:


<?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>
 
     <!-- web service -->
    <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>
  • linhas 15-18: O Spring instancia apenas um objeto, a camada [metier]
  • linha 16: a camada [metier] é instanciada pela classe [Metier.ImportMetierWs], que se encontra na DLL ImpotsV9-metier.
  • linhas 22-46: configuração do cliente para o serviço web remoto. Trata-se de uma cópia/colagem do conteúdo do ficheiro [app.config] no projeto [metier].

Estamos prontos para começar. Execute a aplicação com Ctrl-F5 (o serviço web deve estar iniciado, o SGBD MySQL5 deve estar iniciado, a linha 42 acima relativa à porta deve estar correta):

  

12.5. Um cliente web para o serviço de cálculo de impostos web

Voltemos à arquitetura da aplicação cliente/servidor que acabámos de escrever:

A camada [ui] acima foi implementada por um cliente gráfico do Windows. Agora, vamos implementá-la com uma interface web:

 

Esta é uma alteração importante para os utilizadores. Atualmente, a nossa aplicação cliente/servidor, versão 9, pode servir vários clientes em simultâneo. Trata-se de uma melhoria em relação à versão 8, que apenas servia um cliente de cada vez. A restrição é que os utilizadores que pretendam utilizar o serviço de cálculo de impostos na Web devem ter o cliente Windows que criámos instalado nas suas estações de trabalho. Nesta nova versão, a que chamaremos versão 10, os utilizadores poderão utilizar o seu navegador para aceder ao serviço de cálculo de impostos na Web.

Na arquitetura acima:

  • o lado do servidor permanece inalterado. Mantém-se tal como na versão 9.
  • no lado do cliente, a camada [cliente de serviço web] permanece inalterada. Foi encapsulada na DLL [ImpotsV9-metier]. Iremos reutilizar esta DLL.
  • no final, a única alteração consiste em substituir uma GUI do Windows por uma interface web.

Vamos analisar mais de perto a programação do lado do servidor web. Como o objetivo deste documento não é ensinar programação web, tentaremos explicar a abordagem que iremos adotar, mas sem entrar em demasiados detalhes. Haverá, portanto, um pouco de «magia» nesta secção. No entanto, consideramos que vale a pena dar este passo para mostrar um novo exemplo de arquitetura multicamadas em que uma das camadas é alterada.

A arquitetura da versão 10 é a seguinte:

Já temos todas as camadas, exceto a [web]. Para compreender melhor o que vai ser feito, precisamos de ser mais precisos quanto à arquitetura do cliente. Será a seguinte:

  • o utilizador web tem um formulário web no seu navegador
  • este formulário é enviado para o servidor web 1, que o processa através da camada [web]
  • a camada [web] irá necessitar dos serviços de cliente do serviço web remoto, encapsulados em [ImpotsV9-metier.dll].
  • o cliente do serviço web remoto irá comunicar com o servidor web 2, que hospeda o serviço web remoto.
  • a resposta do serviço web remoto é transmitida à camada web do cliente, que a formata numa página e a envia ao utilizador.

O nosso trabalho aqui é, portanto:

  • criar o formulário web que o utilizador verá no seu navegador
  • escrever a aplicação web, que irá processar o pedido do utilizador e enviar uma resposta sob a forma de uma nova página web. Esta será, na verdade, idêntica ao formulário, ao qual adicionámos o montante do imposto a pagar
  • escrever o "aglutinante" que faz com que tudo funcione em conjunto.

Tudo isto será feito utilizando um novo site criado com o Visual Web Developer:

  • [1]: selecione a opção Ficheiro / Novo Site
  • [2]: escolha um Site ASP.NET
  • [3]: escolha a linguagem de desenvolvimento: C#
  • [4]: indique a pasta na qual criar o projeto
  • [5]: o projeto criado no Visual Web Developer
    • [Default.aspx] é uma página web denominada página predefinida. É a página que será apresentada se for solicitada a URL http://.../ClientAspImpot sem especificar um documento. Esta é a página que conterá o formulário de cálculo de impostos que o utilizador verá no seu navegador.
    • [Default.aspx.cs] é a classe associada à página, que irá gerar o formulário enviado ao utilizador e processá-lo assim que este for preenchido e validado.
    • [web.config] é o ficheiro de configuração da aplicação. Ao contrário de outras ocasiões, vamos mantê-lo.

Se voltarmos à arquitetura que precisamos de construir:

  • [1] será implementado por [Default.aspx]
  • [2] será implementado por [Default.aspx.cs]
  • [3] será implementado pela DLL [ImpotV9-metier]

Vamos começar por implementar a camada [3]. Existem vários passos:

  • em [1], a pasta [lib] do cliente gráfico do Windows 9 é copiada para a pasta do projeto web [ClientAspWsImpot]. Isto é feito utilizando o Explorador do Windows. Para visualizar esta pasta na solução do Web Developer, atualize a solução com o botão [2].
  • Em seguida, adicione-as às referências do projeto [3,4,5]. As DLL referenciadas são automaticamente copiadas para a pasta /bin do projeto [6].

Agora temos as DLL necessárias para executar o Spring, e a camada de cliente para o serviço web remoto também foi implementada. Embora o código para esta última esteja presente, a sua configuração ainda precisa de ser feita. Na versão 9, era configurada pelo seguinte ficheiro [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>
 
     <!-- web service -->
    <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>

Pegamos nesta configuração e integramo-la no ficheiro [web.config] da seguinte forma:


<?xml version="1.0"?>
<configuration>
 
 
    <configSections>
      <sectionGroup name="system.web.extensions"...>
...
      </sectionGroup>
       <!-- start Spring section -->
        <sectionGroup name="spring">
          <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
          <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
       <!-- end Spring section -->
    </configSections>
 
   <!-- start Spring configuration -->
  <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>
   <!-- end Spring configuration -->
 
   <!-- start remote web service client configuration -->
  <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>
   <!-- end remote web service client configuration -->
 
   <!-- other configurations already present in the generated web.config -->
...
</configuration>

Note que a linha 37 refere-se à porta do serviço web remoto. Esta porta pode mudar, uma vez que o Visual Developer inicia o serviço web numa porta aleatória.

Voltemos à arquitetura do cliente web que precisamos de construir:

  • [1] será implementado por [Default.aspx]
  • [2] será implementado por [Default.aspx.cs]
  • [3] foi implementado pela DLL [ImpotV9-metier]

Acabámos de implementar a camada [3]. Passemos agora à interface web [1] implementada pela página [Default.aspx]. Clique duas vezes na página [Default.aspx] para passar para o modo de design.

Existem duas formas de criar uma página web:

  • graficamente, como em [2]. Deve então selecionar o modo [Design] em [1]. Esta barra de botões encontra-se na parte inferior da barra de estado do editor de páginas web.
  • com uma linguagem de tags, como em [3]. Deve então selecionar o modo [Source] em [1].

Os modos [Design] e [Source] são bidirecionais: uma modificação feita no modo [Design] traduz-se numa modificação no modo [Source] e vice-versa. Lembre-se de que o formulário web a apresentar no navegador é o seguinte:

  • em [1], o formulário exibido num navegador
  • em [2], os componentes utilizados para o construir
  • em [3], a página de design do formulário. Inclui os seguintes elementos:
    • na linha A, dois botões de opção denominados RadioButtonOui e RadioButtonNon
    • linha B, um campo de entrada chamado TextBoxEnfants e um rótulo chamado LabelErreurEnfants
    • linha C, um campo de entrada chamado TextBoxSalaire e um rótulo chamado LabelErreurSalaire
    • linha D, um rótulo chamado LabelImpot
    • linha e, dois botões denominados ButtonCalculer e ButtonEffacer

Depois de um componente ter sido colocado na superfície de design, é possível aceder às suas propriedades:

  • em [1], acesso às propriedades do componente
  • em [2], a folha de propriedades do componente [LabelErreurEnfants ]
  • em [3], (ID) é o nome do componente
  • em [4], atribuímos a cor vermelha aos caracteres do rótulo.

Não basta simplesmente colocar componentes no formulário e definir as suas propriedades. Também é necessário organizar o seu layout. Numa interface gráfica do Windows, este layout é absoluto. Basta arrastar o componente para onde quiser que ele fique. Numa página web, é diferente, mais complexo, mas também mais poderoso. Este aspeto não será abordado aqui.

O código-fonte [Default.aspx] gerado por este design é o seguinte:


<%@ 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&#39;enfants
          </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>
            &nbsp;
          </td>
        </tr>
      </table>
      </div>
    </ContentTemplate>
  </asp:UpdatePanel>
  </form>
</body>
</html>

Os componentes do formulário são identificados pelas linhas 23, 24, 33, 36, 44, 47, 55, 63 e 66. O restante é essencialmente formatação.

Voltemos à arquitetura que temos de construir:

  • [1] foi implementado por [Default.aspx]
  • [2] será implementado por [Default.aspx.cs]
  • [3] foi implementado pela DLL [ImpotV9-metier]

As camadas [1] e [3] já foram implementadas. Ainda precisamos de escrever a camada [2], que gera o formulário, o envia ao utilizador, o processa quando o utilizador devolve o formulário preenchido, utiliza a camada [3] para calcular o imposto, gera a página de resposta web e a envia de volta ao utilizador. O código [Default.aspx.cs] faz todo este trabalho:


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)
    {
...
    }
}

O código é muito semelhante ao de um formulário clássico do Windows. Esta é a principal vantagem da tecnologia ASP.NET: não há ruptura entre o modelo de programação do Windows e o modelo de programação web ASP.NET. Basta lembrar-se do seguinte diagrama:

Quando, em [1], o utilizador clica no botão [Calcular], o procedimento é repetido: o ButtonCalculer_Click na linha 6 do [Default.aspx.cs] será executado. Mas, entretanto:

  • os valores do formulário preenchido serão transmitidos do navegador para o servidor web através do protocolo Http
  • o servidor ASP.NET analisará o pedido e transferi-lo-á para a página [Default.aspx]
  • a página [Default.aspx] será instanciada.
  • os seus componentes (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire, LabelErreurEnfants, LabelErreurSalaire, LabelImpot) serão inicializados com o valor que tinham quando o formulário foi inicialmente enviado para o navegador, graças a um mecanismo denominado «ViewState».
  • os valores enviados serão atribuídos aos seus componentes (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire). Por exemplo, se o utilizador tiver definido o número de filhos como 2, obtemos TextBoxEnfants.Text="2".
  • Se a página [Default.aspx] tiver um método [Page_Load], este será executado
  • o método [ButtonCalculer_Click] na linha 6 será executado se o botão [Calculate] for clicado
  • o método [ButtonEffacer_Click] na linha 10 será executado se o botão [Delete] for clicado

Entre o momento em que o utilizador cria um evento no seu navegador e o momento em que este é processado em [Default.aspx.cs], existe uma grande complexidade. Esta complexidade está oculta, e podemos fingir que não existe quando escrevemos os manipuladores de eventos na página web. Mas nunca devemos esquecer que existe uma rede entre o evento e o seu manipulador, e que não se trata de lidar com eventos do rato, como o Mouse_Move, que causariam dispendiosas idas e voltas entre o cliente e o servidor...

O código para gerir cliques nos botões [Calculate] e [Delete] é aquele que teria sido escrito para uma aplicação Windows convencional:


protected void ButtonCalculer_Click(object sender, EventArgs e)
    {
         // data verification
        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;
        }
         // mistake?
        if (erreur) return;
         // erase any errors
        LabelErreurEnfants.Text = "";
        LabelErreurSalaire.Text = "";
         // marital status
        bool marié = RadioButtonOui.Checked;
         // tAX CALCULATION
        try
        {
            LabelImpot.Text = String.Format("{0} euros",Global.Metier.CalculerImpot(marié, nbEnfants, salaire));
        }
        catch (Exception ex)
        {
            LabelImpot.Text = ex.Message;
        }
    }
  • Para compreender este código, é necessário saber
    • no início da sua execução, o formulário [Default.aspx] está tal como o utilizador o preencheu. Isto significa que os campos (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire) têm os valores introduzidos pelo utilizador.
    • No final da sua execução, a mesma página [Default.aspx] será apresentada ao utilizador. Isto é feito automaticamente.

O procedimento ButtonCalculer_Click deve, portanto, basear-se nos valores atuais dos campos (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire) definir o valor de todos os campos (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire, LabelErreurEnfants, LabelErreurSalaire, LabelImpot) da nova página [Default.aspx] que será apresentada ao utilizador.

Não há dificuldades particulares com este código. Apenas a linha 27 precisa de ser explicada. Ela utiliza o CalculerImpot e um campo Global.Metier que ainda não foi definido. Voltaremos a este assunto em breve.

O método ButtonEffacer_Click é o seguinte:


    protected void ButtonEffacer_Click(object sender, EventArgs e)
    {
         // raz form
        TextBoxEnfants.Text = "";
        TextBoxSalaire.Text = "";
        LabelImpot.Text = "";
        LabelErreurEnfants.Text = "";
        LabelErreurSalaire.Text = "";
}

Voltemos à arquitetura que temos de construir:

  • [1] foi implementado por [Default.aspx]
  • [2] foi implementado por [Default.aspx.cs]
  • [3] foi implementado pela DLL [ImpotV9-metier]

Resta apenas colocar a «cola» em torno destas três camadas. Essencialmente, trata-se de:

  • instanciar a camada [3] no arranque da aplicação
  • colocar uma referência a ela num local onde a página web [Default.aspx.cs] possa obtê-la sempre que for instanciada e solicitada a calcular o imposto.

Este não é um problema novo. Já foi encontrado na construção do serviço web remoto e estudado no parágrafo 12.4.1. Sabemos que a solução consiste em:

  • criar um ficheiro [Global.asax] associado a uma classe [Global.cs]
  • instanciar a camada [3] no método Application_Start a partir de [Global.cs]
  • colocar a referência da camada [3] num campo estático da classe [Global.cs], uma vez que o tempo de vida desta classe é o da aplicação.

Como resultado, o nosso projeto web está a evoluir da seguinte forma:

  • em [1], ficheiro [Global.asax].
  • em [2], o código associado [Global.cs]. A pasta [App_Code] onde este ficheiro se encontra não está presente por predefinição na solução Web. Utilize [3] para a criar.

O ficheiro Global.asax é o seguinte:


<%@ Application Language="C#" Inherits="WsImpot.Global"%>

O código [Global.cs] é o seguinte:


using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
    public class Global : System.Web.HttpApplication
    {
         // business layer
        public static IImpotMetier Metier;
 
         // method executed at application startup
        private void Application_Start(object sender, EventArgs e)
        {
            // instantiations [metier] and [dao] layers
            Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
        }
    }
}
  • linha 6: a classe chama-se Global e faz parte do WsImpot (linha 4). O seu nome completo é WsImpot.Global e é este nome que deve ser incluído no Inherits do Global.asax.
  • linha 6: sabemos que a classe associada à classe Global.asax deve derivar de System.Web.HttpApplication.
  • linha 12: o método Application_Start é executado quando a aplicação web é iniciada.
  • linha 15: integramos a camada [metier] (camada [3] da aplicação em construção) utilizando o Spring e a seguinte configuração no [web.config]:

  <!-- objets 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>

A classe [Metier.ImpotMetierWS] na linha (g) acima encontra-se em [ImpotsV9-metier.dll].

A referência da camada [metier] criada é inserida no campo estático na linha 9. Este campo é utilizado na linha 27 do ButtonCalculer_Click :


LabelImpot.Text = String.Format("{0} euros",Global.Metier.CalculerImpot(marié, nbEnfants, salaire));

Estamos prontos para um teste. Temos de iniciar o SGBD MySQL5, o serviço web remoto, e anotar a porta em que este opera:

  

Depois de fazer isso, verifique se, no ficheiro [web.config] do cliente web, a porta do serviço web remoto está correta:

Depois de fazer isso, o cliente web do serviço web remoto pode ser iniciado premindo Ctrl-F5 :

  

12.6. Um cliente de consola Java para o serviço de cálculo de impostos na Web

Para demonstrar que os serviços web podem ser acedidos por clientes escritos em qualquer linguagem, estamos a criar um cliente de consola Java básico. A arquitetura da aplicação cliente/servidor será a seguinte:

  • o cliente [1] será escrito em Java
  • o servidor [2] é aquele escrito em C#

Primeiro, vamos alterar um detalhe no nosso serviço de cálculo de impostos na Web. A sua definição atual em [ServiceImpot.cs] é a seguinte:


...
public class ServiceImpot : System.Web.Services.WebService
{
 
    [WebMethod]
    public int CalculerImpot(bool marié, int nbEnfants, int salaire)
    {
        return Global.Metier.CalculerImpot(marié, nbEnfants, salaire);
    }
 
}

Os testes demonstraram que a ênfase do parâmetro «marié» nas linhas 6 e 8 poderia constituir um problema para a interoperabilidade entre Java e C#. Adotamos a seguinte nova definição:


...
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 serviço será colocado num novo projeto Web Developer chamado WsImpotsSansAccents. O serviço web terá então a URL [/WsImpotSansAccents/ServiceImpot.asmx].

Image

Para escrever o cliente Java, utilizaremos o IDE NetBeans [http://www.netbeans.org/] :

  • em [1], crie um novo projeto
  • em [2,3], selecione um projeto do tipo Java Application.
  • em [4], avance para o passo seguinte
  • em [5], atribua um nome ao projeto
  • em [6], indique a pasta onde será criada uma subpasta com o nome do projeto
  • em [7], atribua um nome à classe que irá conter o código executado manualmente no arranque da aplicação
  • em [8], preencha o
  • em [9]: o projeto Java gerado
  • em [10]: clique com o botão direito do rato no projeto para gerar o cliente do serviço de cálculo de impostos web
  • em [11], o URL do ficheiro que descreve o serviço de cálculo de impostos na Web:

http://localhost:1089/WsImpotSansAccents/ServiceImpot.asmx?WSDL

Esta URL é a do serviço [ServiceImpot.asmx], ao qual adicionamos o parâmetro ?WSDL. O documento localizado nesta URL descreve, em linguagem XML, o que o serviço [15] pode fazer. Trata-se de um elemento padrão de um serviço web.

  • em [12], o pacote (equivalente ao namespace C#) no qual colocar as classes a gerar
  • em [13], deixe o valor padrão
  • em [14], preencha o
  • em [16], o serviço web importado foi integrado no projeto Java. Suporta dois protocolos de comunicação: Soap e Soap12.
  • em [17], a classe [Main] na qual utilizaremos o cliente gerado
  • Em [18], vamos inserir algum código no método [main]. Posicione o cursor no local onde o código deve ser inserido, clique com o botão direito do rato e selecione a opção [19]
  • em [20], indique que pretende gerar o código de chamada para o CalculerImpot do serviço remoto de cálculo de impostos e, em seguida, prima Ok.

O código gerado em [Main] é o seguinte:

public class Main {

    public static void main(String[] args) {
         // TODO code application logic here
       try { // Call Web Service Operation
        wsimpot.ServiceImpot service = new wsimpot.ServiceImpot();
        wsimpot.ServiceImpotSoap port = service.getServiceImpotSoap();
         // TODO initialize WS operation arguments here
        boolean marie = false;
        int nbEnfants = 0;
        int salaire = 0;
         // TODO process result here
        int result = port.calculerImpot(marie, nbEnfants, salaire);
        System.out.println("Result = "+result);
      } catch (Exception ex) {
         // TODO handle custom exceptions here
      }
    }
}

O código gerado mostra como chamar o CalculerImpot do serviço remoto de cálculo de impostos. Se fizermos um paralelo com o que vimos em C#, a variável port na linha 7 é o equivalente ao cliente utilizado em C#. Não faremos mais comentários sobre este código. Vamos reorganizá-lo da seguinte forma:

import wsimpot.ServiceImpot;
public class Main {
    public static void main(String[] args) {
      try {
        // call the CalculerImpot function of the web service
        System.out.println(String.format("Montant à payer : %d euros", new ServiceImpot().getServiceImpotSoap().calculerImpot(true, 2, 60000)));
      } catch (Exception ex) {
        System.out.println(String.format("L'erreur suivante s'est produite %s",ex.getMessage()));
      }
    }
}
  • linha 1: importamos o ServiceImpot, que representa o cliente gerado pelo assistente.
  • linha 6: chamamos o método remoto CalculerImpot seguindo o procedimento indicado no código gerado em main.

Os resultados obtidos na consola em tempo de execução (F6) são os seguintes:

init:
deps-jar:
wsimport-init:
wsimport-client-check-ServiceImpot.asmx:
wsimport-client-ServiceImpot.asmx:
wsimport-client-generate:
wsimport-client-compile:
Compiling 1 source file to C:\data\2007-2008\netbeans\ClientNetbeansPourServiceImpotDotNet\build\classes
compile:
run:
Montant à payer : 4282 euros
BUILD SUCCESSFUL (total time: 7 seconds)