Desenvolvimento - ASP. NET

Efetuando chamadas entre domínios

A finalidade do artigo é mostrar como devemos proceder para permitir o consumo em cada uma das tecnologias: jQuery e Silverlight.

por Israel Aéce



Muitas vezes construímos serviços para que sejam consumidos por aplicações que estão hospedadas no mesmo local. Um exemplo é quando criamos esses serviços para expor alguma funcionalidade, e que sejam consumidosatravés dojQuery ou do Silverlight. Ambas tecnologias possuem bibliotecas que facilitam a comunicação com este tipo de serviço.

Mas isso tudo funciona muito bem enquanto os serviços que são acessados estão dentro do mesmo domínio. Se desejar consumir serviços que estão além do domínio de onde a aplicação está hospedada, teremos que recorrer a técnicas diferentes, dependendo da tecnologia que estamos utilizando. A finalidade do artigo é mostrar como devemos proceder para permitir o consumo em cada uma delas.

jQuery

Como sabemos, o jQuery é uma biblioteca que facilita várias tarefas em JavaScript, abstraindo grande parte da complexidade que teríamos se fossemos utilizar uma funcionalidade diretamente. Uma dessas abstrações é o consumo de serviços, como já detalhei neste outro artigo.

O consumo de serviços funciona bem desde que ele esteja debaixo do mesmo domínio da aplicação Web, que é aquela que consome o serviço. Se ele estiver além, então precisamos de alguma técnica para possibilitar que essa tarefa seja realizada. Uma opção seria a criação de um serviço nesta aplicação cliente, que serviria como um wrapper, e este por sua vez, consumiria diretamente o serviço, sem as imposições de chamada entre domínios que o navegador impõe. Com isso, o código JavaScript irá consumir este serviço local, que por sua vez, encaminharia a requisição para o serviço remoto.

Apesar desta técnica funcionar, ela acaba sendo uma solução ruim, já que teremos que envolver outros elementos para realizar uma tarefa relativamente simples. Felizmente o jQuery fornece nativamente o suporte a uma técnica conhecida como JSONP (JSON with Padding).

O seu funcionamento não é muito complicado. Como disse acima, requisições entre domínios não são permitidas, mas há uma única exceção: a tag <script>, ou seja, podemos definir no elemento src (source) a URL para um recurso que está além do nosso domínio, que o navegador não irá proibir o acesso. O que o JSONP faz é justamente o uso dela, criando dinamicamente esta tag, e definindo no atributo src a URL do serviço que estamos tentando acessar.

Na verdade isso não funciona sozinho, ou seja, precisa de uma certa colaboração por parte do serviço, para que o cliente possa processar o resultado da forma correta. Além dos parâmetros que são exigidos pelo método do serviço, o jQuery inclui um parâmetro chamado callback, que é o nome de uma função criada temporariamente no cliente. Aqui entra em cena a infraestrutura do serviço, que deve ser capaz de retornar o resultado (dados) envolvido nesta função, e quando o mesmo chegar ao cliente, ele invocará para capturar o resultado e encaminhá-lo para o nosso código, e assim iremos manipular da forma que acharmos mais conveniente.

Para fazer tudo isso funcionar, precisamos nos atentar à alguns detalhes do lado do cliente e do lado do serviço. Do lado do cliente, tudo o que precisamos fazer é indicar para a API de AJAX do jQuery que ela deve utilizar JSONP. Para isso, devemos recorrer ao atributo dataType, que indica o tipo de dado/formato que você está esperando que o servidor te retorne. No nosso caso, vamos apontar jsonp, assim como é mostrado no código abaixo:

<script language="javascript" type="text/javascript">
function Recuperar() {
$.ajax(
{
type: "GET",
url: "http://localhost:1446/ServicoDeUsuarios.svc/Recuperar",
data: "nome=Israel",
dataType: "jsonp",
contentType: "application/json",
success:
function (usuario) {
alert(usuario.Nome);
}
}
);
}
</script>

Como já sabemos, o serviço também precisa colaborar com isso. Para que ele consiga gerar o resultado da forma que esperamos, precisamos efetuar uma configuração no binding. Para expor um serviço para clientes AJAX, o WCF fornece um binding chamado WebHttpBinding e, consequentemente, é ele mesmo que expõe uma propriedade chamada CrossDomainScriptAccessEnabled, que recebe um valor boleano indicando se o serviço poderá ou não ser invocado através de outros domínios. Quando definido como True (o padrão é False), ele retornará o resultado da forma que o JSONP espera, e com isso, a chamada será efetuada com sucesso. O código abaixo ilustra a configuração de um serviço que poderá ser invocado através de outros domínios:

<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true"
targetFramework="4.0" />
</system.web>
<system.serviceModel>
<services>
<service name="Servico.ServicoDeUsuarios">
<endpoint address=""
binding="webHttpBinding"
bindingConfiguration="bindingConfig"
contract="Servico.IContratoDeUsuarios" />
</service>
</services>
<bindings>
<webHttpBinding>
<binding name="bindingConfig"
crossDomainScriptAccessEnabled="true" />
</webHttpBinding>
</bindings>
</system.serviceModel>
</configuration>

Se monitoramos as requisições através de uma ferramenta como o Fiddler, podemos reparar o que acontece nos bastidores. Abaixo temos a requisição e a sua respectiva resposta, com alguns headers omitidos para tornar a leitura mais simples. Note que o fato de definirmos o atributo dataType como jsonp fez com que um parâmetro chamado callback fosse atribuído à coleção de querystrings da requisição. Em seguida, no corpo da resposta temos os dados gerados do lado do servidor envolvidos na função que foi gerada do lado do cliente.

[ Requisição ]
GET http://127.0.0.1:1446/ServicoDeUsuarios.svc/Recuperar?callback=jsonp1284996021792&nome=Israel HTTP/1.1
Accept: */*
Host: 127.0.0.1:1446
Connection: Keep-Alive

[ Resposta ]
HTTP/1.1 200 OK
Date: Mon, 20 Sep 2010 15:20:24 GMT
Content-Length: 51
Content-Type: application/x-javascript
Connection: Close

jsonp1284996021792({"Codigo":123,"Nome":"Israel"});

Silverlight

Serviços que são consumidos por clientes Silverlight também sofrem o mesmo problema quando precisam consumir serviços que estão além de seu domínio de origem. Como sabemos, uma aplicação Silverlight não funciona sozinha, ou seja, ela é sempre hospedada em uma aplicação Web normal, e consumir serviços que estão debaixo dessa aplicação, não haverá qualquer problema e nenhuma tarefa extra precisa ser realizada, já que esse tipo de comunicação é permitida.

Mas ao tentar consumir um serviço que está fora do domínio de origem da aplicação, então receberemos uma exceção do tipo CommunicationException, indicando que a chamada entre domínios não é permitida.

Para resolver este problema, temos que colocar um arquivo Xml no mesmo local onde o serviço encontra-se hospedado (no servidor remoto), indicando que este serviço poderá ser consumido pelo cliente A, B ou C, ou se desejar, por qualquer aplicação. O Silverlight pode trabalhar com dois tipos de arquivos: ClientAccessPolicy.xml ou CrossDomain.xml. O primeiro arquivo foi desenvolvido pela própria Microsoft, enquanto o segundo já é utilizado por aplicações Flash, e para reutilizar, a Microsoft também incorporou no Silverlight a capacidade de utilizar este mesmo arquivo, sem a necessidade de criar um segundo só para atender as requisições a partir de clientes Silverlight.

Quando a aplicação Silverlight for efetuar a requisição para um serviço, ela primeiramente tenta fazer o download do arquivo ClientAccessPolicy.xml no mesmo domínio do serviço; caso não encontre, então ela tentará efetuar o download do arquivo CrossDomain.xml, e se mesmo assim não encontrá-lo, então a exceção mencionada acima será disparada. Ao encontrar um destes dois arquivos, o Silverlight analisará se no seu conteúdo, existe uma entrada dizendo que o serviço pode ser consumido pela aplicação cliente em questão, e se puder, efetuará a chamada para o serviço.

Abaixo existe a estrutura do arquivo ClientAccessPolicy.xml, já configurado para permitir que somente uma determinada aplicação possa consumí-lo. E você pode elencar ali quantas aplicações quiser, e se desejar, pode colocar apenas uma única entrada, definindo o atributo uri como *, que determina que toda e qualquer aplicação poderá consumir os serviços que rodam naquele local.

<?xml version="1.0" encoding="utf-8"?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from http-request-headers="*">
<!--
Habilitar para todos os clientes
<domain uri="*"/>
-->
<domain uri="http://localhost:2611/" />
</allow-from>
<grant-to>
<resource path="/"
include-subpaths="true"/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy>

Com tudo isso configurado, podemos ver na imagem abaixo a requisição procurando pelo arquivo acima, e depois de encontrado e analisado, procede para a chamada efetiva para o respectivo serviço:

Conclusão: Vimos no decorrer deste artigo as possibilidades que temos para permitir que um serviço WCF possa ser consumido através de domínios diferentes, algo que é cada vez mais comum em um mundo cada dia mais conectado.

ChamadasEntreDominios.zip (213.04 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.