Visualizador para JSON

by Israel Aece 19. August 2010 11:23

O JSON é um formato que temos para serializar informações, e que possibilita aplicações AJAX consumirem serviços de forma muito mais simples, dispensando todo o "overhead" do XML ou SOAP. É um formato relativamente simples, assim como podemos ver abaixo, onde temos um cliente e seus respectivos pedidos:

{"Codigo":123,"Email":"ia@israelaece.com","Nome":"Israel","Pedidos":[{"Codigo":1,"Data":"\/Date(1282228004105-0300)\/","Valor":1000},{"Codigo":2,"Data":"\/Date(1282228004144-0300)\/","Valor":129}]

Para facilitar a vida dos desenvolvedores, principalmente quando lidamos com a construção de serviços, que exigem a exposição de informações mais complexas do que essa mostrada acima, pode ser útil termos um visualizador para mostrar graficamente a estrutura do documento JSON. Para isso, podemos recorrer ao JSONViewer, uma ferramenta que não exige nenhuma instalação, e tudo o que você precisa fazer é rodá-la. Abaixo podemos ver a imagem do documento JSON acima, totalmente formatado e de forma gráfica.

Depois de baixar, com uma configuração simples, que você encontra no site do projeto, verá que podemos incorporá-lo ao Fiddler, para já monitorar serviços que retornam os resultados em formato JSON.

Tags: , ,

General

Flexibilizando a requisição e resposta à serviços REST

by Israel Aece 3. August 2010 12:46

Ao criar um serviço WCF para ser acessado através do modelo REST, devemos decorar as operações que o compõem o contrato com os atributos WebGetAttribute ou WebInvokeAttribute, dependendo de como as requisições devem chegar até elas.

As requisições para estas operações podem ser enviadas e recebidas através de dois formatos: Xml ou Json, e para configurar isso, podemos recorrer à duas propriedades fornecidas pelos dois atributos listados acima. Essas propriedades são: RequestFormat e ResponseFormat, onde ambas recebem uma das opções expostas pelo enumerador WebMessageFormat.

Isso quer dizer que devemos definir, de forma estática, o formato que a mensagem será aceita e como ela será devolvida para os clientes. O grande problema desta técnica é que as vezes você pode ter um mesmo serviço sendo consumindo por clientes diferentes, que lidam melhor com um formato específico. Por exemplo, você pode ter um mesmo serviço sendo consumido por uma aplicação Silverlight, que possui um suporte melhor ao Xml, e ao mesmo tempo, o mesmo serviço sendo consumido por um código jQuery, que lida melhor com o formato Json. Para tornar o serviço flexível, tínhamos que fazer isso de forma imperativa, ou seja, dentro da implementação da operação, tínhamos que validar qual o formato desejado pelo cliente, e com isso especificar no contexto da requisição qual o formato que deve ser utilizado pelo runtime do WCF para gerar o resultado. O código abaixo ilustra de forma resumida essa condição:

public class Servico : IContrato
{
    public string Ping(string value)
    {
        WebOperationContext.Current.OutgoingResponse.Format =
            VerificarFormato(...) == "json" ? WebMessageFormat.Json : WebMessageFormat.Xml;

        return "resultado da operação";
    }
}

Geralmente o formato é fornecido por uma query string adicional ou analisando os headers da requisição HTTP, que informa o formato através do content-type. O código acima permite definirmos o formato da resposta baseando-se na preferência do usuário, mas como podemos perceber, há muito código para avaliar isso, e me obriga a misturar a minha implementação com código de infraestrutura.

Para facilitar, a Microsoft criou na versão 4.0 do WCF, uma nova propriedade na classe WebHttpBehavior, chamada AutomaticFormatSelectionEnabled. Trata-se de uma propriedade boleana, que quando definida como True, irá interpretar a requisição e gerar a resposta no mesmo formato estipulado pelo cliente, olhando para a propriedade content-type que está nos presente nos headers da requisição. Para habilitar, podemos recorrer ao seguinte código:

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="Service">
        <endpoint
          address=""
          binding="webHttpBinding"
          contract="IService"
          endpointConfiguration="edpConfig" />
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name="edpConfig">
          <webHttp automaticFormatSelectionEnabled="true" />
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Com esta opção habilitada, meu contrato fica totalmente independente do formato, ou seja, se o cliente está requisitando em formato Json, o WCF será capaz de acatar a requisição, processá-la e devolver o resultado no mesmo formato. O mesmo acontece com o Xml. Finalmente, você pode ter um único serviço sendo capaz de receber e retornar mensagens no mesmo formato do cliente, não os obrigando mais ele a trabalhar com o formato específico, que as vezes diferente daquele que é o mais comum.

Tags: , , , , ,

WCF

Consumindo serviços REST com HttpClient

by Israel Aece 15. June 2010 22:20

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 referenciar os 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 o parâmetro genérico T 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 item in 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)

Tags: , ,

WCF

REST e o WSDL

by Israel Aece 8. June 2010 10:57

Há algum tempo, eu escrevi um artigo sobre como expor serviços construídos em WCF através do modelo REST, e além disso, foi comentado também sobre as principais diferenças no modelo REST com SOAP, apontando as características de cada uma deles. Um dos detalhes que ficou para trás foi o documento WSDL.

Como sabemos, o WSDL (Web Service Description Language), é um documento baseado em XML, que descreve todas as características (definições) de um determinado serviço, exposto através do modelo SOAP. É justamente esse documento que é utilizado por ferramentas como o svcutil.exe e a opção "Add Service Reference" do Visual Studio, para gerar uma classe que representará o proxy, que por sua vez, será utilizado pelos clientes para consumir o serviço como se fosse uma classe local, mas durante a execução, a mensagem será enviada ao ponto remoto.

Em serviços baseados no modelo REST, não temos essa opção, ou seja, pois o documento/padrão WSDL não suporta endpoints baseados em formato REST. Uma das dificuldades que isso causa é a não possibilidade de gerar um proxy do lado do cliente para facilitar o consumo deste serviço. Além disso, também iremos nos deparar com a dificuldade em visualizar quais são as operações, parâmetros e eventuais resultados que o serviço expõe para seus clientes.

A finalidade do artigo é abordar como resolveremos o segundo problema, ou seja, a exposição das informações pertinentes à documentação, ou melhor, à descrição do serviço. A primeira opção para expor isso, é utilizando o behavior WebHttpBehavior, que expõe uma propriedade chamada HelpEnabled, que quando definida como True, exibe uma página em formato HTML, com todas as operações que o serviço possui, e com exemplos de como invocar cada uma delas. Abaixo temos o código (declarativo) necessário para habilitar este recurso, e em seguida, temos a imagem que corresponde a página gerada automaticamente:

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="Service">
        <endpoint
          address=""
          binding="webHttpBinding"
          contract="IService"
          endpointConfiguration="edpConfig" />
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name="edpConfig">
          <webHttp helpEnabled="true"/>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

A outra possibilidade que temos é utilizando um novo recurso, que é disponibilizado a partir do WCF-REST Starter Kit, que é conhecido como Automatic Help Page. Tudo o que precisamos fazer aqui é a mudar a factory responsável por criar os hosts que gerenciam a execução do serviço. Depois de instalar o  WCF-REST Starter Kit, precisamos referenciar o assembly Microsoft.ServiceModel.Web.dll, que fornecerá uma classe chamada WebServiceHost2Factory, e devemos utilizá-la no arquivo *.svc, como é mostrado abaixo:

<%@ ServiceHost
    Language="C#"
    Debug="true"
    Service="Service"
    Factory="Microsoft.ServiceModel.Web.WebServiceHost2Factory"
    CodeBehind="~/App_Code/Service.cs" %>

Com essa funcionalidade habilitada, podemos acessar a página que irá descrever o serviço, acrescentando à URL do serviço a string "/help", como podemos notar na imagem abaixo. Note que passamos a ter a descrição do mensagem de requisição e resposta, e além disso, podemos acrescentar mensagens customizadas nas nossas operações, utilizando o atributo WebHelpAttribute, que automaticamente aparecerá nesta mesma página, fornecendo ainda mais informações para facilitar a vida de quem consumirá a respectiva operação.

Conclusão: Apesar dos recursos que vimos neste artigo, isso ainda não é capaz de facilitar a vida de quem o consome, já que ainda será necessário a criação das requisições e o tratamento das respostas de forma bastante manual. Há ainda alguns novos recursos, também fornecidos pelo WCF-REST Starter Kit, quais você pode combinar com essa documentação, e tornar o consumo mais simples.

Tags: , ,

WCF

Autenticação com WCF e jQuery

by Israel Aece 28. May 2010 21:48

Recentemente escrevi sobre o consumo de serviços WCF a partir do jQuery. Naquele artigo foi comentado como construir serviços WCF para ser acessados através do ambiente REST, e além disso, vimos também como proceder para o consumo deste serviço utilizando o jQuery como cliente.

Mas um detalhe que não foi abordado no artigo foi a questão da segurança, ou melhor, do processo de autenticação do cliente que consome o serviço. Por exemplo, imagine que temos um serviço que exige que o usuário se identifique, para que assim possamos determinar se ele terá ou não acesso ao serviço. Apesar do WCF fornecer várias alternativas para isso, quando estamos em um ambiente REST, alguns cuidados especiais são necessários, quais serão abordados no decorrer deste artigo.

O primeiro detalhe para nos atentarmos é como vamos configurar o serviço para que o mesmo possa exigir as credenciais. Para expor um serviço para ser consumido através do ambiente REST, utilizamos um binding exclusivo chamado de WebHttpBinding. Em sua configuração padrão, não há nenhum tipo de autenticação configurada, mas como qualquer binding, podemos recorrer à opção security para isso.

Temos apenas três opções relacionadas à segurança para este binding: None, Transport e TransportCredentialOnly. A primeira opção desabilita qualquer tipo de proteção e autenticação no respectivo serviço. Já a segunda opção, Transport, determina que será o protocolo que deverá garantir a segurança da mensagem (HTTPS) e, finalmente, o TransportCredentialOnly, que não fornecerá integridade e confidencialidade na mensagem que está sendo trafegada, e utilizará apenas o protocolo HTTP exclusivamente para autenticação do usuário.

Depois desta configuração que determina como a comunicação deverá ser protegida, temos uma espécie de "sub-configuração" dela que precisamos nos atentar. Esta sub-configuração determina como as credenciais serão passadas para o serviço. Entre as várias formas, temos o HTTP Basic, que faz com que as credenciais viagem do cliente para o serviço sem qualquer espécie de criptografia. Esse modelo, em sua configuração padrão, tem uma forte afinidade com o Windows/Active Directory, ou seja, para utilizar esse recurso, as credenciais informadas do lado do cliente deverão refletir uma conta válida no Windows/Active Directory, algo que não vamos nos preocupar neste momento. A configuração do serviço WCF deverá ficar da seguinte forma:

<system.serviceModel>
  <services>
    <service
      name="RESTComBasicAuthentication.Services.ServicoDeUsuarios"
      behaviorConfiguration="config">
      <endpoint
        address=""
        binding="webHttpBinding"
        contract="RESTComBasicAuthentication.Services.IUsuarios"
        behaviorConfiguration="edpConfig"
        bindingConfiguration="bc" />
    </service>
  </services>
  <bindings>
    <webHttpBinding>
      <binding name="bc">
        <security mode="TransportCredentialOnly">
          <transport clientCredentialType="Basic" />
        </security>
      </binding>
    </webHttpBinding>
  </bindings>
  <!-- Outras Configurações -->
</system.serviceModel>

Quando utilizamos o modelo Basic, ao tentar acessar um recurso protegido, o próprio browser é capaz de identificar e abrir uma janela com os campos para informarmos o username e password, só que não podemos nos esquecer de que esse serviço será consumido por algum cliente, e que o foco aqui é o uso do jQuery, e com isso, de alguma forma precisamos enviar o username e password do cliente para o serviço. Como já sabemos, o jQuery oferece uma função chamada $.ajax, que nos permite configurar e efetuar a chamada para um determinado serviço ou algum outro método. Internamente, este método recorre a um objeto popularmente conhecido, que é o XMLHttpRequest. Este objeto é o responsável por gerenciar toda a comunicação entre o cliente e o serviço.

De acordo com a especificação deste objeto, há um método chamado Open, que entre os parâmetros convencionais, como endereço até o serviço, o tipo da requisição (Json ou Xml), há também como informar mais duas informações relacionadas ao processo de autenticação: username e password. Através dos parâmetros username e password, definimos duas strings que representam cada uma dessas informações. Como o jQuery abstrai o acesso a este objeto, a função $.ajax também nos permite informar o usuário e senha, como já mencionado acima. Sabendo da existência destes parâmetros, podemos configurar a chamada para o serviço através do jQuery da seguinte forma:

function RecuperarUsuario() {
    $.ajax(
    {
        type: "POST",
        username: "UsuarioQueExisteNoWindows",
        password: "123456",
        url: "http://localhost/Services/ServicoDeUsuarios.svc/RecuperarUsuario",
        contentType: "application/json",
        data: '{ "nome": "Israel Aece", "email": "ia@israelaece.com" }',
        processData: true,
        success:
            function (resultado) {
                alert(resultado.RecuperarUsuarioResult.Nome);
                alert(resultado.RecuperarUsuarioResult.Email);
            },
    });
}

Se você monitorar a requisição para este serviço com alguma ferramenta, como é o caso do Fiddler, você verá que o username e password não serão enviados até que realmente seja necessário. O modelo de autenticação Basic é baseado no processo conhecido como "Desafio HTTP 401", que funciona da seguinte forma:

  • O cliente solicita um recurso (página, serviço, etc.) que está protegido.
  • Ao detectar que o cliente não está autenticado, o servidor exige que ele se autentique e informe as credenciais a partir do modelo Basic. Isso é informado a partir de um header chamado WWW-Authenticate: Basic.
  • Neste momento, o servidor retorna uma resposta com o código 401 (Access Denied), que instrui o cliente (browser) a solicitar as credenciais de acesso.
  • Uma vez informado, o browser recria a mesma requisição, mas agora envia nos headers da mesma o usernamepassword codificados em Base64, sem qualquer espécie de criptografia. Na resposta, o header enviado é o Authorization: Basic [Username+Password Codificado].
  • Quando este header acima estiver presente, o servidor (IIS) é capaz de validá-lo no Windows/Active Directory, e se for um usuário válido, permitirá o acesso, caso contrário retornará a mesma resposta com código 401, até que ele digite uma credencial válida.

Apesar das credenciais estarem em hard-code no exemplo acima, nada impede de criarmos uma tela onde o cliente pode digitar um usuário e senha, mas lembrando que este usuário deverá exisitir no Windows/Active Directory.

Customização

O principal ponto negativo da solução acima é a necessidade de termos os usuários cadastrados no Windows, algo que pode ser inviável em um ambiente que não é controlado, como é o caso da internet. Algo muito comum em aplicações deste tipo, é a validação do usuário em uma base de dados, ou até mesmo, utilizando a estrutura do Membership do ASP.NET. Para atingirmos esse objetivo, precisamos entender um pouco mais sobre o processo de autenticação e também alguns pontos de estensibilidade.

Quando configuramos o modelo Basic, o IIS é o responsável por coordenar todo o processo de acesso ao recurso, e se o usuário não estiver autenticado, o próprio IIS devolve a mensagem com o código 401 e o header específico para o mesmo (1), para que o browser proceda com a solicitação das credenciais (2), e depois de informadas, refaz a requisição embutindo o header com as credencias codificadas (3). A imagem abaixo ilustra esse processo:



No exemplo que vimos acima, o único recurso protegido pelo modelo Basic é apenas o arquivo ServicoDeUsuarios.svc. Agora, se desejamos customizar, temos que trazer todo o controle do processo de autenticação para a aplicação, ou seja, não iremos mais querer que o IIS coordene o processo, que como sabemos, ele recorre ao Windows para validar os usuários. Sendo assim, precisamos permitir o acesso anônimo ao arquivo *.svc.

Há algum tempo eu comentei sobre a possibilidade de efetuar a customização da autenticação do WCF através de usuário e senha, utilizando a classe UserNamePasswordValidator para essa validação. O problema é que o binding WebHttpBinding não suporta a customização deste validador, o que nos obrigaria à descer o nível até o pipeline do ASP.NET para implementar algo específico para o WCF.

Ao invés disso, podemos recorrer aos interceptadores (Interceptors). Os interceptadores não estão nativamente dentro do WCF, mas estão disponíveis ao instalar o WCF-REST Starter Kit. Este kit nada mais é que um conjunto de funcionalidades que podemos incorporar aos nossos projetos WCF baseados em REST, tornando algumas tarefas comuns em algo mais simples de se resolver/implementar, como vai ser o caso aqui.

Ao instalar este kit, teremos à nossa disposição alguns novos assemblies, e entre eles um chamado Microsoft.ServiceModel.Web.dll. Uma das classes fornecidas por ele é a classe abstrata RequestInterceptor. Essa classe fornece um método abstrato chamado ProcessRequest, que permite interceptar a requisição, antes mesmo dela começar a ser executada, fornecendo um parâmetro do tipo RequestContext, que representa o contexto da requisição atual, qual será utilizado para a interação com o cliente que está tentando acessar o recurso.

Com esta possibilidade, podemos criar um interceptador que extrai o header da requisição HTTP que representa a credencial informada pelo usuário (WWW-Authenticate), e a valida em algum repositório de sua escolha, como uma base de dados. A partir daqui é necessário conhecermos como funciona o processo do modelo HTTP Basic, para conseguirmos dialogar com o cliente, para que assim ele consiga coordenar o processo de autenticação do usuário.

Como havia dito acima, o username e password não são enviados até que sejam efetivamente exigidos. Nesta customização, ao identificarmos que o header não está presente na requisição, precisamos configurar a resposta para o cliente com o código 401, que representa acesso não autorizado, e informar na resposta o mesmo header, para continuar obrigando o usuário a informar o username e password.

Para saber se o cliente informou as credenciais, precisamos detectar a presença do header chamado Authorization. Se existir, então precisamos decodificá-lo, utilizando o método FromBase64String da classe Convert, que dado uma string, retorna um array de bytes representando as credenciais separadas por um ":". Depois disso, tudo o que precisamos fazer é separá-los, para que assim podermos efetuar a validação em algum repositório. O código abaixo ilustra esse processo:

string credentials = mp.Headers["Authorization"];

if (!string.IsNullOrWhiteSpace(credentials))
{
    string decodedCredentials =
        Encoding.Default.GetString(Convert.FromBase64String(credentials.Substring(6)));

    int separator = decodedCredentials.IndexOf(':');
    username = decodedCredentials.Substring(0, separator);
    password = decodedCredentials.Substring(separator + 1);
}

Caso o header não exista ou o usuário não existir na base de dados, então podemos criar o header para que o cliente seja - novamente - obrigado a informar as credenciais. Para isso, utilizamos as propriedades da mensagem, e configuramos a mensagem de retorno. A mensagem é representada no WCF pela classe Message. O código abaixo ilustra a criação, configuração e resposta ao cliente com esta mensagem recém criada:

private void GenerateAccessDeniedMessage(ref RequestContext requestContext)
{
    Message reply = Message.CreateMessage(MessageVersion.None, null, null
        new DataContractJsonSerializer(typeof(object)));

    HttpResponseMessageProperty hrp = new HttpResponseMessageProperty();
    hrp.StatusCode = HttpStatusCode.Unauthorized;
    hrp.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", this.Realm));
    reply.Properties[HttpResponseMessageProperty.Name] = hrp;

    requestContext.Reply(reply);
    requestContext = null;
}

Se o usuário for válido, então é necessário inicializar o contexto de segurança do WCF, que é representado pela classe ServiceSecurityContext, que utiliza uma instância da classe GenericPrincipal para identificar o usuário logado.

Finalmente, tudo isso não funciona por si só. Ainda precisamos acoplar a instância desta classe à execução. Para isso, devemos recorrer a customização da classe que cria o host, que é responsável por gerenciar a execução da classe que representa o serviço. A classe ServiceHostFactory fornece um método chamado CreateServiceHost, que como o próprio nome diz, permite retornar qualquer classe que herde direta ou indiretamente da classe ServiceHost. Dentro do assembly do WCF-Rest Starter Kit, temos uma versão específica de host, chamada de WebServiceHost2. A grande diferença deste host, é que ele expõe uma propriedade chamada Interceptors, que permite adicionarmos qualquer interceptador, desde que ele herde da classe RequestInterceptor, como já vimos acima. Depois que a factory customizada foi criada, devemos alterar o arquivo *.svc para que ela seja utilizada:

<%@ ServiceHost
    Language="C#"
    Debug="true"
    Factory="RESTComBasicAuthentication.CustomRestHostFactory"
    Service="RESTComBasicAuthentication.Services.ServicoDeUsuarios"
    CodeBehind="ServicoDeUsuarios.svc.cs" %>

Uma vez que acoplamos este interceptador, ele é quem gerenciará o processo de autenticação, ou seja, o cliente irá dialogar com este interceptador, mas para que isso funcione corretamente, é necessário que a autenticação Basic esteja desabilitada no IIS. Através da imagem abaixo, podemos reparar este novo comportamento:

Conclusão: Apesar de não temos nativamente no WCF uma forma de customizar a autenticação em serviços REST, podemos recorrer ao WCF-REST Starter Kit para conseguir efetuar essa customização, utilizando a infraestrutura oferecida pelo modelo HTTP Basic para enviar as credenciais. Apesar de tudo funcionar como deveria, é importante dizer que os dados continuam sendo trafegados sem qualquer proteção, e para evitar que alguém visualize o conteúdo, é importante que toda a comunicação seja protegida pelo HTTPS.

RESTComBasicAuthentication.zip (328.00 kb)

Tags: , , ,

CSD | WCF

Tratando erros com jQuery e WCF

by Israel Aece 18. May 2010 22:03

Neste artigo eu mostrei como construir serviços WCF para serem consumidos através do jQuery. Nele falamos sobre os cuidados que devemos ter para criar e expor serviços WCF, mas não chegamos a falar sobre alguns detalhes, não menos importante, como é o caso do tratamento de erros, que é algo tão comum em qualquer tipo de aplicação.

Ao executar alguma operação, uma exceção pode ser disparada por algum motivo. Como já falamos, ao utilizar a biblioteca do jQuery para invocar algum serviço, podemos através de um callback, especificarmos um código para ser disparado quando algum problema ocorrer do lado do serviço. Para configurar isso, podemos utilizar o parâmetro error da função $.ajax. O código abaixo ilustra como podemos configurar o parâmetro error, exibindo uma mensagem informando ao usuário que algum problema ocorreu:

function RecuperarUsuario() {
    $.ajax(
    {
        type: "POST",
        url: "http://localhost/Services/ServicoDeUsuarios.svc/RecuperarUsuario",
        contentType: "application/json",
        data: '{ "nome": "", "email": "ia@israelaece.com" }', //Nome vazio causa o erro
        processData: false,
        success:
            function (resultado) {
                alert(resultado.RecuperarUsuarioResult.Nome);
                alert(resultado.RecuperarUsuarioResult.Email);
            },
        error:
            function (xhr, textStatus, errorThrown) {
                alert('Algum Problema Ocorreu!');
            }
        });
    }

Esse tipo de código funciona normalmente, até que você precise capturar a mensagem exata do erro que ocorreu. Quando executamos uma operação que está disparando alguma exceção, o WCF acaba retornando a seguinte mensagem: "Request server encountered an error processing the request. See server logs for more details". O problema é que o erro não será retornado em um formato que é facilmente entendido/preferido pelo jQuery, que é o JSON.

Ao configurar o contrato para expor via AJAX, podemos definir através do atributo WebInvokeAttribute, que a requisição (RequestFormat) e a resposta (ResponseFormat) sejam formatadas em JSON, mas isso não inclui eventuais exceções que sejam disparadas pelas respectivas operações, mesmo que esse problema esteja definido como um contrato de fault (FaultContractAttribute).

Para tentar resolver isso, podemos recorrer a alguns pontos de estensibilidade do WCF, para conseguirmos interceptar o erro que ocorreu. Ao interceptar, deveremos transformar a mensagem em algum objeto que deverá ser entendido pelo jQuery, entregando para o cliente todas as informações necessárias para que o mesmo consiga tratar o erro ocorrido.

Para descrever o problema, vamos criar uma classe que possui características que detalham o erro ocorrido. Essa classe será chamada de GenericErrorDescription, que possuirá apenas uma única propriedade (para manter a simplicidade), chamada de Message, do tipo string. Como a finalidade será transformar a exceção em formato JSON para facilitar o consumo pelo jQuery, criaremos uma classe chamada de JsonErrorHandler, e nela implementaremos a interface IErrorHandler, que é uma espécie de interceptador (mais detalhes neste artigo). O principal método fornecido por esta interface, é chamado de ProvideFault, que passa como parâmetro a instância da exceção que ocorreu e uma instância da classe Message, que corresponde a mensagem que será devolvida para o cliente.

Na implementação deste método, utilizaremos o método estático CreateMessage da classe Message, que criará a mensagem de retorno, utilizando o serializador JSON para que a mesma será formatada neste padrão, e além disso, esse serializador deverá serializar a instância da classe que representa o erro, que no nosso exemplo é a GenericErrorDescription. Na sequência configuramos a instância da classe HttpResponseMessageProperty, para customizarmos a mensagem, informando que o tipo a ser retornado será application/json, para que o jQuery consiga, facilmente, entender e interpretar esse conteúdo. Note também que mantemos o StatusCode do HTTP como 500, ou seja, um erro interno, e como sua descrição, definimos a mesma mensagem gerada pela exceção disparada pela operação.

A classe JsonErrorHandler ainda implementa a interface IEndpointBehavior, que nos permite acoplar a instância desta classe em um endpoint específico, do lado do serviço (dispatcher), na coleção de tratadores de erros. O código abaixo ilustra a classe JsonErrorHandler já devidamente implementada:

public class JsonErrorHandler : IEndpointBehavior, IErrorHandler
{
    public bool HandleError(Exception error)
    {
        return true;
    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {
        GenericErrorDescription desc = 
            new GenericErrorDescription() { Message = error.Message };

        fault = Message.CreateMessage(version, "", desc, 
            new DataContractJsonSerializer(typeof(GenericErrorDescription)));
        fault.Properties.Add(WebBodyFormatMessageProperty.Name, 
            new WebBodyFormatMessageProperty(WebContentFormat.Json));

        var msgp = new HttpResponseMessageProperty();
        msgp.Headers[HttpResponseHeader.ContentType] = "application/json";
        msgp.StatusCode = HttpStatusCode.InternalServerError;
        msgp.StatusDescription = desc.Message;

        fault.Properties.Add(HttpResponseMessageProperty.Name, msgp);
    }

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bp) { }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime cr) { }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher ed)
    {
        ed.ChannelDispatcher.ErrorHandlers.Add(this);
    }

    public void Validate(ServiceEndpoint endpoint) { }
}

Só que essa classe por si só não funciona. Precisamos efetivamente acoplar ao pipeline do WCF, e para isso, podemos criar um extension element, que nos dará a oportunidade de efetuar essa configuração de modo declarativo, ou seja, através do arquivo Web.config. Basicamente, essa classe será responsável por criar instâncias da classe que criamos acima, ou seja, do tratador de erros:

public class JsonErrorElement : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get
        {
            return typeof(JsonErrorHandler);
        }
    }

    public override object CreateBehavior()
    {
        return new JsonErrorHandler();
    }
}

Depois das classes devidamente criadas, tudo o que precisamos fazer agora é configurarmos o arquivo Web.config do serviço, acoplando o extension element ao endpoint de acesso ao serviço. A configuração final do Web.config deve ficar da seguinte forma:

<system.serviceModel>
  <services>
    <service name="ServicoDeUsuarios" behaviorConfiguration="config">
      <endpoint
        binding="webHttpBinding"
        contract="IUsuarios"
        behaviorConfiguration="edpConfig" />
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior name="config">
        <serviceDebug includeExceptionDetailInFaults="false" />
      </behavior>
    </serviceBehaviors>
    <endpointBehaviors>
      <behavior name="edpConfig">
        <jsonErrorHandler />
      </behavior>
    </endpointBehaviors>
  </behaviors>
  <extensions>
    <behaviorExtensions>
      <add
        name="jsonErrorHandler"
        type="JsonErrorElement, WCFExtensions, Version=1.0.0.0, ..." />
    </behaviorExtensions>
  </extensions>
</system.serviceModel>

Já do lado do cliente, as mudanças são bastante ligeiras. Tudo o que precisamos fazer é uma pequena mudança no código que é disparado quando o erro ocorre. Uma vez que o interceptador de erros está acoplado do WCF, a mensagem com o erro de retorno será formatada em JSON, e se analisarmos o retorno, veremos o seguinte resultado:

{"Message": "Informe o nome do usuario\u000d\u000aParameter name: nome"}

Como a mensagem de retorno está em formato JSON, utilizaremos o método parse da classe JSON, exposta pelo JSON2. Esse método é responsável por transformar o conteúdo JSON em um objeto, respeitando as mesmas propriedades serializadas pela classe GenericErrorDescription. Com isso, o nosso código de consumo ao serviço será alterado para:

function RecuperarUsuario() {
    $.ajax(
    {
        type: "POST",
        url: "http://localhost/Services/ServicoDeUsuarios.svc/RecuperarUsuario",
        contentType: "application/json",
        data: '{ "nome": "", "email": "ia@israelaece.com" }', //Nome vazio causa o erro
        processData: false,
        success:
            function (resultado) {
                alert(resultado.RecuperarUsuarioResult.Nome);
                alert(resultado.RecuperarUsuarioResult.Email);
            },
        error:
            function (xhr, textStatus, errorThrown) {
                var erro = JSON.parse(xhr.responseText);
                alert(erro.Message);
            }
        });
    }

Se quiser levar mais detalhes do erro, você ainda tem algumas outras alternativas, mas não tão interessantes como essa. Você poderia optar por definir o atributo includeExceptionDetailInFaults do elemento serviceDebug para True, mas isso iria expor mais informações do que realmente deveria, como por exemplo a Stack Trace, algo que não deveria ultrapassar o serviço. Além dela, ainda poderia envolver toda a operação em um bloco try/catch, e caso algum problema ocorra, utilizamos a classe WebOperationContext para customizar o StatusCode e o ContentType através dela, mas isso torna a classe que representa o serviço dependente do protocolo HTTP, o que não é uma boa opção.

Conclusão: Graças a grande flexibilidade fornecida pelo WCF, podemos contornar alguma de suas "limitações" de uma forma bastante elegante, sem precisar misturar em minhas operações, códigos que estão relacionados puramente à infraestrutura.

Tags: , , , ,

WCF

Utilizando jQuery para invocar Actions

by Israel Aece 5. May 2010 16:51

Com jQuery podemos acessar serviços WCF para tornar a experiência do usuário muito melhor, evitando a atualização completa da página. Neste artigo vimos como proceder para preparar um serviço WCF para ser exposto e ser consumido via AJAX, e além disso, exploramos a API do jQuery para efetuar a comunicação com esse serviço.

Só que muitas vezes, para ter a riqueza necessário do lado do cliente, tudo o que precisamos fazer é consumir métodos que estão no código C#/VB.NET, dentro da própria aplicação. Se a aplicação está baseada no ASP.NET MVC, talvez tenhamos a necessidade de invocar uma ação (action) de um determinado controller. Felizmente o jQuery não está limitado à executar apenas métodos expostos por serviços WCF, mas também podemos acessar os métodos criados nos controllers, utilizando a mesma API. A finalidade deste artigo é mostrar como proceder para atingir esse objetivo.

Como todos sabemos, todas as actions de um controller sempre devem retornar a instância de uma das classes derivadas de ActionResult, e como o próprio nome diz, é responsável por receber, armazenar e devolver o resultado da respectiva ação. Uma das classes derivadas dela é a JsonResult, que representa o resultado em formato JSON, que é o formato mais conveniente para o jQuery trabalhar.

Sendo assim, para aquelas actions que desejamos consumir através do jQuery, precisamos retornar uma instância da classe JsonResult, abastecendo a propriedade Data com algum objeto que desejamos visualizar do lado do cliente. Essa propriedade recebe um System.Object, o que nos permite definirmos qualquer objeto, incluindo tipos anônimos. A partir da versão 2.0 do ASP.NET MVC, a Microsoft adicionou nesta classe uma nova propriedade chamada de JsonRequestBehavior, que recebe uma das duas opções fornecidas pelo enumerador com o mesmo nome. Basicamente, a ideia desta propriedade é controlar se o acesso via GET é permitido ou não. Por questões de segurança, essa propriedade é definida com a opção DenyGet, o que nega a possibilidade de conseguir invocar através de GET. Você pode trocar para a opção AllowGet, mas é importante que você conheça os eventuais problemas que podem acontecer se isso estiver habilitado. Com esse conhecimento da classe JsonResult, podemos ter a seguinte action:

public class UsuariosController : Controller
{
    public ActionResult RecuperarUsuario()
    {
        return new JsonResult()
        {
            Data = new Usuario() { Codigo = 123, Nome = "Israel" }
        };
    }
}

Depois do controller e da action criados, vamos recorrer ao jQuery para consumir este método. O jQuery fornece alguns atalhos, que evita a configuração total da função $.ajax. Um desses atalhos é a função $.getJSON, que dado uma URL e um callback, podemos referenciar o método criado anteriormente, e com a função de callback processar o resultado. O problema é que a função $.getJSON efetua a solicitação via GET, e como vimos acima, não estamos permitindo isso (JsonRequestBehavior). Sendo assim, vamos recorrer a função de baixo nível $.ajax, configurando apenas o que é necessário para executar o método:

function RecuperarUsuario() {
    $.ajax(
    {
        type: "POST",
        url: "/Usuarios/RecuperarUsuario",
        contentType: "application/json",
        success:
            function (resultado) {
                alert(resultado.Codigo);
                alert(resultado.Nome);
            },
    }

Como percebemos no código acima, ele está invocando o método RecuperarUsuario no controller Usuarios, através de POST, e quando o resultado é devolvido, exibimos a mensagem na tela. Note que há uma barra (/) antes do nome do controller. Isso é necessário porque a view corrente, que possui o código jQuery, não faz parte deste controller, o que nos obriga a mencionar a partir no nível raiz. Se quisermos acessar uma action que está no controller da view, então tudo o que é necessário é informar o nome da mesma, sem mencionar o controller.

Já quando a action exigir parâmetros, é necessário informá-los durante a chamada. Se modificarmos a action para ela passar a receber dois parâmetros, sendo o código e o nome do usuário, precisamos informar isso na chamada para o método, ficando da seguinte forma:

function RecuperarUsuario() {
    $.ajax(
    {
        type: "POST",
        url: "/Usuarios/RecuperarUsuario",
        data: { "codigo": 123, "nome": "Israel Aece" },
        processData: true,
        success:
            function (resultado) {
                alert(resultado.Codigo);
                alert(resultado.Nome);
            },
    }

Como a opção processData está definida como True, ele converterá o JSON que informamos no parâmetro data em uma espécie de coleção de chave/valor, e como estamos efetuando a requisição através de POST, as parâmetros serão colocados no corpo da mensagem, separando cada par de chave com o caracter &. Note que na chamada acima, não definimos a propriedade contentType para application/json, já que o conteúdo a ser enviado trata-se de parâmetros em suas formas tradicionais.

O problema maior é quando a action recebe um parâmetro complexo, como por exemplo, a instância da classe Usuario. Neste caso, temos que recorrer à biblioteca JSON2, que fornecerá recursos para serializar a instância de um objeto em formato JSON, para que assim possa trafegar até a action correspondente. Com isso, o código para a chamada à ela será da seguinte forma:

function Adicionar() {
    var usuario = { "Codigo": 123, "Nome": "Israel" };

    $.ajax(
    {
        type: "POST",
        url: "/User/Adicionar",
        contentType: "application/json",
        data: JSON.stringify(usuario),
        processData: false,
        success:
            function (resultado) {
                alert(resultado);
            },
    });
}

Do lado do servidor, teremos um método chamado Adicionar, que recebe a instância da classe Usuario. O código abaixo ilustra como devemos configurar a action com este parâmetro, e note que a propriedade Data retorna uma mensagem, informando que o usuário foi cadastrado com sucesso.

public class UsuariosController : Controller
{
    public ActionResult Adicionar(Usuario usuario)
    {
        return new JsonResult()
        {
            Data = "Usuario cadastrado com sucesso."
        };
    }
}

Só que do lado da action também será necessário efetuar um código adicional. Como estamos mandando o conteúdo em formato JSON, o ASP.NET MVC não conseguirá extrair o objeto diretamente dele, o que nos obriga a criar um binder exclusivo para isso, onde podemos utilizar algum serializador JSON que faça esse trabalho.

Para atingir esse objetivo, podemos utilizar um ponto de estensibilidade do ASP.NET MVC, que nos permite criar um atributo herdando da classe CustomModelBinderAttribute, onde podemos definir como vamos efetuar a "tradução" do(s) parâmetro(s) que estão no corpo da requisição, em um objeto conhecido pela aplicação, e que no nosso exemplo será a classe Usuario. Esta classe possui um método chamado GetBinder, que retorna a instância de uma classe que implementa a interface IModelBinder, qual utilizaremos o serializador DataContractJsonSerializer, que é o mesmo serializador utilizado pelo WCF. O código abaixo ilustra como ele deve ficar:

public class JsonBinderAttribute : CustomModelBinderAttribute
{
    public override IModelBinder GetBinder()
    {
        return new JsonModelBinder();
    }

    public class JsonModelBinder : IModelBinder
    {
        public object BindModel(ControllerContext cc, ModelBindingContext bc)
        {
            return new DataContractJsonSerializer(bc.ModelType)
                .ReadObject(cc.HttpContext.Request.InputStream);
        }
    }
}

Agora, tudo o que precisamos fazer é decorar o parâmetro da action com o atributo que criamos acima. Isso dirá ao runtime do ASP.NET MVC que deverá utilizar este binder para preencher o conteúdo do parâmetro usuario. Abaixo temos a mesma action Adicionar, mas com a alteração necessária para que funcione devidamente, utilizando o binder recém criado:

public ActionResult Adicionar([JsonBinder]Usuario usuario)
{
    //...
}

Conclusão: Este artigo mostrou mais uma das possibilidades que temos ao utilizar o jQuery, onde conseguimos acessar métodos escritos em C#/VB.NET, sem a necessidade de uma atualização geral da página. Como já sabemos, a experiência do usuário aumenta, e nós como desenvolvedores, podemos fazer uso de uma API consistente, independente se estamos consumindo métodos locais ou remotos.

Tags: , , ,

ASP.NET

multipleSiteBindingsEnabled

by Israel Aece 4. May 2010 22:35

Há algum tempo eu comentei aqui sobre uma dificuldade que existe quando hospedamos serviços WCF no IIS. Neste mesmo post, eu mostrei a solução que foi incorporada na versão 3.5 do WCF.

Para facilitar isso, a partir da versão 4.0 do WCF temos uma opção chamada multipleSiteBindingsEnabled, disponível através do elemento serviceHostingEnvironment, que quando definido como True, permite que o serviço fique disponível a partir dos múltiplos endereços de acesso configurados no IIS. Abaixo temos o arquivo de configuração com esta opção habilitada:

<system.serviceModel>
    <serviceHostingEnvironment multipleSiteBindingsEnabled=”true”/>
</system.serviceModel>

Tags: , ,

WCF

Powered by BlogEngine.NET 1.5.0.0
Theme by Mads Kristensen

Sobre

Meu nome é Israel Aece e sou especialista em tecnologias de desenvolvimento Microsoft, atuando como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. [ Mais ]

Twitter

Host