Desenvolvimento - Web Services

Consumindo serviços REST com HttpClient

Essa biblioteca auxilia muito no consumo de serviços que foram expostos através do modelo REST, independentemente se eles foram ou não criados através do WCF.

por Israel Aéce



Como mencionei no último artigo, serviços expostos através do modelo REST não expõem o documento WSDL, usado para descrever as funcionalidades dos serviços. Sem esse tipo de documento, força os desenvolvedores a efetuar a requisição e recuperar a resposta utilizando as classes de baixo nível, tais como HttpWebRequest e HttpWebResponse, que estão dentro do namespace System.Net.

Apesar de funcionar, certas operações são extremamente difíceis de realizar. Se a requisição for feita via GET, então acaba sendo mais simples, mas tudo fica mais difícil quando é exigido que algo seja passado no corpo da mensagem, como é o caso do POST. Isso nos obriga a efetuar a construção do corpo de forma manual, sem muitos auxiliadores para ajudar nessa tarefa árdua.

Quando referenciamos um serviço em uma aplicação, o Visual Studio utiliza o documento WSDL para criar o proxy, e com isso, o consumo do serviço fica extremamente simples, já que invocamos seus respectivos métodos, como se fossem métodos locais, mas que durante a execução são encaminhados para o serviço remoto, abstraindo toda a necessidade de conhecer os detalhes do protocolo que está sendo utilizando. Como isso não é possível com serviços REST, a Microsoft incluiu no WCF-REST Starter Kit, uma biblioteca chamada HttpClient. Essa biblioteca traz várias funcionalidades para tornar o consumo de serviços REST dentro de aplicações construídas em .NET bem mais simples do que trabalhar diretamente com a classes de baixo nível que foram mencionadas acima. A finalidade deste artigo é explorar algumas dessas funcionalidades.

Ao instalar o WCF-REST Starter Kit, podemos reparar que no diretório da instalação há dois assemblies: Microsoft.Http.dll e Microsoft.Http.Extensions.dll. Esses assemblies contém todos os recursos que iremos utilizar no decorrer deste artigo, sendo o primeiro, aquele que contém todas as principais funcionalidades para efetuar a comunicação com serviços REST, enquanto o segundo, traz algumas extensões para facilitar algumas tarefas que serão bastante rotineiras durante o consumo destes serviços.

Antes de efetivamente comerçarmos a falar sobre essas classes, vamos analisar quais são as operações que o serviço está expondo. Através da interface abaixo, podemos reparar que há apenas dois métodos, onde o primeiro recebe como parâmetro uma instância da classe Usuario, e retorna uma string contendo a mensagem de sucesso ou falha da adição deste usuário em algum repositório. Já o segundo método, retorna um array, onde cada elemento é representado pela classe Usuario, contendo todos os usuários cadastrados em um determinado repositório. A classe Usuario também é extremamente simples, pois contém apenas duas propriedades: Nome e Codigo, do tipo string e inteiro, respectivamente.

[ServiceContract(Namespace = "http://www.israelaece.com/servicos")]
public interface IUsuarios
{
[WebInvoke]
string Adicionar(Usuario usuario);

[WebGet]
Usuario[] RecuperarUsuarios();
}

Depois do serviço exposto para ser consumido através do modelo REST (WebHttpBinding), resta nos criar uma aplicação para consumí-lo, para que assim vejamos a biblioteca que é tema deste artigo em ação. Depois de criado a aplicação que irá consumir o serviço REST, devemos referenciaros dois assemblies (DLLs)que vimos acima, fornecidos pelo WCF-REST Starter Kit.

Antes de efetivamente consumirmos o serviço, é necessário saber como as mensagens serão trocadas, ou melhor, como elas devem ser formatadas. Como dito no artigo anterior, podemos recorrer à duas formas de expor as funcionalidades de serviços REST, e com aquelas informações será possível a saber quais as funcionalidades e como as mensagens devem ser trocadas. De acordo com a configuração que definimos no contrato do serviço, ele pode ser acessado utilizando o formato Xml. Ainda analisando a página gerada automaticamente pelo WCF-REST Starter Kit, podemos visualizar o schema da mensagem ou um exemplo, de como o corpo deve ser formatado para enviar ou como ele é formatado na recepção da resposta. Com isso, a mensagem para a primeira operação deve ter o corpo formatado da seguinte forma:

<xs:schema
elementFormDefault="qualified"
targetNamespace="http://www.israelaece.com/servicos">
<xs:complexType name="Usuario">
<xs:sequence>
<xs:element minOccurs="0" name="Codigo" type="xs:int" />
<xs:element minOccurs="0" name="Nome" nillable="true" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:element name="Usuario" nillable="true" type="tns:Usuario" />
</xs:schema>

Repare que o corpo deve conter um elemento que representará a instância da classe Usuario, com as respectivas propriedades preenchidas. Se analisarmos agora o mesmo documento para a segunda operação, veremos que ele será ligeiramente diferente, já que ela retornará uma coleção de elementos do tipo Usuario:

<xs:schema
elementFormDefault="qualified"
targetNamespace="http://www.israelaece.com/servicos">
<xs:complexType name="ArrayOfUsuario">
<xs:sequence>
<xs:element
minOccurs="0"
maxOccurs="unbounded"
name="Usuario"
nillable="true"
type="tns:Usuario" />
</xs:sequence>
</xs:complexType>
<xs:element name="ArrayOfUsuario" nillable="true" type="tns:ArrayOfUsuario" />
<xs:complexType name="Usuario">
<xs:sequence>
<xs:element minOccurs="0" name="Codigo" type="xs:int" />
<xs:element minOccurs="0" name="Nome" nillable="true" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:element name="Usuario" nillable="true" type="tns:Usuario" />
</xs:schema>

Ao contrário do que acontece com o modelo SOAP, onde o Visual Studio é capaz de analisar o documento WSDL e reconstruir os tipos do lado do cliente, no modelo REST isso não será possível, assim como já foi discutido. Com isso, é necessário fazermos isso manualmente do lado do cliente. Analisando os dois códigos acima, podemos perceber que é necessário a criação de duas classes, sendo uma delas para representar a classe Usuario, enquanto a segunda, representará a coleção de usuários. Com isso a nossa classe Usuario, do lado do cliente, deve seguir o seguinte formato:

[DataContract(Namespace = "http://www.israelaece.com/servicos")]
public class Usuario
{
[DataMember]
public int Codigo { get; set; }

[DataMember]
public string Nome { get; set; }
}

Além da classe Usuario que precisamos reconstruir, ainda precisamos nos atentar no retorno do método RecuperarUsuarios, que retorna um array, onde cada elemento deste objeto será representado pela classe criada acima. Para representar esse array contendo os usuários, podemos criar uma classe que herda da lista genérica List<T>, onde podemos substituir oparâmetro genéricoT por Usuario. Além disso, ainda precisamos "mapear" o Xml correspondente à resposta para este tipo. Na formatação do Xml que vimos acima, podemos reparar que os usuários serão retornados dentro de um elemento complexo chamado ArrayOfXXX, onde XXX representa o tipo que está no interior daquela coleção. Para configurar esse "mapeamento", utilizaremos o atributo CollectionDataContractAttribute, que podemos coordenar como a serialização/deserialização será realizada.

[CollectionDataContract(
Name = "ArrayOfUsuario",
Namespace = "http://www.israelaece.com/servicos")]
public class ColecaoDeUsuarios : List<Usuario> { }

Depois da reconstrução dos tipos que serão utilizados para representar os tipos que o serviço trabalha, vamos começar a analisar as classes que são fornecidas pela biblioteca tema deste artigo. Para inicializar, a primeira classe que vamos analisar é a HttpClient. Esta classe, que está debaixo do namespace Microsoft.Http, é responsável por gerenciar toda a comunicação com um determinado serviço. Em seu construtor, recebe o endereço base até o serviço, pois se ele fornecer mais do que uma operação, você pode reutilizar a mesma classe (HttpClient) para executar as requisições.

Seguindo o exemplo do serviço que foi criado acima, a primeira operação que vamos consumir é a Adicionar, que recebe uma instância da classe Usuario como parâmetro e retorna uma string contendo o resultado. Como já era de se esperar, existe uma classe que representa a requisição, chamada de HttpRequestMessage. Essa classe, em seu construtor, recebe uma string contendo o método HTTP que a requisição deverá ser executada. Além disso, ainda temos que informar o nome da operação a ser executada. É importante dizer que essa classe implementa a interface IDisposable, que faz a limpeza explícita dos recursos que ela utiliza, e a boa prática é envolvê-la em um bloco using. O código abaixo ilustra o uso dessas classes:

using (HttpClient http = new HttpClient("http://localhost:1572/ServicoDeUsuarios.svc/"))
{
using (HttpRequestMessage request = new HttpRequestMessage("POST", "Adicionar"))
{
Usuario u = new Usuario() { Codigo = 123, Nome = "Israel" };
request.Content = HttpContentExtensions.CreateDataContract<Usuario>(u);

using (HttpResponseMessage response = http.Send(request))
{
response.EnsureStatusIsSuccessful();
Console.WriteLine(response.Content.ReadAsXElement().Value);
}
}
}

Ainda analisando o código que faz a requisição à operação Adicionar, podemos perceber que após criado a instância da classe que representa a requisição, precisamos definir o corpo da mesma, que deve ser representado pela classe HttpContent. Como sabemos, a operação deve receber a instância da classe Usuario, mas serializada em um formato específico. Para nos auxiliar, há uma classe estática chamada HttpContentExtensions, que fornece uma porção de métodos genéricos, que dado um tipo e seu respectivo valor, retorna uma instância da classe HttpContent, com aquele objeto formatado. Os métodos que esta classe fornece são:

  • CreateAtom10SyndicationFeed: Retorna um objeto em formato Atom 1.0. Mais detalhes neste artigo.
  • CreateDataContract: Retorna um objeto serializado em formato Xml, respeitando as regras impostos pelo serializador do WCF.
  • CreateJsonDataContract: Retorna um objeto serializado no formato Json.
  • CreateRss20SyndicationFeed: Retorna um objeto em formato RSS 2.0. Mais detalhes neste artigo.
  • CreateXmlSeriliazable: Retorna um objeto serializado utilizando o serializador Xml do .NET (XmlSerializer).

O retorno de um destes métodos deve ser atributo à propriedade Content da classe HttpRequestMessage, assim como notamos no código acima. Depois disso, vamos recorrer à instância da classe HttpClient para enviar a requisição ao respectivo serviço. Essa classe fornece vários métodos autoexplicativos: Get, Post, Put e Delete. Cada um desses métodos recebe, individualmente, parâmetros como Uri, HttpContent, etc., mas internamente, todos recorrem ao método Send, que encapsula a criação do objeto HttpRequestMessage. O código abaixo exibe a utilização do método Post ao invés do método Send:

using (HttpClient http = new HttpClient("http://localhost:1572/ServicoDeUsuarios.svc/"))
{
Usuario u = new Usuario() { Codigo = 123, Nome = "Israel" };

using (HttpResponseMessage response =
http.Post("Adicionar", HttpContentExtensions.CreateDataContract<Usuario>(u))
{
response.EnsureStatusIsSuccessful();
Console.WriteLine(response.Content.ReadAsXElement().Value);
}
}

Os métodos utilizados para efetivamente invocar uma operação, retorna uma instância da classe HttpResponseMessage, que como o próprio nome diz, representa o resultado da requisição. Entre os vários métodos que essa classe fornece, alguns métodos de extensão foram adicionados a ele, através da classe HttpMessageExtensions. Entre esses métodos, temos um chamado de EnsureStatusIsSuccessful, que verifica se a requisição foi realizada com sucesso, e se não foi, uma exceção será disparada. Esse método recorre, internamente, ao método público e estático EnsureStatusIs, que dado um código de resposta do protocolo HTTP, verifica se a resposta é igual a este código, disparando uma exceção caso não tenha sido.

E, assim como o objeto que representa a requisição, o objeto de resposta também possui uma propriedade chamada Content, que retorna a instância de uma classe do tipo HttpContent. Essa classe também recebe alguns métodos de extensão, que podemos ler o seu conteúdo já deserializando em um determinado tipo. Para isso, temos os seguintes métodos, que estão prefixados com a classe que define a extensão:

  • HttpContent.ReadAsByteArray: Retorna um array de bytes que representa o corpo da resposta.
  • HttpContent.ReadAsStream: Retorna um stream que contendo o corpo da resposta.
  • HttpContent.ReadAsString: Retorna uma string contendo o corpo da resposta.
  • XElementContentExtensions.ReadAsXElement: Retorna um objeto do tipo XElement, que pode ser utilizado para interagir com o corpo da mensagem retornado pelo serviço.
  • DataContractContentExtensions.ReadAsDataContract: Retorna um objeto (definido pelo parâmetro genérico) que corresponde ao corpo da mensagem.

Como sabemos que o resultado da operação é formatado em Xml, podemos optar por ler utilizando o método ReadAsXElement, que nos dará um objeto do tipo XElement, onde podemos recorrer à propriedade Value para visualizar a string que foi retornado pelo serviço.

Para finalizar, temos o segundo método, que é o RecuperarUsuarios, que através do modelo GET, retornará um array contendo os usuários. A única mudança considerável em relação ao código anterior, é que utilizamos o método Get da classe HttpClient, e para capturar o resultado, recorremos ao método ReadAsDataContract<T>, para já converter na coleção que criamos acima.

using (HttpClient http = new HttpClient("http://localhost:1572/ServicoDeUsuarios.svc/"))
{
using (HttpResponseMessage response = http.Get("RecuperarUsuarios"))
{
response.EnsureStatusIsSuccessful();

ColecaoDeUsuarios usuarios =
response.Content.ReadAsDataContract<ColecaoDeUsuarios>();

foreach (var itemin usuarios)
{
Console.WriteLine(item.Nome);
}
}
}

Conclusão: Essa biblioteca auxilia muito no consumo de serviços que foram expostos através do modelo REST, independentemente se eles foram ou não criados através do WCF. Essa biblioteca facilitará o consumo destes tipos em serviços em qualquer aplicativo que seja construído em cima da plataforma .NET, sem a necessidade de conhecer as classes de baixo nível.

HttpClient.zip (142.60 kb)

Israel Aéce

Israel Aéce - Especialista em tecnologias de desenvolvimento Microsoft, atua como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. Como instrutor Microsoft, leciona sobre o desenvolvimento de aplicações .NET. É palestrante em diversos eventos Microsoft no Brasil e autor de diversos artigos que podem ser lidos a partir de seu site http://www.israelaece.com/. Possui as seguintes credenciais: MVP (Connected System Developer), MCP, MCAD, MCTS (Web, Windows, Distributed, ASP.NET 3.5, ADO.NET 3.5, Windows Forms 3.5 e WCF), MCPD (Web, Windows, Enterprise, ASP.NET 3.5 e Windows 3.5) e MCT.