Desenvolvimento - Web Services

WebServices com Banco de Dados

Um WebService é essencialmente um conjunto de funções chamáveis via Internet, usando o protocolo SOAP. Este artigo parte do princípio que você conhece um pouco da tecnologia por trás dos WebServices. Neste artigo mostrarei como criar WebServices para acesso a bancos de dados, um assunto que traz algumas questões bastante específicas e importantes. Usarei o Microsoft® Visual Studio .NET Beta 2®.

por Mauro Sant'Anna



Um WebService é essencialmente um conjunto de funções chamáveis via Internet, usando o protocolo SOAP. Este artigo parte do princípio que você conhece um pouco da tecnologia por trás dos WebServices. Caso contrário, sugiro a leitura de outro artigo meu em http://www.microsoft.com/brasil/msdn/web_services.asp.

Neste artigo mostrarei como criar WebServices para acesso a bancos de dados, um assunto que traz algumas questões bastante específicas e importantes. Usarei o Microsoft® Visual Studio .NET Beta 2®.

Consulta simples

Em primeiro lugar, vamos efetuar uma consulta simples. Estou chamando de consulta simples porque ela retorna apenas um valor, algo também conhecido no jargão SQL como um “escalar”.

Utilizarei a base de dados “Northwind” que vem como exemplo no SQL Server 7 ou mais novo. A consulta que farei será “retornar o nome do produto na tabela Products que corresponde ao código de produto fornecido”. Um exemplo de consulta seria:

Listagem 1: Exemplo de Consulta

SELECT ProductName FROM Products WHERE ProductID = 4

O método em C# do WebService terá o seguinte “protótipo”:

Listagem 2: Método PegaNomePorCodigo

public string PegaNomePorCodigo(int Codigo)

Em primeiro lugar, criaremos um WebService no Visual Studio .NET. Rode o Visual Studio e peça “New | Project | Visual C# Projects | ASP.NET Web Service”. Dê um nome ao projeto, por exemplo “WSBB”:

Criando um Projeto ASP.Net Web Service

Figura 1: Criando um Projeto ASP.Net Web Service

O Visual Studio .NET possui uma ferramenta capaz de acessar diretamente bancos de dados no “Server Explorer”. Isto ajudará um pouco no nosso exemplo, pois o próprio Visual Studio irá criar uma “string de conexão”, necessária ao acesso a banco de dados.

Peça para visualizar o “Server Explorer” com o comando “View | Server Explorer”. Clique com o botão direito sobre “Data Connections” e peça “Add Connection...”:

Vizualizar o Server Explorer - Add Conection...

Figura 2: Vizualizar o Server Explorer - Add Conection...

A tela de criação de uma conexão aparecerá. A primeira página (“Provider”) já estará com “Microsoft OLE DB Provider for SQL Server” selecionado. Deixe como está:

Criação de Conexão

Figura 3: Criação de Conexão

Estamos mais interessados na página “Connection”:

Página Connection

Figura 4: Página Connection

Entre com o nome do servidor, nome do usuário e senha do usuário, que serão diferentes dos mostrados acima. Selecione também a base de dados “Northwind”.

Adicione a área de trabalho um componente da classe “OleDbConnection” da página “Data” do “Toolbox”:

Adicionando o componente OleDbConnection

Figura 5: Adicionando o componente OleDbConnection

Ajuste a propriedade “ConnectionString” para se referir à conexão recém-criada:

OleDbConnection1- Ajustando a propriedade ConnectionString

Figura 6: OleDbConnection1- Ajustando a propriedade ConnectionString

Coloque um componente OleDbCommand e ajuste as seguintes propriedades:

  • Connection: oleDbConnection1 (a conexão recém-criada);
  • CommandText: SELECT ProductName FROM Products WHERE (ProductID = ?).

Note que o nome do componente é oleDbCommand1. Você pode também usar o “Query Builder” para criar o comando SQL clicando nas reticências ao lado do comando SQL:

OleDbConnection1- Ajustando a propriedade ConnectionString

Figura 7: OleDbConnection1- Ajustando a propriedade ConnectionString

Observe que a interrogação do comando será um parâmetro na query e será substituída em tempo de execução.

Colocaremos agora o código do WebService. Peça para editar o código e digite as linhas a seguir, logo abaixo da declaração da classe:

Listagem 3: Implementação do método PegaNomePorCodigo

[WebMethod(Description = "Pega o nome do produto dado seu código")]
public string PegaNomePorCodigo(int Codigo) {
   string Resultado = "";
   // Abre a conexão com bancos de dados
   oleDbConnection1.Open();
   try {
          // Ajusta parâmetro da query
          oleDbCommand1.Parameters["ProductID"].Value = Codigo;
          // Executa e pega resultado
          Resultado = (string) oleDbCommand1.ExecuteScalar();
   }
   finally {
          // Fecha a conexão
          oleDbConnection1.Close();
   }
   // Retorna o resultado (string)
   return Resultado;
}

Veja o código digitado no ambiente de desenvolvimento:

Código no Microsoft Visual C#

Figura 8: Código no Microsoft Visual C#

Observe alguns detalhes no código:

  • A conexão do banco de dados é aberta e fechada a cada invocação do serviço. Ao contrário do que possa parecer a princípio, não existe grande perda de performance por causa disto. As rotinas de acesso a banco de dados fazem “cache” de conexões, reutilizando rapidamente este recurso;
  • O parâmetro da “query” está sendo substituído com o valor efetivamente passado, em tempo de execução;
  • O método ExecuteScalar é usado para executar um comando SQL que retorna uma tabela com apenas uma linha e uma coluna, como o caso acima;
  • Colocamos o fechamento da conexão dentro de um try/finally. Isto é absolutamente obrigatório, pois garante a liberação da conexão caso ocorra algum erro.

Você pode testar o WebService executando o programa com “Debug | Start Without Debugging”. Será criada uma página Web para testar o WebService:

Testando o WebService

Figura 9: Testando o WebService

Clique no nome do WebService (“PegaNomePorCodigo”). A seguinte tela aparecerá:

WebService PegaNomePorCodigo

Figura 10: WebService PegaNomePorCodigo

A tela acima permite tanto entrar com um valor para testes como também exibe o que será enviado e recebido via HTTP. Existe um aviso sobre o qual comentarei a seguir. Entre o código de produto, 5 por exemplo e veja o resultado:

Chamada do WebService

Figura 11: Chamada do WebService

O navegador apresenta o resultado da chamada do WebService.

Namespace do WebService

O protocolo SOAP exige que todo WebService tenha um nome único no universo. Isto é obtido em C# colocando-se um “atributo” antes da classe que implementa o WebService. Este atributo deve ter o nome de um domínio para o qual você tem registro na Web (FAPESP, no Brasil).

No nosso caso colocarei o seguinte domínio fictício antes da declaração da classe:

Listagem 4: Domínio do web servisse

[WebService(Namespace="http://picaplan.com.br/webservices/", Description="Testes com acesso a bancos de dados")] 
public class Service1 : System.Web.Services.WebService {

Ao fazer isto, o aviso mostrado anteriormente desaparece:


Figura 12:

Consultas mais complexas

Um comando SQL normalmente retorna um “conjunto de resultado”, muito semelhante a uma tabela, ou seja, pode ter várias “linhas” e cada linha pode ter várias “colunas”. Surge a questão de como representar um “conjunto de resultado” como retorno de um WebService.

Basicamente, temos duas alternativas:

  1. Retornar um objeto da classe DataSet. O DataSet é a classe da biblioteca ADO.NET feita especialmente para representar um “banco de dados em memória”. Surpreendentemente - dada a sua complexidade - a classe DataSet é “serializável", ou seja, pode ser convertida para XML usada como retorno de um WebMethod.
  2. Retornar um “struct” ou “array de struct”. A desvantagem neste caso é que o código é mais complexo, mas em compensação podemos chamar o WebService mais facilmente de outros sistemas operacionais. Não estou querendo dizer que outros sistemas operacionais não sejam capazes de “entender” um DataSet; isto é possível, apenas mais difícil.

Veremos a seguir as duas maneiras. Antes de qualquer coisa, acrescentaremos um componente OleDBCommand e alteraremos as seguintes propriedades:

  • Connection: oleDbConnection1 (a conexão criada anteriormente)
  • CommandText: SELECT ProductID, ProductName, CategoryID, QuantityPerUnit, UnitPrice FROM Products WHERE (CategoryID = ?)

Veja o comando SQL acima no Query Builder:

Estruturação de query via design

Figura 13: Estruturação de query via design

O componente recém colocado no projeto será utilizado para consulta nos exemplos a seguir.

Retornando um DataSet

Neste caso, retornaremos um objeto da classe DataSet. Este objeto será perfeitamente descrito em XML e pode ser consumido fora da plataforma .NET, mas é mais facilmente acessível dentro da própria plataforma.O código do WebService que retorna o DataSet é o seguinte:

Listagem 5: Retornando um DataSet

[WebMethod(Description = "Pega produtos dado o código da categoria e retorna um DataSet")]
public DataSet PegaProdutosDaCategoriaDS(int Codigo) {
     // Cria um DataSet para conter o resultado
     DataSet Resultado = new DataSet();
     // Abre a conexão
     oleDbConnection1.Open();
     try {
            // Ajusta parâmetro da query
            oleDbCommand2.Parameters["CategoryID"].Value = Codigo;
            // Executa comando e pega o resultado no DataReader
            System.Data.OleDb.OleDbDataReader Prod = oleDbCommand2.ExecuteReader();
            try {
     // Copia o conteúdo DataReader para um DataTable com uma função desta classe
                   DataTable Tbl = ConvDataTable(Prod, "Products");
                   // Adiciona tabela no DataSet
                   Resultado.Tables.Add(Tbl);
            }
            finally {
                   // Fecha o DataReader
                   Prod.Close();
            }
     }
     finally {
            // Fecha a conexão
            oleDbConnection1.Close();
     }
     // Retorna o resultado (DataSet)
     return Resultado;
}
DataTable ConvDataTable(IDataReader DR, string Nome) {
     // Cria DataTable
     DataTable Resultado = new DataTable(Nome);
     // Copia estrutura
     for(int i = 0; i

O código acima é explicado pelos comentários. Observe, no entanto, a função ConvDataTable que pega um DataReader e preenche uma nova tabela. Apesar de ser uma rotina meio óbvia, não pude localizar nada parecido na biblioteca ADO.NET.

Veja o resultado da consulta no navegador:

Resultado da Consulta

Figura 14: Resultado da Consulta

Retornando um array de struct

Neste caso, iremos declarar um “struct” com os campos desejados e retornar um array deste novo tipo. Veja o código:

Listagem 6: Implementação do método PegaProdutosDaCategoriaArray

[WebMethod(Description = "Pega produtos dado o código da categoria e retorna um struct []")]
public Produto [] PegaProdutosDaCategoriaArray(int Codigo) {
   // Cria uma lista para conter os valores achados
   ArrayList Produtos = new ArrayList();
   // Abre a conexão
   oleDbConnection1.Open();
   try {
          // Ajusta parâmetro da query
          oleDbCommand2.Parameters["CategoryID"].Value = Codigo;
          // Executa comando e pega o resultado no DataReader
          System.Data.OleDb.OleDbDataReader Prod = oleDbCommand2.ExecuteReader();
          try {
                 // Varre todo o DataReader
                 while (Prod.Read()) {
                       // Declara uma variável para conter o registro
                       Produto P;
                       // Copia os campos
                       P.CategoryID = (int) Prod["CategoryID"];
                       P.ProductID = (int) Prod["ProductID"];
                       P.ProductName = (string) Prod["ProductName"];
                       P.QuantityPerUnit = (string) Prod["QuantityPerUnit"];
                       P.UnitPrice = (decimal) Prod["UnitPrice"];
                       // Adiciona na lista
                       Produtos.Add(P);
                 }
          }
          finally {
                 // Fecha o DataReader
                 Prod.Close();
          }
   }
   finally {
          // Fecha a conexão
          oleDbConnection1.Close();
   }
   // Cria um array de Prodtos para conter o resultado
   Produto [] Resultado = new Produto[Produtos.Count];
   // Copia da lista para array de Prodito
   Produtos.CopyTo(Resultado);
   // Retorna o resultado (Produto [])
   return Resultado;
}

Observe que cada struct individual é preenchida dentro do loop e colocada em um ArrayList. Depois que o ArrayList for completamente preenchido, seus valores são copiados para o array a ser efetivamente retornado pela função.

Conclusão

É bastante simples criar um WebService que executa consulta a bancos de dados. Temos basicamente duas maneiras de retornar os resultados; uma delas retorna um DataSet, sendo mais adequada a ser usada por outro aplicativo .NET. Outra alternativa é retornar um array de struct, uma maneira mais genérica.

Mauro Sant'Anna

Mauro Sant'Anna - Mauro tem mais de 20 anos de experiência no desenvolvimento de software, com produtos publicados no Brasil, Portugal e Estados Unidos, além de extensa experiência em treinamento e consultoria no desenvolvimento de software, tanto criando material como ministrando cursos.
Mauro é um "Microsoft Most Valuable Professional" (MVP - www.microsoft.com/mvp), “Microsoft Regional Director” (RD - www.microsoft.com/rd), membro do INETA Speaker’s Bureau (www.ineta.org) e possui as certificações MCP, MCSA (Windows 2000/2003), MCAD (C# e VB), MCDBA, MCSE (Windows 2000/2003).
Sua empresa, a M. A. S Informática (www.mas.com.br), treinou centenas de turmas em desenvolvimento de software nos últimos anos.