Skip to content

12. Serviços Web

12.1. Introduction

Apresentamos no capítulo anterior várias aplicações cliente-servidor TCP/IP. Na medida em que os clientes e o servidor trocam linhas de texto, eles podem ser escritos em qualquer linguagem. O cliente precisa apenas conhecer o protocolo de comunicação esperado pelo servidor.

Os serviços da Web também são aplicativos de servidor TCP/IP. Eles apresentam as seguintes características:

  • São hospedados por servidores web e o protocolo de comunicação cliente-servidor é o HTTP (HyperText Transport Protocol), um protocolo que opera sobre o TCP-IP.
  • O serviço web possui um protocolo de comunicação padrão, independentemente do serviço prestado. Um serviço web oferece diversos serviços: S1, S2, ..., Sn. Cada um deles aguarda parâmetros fornecidos pelo cliente e retorna um resultado a ele. Para cada serviço, o cliente precisa saber:
    • o nome exato do serviço, se
    • a lista de parâmetros que devem ser fornecidos e seus tipos
    • o tipo de resultado retornado pelo serviço

Uma vez conhecidos esses elementos, o diálogo cliente-servidor segue o mesmo formato, independentemente do serviço web consultado. A programação dos clientes é, assim, padronizada.

  • Por motivos de segurança contra ataques provenientes da Internet, muitas organizações possuem redes privadas e abrem para a Internet apenas algumas portas de seus servidores: essencialmente a porta 80 do serviço web. Todas as outras portas estão bloqueadas. Assim, as aplicações cliente-servidor, conforme apresentadas no capítulo anterior, são desenvolvidas dentro da rede privada (intranet) e, em geral, não são acessíveis de fora. Hospedar um serviço em um 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 acessar esse objeto remoto como se ele fosse local. Isso oculta toda a parte de comunicação de rede e permite construir um cliente independente dessa camada. Se essa camada vier a mudar, o cliente não precisa ser modificado.
  • Assim como nas aplicações cliente-servidor TCP/IP apresentadas no capítulo anterior, o cliente e o servidor podem ser escritos em qualquer linguagem de programação. Eles trocam linhas de texto. Essas linhas contêm duas partes:
    • os cabeçalhos necessários ao protocolo HTTP
    • o corpo da mensagem. Para uma resposta do servidor ao cliente, o corpo da mensagem está no formato XML (eXtensible Markup Language). Para uma solicitação do cliente ao servidor, o corpo da mensagem pode assumir várias formas, incluindo XML. A solicitação XML do cliente pode ter um formato específico chamado SOAP (Simple Object Access Protocol). Nesse caso, a resposta do servidor também segue o formato SOAP.

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

Trata-se de uma extensão da arquitetura de três camadas, à qual são adicionadas classes especializadas de comunicação de rede. Já encontramos uma arquitetura semelhante com o aplicativo cliente gráfico do Windows e o servidor TCP de impostos no parágrafo 11.9.1.

Vamos esclarecer esses conceitos gerais com um primeiro exemplo.

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

Vamos construir um primeiro aplicativo cliente/servidor com a seguinte arquitetura simplificada:

12.2.1. A parte do servidor

Já mencionamos que um serviço web é hospedado por um servidor web. A criação de um serviço web se enquadra no âmbito geral da programação web, no lado do servidor. Anteriormente, tivemos a oportunidade de desenvolver clientes web, o que também é programação web, mas, dessa vez, no lado do cliente. O termo “programação web” geralmente se refere à programação do lado do servidor, e não à do lado do cliente. Para desenvolver serviços web ou, de maneira mais geral, aplicativos web, o Visual C# não é a ferramenta adequada. Vamos utilizar o Visual Developer, uma das versões Express do Visual Studio 2008, disponível para download em [2] no endereço [1]: [http://msdn.microsoft.com/fr-fr/express/future/bb421473(en-us).aspx] (maio de 2008):

  • [1]: o endereço para download
  • [2]: a aba de downloads
  • [3]: baixar o Visual Developer 2008

Para criar um primeiro serviço web, pode-se proceder da seguinte forma, após iniciar o Visual Developer:

  • [1]: selecionar a opção Arquivo / Novo site da Web
  • [2]: selecionar o tipo de aplicativo ASP.NET Serviço Web
  • [3]: selecionar a linguagem de desenvolvimento: C#
  • [4]: indicar a pasta onde criar o projeto
  • [5]: o projeto criado no Visual Web Developer
  • [6]: a pasta do projeto no disco

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

  • uma raiz na qual se encontram os documentos do site (páginas HTML estáticas, imagens, páginas .aspx dinâmicas, serviços web .asmx, etc.). Nela também se encontra o arquivo [web.config], que é o arquivo de configuração da aplicação web. Ele desempenha a mesma função que o arquivo [App.config] das aplicações Windows e está estruturado da mesma forma.
  • uma pasta [App_Code], na qual se encontram as classes e interfaces do site destinadas à compilação.
  • uma pasta [App_Data], na qual serão colocados os dados utilizados pelas classes de [App_Code]. Nela, por exemplo, poderá ser encontrado um banco de dados SQL Server *.mdf.

[Service.asmx] é o serviço web cuja criação solicitamos. Ele 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 hospedará o aplicativo. No modo de produção, esse servidor é geralmente o IIS (Internet Information Server), o servidor web da Microsoft. O Visual Web Developer inclui um servidor web leve que é utilizado 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# do serviço web está no arquivo [~/App_Code/Service.cs] (atributo CodeBehind). É lá que o servidor web irá buscá-lo para compilá-lo.
  • 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)]
// Para permitir que este serviço web seja chamado a partir de um script, usando ASP.NET AJAX, remova o comentário da linha a seguir. 
// [System.Web.Script.Services.ScriptService]
public class Service : System.Web.Services.WebService
{
    public Service () {

        //Remova o comentário da linha a seguir se estiver usando componentes projetados 
        //InitializeComponent(); 
    }

    [WebMethod]
    public string HelloWorld() {
        return "Hello World";
    }
    
}

A classe Service se assemelha a uma classe C# clássica, mas com alguns pontos a serem observados:

  • linha 7: a classe deriva da classe WebService definida no espaço de nomes System.Web.Services. Essa herança nem sempre é obrigatória. Neste exemplo, especificamente, seria possível dispensá-la.
  • 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, assim, evitar conflitos com classes de outros fornecedores que possam ter o mesmo nome. Para os serviços web, o procedimento é o mesmo. Cada serviço web deve poder ser identificado por um nome único, neste caso, http://tempuri.org/. Esse nome pode ser qualquer um. Ele não precisa necessariamente ter o formato de uma URI HTTP.
  • linha 15: o método HelloWorld é precedido por um atributo [WebMethod], que indica ao compilador que o método deve ser tornado visível aos clientes remotos do serviço web. Um método que não seja precedido por esse atributo não é visível para os clientes do serviço web. Pode ser um método interno utilizado por outros métodos, mas que não se destina a ser publicado.
  • linha 9: o construtor do serviço web. Ele é desnecessário em nossa aplicação.

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


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 arquivo 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 arquivo tem 140 linhas. É complexo e não faremos comentários sobre ele. Vamos mantê-lo como está. Acima, encontramos as tags <configuration>, <configSections>, <sectionGroup>, <appSettings>, <connectionString>, que encontramos no arquivo [App.config] dos aplicativos do Windows.

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

  • [1,2]: clica-se com o botão direito do mouse em [Service.asmx] e solicita-se a visualização da página em um navegador
  • [3]: O Visual Web Developer inicia seu servidor web integrado e exibe o ícone deste no canto inferior direito da barra de tarefas. O servidor web é iniciado em uma porta aleatória, neste caso 1906. A URI exibida /WsHello é o nome do site [4].

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

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

Ao clicar no link [2] acima, somos direcionados para a seguinte página:

  • em [1], observe-se a URI [http://localhost:1906/WsHello/Service.asmx?op=DisBonjourALaDame] da nova página. Essa é a 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 retorna um resultado do tipo string também. A página nos permite executar o método [DisBonjourALaDame]: em [2], inserimos o valor do parâmetro nomDeLaDame e, em [3], solicitamos a execução do método. Obtemos o seguinte resultado:

  • em [1], observe-se que a URI da resposta não é idêntica à da solicitação. Ela mudou.
  • em [2], a resposta do servidor web. Observa-se o seguinte:
    • trata-se de uma resposta XML e não HTML
    • o resultado do método [DisBonjourALaDame] está encapsulado em uma tag <string> que representa seu tipo.
    • a tag <string> possui 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 fez sua solicitação, é preciso examinar o código HTML do 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 se chama nomDeLaDame (atributo name).

Solicitar a execução do serviço web [/Service.asmx] nos permitiu testar seus métodos e obter um entendimento básico das trocas entre cliente e servidor.

12.2.2. A parte do cliente

É possível implementar o cliente do serviço web remoto acima com um cliente TCP/IP básico. Veja, por exemplo, o diálogo cliente/servidor realizado com um cliente putty conectado 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. Isso significa que o cliente pode enviar os valores do POST.
  • linha 11: os valores enviados no formato param1=val1&param2=val2& .... Alguns caracteres devem estar na forma permitida em uma URL. É o que chamamos anteriormente de URL codificada. Aqui, o formulário possui apenas um único parâmetro chamado nomDeLaDame. O valor enviado tem, no total, 23 caracteres. Esse tamanho deve ser declarado no cabeçalho HTTP da linha 4.
  • linhas 12-22: a resposta do servidor
  • linha 22: o resultado do método web [DisBonjourALaDame].

Com o Visual C#, é possível gerar, com a ajuda de um assistente, o cliente de um serviço web remoto. É isso que veremos agora.

A camada [1] acima é implementada por um projeto do Visual Studio C# do tipo Aplicativo Windows, denominado ClientWsHello:

  • em [1], o projeto ClientWsHello no Visual C#
  • em [2], o namespace padrão do projeto será Client (clique com o botão direito do mouse no projeto / Propriedades / Aplicativo). Esse namespace servirá para construir o namespace do cliente que será gerado.
  • No [3], clique com o botão direito do mouse no projeto para adicionar uma referência a um serviço web remoto
  • em [4], insira a URI do serviço web criado anteriormente
  • no [4b], conecte o Visual C# ao serviço web indicado pelo [4]. O Visual C# irá recuperar a descrição do serviço web e, com base nessa descrição, poderá gerar um cliente.
  • em [5], uma vez recuperada a descrição do serviço web, o Visual C# pode exibir seus métodos públicos
  • Em [6], indique um namespace para o cliente que será gerado. Este será adicionado ao namespace definido em [2]. Assim, o namespace do cliente será Client.WsHello.
  • No [6b], confirme o assistente.
  • Em [7], a referência ao serviço web WsHello aparece no projeto. Além disso, foi criado um arquivo de configuração [app.config].
  • No [8], visualize todos os arquivos do projeto.
  • No [9], a referência ao serviço web WsHello contém diversos arquivos que não explicaremos aqui. No entanto, daremos uma olhada no arquivo [Reference.cs], que é o código C# do 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 quisermos alterar esse namespace, é aqui que devemos fazê-lo.
  • linha 3: a classe ServiceSoapClient é a classe do cliente gerado. Trata-se de uma classe proxy, no sentido de que ela oculta do aplicativo Windows o fato de que um serviço web remoto está sendo utilizado. O aplicativo do Windows utilizará a classe remota WsHello por meio da classe local Client.WsHello.ServiceSoapClient. Para criar uma instância do cliente, utilizaremos o construtor da linha 5:
Client.WsHello.ServiceSoapClient client=new Client.WsHello.ServiceSoapClient();
  • linha 8: o método DisBonjourALaDame é o equivalente, no lado do cliente, ao método DisBonjourALaDame do serviço web. O aplicativo Windows utilizará o método remoto DisBonjourALaDame por meio do método local Client.WsHello.ServiceSoapClient.DisBonjourALaDame da seguinte forma:
string bonjour=client.DisBonjourALaDame("Carla Bruni");

O arquivo [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>

Desse arquivo, consideraremos apenas a linha 8, que contém a URI do serviço web. Se a URI do serviço for alterada, não é necessário reconstruir o cliente Windows. Basta alterar a URI do arquivo [app.config].

Voltemos à arquitetura do aplicativo Windows que queremos construir:

Criamos a camada [client] do serviço web. A camada [ui] será a seguinte:

n.º
tipo
nome
função
1
TextBox
textBoxNomDame
nome da senhora
2
Button
buttonSalutations
para se conectar ao serviço web remoto WsHello e chamar o método DisBonjourALaDame.
3
Rótulo
labelBonjour
o resultado retornado 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) {
            // ampulheta
            Cursor=Cursors.WaitCursor;
            // consulta ao serviço web
            labelBonjour.Text = new ServiceSoapClient().DisBonjourALaDame(textBoxNomDame.Text.Trim());
            // cursor normal
            Cursor = Cursors.Arrow;
        }
    }
}
  • linha 15: o cliente do serviço web é instanciado. Ele é do tipo Client.WsHello.ServiceSoapClient. O espaço de nomes Client.WsHello é declarado na linha 3. O método local ServiceSoapClient().DisBonjourALaDame é chamado. Sabe-se que ele, por sua vez, consulta o método remoto de mesmo nome do serviço web.

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

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

O serviço web anterior oferecia um único método. Consideraremos um serviço web que oferecerá as quatro operações aritméticas:

  1. adicionar(a,b), que retornará a+b
  2. subtrair(a,b), que retornará a-b
  3. multiplicar(a,b), que retornará a*b
  4. dividir(a,b), que retornará a/b

e 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 para chamar o serviço web
  • em [5], o resultado retornado pelo serviço web

12.3.1. A parte do servidor

Estamos desenvolvendo um projeto do tipo serviço web com o Visual Web Developer:

  • em [1], o aplicativo web WsOperations gerado
  • No [2], o aplicativo web WsOperations foi reformulado da seguinte maneira:
  • a página da web [Service.asmx] foi renomeada para [Operations.asmx]
  • a classe [Service.cs] foi renomeada para [Operations.cs]
  • o arquivo [web.config] foi excluído para demonstrar que não é indispensável.

A página da 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 em operação, procedemos conforme indicado em [3]. Obtemos, então, a página de teste dos 4 métodos do serviço web WsOperations:

Image

O leitor é convidado a testar os quatro métodos.

12.3.2. A parte do cliente

Com o Visual C#, criamos um aplicativo para Windows ClientWsOperations:

  • em [1], o projeto ClientWsOperations no Visual C#
  • em [2], o namespace padrão do projeto será Client (clique com o botão direito do mouse no projeto / Propriedades / Aplicativo). Esse namespace servirá para construir o namespace do cliente que será gerado.
  • em [3], clique com o botão direito do mouse no projeto para adicionar uma referência a um serviço web existente
  • no [4], insira a URI do serviço web criado anteriormente. Para isso, verifique o que está exibido no campo de endereço do navegador que exibe a página de teste do serviço web.
  • em [4b], conecte o Visual C# ao serviço web indicado por [4]. O Visual C# irá recuperar a descrição do serviço web e, com base nessa descrição, poderá gerar um cliente.
  • No [5], uma vez recuperada a descrição do serviço web, o Visual C# pode exibir seus métodos públicos
  • em [6], indicar um namespace para o cliente que será gerado. Este será adicionado ao namespace definido em [2]. Assim, o namespace do cliente será Client.WsOperations.
  • Em [6b], confirme o assistente.
  • Em [7], a referência ao serviço web WsOperations aparece no projeto. Além disso, foi criado um arquivo de configuração [app.config].

Vale lembrar que o cliente gerado é do tipo Client.WsOperations.OperationsSoapClient, onde

  • Client.WsOperations é o espaço de nomes do cliente do serviço web
  • Operations é a classe do serviço web remoto.

Embora exista uma maneira lógica de construir esse nome, muitas vezes é mais simples localizá-lo no arquivo [Reference.cs], que é um arquivo oculto por padrã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 Ajouter, Soustraire, Multiplier, Diviser do serviço web remoto serão acessados por meio dos métodos proxy de mesmo nome (linhas 8, 12, 16, 20) do cliente do tipo Client.WsOperations.OperationsSoapClient (linha 3).

Resta-nos construir a interface gráfica:

n.º
tipo
nome
função
1
ComboBox
comboBoxOperations
lista de operações aritméticas
2
TextBox
textBoxA
número a
3
TextBox
textBoxB
número b
4
Botão
buttonExécuter
consulta o serviço web remoto
5
Etiqueta
labelRésultat
o resultado da operação

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


using System;
using System.Windows.Forms;
using Client.WsOperations;

namespace ClientWsOperations {
    public partial class Form1 : Form {
        // tabela de operações
        private string[] opérations = { "Ajouter", "Soustraire", "Multiplier", "Diviser" };
        // serviço web a ser contatado
        private OperationsSoapClient opérateur = new OperationsSoapClient();

        // fabricante
        public Form1() {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e) {
            // preenchimento do menu suspenso de operações
            comboBoxOperations.Items.AddRange(opérations);
            comboBoxOperations.SelectedIndex = 0;
        }

        private void buttonExécuter_Click(object sender, EventArgs e) {
            // verificação dos parâmetros a e b da operação
            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;
            }
            // execução da operação
            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;
                }
                // exibição do resultado
                labelRésultat.Text = c.ToString();
            } catch (Exception ex) {
                textBoxMessage.Text = ex.Message;
            }
        }
    }
}
  • linha 3: o 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: a lista suspensa de operações é preenchida durante o carregamento inicial do formulário
  • linha 23: execução da operação solicitada pelo usuário
  • linhas 25-37: verifica-se se os valores inseridos a e b são, de fato, números reais
  • linhas 41-54: um switch para executar a operação remota solicitada pelo usuário
  • linhas 43, 46, 49, 52: é o cliente local que é consultado. De forma transparente, este, por sua vez, consulta o serviço web remoto.

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

Retomamos o aplicativo de cálculo de impostos, agora já bem conhecido. Da última vez que trabalhamos com ele, transformamos em um servidor TCP remoto que podia ser chamado pela internet. Agora, estamos transformando-o em um serviço web.

A arquitetura da versão 8 era a seguinte:

A arquitetura da versão 9 será semelhante:

Trata-se de uma arquitetura semelhante à da versão 8, analisada no parágrafo 11.9.1, mas na qual o servidor e o cliente TCP são substituídos por um serviço web e seu cliente proxy. Vamos retomar integralmente as camadas [ui], [metier] e [dao] da versão 8.

12.4.1. A parte do servidor

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

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

A página da 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 exibirá apenas o método CalculerImpot da linha 9.

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

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

  • no [1], o projeto. Nele, encontravam-se os seguintes elementos:
    • [ServeurImpot.cs]: o servidor TCP/IP para cálculo de impostos na forma de um aplicativo de console.
    • [dbimpots.sdf]: o banco de dados SQL Server Compact da versão 7, descrito no parágrafo 9.8.5.
    • [App.config]: o arquivo de configuração do aplicativo.
  • Na pasta [2], a pasta [lib] contém os arquivos DLL necessários para o projeto:
    • [ImpotsV7-dao]: a camada [dao] da versão 7
    • [ImpotsV7-metier]: a camada [metier] da versão 7
    • [antlr.runtime, CommonLogging, Spring.Core] para Spring
  • em [3], as referências do projeto

As camadas [metier] e [dao] da versão já existem: são as utilizadas nas versões 7 e 8. Elas estão no formato DLL, que integramos ao projeto da seguinte maneira:

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

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

Se os elementos do servidor [1], [serveur], [metier], [dao], [entites] e [spring] estiverem todos presentes no projeto do Visual Studio, falta-nos o elemento que irá instanciá-los na inicialização da aplicação web. Na versão 8, uma classe principal com o método estático [Main] era responsável por instanciar as camadas com a ajuda do Spring. Em um aplicativo web, a classe capaz de realizar uma tarefa semelhante é a classe associada ao arquivo [Global.asax] :

  • no [1], adiciona-se um novo elemento ao projeto web
  • no [2], escolhe-se o tipo “Global Application Class”
  • em [3], o nome proposto por padrão para esse elemento
  • em [4], confirma-se a adição
  • em [5], o novo elemento foi integrado ao projeto

Vamos examinar o conteúdo do arquivo [Global.asax]:


<%@ Application Language="C#" %>

<script runat="server">

    void Application_Start(object sender, EventArgs e) 
    {
        // Código executado na inicialização do aplicativo
    }
    
    void Application_End(object sender, EventArgs e) 
    {
        //  Código executado ao encerrar o aplicativo
    }
        
    void Application_Error(object sender, EventArgs e) 
    { 
        // Código executado quando ocorre um erro não tratado
    }

    void Session_Start(object sender, EventArgs e) 
    {
        // Código executado ao iniciar uma nova sessão
    }

    void Session_End(object sender, EventArgs e) 
    {
        // Código executado ao término de uma sessão. 
    }
       
</script>

O arquivo é uma mistura de tags destinadas ao servidor web (linhas 1, 3, 30) e código C#. Esse método era o único utilizado com o ASP, o antecessor do ASP.NET, que é a tecnologia atual da Microsoft para programação web. Com o ASP.NET, esse método ainda pode ser utilizado, mas não é o método padrão. O método padrão é o chamado “CodeBehind”, que encontramos nas páginas dos serviços web, por exemplo, aqui no [ServiceImpot.asmx]:


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

O atributo CodeBehind especifica onde se encontra o código-fonte da página [ServiceImpot.asmx]. Sem esse atributo, o código-fonte estaria na página [ServiceImpot.asmx] com uma sintaxe semelhante à encontrada em [Global.asax]. Não manteremos o arquivo [Global.asax] tal como foi gerado, mas seu código nos permite entender para que serve:

  • a classe associada a Global.asax é instanciada no início da aplicação. Seu ciclo de vida é o mesmo da aplicação como um todo. Concretamente, ela só desaparece quando o servidor web é desligado.
  • O método Application_Start é executado em seguida. Essa é a única vez em que ele será executado. Por isso, ele é usado para instanciar objetos compartilhados entre todos os usuários. Esses objetos são colocados:
    • ou nos campos estáticos da classe associada a Global.asax. Como essa classe está sempre presente, qualquer consulta de qualquer usuário pode ler as informações nela contidas.
    • ou no contêiner Application. Esse contêiner também é criado no início da aplicação e sua duração é a mesma da aplicação.
      • Para inserir um dado nesse contêiner, escreve-se Application["clé"]=valor;
      • para recuperá-lo, escreve-se T valor=(T)Application["clé"]; onde T é o tipo de valeur.
  • O método Session_Start é executado sempre que um novo usuário faz uma solicitação. Como se reconhece um novo usuário? Cada usuário (geralmente um navegador) recebe, ao final de sua primeira solicitação, um token de sessão, que é uma sequência de caracteres única para cada usuário. Em seguida, o usuário reenvia o token de sessão que recebeu a cada nova solicitação que faz. Isso permite que o servidor web o reconheça. Ao longo das diferentes solicitações de um mesmo usuário, dados específicos a ele podem ser armazenados no contêiner Session:
    • para inserir um dado nesse contêiner, escreve-se Session["clé"]=valor;
    • para recuperá-lo, escreve-se T valor=(T)Session["clé"]; onde T é o tipo de valeur.

A duração de uma sessão é limitada, por padrão, a 20 minutos de inatividade do usuário (c.a.d, ou seja, ele não reenviou seu token de sessão há 20 minutos).

  • O método Application_Error é executado quando uma exceção não tratada pelo aplicativo web é encaminhada até o servidor web.
  • Os demais métodos são utilizados com menos frequência.

Após essas informações gerais, para que serve o Global.asax? Vamos utilizar seu método Application_Start para inicializar as camadas [metier], [dao] e [entites] contidas nas camadas DLL e [ImpotsV7-metier, ImpotsV7-dao]. Usaremos o Spring para instanciá-las. As referências das camadas assim criadas serão, em seguida, armazenadas em campos estáticos da classe associada a Global.asax.

Primeiro passo: transferimos o código C# de Global.asax para uma classe separada. O projeto passa a ter a seguinte estrutura:

Em [1], o arquivo [Global.asax] será associado à classe [Global.cs] [2], contendo apenas a seguinte linha:


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

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


using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
    public class Global : System.Web.HttpApplication
    {
        // camada de negócios
        public static IImpotMetier Metier;

        // método executado ao iniciar o aplicativo
        private void Application_Start(object sender, EventArgs e)
        {
            // instâncias das camadas [metier] e [dao]
            Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
        }
    }
}
  • linha 4: o namespace da classe
  • linha 6: a classe Global. É possível atribuir-lhe o nome que se desejar. O importante é que ela derive da classe System.Web.HttpApplication.
  • linha 9: um campo estático público que conterá uma referência à camada [metier].
  • linha 12: o método Application_Start, que será executado ao iniciar o aplicativo.
  • linha 15: o Spring é utilizado para processar o arquivo [web.config], no qual ele encontrará os objetos a serem instanciados para criar as camadas [metier] e [dao]. Não há diferença entre o uso do Spring com o [App.config] em um aplicativo Windows e o uso do Spring com o [web.config] em um aplicativo web. Além disso, [web.config] e [App.config] têm a mesma estrutura. A linha 15 armazena a referência da camada [metier] no campo estático da linha 9, para que essa referência esteja disponível para todas as consultas de todos os usuários.

O arquivo [web.config] terá a seguinte estrutura:


<?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 arquivo [App.config] utilizado na versão 7 do aplicativo e analisado no parágrafo 9.8.4.

  • linhas 16-20: definem uma camada [dao] que opera com um banco de dados MySQL5. Esse banco de dados foi descrito no parágrafo 9.8.1.
  • linhas 21-23: definem a camada [metier]

Voltemos ao quebra-cabeça do servidor:

Ao iniciar o aplicativo, as camadas [metier] e [dao] foram instanciadas. O tempo de vida das camadas é o mesmo do próprio aplicativo. Quando o serviço web é instanciado? Na verdade, a cada solicitação que recebe. Ao final da solicitação, o objeto que a atendeu é excluído. Um serviço web é, portanto, à primeira vista, sem estado. Ele não pode armazenar informações entre duas solicitações em campos que lhe pertençam. Ele pode armazená-las na sessão do usuário. Para isso, os métodos que ele expõe devem ser marcados com um atributo especial:


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

Na linha 1 acima, o método CalculerImpot tem permissão para acessar o contêiner Session, sobre o qual falamos anteriormente. Não precisaremos usar esse atributo em nossa aplicação. O serviço web WsImpot será, portanto, instanciado a cada solicitação e será sem estado.

Agora podemos 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 único método do serviço web
  • linha 12: utiliza-se o método CalculerImpot da camada [metier]. Uma referência a essa camada é encontrada no campo estático Metier da classe Global. Esta pertence ao espaço de nomes WsImpot (linha 2).

Estamos prontos para iniciar o serviço web. Antes disso, é necessário iniciar o SGBD e o MySQL5 para que o banco de dados bdimpots fique acessível. Feito isso, iniciamos o serviço web [1]:

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 desenvolvido, passamos para o cliente. Voltemos à arquitetura da aplicação cliente/servidor:

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

Para desenvolver a parte [client] da versão 9, partiremos da parte [client] da versão 8 e, em seguida, faremos as alterações necessárias. Duplicamos o projeto do Visual Studio analisado no parágrafo 11.9.4.1, renomeamos-o como ClientWsImpot e o carregamos no Visual Studio:

A solução do Visual Studio da versão 8 era composta por dois projetos:

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

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

  • o projeto [metier] deve passar a ser o cliente de um serviço web
  • o projeto [ui] deve fazer referência ao 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 é sua implementação por um cliente TCP
  • em [2], removemos a implementação ImpotMetierTcp. Precisamos criar outra implementação da interface IImpotMetier, que será cliente de um serviço web.
  • Em [3], nomeamos Client como o espaço de nomes padrão do projeto [metier]. O DLL que será gerado se chamará [ImpotsV9-metier.dll].
  • em [4], criamos uma referência ao serviço web WsImpot.
  • em [5], nós o configuramos e validamos.
  • No [6], a referência ao serviço web WsImpot foi criada e um arquivo [app.config] foi gerado.

No arquivo oculto [Reference.cs]:

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

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

Resta-nos implementar a interface IImpotMetier:


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

Nós a implementamos com a seguinte classe ImpotMetierWs:


using System.Net.Sockets;
using System.IO;
using Client.WsImpot;

namespace Metier {
    public class ImpotMetierWs : IImpotMetier {

        // cliente do serviço web remoto
        private ServiceImpotSoapClient client = new ServiceImpotSoapClient();

        // cálculo do imposto
        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 client é inicializado com uma instância de um cliente do serviço web de cálculo de impostos.
  • linha 12: o único método da interface IImpotMetier a ser implementado.
  • linha 13: utiliza-se o método CalculerImpot do cliente do serviço web remoto de cálculo de impostos. No final, será consultado o método CalculerImpot do serviço web remoto.

É possível gerar o DLL do projeto:

  • em [1]; o projeto [client] em seu estado final
  • em [2], geração do DLL do projeto
  • em [3], o DLL e o ImpotsV9-metier.dll estão na pasta /bin/Release do projeto.

12.4.2.2. A nova camada [ui]

A camada [client] do cliente já foi escrita. Resta-nos escrever a camada [ui]. Voltemos ao projeto do Visual Studio:

  • em [1], o projeto [ui], proveniente da versão 8
  • para [2], o DLL e o ImpotsV8-metier da camada antiga [metier] são substituídos pelo DLL ImpotsV9-metier da nova camada
  • em [3], a DLL e a ImpotsV9-metier são adicionadas às referências do projeto.

A segunda alteração ocorre no [App.config]. É importante lembrar que esse arquivo é utilizado pelo Spring para instanciar a camada [metier]. Como esta foi alterada, a configuração do [App.config] deve ser alterada. Por outro lado, o [App.config] deve ter a configuração que permite conectar-se ao serviço web remoto de cálculo de impostos. Essa configuração foi gerada no arquivo [app.config] do projeto [metier] quando a referência ao serviço web remoto foi adicionada a ele.

O arquivo [App.config] passa a ter a seguinte forma:


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

    <!-- serviço web -->
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="ServiceImpotSoap" closeTimeout="00:01:00" openTimeout="00:01:00"
                        receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false"
                        bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                        maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                        messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
                        useDefaultWebProxy="true">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <security mode="None">
                        <transport clientCredentialType="None" proxyCredentialType="None"
                                realm="" />
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
                    binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
                    contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
        </client>
    </system.serviceModel>
</configuration>
  • linhas 15-18: o Spring instancia apenas um objeto, a camada [metier]
  • linha 16: a camada [metier] é instanciada pela classe [Metier.ImpotMetierWs], que se encontra na DLL ImpotsV9-metier.
  • linhas 22-46: a configuração do cliente do serviço web remoto. Trata-se de um copiar/colar do conteúdo do arquivo [app.config] do projeto [metier].

Estamos prontos. Executamos o aplicativo com Ctrl-F5 (o serviço web deve estar em execução, os arquivos SGBD e MySQL5 devem estar em execução, e a porta indicada na linha 42 acima deve estar correta):

  

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

Vamos revisar a arquitetura da aplicação cliente/servidor que acabamos de escrever:

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

 

Essa é uma mudança importante para os usuários. Atualmente, nosso aplicativo cliente/servidor, versão 9, pode atender a vários clientes simultaneamente. Trata-se de uma melhoria em relação à versão 8, na qual ele atendia apenas a um cliente por vez. A restrição é que os usuários que desejarem utilizar o serviço web de cálculo de impostos devem ter instalado em seus computadores o cliente Windows que desenvolvemos. Nesta nova versão, que chamaremos de versão 10, os usuários poderão acessar, por meio de seus navegadores, o serviço web de cálculo de impostos.

Na arquitetura acima:

  • o lado do servidor não sofre alterações. Ele permanece como estava na versão 9.
  • No lado do cliente, a camada [client du service web] permanece inalterada. Ela foi encapsulada nas camadas DLL e [ImpotsV9-metier]. Vamos reutilizar essa DLL.
  • No final das contas, a única mudança consiste em substituir uma interface gráfica do Windows por uma interface web.

Vamos abordar novos conceitos de programação web do lado do servidor. Como o objetivo deste documento não é ensinar programação web, tentaremos explicar a abordagem que será seguida, mas sem entrar em detalhes. Portanto, haverá um lado um pouco “mágico” nesta seção. No entanto, nos parece interessante seguir essa abordagem para mostrar um novo exemplo de arquitetura multicamadas em que uma das camadas é alterada.

A arquitetura da versão 10 é, portanto, a seguinte:

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

  • o usuário da web tem em seu navegador um formulário web
  • esse formulário é enviado para o servidor web 1, que o processa por meio da camada [web]
  • a camada [web] precisará dos serviços do cliente do serviço web remoto, encapsulados em [ImpotsV9-metier.dll].
  • O cliente do serviço web remoto se comunicará com o servidor web 2, que hospeda o serviço web remoto.
  • A resposta do serviço web remoto será encaminhada até a camada web do cliente, que a formatará em uma página a ser enviada ao usuário.

Nosso trabalho aqui é, portanto:

  • construir o formulário da web que o usuário verá em seu navegador
  • escrever o aplicativo web que processará a solicitação do usuário e enviará a ele uma resposta na forma de uma nova página web. Essa página será, na verdade, a mesma do formulário, na qual teremos adicionado o valor do imposto a pagar
  • escrever a “cola” que faz com que tudo isso funcione em conjunto.

Tudo isso será feito com a ajuda de um novo site criado com o Visual Web Developer:

  • [1]: selecione a opção Arquivo / Novo Site
  • [2]: escolher um tipo de aplicativo “ASP.NET Web Site”
  • [3]: selecione a linguagem de desenvolvimento: C#
  • [4]: indicar a pasta onde criar o projeto
  • [5]: o projeto criado no Visual Web Developer
    • [Default.aspx] é uma página da web chamada de página padrão. É ela que será exibida se for acessada a URL http://.../ClientAspImpot sem especificar um documento. É nessa página que estará o formulário de cálculo do imposto que o usuário verá em seu navegador.
    • [Default.aspx.cs] é a classe associada à página, responsável por gerar o formulário enviado ao usuário e, posteriormente, processá-lo quando este o preencher e validar.
    • [web.config] é o arquivo de configuração do aplicativo. Ao contrário das vezes anteriores, vamos mantê-lo.

Se voltarmos à arquitetura que precisamos construir:

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

Vamos começar implementando a camada [3]. Há várias etapas:

  • em [1], a pasta [lib] do cliente gráfico do Windows versão 9 é copiada para a pasta do projeto web [ClientAspWsImpot]. Isso é feito com o Explorador do Windows. Para que essa pasta apareça na solução do Web Developer, é necessário atualizar a solução com o botão [2].
  • Em seguida, adicione-as às referências do projeto [3,4,5]. As DLLs referenciadas são automaticamente copiadas para a pasta /bin do projeto [6].

Agora temos as DLLs necessárias para o funcionamento do Spring, e a camada cliente do serviço web remoto também está implementada. Embora o código deste esteja presente, sua configuração ainda precisa ser feita. Na versão 9, ele era configurado pelo seguinte arquivo [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>

    <!-- serviço web -->
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
...
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
                    binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
                    contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
        </client>
    </system.serviceModel>
</configuration>

Retomamos essa configuração exatamente como está e a integramos ao arquivo [web.config] da seguinte maneira:


<?xml version="1.0"?>
<configuration>


    <configSections>
      <sectionGroup name="system.web.extensions"...>
...
      </sectionGroup>
      <!-- início da seção Spring -->
        <sectionGroup name="spring">
          <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
          <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
      <!-- fim da seção Spring -->
    </configSections>

  <!-- início da configuração do 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>
  <!-- fim da configuração do Spring -->

  <!-- início da configuração do cliente do serviço web remoto -->
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
...
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
                    binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
                    contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
    </client>
  </system.serviceModel>
  <!-- fim da configuração do cliente do serviço web remoto -->

   <!-- outras configurações já presentes no web.config gerado -->
...
</configuration>

Observe que a linha 37 faz referência à porta do serviço web remoto. Essa porta pode mudar, pois o Visual Developer inicia o serviço web em uma porta aleatória.

Voltemos à arquitetura do cliente web que precisamos construir:

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

Acabamos de implementar a camada [3]. Passamos para a interface web [1], implementada pela página [Default.aspx]. Clique duas vezes na página [Default.aspx] para entrar no modo de design.

Existem duas maneiras de criar uma página da web:

  • graficamente, como em [2]. Nesse caso, é preciso selecionar o modo [Design] em [1]. Essa barra de botões fica na parte inferior da barra de status do editor da página da web.
  • com uma linguagem de tags, como em [3]. Nesse caso, é preciso selecionar o modo [Source] em [1].

Os modos [Design] e [Source] são bidirecionais: uma alteração feita no modo [Design] resulta em uma alteração no modo [Source] e vice-versa. Vale lembrar que o formulário da web a ser exibido no navegador é o seguinte:

  • em [1], o formulário exibido em um navegador
  • em [2], os componentes utilizados para construí-lo
  • em [3], a página de design do formulário. Ela inclui os seguintes elementos:
    • linha A, dois botões de opção chamados 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 chamados ButtonCalculer e ButtonEffacer

Depois que um componente é colocado na área de projeto, é possível acessar suas propriedades:

  • em [1], acesso às propriedades de um componente
  • em [2], a ficha 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 colocar componentes no formulário e definir suas propriedades. É preciso também organizar seu layout. Em uma interface gráfica do Windows, esse layout é absoluto. Basta arrastar o componente para onde se deseja que ele fique. Em uma página da web, é diferente: mais complexo, mas também mais poderoso. Esse aspecto não será abordado aqui.

O código-fonte [Default.aspx] gerado por esse projeto é 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;filhos
          </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>

É possível reconhecer os componentes do formulário nas linhas 23, 24, 33, 36, 44, 47, 55, 63 e 66. O restante é basicamente formatação.

Voltemos à arquitetura que precisamos construir:

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

As camadas [1] e [3] já estão implementadas. Resta-nos escrever a camada [2], que gera o formulário, o envia ao usuário, o processa quando este o devolve preenchido, utiliza a camada [3] para o cálculo do imposto, gera a página da web de resposta ao usuário e a reenvia a ele. É o código [Default.aspx.cs] que realiza todo esse 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)
    {
...
    }
}

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

Quando, em [1], o usuário clicar no botão [Calculer], a procedimento ButtonCalculer_Click da linha 6 de [Default.aspx.cs] será executado. Mas, enquanto isso:

  • os valores do formulário preenchido serão transmitidos do navegador para o servidor web por meio do protocolo HTTP
  • o servidor ASP.NET analisará a solicitação e a encaminhará para a página [Default.aspx]
  • a página [Default.aspx] será instanciada.
  • seus componentes (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire, LabelErreurEnfants, LabelErreurSalaire, LabelImpot) serão inicializados com o valor que possuíam quando o formulário foi enviado inicialmente ao navegador por meio de um mecanismo chamado “ViewState”.
  • Os valores enviados serão atribuídos aos seus componentes (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire). Assim, se o usuário tiver inserido 2 como número de filhos, teremos TextBoxEnfants.Text="2".
  • Se a página [Default.aspx] tiver um método [Page_Load], este será executado
  • o método [ButtonCalculer_Click] da linha 6 será executado se o botão clicado for o [Calculer]
  • o método [ButtonEffacer_Click] da linha 10 será executado se o botão clicado for o [Effacer]

Entre o momento em que o usuário cria um evento em seu navegador e aquele em que ele é processado no [Default.aspx.cs], há uma grande complexidade. Essa complexidade fica oculta, e podemos agir como se ela não existisse ao escrevermos os manipuladores de eventos da página da web. Mas nunca se deve esquecer que há a rede entre o evento e seu manipulador e que, portanto, não se trata de gerenciar eventos do mouse, como Mouse_Move, que provocariam idas e vindas dispendiosas entre cliente e servidor...

O código dos manipuladores de cliques nos botões [Calculer] e [Effacer] é o mesmo que se escreveria para um aplicativo clássico do Windows:


protected void ButtonCalculer_Click(object sender, EventArgs e)
    {
        // verificação de dados
        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;
        }
        // erro?
        if (erreur) return;
        // apagando eventuais erros
        LabelErreurEnfants.Text = "";
        LabelErreurSalaire.Text = "";
        // estado civil
        bool marié = RadioButtonOui.Checked;
        // cálculo do imposto
        try
        {
            LabelImpot.Text = String.Format("{0} euros",Global.Metier.CalculerImpot(marié, nbEnfants, salaire));
        }
        catch (Exception ex)
        {
            LabelImpot.Text = ex.Message;
        }
    }
  • Para entender esse código, é preciso saber
    • que, no início de sua execução, o formulário [Default.aspx] está preenchido conforme o usuário o preencheu. Assim, os campos (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire) contêm os valores inseridos pelo usuário.
    • de modo que, ao final de sua execução, a mesma página [Default.aspx] será enviada de volta ao usuário. Isso ocorre automaticamente.

O procedimento ButtonCalculer_Click deve, portanto, a partir dos 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á enviada ao usuário.

Não há nenhuma dificuldade específica neste código. Apenas a linha 27 merece uma explicação. Ela utiliza o método CalculerImpot de um campo Global.Metier que ainda não foi abordado. Voltaremos a esse assunto em breve.

O método ButtonEffacer_Click é o seguinte:


    protected void ButtonEffacer_Click(object sender, EventArgs e)
    {
        // zerar formulário
        TextBoxEnfants.Text = "";
        TextBoxSalaire.Text = "";
        LabelImpot.Text = "";
        LabelErreurEnfants.Text = "";
        LabelErreurSalaire.Text = "";
}

Voltemos à arquitetura que precisamos construir:

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

Resta-nos agora criar a “ligação” entre essas três camadas. Trata-se essencialmente de:

  • instanciar a camada [3] ao iniciar o aplicativo
  • colocar uma referência a ela em um local onde a página da web [Default.aspx.cs] possa buscá-la sempre que for instanciada e for solicitada a calcular o imposto.

Esse não é um problema novo. Ele já foi encontrado na construção do serviço web remoto e analisado no parágrafo 12.4.1. Sabe-se que a solução consiste em:

  • criar um arquivo [Global.asax] associado a uma classe [Global.cs]
  • instanciar a camada [3] no método Application_Start da classe [Global.cs]
  • colocar a referência da camada [3] em um campo estático da classe [Global.cs], pois o tempo de vida dessa classe é o mesmo da aplicação.

Assim, nosso projeto web evolui da seguinte forma:

  • para [1], o arquivo [Global.asax].
  • em [2], o código [Global.cs] associado. A pasta [App_Code] na qual esse arquivo se encontra não está presente por padrão na solução web. Use [3] para criá-la.

O arquivo 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
    {
        // camada de negócios
        public static IImpotMetier Metier;

        // método executado na inicialização do aplicativo
        private void Application_Start(object sender, EventArgs e)
        {
            // instâncias das camadas [metier] e [dao]
            Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
        }
    }
}
  • linha 6: a classe se chama Global e faz parte do espaço de nomes WsImpot (linha 4). Portanto, seu nome completo é WsImpot.Global, e é esse nome que deve ser inserido no atributo Inherits de Global.asax.
  • linha 6: sabemos que a classe associada a Global.asax deve, obrigatoriamente, derivar da classe System.Web.HttpApplication.
  • linha 12: o método Application_Start é executado ao iniciar o aplicativo web.
  • linha 15: instanciamos a camada [metier] (camada [3] do aplicativo em desenvolvimento) usando o Spring e a seguinte configuração em [web.config]:

  <!-- objetos Spring -->
  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <object name="metier" type="Metier.ImpotMetierWS, ImpotsV9-metier">
      </object>
    </objects>
</spring>

A classe [Metier.ImpotMetierWS] da linha (g) acima está localizada em [ImpotsV9-metier.dll].

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


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

Estamos prontos para um teste. É preciso iniciar o SGBD MySQL5, o serviço web remoto, e anotar a porta na qual ele opera:

  

Feito isso, é preciso verificar se, no arquivo [web.config] do cliente web, a porta do serviço web remoto está correta:

Feito isso, o cliente web do serviço web remoto pode ser iniciado pressionando Ctrl-F5:

  

12.6. Um cliente de console Java para o serviço web de cálculo de impostos

Para demonstrar que os serviços web são acessíveis por clientes escritos em qualquer linguagem, vamos criar um cliente Java de console básico. A arquitetura da aplicação cliente/servidor será a seguinte:

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

Primeiramente, vamos alterar um detalhe em nosso serviço web de cálculo de impostos. 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 mostraram que o acento no parâmetro marié nas linhas 6 e 8 poderia ser um problema na interoperabilidade entre Java e C#. Adotaremos 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);
    }

}

Esse serviço será colocado em um novo projeto do Web Developer chamado WsImpotsSansAccents. O serviço web terá, então, a URL [/WsImpotSansAccents/ServiceImpot.asmx].

Image

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

  • no [1], crie um novo projeto
  • no [2,3], selecione um projeto Java do tipo Java Application.
  • em [4], passe para a próxima etapa
  • em [5], nomeie o projeto
  • em [6], indicar a pasta onde será criada uma subpasta com o nome do projeto
  • em [7], nomeie a classe que conterá o método main executado ao iniciar o aplicativo
  • em [8], encerrar o assistente
  • em [9]: o projeto Java gerado
  • em [10]: clique com o botão direito do mouse no projeto para gerar o cliente do serviço web de cálculo de impostos
  • em [11], a URL do arquivo que descreve o serviço web de cálculo de impostos:

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

Essa URL é a do serviço [ServiceImpot.asmx], à qual se adiciona o parâmetro ?WSDL. O documento localizado nessa URL descreve, em linguagem XML, o que o serviço [15] é capaz de fazer. Trata-se de um elemento padrão de um serviço web.

  • em [12], o pacote (equivalente ao namespace do C#) no qual serão colocadas as classes que serão geradas
  • em [13], mantenha o valor padrão
  • em [14], conclua o assistente
  • No [16], o serviço web importado foi integrado ao projeto Java. Ele suporta dois protocolos de comunicação: Soap e Soap12.
  • em [17], a classe [Main] na qual utilizaremos o cliente gerado
  • em [18], vamos inserir código no método [main]. Posicione o cursor no local onde o código deve ser inserido, clique com o botão direito e selecione a opção [19]
  • em [20], indique que deseja gerar o código de chamada da função CalculerImpot do serviço remoto de cálculo de impostos e, em seguida, clique em OK.

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

public class Main {

    public static void main(String[] args) {
         // TODO código da lógica da aplicação aqui
      try { // Chamar operação de serviço web
        wsimpot.ServiceImpot service = new wsimpot.ServiceImpot();
        wsimpot.ServiceImpotSoap port = service.getServiceImpotSoap();
         // TODO inicialize os argumentos da operação WS aqui
        boolean marie = false;
        int nbEnfants = 0;
        int salaire = 0;
         // TODO processar o resultado aqui
        int result = port.calculerImpot(marie, nbEnfants, salaire);
        System.out.println("Result = "+result);
      } catch (Exception ex) {
         // TODO: tratar exceções personalizadas aqui
      }
    }
}

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

import wsimpot.ServiceImpot;
public class Main {
    public static void main(String[] args) {
      try {
         // chama-se a função CalculerImpot do serviço web
        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 a classe 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 no console durante a 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)