Criando serviços REST com WCF

Rafael Godinho
Especialista em Desenvolvimento – Microsoft Brasil
Junho 2009

Tecnologias : Windows Communication Foundation, .NET Framework 3.5 SP1, Visual Studio 2008 SP1

Sumário: Neste artigo veremos conceitos de REST, sua origem e seu funcionamento. Iremos também ver como criar serviços REST utilizando o Windows Communication Foundation (WCF), as possibilidades de hospedagem do serviço e as opções de consumo nas aplicações cliente.

 Clique aqui para baixar o código fonte

Conteúdo

arrow_px_down.gif Introdução
arrow_px_down.gif Surgimento do REST
arrow_px_down.gif Funcionamento do REST
arrow_px_down.gif Formato de representação de recursos
arrow_px_down.gif Cenário de uso
arrow_px_down.gif Criando serviços REST com WCF
arrow_px_down.gif Hospedando o serviço criado
     arrow_px_down.gif Hospedando serviços em uma aplicação .NET
     arrow_px_down.gif Hospedando serviços no IIS
arrow_px_down.gif Consumindo serviços REST
     arrow_px_down.gif JavaScript
     arrow_px_down.gif ASP.NET Ajax
     arrow_px_down.gif .NET com chamadas HttpWebRequest
     arrow_px_down.gif Silverlight
     arrow_px_down.gif .NET com chamadas WCF
arrow_px_down.gif Qual opção de binding escolher?
arrow_px_down.gif Novidades do .NET Framework 3.5 SP1
arrow_px_down.gif Conclusão
arrow_px_down.gif Referências + Tópicos Relacionados
arrow_px_down.gif Sobre o Autor

Introdução

Você já parou para pensar por que a Internet é o sucesso que é hoje? A resposta para esta pergunta pode ser encontrada na simplicidade e nos padrões abertos adotados na Internet. Qualquer tecnologia que conseguir entender seu modelo de endereçamento, protocolo de aplicação, códigos de resposta e representação de informações através de linguagens de marcação pode fazer parte da web. Outro fato interessante é que ela surgiu em uma época que a conectividade era primitiva e foi desenhada para suportar um alto número de conexões, por isso sua comunicação é stateless e é recomendável o uso de caches nos clientes, servidores e gateways.  Hoje, podemos acessar a web praticamente de qualquer lugar do planeta à partir de uma infinidade diferente de dispositivos com poder de processamento igualmente distintos.

Apesar da web ter evoluído com o passar do tempo, indo de páginas estáticas até conteúdo dinâmico gerado nos servidores, do consumo de informação à geração de informação pelo próprios usuários no modelo da web 2.0, dos diretórios estáticos de páginas à indexação dos grandes mecanismos de busca, na grande maioria dos casos a internet continua servindo para o mesmo propósito: prover informação para uma pessoa.

Surgimento do REST

Na época da bolha das empresas ponto com, na década de 1990, as pessoas começaram a perceber a web como uma maneira de fazer negócios daí surgiu a necessidade de algum meio de se realizar transações pela Internet. Com isso, criou-se um novo paradigma: como evoluir de uma web de páginas para uma web de serviços? Logo as pessoas começaram a trocar idéias o que fez com que, em 1998, algumas empresas, entre elas a Microsoft, criassem um padrão chamado XML-RPC. Com o passar do tempo algumas funcionalidades foram adicionadas e o padrão evoluiu, sendo uma das bases para o SOAP. Na primeira especificação do protocolo o significado era Simple Object Access Protocol, hoje deixou de ser um acrônimo e é conhecido somente como SOAP.

O funcionamento do SOAP é baseado em ações (actions), que nada mais é do que uma abstração para chamadas de métodos, no geral um serviço SOAP possui um único endereço de acesso e várias ações. O que pode ocorrer sobre vários protocolos, como, RPC, SMTP e HTTP, tendo como grande benefício o controle fim a fim da mensagem.

Quando utilizado em conjunto com HTTP, o SOAP utiliza somente o verbo POST para trasmissão das informações, um verbo que não pode ser utilizado com cache uma vez que a especificação HTTP define que apenas o verbo GET pode ser utilizado desta maneira. O uso do POST ocorre até mesmo para consulta de recursos somente leitura, cenário que poderia ser beneficiado pelo uso de cache. Apesar desta limitação não podemos nos esquecer que o SOAP foi projetado para ser utilizado com vários protocolos diferentes e ele não pode tirar vantagem de características específicas à um ou outro protocolo.

Com o passar do tempo, enquanto vários estavam focados do uso do SOAP, uma outra linha de pensamento começou a surgir. Ela era composta por pessoas com diferentes perspectivas de interoperabilidade, que não ia de encontro com os cenários empresariais muito bem atendidos pelo SOAP, motivando o surgimento do REST. O termo REST foi utilizado pela primeira vez na tese de doutorado do Dr. Roy Fielding, um dos criadores do protocolo HTTP. Na tese ele escreveu sobre o sucesso da Internet, do world wide web e do próprio protocolo, descrevendo o funcionamento da Internet como sendo um estado representacional de transferência, em inglês Representational State Transfer, ou seja, REST, considerado como um estilo arquitetural e não uma especificação.

Os serviços que seguem este estilo arquitetural são conhecidos como serviços RESTful e se baseiam no funcionamento da web e do protocolo HTTP. Este funcionamento utiliza a estrutura de recursos, que pode ser definido como qualquer coisa importante o suficiente para ser referenciada ou utilizada por um sistema, algo que pode ser armazenado no computador ou representado como uma sequência de bits. Como exemplos de recurso temos: um arquivo de áudio, uma linha em uma tabela de um banco de dados ou o retorno de um algoritmo, podendo ser um objeto físico, como um carro, ou um conceito abstrato como uma emoção.

A exposição do endereço do serviço na rede é feita através de uma URI (Uniform Resource Indicator). A URI pode ser considerada uma das tecnologias fundamentais da web, no início da internet existiam outros sistemas de hipertexto além do Html e também outros protocolos além do Http, mas eles não se comunicavam. Com o passar do tempo o Html e o Http passaram a ser amplamente utilizados porque eles tinham uma forma simples de nomear os itens, onde cada recurso na web tem, pelo menos, uma URI.

Funcionamento do REST

O funcionamento da web de serviços é baseado no mesmo princípo da web de páginas, afinal já que esta última conseguiu tanto sucesso faz sentido repetir a fórmula. Com isso os serviços devem ser simples e abertos e qualquer pessoa, em qualquer lugar, utilizando qualquer plataforma deve ser capaz de utilizá-los. Devem ser baseados em um protocolo universal, que para web podemos considerar o HTTP, um protocolo aberto e amplamente conhecido. Os serviços devem também expor recursos que possam ser associados através de URIs, característica que faz parte das raízes do funcionamento da web e por uma questão de escala, devem ser apresentados através de uma comunicação sem estado (stateless) e sempre que possível utilizar algum tipo de cache pelos mecanismos já existentes na infra da web.

As chamadas às URIs são realizadas através de verbos HTTP. O verbo utilizado na requisição indica para o serviço que ação deve ser realizada com o recurso, os verbos mais comuns para serem utilizados com o REST são GET, POST, PUT e DELETE. O verbo GET indica que o cliente quer obter uma representação de somente leitura do recurso. O POST é utilizado para criar um novo recurso. O PUT é tipicamente utilizado na atualização de um recurso existente. Já o DELETE, como o próprio nome indica, é utilizado para excluir um recurso existente. Um resumo do uso dos verbos é demonstrado na figura 1.

Figura 1 – Verbos HTTP e suas utilizações

Formato de representação de recursos

Pelo fato do REST ser um estilo arquitetural e não uma especificação, não existe uma padronização sobre o formato das informações trocadas entre os clientes e os serviços, este formato é definido no cabeçalho Content-Type das requisições e respostas HTTP e formatos utilizados na Web são fortes candidatos para uso, onde podemos destacar:

  • XML:Possivelmente o formato mais popular para representação de recursos. É um formato amplamente conhecido, existindo bibliotecas para processamento de XML para várias plataformas. O seu Content-Type é application/xml.
  • RSS/Atom:O Really Simple Syndication (RSS) e o Atom Syndication Format são representações XML utilizadas para publicação de feeds, muito popular em blogs e feed readers. O Atom é um formato mais recente e vem sendo cada vez mais utilizado pelos desenvolvedores e empresas. O Content-Type do RSS é application/rss+xml e do Atom é application/atom+xml.
  • JSON:O JavaScript Object Notation é um formato baseado em texto bastante simples e apesar do seu nome, atualmente vem sendo bastante utilizado em várias linguagens de programação e não somente com JavaScript. O seu principal benefício é a economia da banda de comunicação entre o cliente e o serviço, pois o resultado de sua serialização tende a ser muito mais enxuto que o equivalente em XML. Seu Content-Type é application/json.

Cenário de uso

Usando os princípios de funcionamento do REST, veremos um exemplo da interação que ocorre entre o cliente e o serviço. O exemplo deste cenário é um serviço de cadastro de livros de uma biblioteca hospedado no endereço minhaUri.com.br.

Se o cliente do serviço quiser listar todos os livros cadastrados na biblioteca, ele faz um GET na URI/livros e recebe uma lista dos livros cadastrados, conforme a figura 2.

Figura 2 - Listagem de todos os livros da biblioteca

Dado estas condições, o cliente pode acessar um livro específico fazendo um GET na URI/livros/1 e teria de retorno do serviço as informações do livro com id igual à 1, como podemos observar na figura 3.

Figura 3 – Buscando um livro específico

Já para incluir um novo livro, o cliente deve realizar uma requisição POST para o serviço que responde com o código de status 201, indicando que um recurso foi criado, uma URI para acessar o recurso recém criado e uma referência para ele, demonstrado na figura 4.

Figura 4- Criação de um livro através do verbo POST

Existindo a necessidade de atualizar um livro já existente, o cliente chama o serviço na URI/livros/1, onde atualizaria o livro com id igual à 1, utilizando o verbo PUT e assim como na inclusão, o serviço também retorna uma instância do recurso atualizado, veja um exemplo na figura 5.

Figura 5 – Alteração de um livro existente

A última ação que o cliente pode fazer é excluir um livro, o que é feito através de uma requisição com o verbo DELETE na URI/livros/1, excluindo o livro de id igual à 1. O exemplo desta última chamada está na figura 6.

Figura 6 – Exclusão de um livro com o verbo DELETE

Um outro ponto que pode ser percebido no uso de REST é a facilidade de criação de interfaces. Primeiro define-se uma URI de acesso, em seguida os verbos HTTP que serão tratados e por último o formato do recurso que será disponibilizado. Este modo de funcionamento torna fácil o entendimento do comportamento do serviço, fazendo com que uma vez entendido o funcionamento de um serviço REST fique fácil de entender o funcionamento de outros, o que irá mudar será a URI, o formato de representação do recurso utilizado e os verbos HTTP tratados.

Além dos cenários acima, existem outras possibilidades do mapeamento do código de retorno HTTP , como na validação das requisições e tratamento de erros, detalhes da implementação serão tratados ao longo do artigo.

Criando serviços REST com WCF

Como é de conhecimento, o WCF possui uma grande flexibilidade na sua estrutura interna para hospedagem e publicação de serviços utilizando vários protocolos e padrões de comunicação e à partir da versão 3.5 do .NET Framework ele também suporta REST de maneira nativa. Nesta versão foi disponibilizado um conjunto de classes que tratam da transferência de mensagens em protocolo HTTP ou HTTPS sem o uso de envelopes SOAP, suporte ao controle dos verbos HTTP e também a possibilidade de transferir as informações em formato JSON ou XML. Informações introdutórias sobre WCF podem ser encontradas no artigo Introducing Windows Communication Foundation de autoria de David Chappell.

Para tratar da transmissão das mensagens REST através do protocolo HTTP ou HTTPS existe um novo tipo de binding chamado de WebHttpBinding, que instrui o WCF a não utilizar envelopes SOAP para o processamento das mensagens. Em conjunto com o binding também deve ser utilizado um novo behavior, o WebHttpBehavior, que é o responsável por identificar os diferentes verbos HTTP das requisições e de fazer o redirecionamento da requisição para o método adequado do serviço. Também está disponível um behavior específico para processamento de chamadas realizadas através de ASP.NET Ajax, é o WebScriptEnablingBehavior, que herda de WebHttpBehavior. Abaixo vemos como o novo binding e o seu behavior podem ser utilizados em um arquivo de configurações:

<system.serviceModel>
    <services>
        <service name="RESTfulService. BibliotecaService">
            <host>
                <baseAddresses>
                    <add baseAddress="https://localhost/RESTfulService/BibliotecaService/" />
                </baseAddresses>
            </host>
            <endpoint address="" binding="webHttpBinding" behaviorConfiguration="WebHttpBehavior" contract="RESTfulService.IBiblioteca" />
        </service>
    </services>
    <behaviors>
        <endpointBehaviors>
            <behavior name="WebHttpBehavior">
                <webHttp/>
            </behavior>
        </endpointBehaviors>
    </behaviors>
</system.serviceModel>

Listagem 1 – Uso do WebHttpBinding e do WebHttpBehavior em um trecho de arquivo de configurações

Com o objetivo de tratar os verbos HTTP, foram adicionados dois novos atributos ao WCF, o WebGetAttribute e o WebInvokeAttribute. O primeiro, como o nome já indica, trata as requisições do verbo GET e o segundo trata as requisições de quaisquer outros verbos. Ambos possuem características em comum, como o formato da requisição e da resposta, que pode ser XML ou JSON, além disso o WebInvokeAttribute também deve receber a informação do verbo HTTP que irá tratar.

Uma outra informação que deve ser definida nos atributos é o template da URI, que define como os métodos serão identificados e como os seus parâmetros serão repassados para execução do serviço. Para atingir este objetivo, o conteúdo da URI é dividido em duas categorias lógicas, uma parte estática e outra parte dinâmica. A parte estática, em conjunto com o método HTTP, é utilizada para reconhecimento da requisição para posterior redirecionamento para o método adequado. Já a parte dinâmica é utilizada para reconhecer termos que devem ser repassados como parâmetros para os métodos, em linhas gerais, tudo que estiver dentro de "{" e "}" é considerado dinâmico e será associado ao parâmetro com o mesmo nome. Um exemplo da definição de um serviço REST pode ser vista na listagem 2.

[ServiceContract]
public interface IBiblioteca
{
    [OperationContract]
    [WebGet(UriTemplate="/Livros")]
    BusinessEntities.Livros ListarLivros();
    [OperationContract]
    [WebGet(UriTemplate = "/Livros/{id}")]
    BusinessEntities.Livro BuscarLivro(string id);
    [OperationContract]
    [WebInvoke(Method="POST", UriTemplate = "/Livros")]
    BusinessEntities.Livro IncluirLivro(BusinessEntities.Livro livro);
    [OperationContract]
    [WebInvoke(Method = "PUT", UriTemplate = "/Livros/{id}")]
    BusinessEntities.Livro AlterarLivro(string id, BusinessEntities.Livro livro);
    [OperationContract]
    [WebInvoke(Method = "DELETE", UriTemplate = "/Livros/{id}")]
    void ExcluirLivro(string id);
}

Listagem 2 – Definição de um serviço REST

Como pode-se notar, alguns métodos do contrato IBiblioteca retornam uma instância do tipo Livro serializada em XML. Para conseguirmos uma economia de banda e facilitar a interpretação do resultado pelos consumidores do serviço é recomendável que o XML seja o mais simples possível e uma maneira de atingir este objetivo é omitindo as informações do Namespace conforme a listagem 3.

[DataContract(Namespace="")]
public class Livro
{
    [DataMember]
    public int Id { get; set; }
    [DataMember]
    public string Titulo { get; set; }
}

Listagem 3 – Definição do tipo Livro

O método ListarLivros possui como retorno uma lista de livros. Isto pode ser facilmente alcançado criando uma classe que herde de List<Livros>. Uma customização recomendada para esta classe é a troca de sua tag XML através do atributo CollectionDataContract, impedindo o uso da tag padrão ArrayOfLivro, deixando o XML muito mais claro. Um exemplo de como a lista de livros pode ser feita está na listagem 4.

[CollectionDataContract(Name="Livros", Namespace="")]
public class Livros : List<Livro>
{
}

Listagem 4 – Definição da lista de livros

A implementação do serviço pode ser vista na listagem 5, detalhes da maioria dos métodos não são mostrados por não ser o escopo do artigo, entretando eles podem ser encontrados no download do código fonte.

public class BibliotecaService : IBiblioteca
{
    public BusinessEntities.Livros ListarLivros()
    {
        ...
    }
    public BusinessEntities.Livro BuscarLivro(string id)
    {
        ValidateId(id);
        var business = new BusinessComponents.Biblioteca();
        var livro = business.BuscarLivro(Convert.ToInt32(id));

        if (livro == null)
        {
            SetStatusAsNotFound();
        }

        return livro;
    }

    public BusinessEntities.Livro IncluirLivro(BusinessEntities.Livro livro)
    {
        var business = new BusinessComponents.Biblioteca();
        var novoLivro = business.IncluirLivro(livro);
        SetStatusAsCreated(novoLivro);
        return novoLivro;
    }

    public BusinessEntities.Livro AlterarLivro(string id, BusinessEntities.Livro livro)
    {
        ...
    }
    public void ExcluirLivro(string id)
    {
        ...
    }
}

Listagem 5 – Código do serviço

É interessante notar que o método BuscarLivro retorna o código de status Not Found, código HTTP 404 se nenhum livro for encontrado e o método IncluirLivro deve retornar o status Created, código HTTP 201 quando um livro for criado, juntamente com a URI para acessar este novo livro. A listagem 6 mostra o código responsável por isso.

private static void SetStatusAsNotFound()
{
    var ctx = WebOperationContext.Current.OutgoingResponse;
    ctx.SetStatusAsNotFound();
    ctx.SuppressEntityBody = true;
}

private void SetStatusAsCreated(BusinessEntities.Livro novoLivro)
{
    OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
    ctx.SetStatusAsCreated(CreateUri(novoLivro));
}

private Uri CreateUri(BusinessEntities.Livro livro)
{
    UriTemplate ut = new UriTemplate("/Livros/{id}");
  Uri baseUri = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri;
    Uri ret = ut.BindByPosition(baseUri, livro.Id.ToString());

    return ret;
}

Listagem 6 – Tratando status 404 e 201

Uma outra possibilidade de modificar o código de retorno da requisição HTTP é atribuindo um dos itens do enumeratorSystem.Net.HttpStatusCode no status da resposta da requisição. O exemplo da listagem 7 demonstra como retornar o status BadRequest, código 400 HTTP.

private void ValidateId(string id)
{
    int parseResult = 0;
  if (!int.TryParse(id, out parseResult))
  {
      var ctx = WebOperationContext.Current.OutgoingResponse;
    ctx.StatusCode = System.Net.HttpStatusCode.BadRequest;
    }
}

Listagem 7 – Retornando código de status bad request para uma requisição

Hospedando o serviço criado

Uma vez que o serviço WCF estiver pronto é necessário definir um modo de hospedagem, dentre as possibilidades podemos destacar a hospedagem em qualquer aplicativo .NET, desde uma aplicação console até mesmo um serviço do windows, e também o IIS.

Hospedando serviços em uma aplicação .NET

Para hospedar um serviço WCF em uma aplicação .NET, o que é conhecido como self-hosting, é necessário fazer uso da classe ServiceHost, ou de uma de suas classes derivadas, responsável por toda a estrutura dos endpoints e pela abertura e fechamento dos canais de comunicação com os clientes do serviço.

A criação dos endpoints pode ser feita via código, utilizando o método AddServiceEndpoint da classe ServiceHost, ou via arquivo de configuração. Um ponto importante que deve ser lembrado é que ao criar o endpoint via código o binding do tipo WebHttpBinding e o behavior do tipo WebHttpBehavior deve ser utilizado, a listagem 8 mostra um exemplo desta criação.

var sh = new ServiceHost(typeof(RESTfulService.BibliotecaService));
var se = sh.AddServiceEndpoint(typeof(RESTfulService.IBiblioteca), new WebHttpBinding(), "https://localhost:8082/RESTfulService/BibliotecaService/");
se.Behaviors.Add(new WebHttpBehavior());
sh.Open();

Listagem 8 – Hospedagem de serviço WCF com criação de endpoints via código.

Existe também a possibilidade de criar os endpoints em um arquivo de configuração, este exemplo pode ser observado na listagem 9, que utiliza o arquivo app.config demonstrado na listagem 1.

var sh = new ServiceHost(typeof(RESTfulService.BibliotecaService));
ServiceHost.Open();

Listagem 9 – Hospedagem de serviço WCF com criação de endpoints em arquivo de configuração.

            O .NET Framework 3.5 fornece como novidade para este modelo de hospedagem a classe WebServiceHost, derivada de ServiceHost, que cria automaticamente um endpoint com o binding do tipo WebHttpBinding e adiciona o WebHttpBehavior para este endpoint. Com isso, a codificação fica bastante reduzida e também não é necessário o uso de arquivo de configuração, o exemplo deste código pode ser visto na listagem 10.

var sh = new WebServiceHost(typeof(RESTfulService.BibliotecaService), new Uri("https://localhost:8082/RESTfulService/BibliotecaService/"));
sh.Open();

Listagem 10 – Uso de WebServiceHost para hospedar o serviço.

Hospedando serviços no IIS

Uma outra possibilidade de hospedagem de um serviço WCF é com a utilização do Internet Information Services (IIS), o que é conhecido como managed hosting. O uso de managed hosting fornece muitas vantagens, como, gerenciamento automático do ciclo de vida do serviço e application pooling e recycling. Além disso algumas das responsabilidades do modelo self-hosting são transferidas para o IIS, como a criação, configuração e abertura do ServiceHost.

Para integrar o WCF com o IIS é utilizado um HTTP Handler para estender o ASP.NET, o que  habilita o gerenciamento de arquivos com extensão .svc, considerados arquivos de serviço. Somente a título de curiosidade o HTTP Handler utilizado pelo WCF é o System.ServiceModel.Activation.HttpHandler.

Um arquivo .svc nada mais é que um arquivo texto que deve ser colocado em uma aplicação do IIS, este arquivo segue a convensão do ASP.NET de conter uma diretiva na primeira linha. Esta diretiva é a ServiceHost e seu único atributo obrigatório é o Service, que deve indicar uma classe que implementa um ServiceContract. Na listagem 11 temos um exemplo de um arquivo .svc simples.

<%@ ServiceHost Service="RESTfulService.BibliotecaService" %>

Listagem 11 – Hospedagem do serviço em arquivo .svc.

Para que este exemplo funcione é necessário que o web.config, arquivo de configuração para aplicações ASP.NET, possua algumas entradas de definição de serviços, conforme a listagem 12 abaixo.

<system.serviceModel>
    <behaviors>
        <endpointBehaviors>
            <behavior name="web">
                <webHttp/>
            </behavior>
        </endpointBehaviors>
    </behaviors>
    <services>
        <service name="RESTfulService.BibliotecaService">
            <endpoint address="" binding="webHttpBinding" contract="RESTfulService.IBiblioteca" behaviorConfiguration="web"/>
        </service>
    </services>
</system.serviceModel>

Listagem 12 – Arquivo web.config para hospedar serviço REST no IIS.

Para instanciar o serviço WCF, o ASP.NET utiliza um padrão de projeto do tipo fábrica responsável pela criação de objetos do tipo ServiceHost ou de tipos derivados. Por padrão se nenhuma fábrica específica for definida, o ASP.NET utiliza a classe System.ServiceModel.Activation.ServiceHostFactory como fábrica. De maneira análoga ao modelo de self-hosting também é possível utilizar os benefícios do WebServiceHost, para isso o .NET Framework 3.5 fornece nativamente a fábrica System.ServiceModel.Activation.WebServiceHostFactory. Um exemplo de uso da fábrica WebServiceHostFactory pode ser visto na listagem 13.

<%@ ServiceHost Service="RESTfulService.BibliotecaService"

Factory="System.ServiceModel.Activation.WebServiceHostFactory" %>

Listagem 13 –Uso de WebServiceHostFactory para hospedar o serviço.

Consumindo serviços REST

Até o momento foi demonstrado no artigo somente uma das metades da equação dos serviços REST, a metade da exposição dos serviços. Veremos agora a outra metade, a que define como consumir os serviços. O uso dos serviços por parte dos clientes pode ser feito através de várias tecnologias, como, JavaScript, ASP.NET Ajax, .NET, Silverlight, e o próprio WCF.

JavaScript

A idéia do uso do JavaScript em aplicações web é trazer a riqueza das aplicações desktop para o navegador de Internet e assim melhorar a interatividade do usuário. Uma das possibilidades que o JavaScript oferece é realizar chamadas HTTP assíncronas para um servidor web, um dos componentes do que é conhecido hoje em dia como Ajax. Apesar do termo Ajax ter surgido somente nos anos 2000, sua abordagem de desenvolvimento já existia desde o início da década de 1990 e foi evoluindo com as novas versões dos navegadores de Internet, que foram agregando novas funcionalidades e possibilidades para os desenvolvedores de aplicações.

As chamadas HTTP assíncronas via JavaScript são realizadas através de objetos do tipo XmlHttpRequest, à partir do Internet Explorer 7, ou de objetos ActiveX do tipo MSXML2.XMLHTTP, para as versões anteriores do Internet Explorer. Como o serviço REST suporta nativamente chamadas HTTP, é bastante simples o uso de JavaScript para acessá-lo.

Um exemplo que podemos ter é uma página acessando o serviço de biblioteca criado anteriormente, listando os livros e adicionando as informações recebidas em uma Div. A listagem 14 mostra como isso é feito à partir do clique de um botão.

<html>
<head>
    <title>Exemplo REST com JavaScript</title>
    <script type="text/javascript">
        function BuscarDadosButton_Click() {
            xmlHttp = new XMLHttpRequest();
            xmlHttp.onreadystatechange = function() {
                if (xmlHttp.readyState == 4) {
                    var doc = xmlHttp.responseXML;
                    var nodes = doc.selectNodes("//Livro");
                    var div = document.getElementById("LivrosDiv");
                    var titulo = null;

                    for (var i = 0; i < nodes.length; i++) {
                        titulo = nodes[i].selectSingleNode("Titulo").text;
                        div.innerHTML += "<br />" + titulo;
                    }

                    div.innerHTML += "<hr />";
                }
            }

            var serviceUri = "https://localhost/BibliotecaService/Livros";
            xmlHttp.open("GET", serviceUri, true);
            xmlHttp.setRequestHeader("Content-type", "application/xml");
            xmlHttp.send();
        }
    </script>
</head>
<body>
    <input type="button" value="Buscar Dados" id="BuscarDadosButton" onclick="BuscarDadosButton_Click()" />
    <br />
    <div id="LivrosDiv" />
</body>
</html>

Listagem 14 –JavaScript acessando serviço REST e manipulando XML

No exemplo anterior o serviço foi acessado utilizando XML, definido através do Content-type com valor application/xml. Uma alternativa ao XML seria o uso de JSON e para que o cliente consiga acessar o serviço utilizando JSON é necessário que o contrato do serviço esteja preparado para tratar o formato tanto na requisição quanto na resposta, o que pode ser visto na listagem 15.

[OperationContract]
[WebGet(UriTemplate = "/Livros?format=json", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
BusinessEntities.Livros ListarLivrosJSON();

[OperationContract]
[WebGet(UriTemplate = "/Livros/{id}?format=json", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
BusinessEntities.Livro BuscarLivroJSON(string id);

[OperationContract]
[WebInvoke(Method = "POST",UriTemplate = "/Livros?format=json", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
BusinessEntities.Livro IncluirLivroJSON(BusinessEntities.Livro livro);

[OperationContract]
[WebInvoke(Method = "PUT", UriTemplate = "/Livros/{id}?format=json", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
BusinessEntities.Livro AlterarLivroJSON(string id, BusinessEntities.Livro livro);

[OperationContract]
[WebInvoke(Method = "DELETE", UriTemplate = "/Livros/{id}?format=json", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
void ExcluirLivroJSON(string id);

Listagem 15 –Contrato do serviço suportando JSON.

A listagem 16 mostra o código JavaScript modificado para tratar os dados através de uma serialização JSON.

<html>
<head>
    <title>Exemplo REST com JavaScript e JSON</title>
    <script type="text/javascript">
        function BuscarDadosButton_Click() {
            xmlHttp = new XMLHttpRequest();

            xmlHttp.onreadystatechange = function() {
                if (xmlHttp.readyState == 4) {
                    var textoResultado = xmlHttp.responseText;
                    window.alert(textoResultado);
                    var resultado = eval(textoResultado);

                    var div = document.getElementById("LivrosDiv");
                    var titulo = null;

                    for (var i = 0; i < resultado.length; i++) {
                        titulo = resultado[i].Titulo;
                        div.innerHTML += "<br />" + titulo;
                    }

                    div.innerHTML += "<hr />";
                }
            }

            var serviceUri = "https://localhost/BibliotecaService/Livros?format=json";
            xmlHttp.open("GET", serviceUri, true);
            xmlHttp.setRequestHeader("Content-type", "application/json");
            xmlHttp.send();
        }
    </script>
</head>
<body>
    <input type="button" value="Buscar Dados" id="BuscarDadosButton" onclick="BuscarDadosButton_Click()" />
    <br />
    <div id="LivrosDiv" />
</body>
</html>

Listagem 16 –JavaScript acessando serviço REST e manipulando JSON

Apesar dos dois códigos JavaScript terem uma estrutura muito parecida, o código JSON pode ser considerado mais simples. Ao invés de manipular o retorno do serviço utilizando XML DOM (Document Object Model) é feito uso da função eval do JavaScript para transformar os dados retornados pelo serviço em objetos JavaScript. Como a prática tem demonstrado que a manipulação de objetos é muito mais fácil que a manipulação de XML, essa abordagem permite que o desenvolvedor seja muito mais produtivo no desenvolvimento do código.

ASP.NET Ajax

O ASP.NET fornece uma série de recursos para que os desenvolvedores construam aplicações do tipo Ajax de maneira mais fácil e rápida sem a necessidade de manipular diretamente JavaScript. Dentre os recursos disponíveis existe um que facilita o uso de serviços REST pelas aplicações.

O primeiro passo necessário é um serviço compatível com o WebScriptEnablingBehavior. Devido à uma restrição do ASP.NET Ajax o serviço precisa ser acessado somente pelos verbos GET e POST e o seu contrato não deve fazer uso de UriTemplate. Um exemplo da definição deste tipo de contrato pode ser visto de listagem 17.

[ServiceContract(Namespace="")]
public interface IBibliotecaWrapper
{
    [OperationContract]
    [WebGet()]
    BusinessEntities.Livros ListarLivros();
    [OperationContract]
    [WebGet()]
    BusinessEntities.Livro BuscarLivro(string id);
    [OperationContract]
    [WebInvoke(Method = "POST")]
    BusinessEntities.Livro IncluirLivro(BusinessEntities.Livro livro);
    [OperationContract]
    [WebInvoke(Method = "POST")]
    BusinessEntities.Livro AlterarLivro(string id, BusinessEntities.Livro livro);
    [OperationContract]
    [WebInvoke(Method = "POST")]
    void ExcluirLivro(string id);
}

Listagem 17 – Contrato de serviço suportado pelo WebScriptEnablingBehavior

Uma vez que o contrato foi definido em uma interface ele precisa ser implementado em uma classe, como demonstrado no código da listagem 18. Os detalhes de cada método foram suprimidos para facilitar o entendimento, mas notem que o código é bastante parecido com o código da listagem 5, a diferença está na interface implementada.

public class BibliotecaWrapper : IBibliotecaWrapper
{
    public BusinessEntities.Livros ListarLivros()
    {
        ...
    }

    public BusinessEntities.Livro BuscarLivro(string id)
    {
        ...
    }

    public BusinessEntities.Livro IncluirLivro(BusinessEntities.Livro livro)
    {
        ...
    }

    public BusinessEntities.Livro AlterarLivro(string id, BusinessEntities.Livro livro)
    {
        ...
    }

    public void ExcluirLivro(string id)
    {
        ...
    }
}

Listagem 18 –Implementação do serviço que será chamado pelo ASP.NET Ajax.

O penúltimo passo do uso de serviços com ASP.NET Ajax é a hospedagem e o jeito mais fácil de fazê-lo é através da WebScriptServiceHostFactory. Este tipo de fábrica não requer configurações dos endpoints do serviço, criando automaticamente um endpoint associado à um binding do tipo WebHttpBinding e à um behavior do tipo WebScriptEnablingBehavior. Um exemplo do seu uso pode ser observado na listagem 19.

<%@ ServiceHost Service="RESTfulService.BibliotecaWrapper" Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" %>

Listagem 19 – Uso do WebScriptServiceHostFactory

Tendo o serviço criado e hospedado, é possível acessá-lo via ASP.NET Ajax. Para isso, primeiro devemos incluir um ScriptManager e dentro dele uma tag de ServiceReference com a propriedade Path apontando para o serviço criado. Este trecho de código está demonstrado na listagem 20.

    <asp:ScriptManager runat="server" ID="scriptManager">
        <Services>
            <asp:ServiceReference InlineScript="false" Path="~/WebScriptServiceHostFactory.svc"/>
        </Services>
    </asp:ScriptManager>

Listagem 20 – Trecho de código ASP.NETAjax preparado para acessar serviço REST

A tag ServiceReference é responsável por criar um proxy para que o serviço possa ser utilizado em código JavaScript. Este acesso é feito de maneira assíncrona e deve ser obedecer o formato NomeDoServico.Metodo(FuncaoCallBack), conforme demonstrado no treco de código da listagem 21.

function BuscarDadosButton_Click() {
    IBibliotecaWrapper.ListarLivros(ListarLivrosDone);
}

function ListarLivrosDone(resultado) {
    var div = document.getElementById("LivrosDiv");
    var titulo = null;

    for (var i = 0; i < resultado.length; i++) {
        titulo = resultado[i].Titulo;
        div.innerHTML += "<br />" + titulo;
    }

    div.innerHTML += "<hr />";
}

Listagem 21 – Trecho de código acessando proxy JavaScript

.NET com chamadas HttpWebRequest

Os programas .NET podem utilizar as classes do namespaceSystem.Net para criar uma requisição HTTP diretamente no código e posteriormente tratar a resposta. O código da listagem 22 demonstra como é fácil criar requisições HTTP utilizando a classe HttpWebRequest.

var request = System.Net.HttpWebRequest.Create("https://localhost/IISHost/WebServiceHostFactory.svc/Livros");
var response = request.GetResponse();
var streamReader = new System.IO.StreamReader(response.GetResponseStream());

MessageBox.Show(streamReader.ReadToEnd());
streamReader.Close();
response.Close();

Listagem 22 – Criação de requisição HTTP com HttpWebRequest

A classe HttpWebRequest também permite a criação de requisição assíncronas e, assim como as outras tecnologias apresentadas, não congela a camada de apresentação. A listagem 23 mostra um exemplo de chamada assíncrona.

private void DotNetAsyncButton_Click(object sender, RoutedEventArgs e)
{
    var request = System.Net.HttpWebRequest.Create("https://localhost/IISHost/WebServiceHostFactory.svc/Livros");
    var response = request.BeginGetResponse(new AsyncCallback(GetResponseCallBack), request);
}

private void GetResponseCallBack(IAsyncResult asyncResult)
{
    var request = (System.Net.WebRequest)asyncResult.AsyncState;
    var response = request.EndGetResponse(asyncResult);
    var streamReader = new System.IO.StreamReader(response.GetResponseStream());

    MessageBox.Show(streamReader.ReadToEnd());

    streamReader.Close();
    response.Close();
}

Listagem 23 – Uso de HttpWebRequest para criação de requisição assíncrona

As demonstrações acima são bastante simples, mas é possível perceber a facilidade como o serviço REST pode ser acessado o que permite abstratir a idéia para o uso com outras tecnologias ou linguagens.

Silverlight

O Silverlight é um runtime cross-platform para desenvolvimento de aplicações web. Assim como as outras tecnologias que executam nos browsers, o acesso aos serviços pelo Silverlight é feito de maneira assíncrona, o que previne que a tela do browser fique congelada.

Para acessar serviços REST com Silverlight é necessário utilizar a classe WebClient, do namespaceSystem.Net, utilizando os métodos DownloadStringAsync e OpenReadAsync para leitura de dados e os métodos UploadStringAsync e OpenWriteAsync para gravar informações. O código da listagem 24 demonstra o uso do método OpenReadAsync.

var webClient = new WebClient();
webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(webClient_OpenReadCompleted);
webClient.OpenReadAsync(new Uri("https://localhost/IISHost/WebServiceHostFactory.svc/Livros"));

Listagem 24 – Acessando um serviço REST com Silverlight

Quando o retorno do serviço é recebido o evento correspondente é disparado para que o tratamento necessário possa ser realizado.  Um exemplo deste código pode ser visto na listagem 25.

void webClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
    var streamReader = new System.IO.StreamReader(e.Result);
    MessageBox.Show(streamReader.ReadToEnd());
    streamReader.Close();
}

Listagem 25 – Tratando o retorno do serviço REST em Silverlight

.NET com chamadas WCF

De maneira análoga à criação do serviço do lado do servidor também é possível definir um contrato WCF para acessá-lo no código do cliente. Para isso é necessário criar uma interface decorada com o atributo ServiceContract e ter as definições dos métodos decoradas com os atributos OperationContract e WebGet ou WebInvoke com o verbo HTTP correspondente e do UriTemplate adequado para o serviço. Se o serviço retornar algum DataContract também é necessário criar a sua definição na aplicação cliente. A listagem 26 mostra o exemplo de uma interface para acessar um dos serviços REST do Twitter.

[ServiceContract]
interface ITwitterStatus
{
    [OperationContract]
    [WebInvoke(UriTemplate = "/statuses/update.xml?status={text}")]
    void UpdateStatus(string text);
}

Listagem 26 – Definição dos contratos de um cliente REST com WCF

Para acessar o serviço, uma das opções é instanciar um objeto do tipo WebChannelFactory, que automaticamente configura o seu acesso com WebHttpBinding e o WebHttpBehavior, um exemplo do seu uso está na listagem 27.

using (WebChannelFactory<ITwitterStatus> cf = new WebChannelFactory<ITwitterStatus>("TwitterClient"))
{
    cf.Credentials.UserName.UserName = "seu_usuario";
    cf.Credentials.UserName.Password = "sua_senha ";
    ITwitterStatus s = cf.CreateChannel();
    s.UpdateStatus("seu_status");
}

Listagem 27 – Uso do WebChannelFactory

Algumas APIs do Twitter precisam que o usuário tenho sido autenticado, o que é feito através de BasicAuthentication. A listagem 28 traz o arquivo de configuração do exemplo.

<system.serviceModel>
    <bindings>
      <webHttpBinding>
        <binding name="TwitterConfig">
          <security mode="TransportCredentialOnly">
            <transport clientCredentialType="Basic" realm="Twitter API" />
          </security>
        </binding>
      </webHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://twitter.com" binding="webHttpBinding" bindingConfiguration="TwitterConfig" contract="WPFTwitterClient.ITwitterStatus" name="TwitterClient" />
    </client>
</system.serviceModel>

Listagem 28 – Arquivo de configuração do exemplo do Twitter

Quando falamos da criação de uma solução com WCF, sempre existe a possibilidade de sermos responsáveis pela criação do código do lado do servidor e também do lado do cliente. Se isto ocorrer, uma boa prática é que a definição dos contratos fique em um Assembly e tanto o serviço quanto o cliente compartilhem este contrato, cada um em seu Assembly correspondente. A figura 7 mostra como seria essa divisão em um diagrama de componentes UML.

Figura 7 – Compartilhamento de contrato entre servidor e cliente

Assim como as outras tecnologias apresentadas, o WCF também permite a realização de chamadas assíncronas, para isso é necessário que o contrato do cliente contenha a definição de duas operações extras para cada operação original do contrato. A primeira operação deve conter a estrutura Begin<NomeMétodo>, deve retornar um IAsyncResult e também precisa receber todos os parâmetros originais da operação síncrona além de um AsyncCallback, que indica o método que será chamado para indicar o final da requisição, e um object para sincronismo de estado entre a chamada assíncrona e o retorno assíncrono. A segunda operação deve ser nomeada como End<NomeMétodo>, deve ter o retorno original da operação síncrona e deve também receber como parâmetro um IAsyncResult. A listagem 29 mostra a definição de um contrato WCF para suportar operações assíncronas.

[ServiceContract]
public interface IBiblioteca
{
    [OperationContract]
    [WebGet(UriTemplate = "/Livros")]
    Livros ListarLivros();

    [OperationContract(AsyncPattern=true)]
    IAsyncResult BeginListarLivros(AsyncCallback requestCallback, object stateObject);

    Livros EndListarLivros(IAsyncResult asyncResult);
    ...
}

Listagem 29 – Definição de um contrato cliente para chamadas assíncronas

O processo de acessar os métodos assincronamente com WCF é feito de maneira similar às outras tecnologias apresentadas. Primeiro devemos chamar a operação Begin informando o método de callback e no método de callback deve ser  chamada a operação End. A listagem 30 demonstra esta chamada.

private void AsyncButton_Click(object sender, RoutedEventArgs e)
{
    var channel = new WebChannelFactory<IBiblioteca>(new Uri("https://localhost/IISHost/WebServiceHostFactory.svc"));
    var asyncService = channel.CreateChannel();
    asyncService.BeginListarLivros(new AsyncCallback(ProcessListarLivros), asyncService);
}

private void ProcessListarLivros(IAsyncResult asyncResult)
{
    var asyncService = (IBiblioteca)asyncResult.AsyncState;
    var livros = asyncService.EndListarLivros(asyncResult);

    foreach (var item in livros)
    {
        MessageBox.Show(item.Titulo);
    }
}

Listagem 30 – Chamada assíncrona com WCF

Qual opção de hospedagem escolher?

Foram apresentadas várias maneiras para criação de serviços REST com WCF e devido ao fato que algumas destas maneiras funcionarem de modo muito similar, é compreensível que exista uma dúvida na mente do leitor para saber qual a melhor prática na criação de serviços REST.

O primeiro ponto a se atentar é em relação à hospedagem do serviço, onde temos a opção de hospedá-lo através de um Self-Hosting ou através de um Managed-Hosting. A opção de Self-Hosting é mais recomendada em cenários onde não existe uma previsão de controle sobre o ambiente que o serviço irá ser executado, um exemplo deste cenário pode ser uma empresa criadora de produtos que não tem controle sobre o ambiente onde seus produtos serão executados e precisa estar preparada para os mais variados cenários de uso. Já a hospedagem do tipo Managed-Hosting é recomendada quando existe um controle sobre o ambiente que o serviço irá executar ou quando existe a necessidade de alta escalabilidade e disponibilidade.

O segundo ponto de atenção é quanto ao uso da classe ServiceHost, onde é possível utilizar ela mesma ou a classe WebServiceHost que é uma classe derivada. A escolha de qual classe utilizar para hospedar o serviço vai depender se ele terá somente endpoints do tipo REST ou não. Se existir a necessidade de utilizar endpoints mistos, ou seja, endpoint REST e não REST, com certeza a opção será pelo uso da classe ServiceHost, já se o serviço somente irá expor endpoints REST a melhor escolha será a classe WebServiceHost.

A terceira preocupação é quanto ao behavior, onde podemos utilizar o WebHttpBehavior ou o WebScriptEnablingBehavor. O WebScriptEnablingBehavor deve ser utilizado quando o cliente do serviço será uma aplicação ASP.NET Ajax, já o WebHttpBehavior é a melhor indicação para outros cenários de REST.

O quarto e último ponto é sobre o uso da classe ServiceHostFactory. O seu uso se dá de maneira similar ao uso da classe ServiceHost sendo indicado para cenários quando há a necessidade de expor outros endpoints além do REST. Já a sua classe derivada WebServiceHostFactory é indicada quando todos os endpoints serão do tipo REST e a WebScriptServiceHostFactory pode ser considerada a melhor opção quando o serviço for consumido por aplicações ASP.NET Ajax.

Novidades do .NET Framework 3.5 SP1

O service pack 1 (SP1) para o .NET Framework 3.5 trouxe uma séria de novidades, dentre as quais podemos destacar algumas que afetam diretamente o desenvolvimento de serviços REST: melhora no suporte de protocolos de syndication, mudanças no tratamento das URIs e na serialização de objetos.

Quanto aos protocolos de syndication, o SP1 incorpora suporte à especificação do protocolo AtomPub (Atom Publishing Protocol) através de novos tipos e formatadores no namespaceSystem.ServiceModel.Syndication. Apesar do formato Atom ser bastante útil para blogs ou portais de notícias, ele está se tornando bastante popular na criação de endpoints REST, permitindo criar, buscar, atualizar ou excluir recursos.

Quanto ao tratamento das URIs o SP1 incorpora a possibilidade de criar segmentos compostos de caracteres especiais como “().;” para a URI e também permite o uso de valores padrão para os parâmetros das operações, o que é bastante útil e poderia estar dificultando o uso do WCF 3.5 em alguns cenários. A listagem 31 demonstra alguns exemplos de URIs que não funcionavam com o WCF 3.5 e agora funcionam na versão 3.5 SP1.

[OperationContract]
[WebGet(UriTemplate = "/Static/{value1};{value2}")]
void Test1(string value1, string value2);

[OperationContract]
[WebGet(UriTemplate = "/Static/{value1}.{value2}")]
void Test2(string value1, string value2);

[OperationContract]
[WebGet(UriTemplate = "/Static/{id=10}")]
void Test3(string id);

Listagem 31 – Novas possibilidades do UriTemplate

Uma outra funcionalidade que SP1 do .NET Framework 3.5 traz é a possibilidade de uso do DataContractSerializer, que é uma classe responsável pela serialização de objetos, com objetos do tipo POCO (Plain Old CLR Object). Os objetos POCO nada mais são que instâncias de classes sem atributos de serialização, como DataContractAttribute e SerializableAttribute. Apesar desta novidade não ser específica para serviços WCF, ela é bastante útil na criação de serviços que utiliizem tipos CLR pré-existentes.

Conclusão

A tendência é que a web continue mudando e fique cada vez mais voltada aos serviços. Tanto REST quanto SOAP são ótimas opções para integração entre sistemas através da Internet, sendo que o REST é mais recomendado para aplicações da Web 2.0, onde a agilidade é essencial, e o SOAP para aplicações empresariais, onde é necessário maior formalismo. O WCF 3.5 está preparado para os dois mundos, sendo de grande utilidade para o desenvolvimento de serviços interoperáveis.

Referências + Tópicos Relacionados

- RESTful .NET (http://oreilly.com/catalog/9780596519209/?CMP=AFC-ak_book&ATT=RESTful%20.NET)

- A Guide to Designing and Building RESTful Web Services with WCF 3.5 ( https://msdn.microsoft.com/en-us/library/dd203052.aspx )

- REST no MSDN ( https://msdn.microsoft.com/en-us/netframework/cc950529.aspx )

- Architectural Styles and the Design of Network-based Software Architectures ( http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm )

- XML-RPC Specification ( http://www.xmlrpc.com/spec )

- RESTful Web Services ( http://oreilly.com/catalog/9780596529260 )

- Architecture of the World Wide Web, Volume One ( http://www.w3.org/TR/2004/REC-webarch-20041215/ )

- Introducing Windows Communication Foundation ( http://www.davidchappell.com/writing/white_papers/Introducing_WCF_in_.NET_Framework_3.5_v1.0.docx )

- Twitter API Wiki ( http://apiwiki.twitter.com/ )

Sobre o Autor

Rafael Godinho é especialista em desenvolvimento da Microsoft Brasil e trabalha na área de TI há mais de 10 anos. Nesse tempo participou na elaboração de projetos críticos com tecnologia Microsoft para grandes clientes do setor financeiro e manufatura. Gosta muito de tecnologia, mas ultimamente tem gasto seu tempo livre andando de bicicleta.