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

Influencia do maxRequestLength no WCF

by Israel Aece 9. July 2010 10:17

Como eu comentei neste outro artigo, podemos utilizar no WCF um recurso chamado streaming, que é uma alternativa interessante quando precisamos enviar grandes quantidades de informações, que nestes cenários, o mais comum é o envio ou o recebimento de arquivos. Para que tudo isso funcione, precisamos nos atentar a efetuar algumas configurações no WCF com relação as cotas e timeouts, que impõem limites durante o tráfego das informações.

Em princípio, esses são os únicos cuidados que devemos ter. Poderemos começar a ter outros problemas, se esse serviço for hospedado no IIS. Para que fosse possível hospedar um serviço WCF no IIS, a Microsoft utilizou a estrutura do pipeline do ASP.NET para receber e desviar a requisição para serviços WCF para o seu respectivo handler. Antes de qualquer análise das cotas que configuramos para o serviço, há um detalhe importante que devemos nos preocupar. Trata-se da propriedade MaxRequestLength da classe HttpRuntime, que determina a quantidade máxima (em KBytes) de conteúdo que uma requisição do ASP.NET poderá receber, onde o seu valor padrão é 4 MB. Essa propriedade influencia quando queremos efetuar o upload de um arquivo grande através de uma aplicação ASP.NET.

Como a requisição chega para o IIS, que por sua vez delega o processamento para a estrutura do ASP.NET, o mesmo valida o tamanho deste conteúdo, e se for maior do que o valor estipulado nesta propriedade, você receberá uma exceção do tipo HttpException, com a seguinte mensagem: Maximum request length exceeded. É algo até complicado de se diagnosticar, já que essa exceção não é propagada para o cliente que consome o serviço, e o tracing do WCF neste caso, ajudará a desvendar este problema. De qualquer forma, quando for hospedar um serviço WCF no IIS, é importante que você sincronize as cotas com o atributo maxRequestLength exposto pelo elemento httpRuntime, assim como podemos notar no exemplo abaixo, que está configurado para receber um arquivo de até 100 MB:

<?xml version="1.0"?>
<configuration>
  <system.web>
    <httpRuntime maxRequestLength="102400"/>
  </system.web>
  <system.serviceModel>
    <services>
      <service name="Service">
        <endpoint address=""
                  binding="basicHttpBinding"
                  contract="IService"
                  bindingConfiguration="config" />
      </service>
    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="config"
                 messageEncoding="Mtom"
                 transferMode="Streamed"
                 maxBufferSize="104857600"
                 maxBufferPoolSize="104857600"
                 maxReceivedMessageSize="104857600">
        </binding>
      </basicHttpBinding>
    </bindings>
</configuration>

Tags: , ,

WCF

MaxItemsInObjectGraph

by Israel Aece 11. February 2010 06:55

Há algum tempo, eu comentei aqui sobre os limites e cotas que o WCF possui, que se não se atentar em ajustar de acordo com a sua necessidade, exceções começam a ser disparadas se você excede os valores que lá estão.

Além daquelas configurações, ainda há uma outra configuração/cota que interfere na quantidade de informações que trafegam entre as partes, que é a MaxItemsInObjectGraph. Essa propriedade recebe um número inteiro (que por padrão é 65.536 (64KB)), que especifica a quantidade máxima de objetos que podem ser serializados ou deserializados pelo DataContractSerializer. Se tentar enviar e/ou receber uma quantidade de objetos maior que o valor especificado por essa cota, você pode se deparar com a seguinte exceção do tipo SerializationException, como é mostrado abaixo:

Unhandled Exception: System.ServiceModel.CommunicationException: There was an error while trying to serialize parameter http://tempuri.org/:clientes. The InnerException message was 'Maximum number of items that can be serialized or deserialized in an object graph is '200'. Change the object graph or increase the MaxItemsInObjectGraph quota. '.  Please see InnerException for more details. ---> Systm.Runtime.Serialization.SerializationException: Maximum number of items that can be serialized or deserialized in an object graph is '200'. Change the object graph or increase the MaxItemsInObjectGraph quota.

A configuração desta opção não está em nível de binding, ao contrário do que acontece com as outras cotas, pois ela está relacionada ao serializador que o serviço utiliza para uma operação em particular. É importante dizer que essa configuração deve estar sincronizada entre o cliente e serviço, caso contrário, o erro persistirá. Há diversas formas para configurarmos ela, e a primeira delas é através da propriedade MaxItemsInObjectGraph exposta pelo atributo ServiceBehaviorAttribute:

[ServiceBehavior(MaxItemsInObjectGraph = 1000)]
public class Servico : IContrato
{
    public Cliente[] Ping(Cliente[] clientes)
    {
        //...
    }
}

A outra forma de configuração é acessando diretamente as descrições do serviço, de forma imperativa, onde poderá customizar essa informação para uma operação específica. O código abaixo mostra como proceder para alterá-la do lado do serviço e do lado do cliente, respectivamente:

serviceHost
    .Description
    .Endpoints[0]
    .Contract
    .Operations[0]
    .Behaviors
    .Find<DataContractSerializerOperationBehavior>().MaxItemsInObjectGraph = 1000;

proxy
    .Endpoint
    .Contract
    .Operations[0]
    .Behaviors
    .Find<DataContractSerializerOperationBehavior>().MaxItemsInObjectGraph = 1000;

Além disso, ainda podemos optar por configurá-la através do respectivo arquivo de configuração da aplicação (cliente ou serviço). Para isso, utilizamos o elemento dataContractSerializer, que é um behavior que será aplicado no endpoint do serviço, como podemos reparar abaixo:

<configuration>
    <system.serviceModel>
        <behaviors>
            <endpointBehaviors>
                <behavior name="config">
                    <dataContractSerializer maxItemsInObjectGraph="1000" />
                </behavior>
            </endpointBehaviors>
        </behaviors>
        <client>
            <endpoint address=""
                      behaviorConfiguration="config"
                      binding="basicHttpBinding"
                      contract="IContrato" />
        </client>
    </system.serviceModel>
</configuration>

Tags:

WCF

Usando LINQ To SQL com WCF

by Israel Aece 2. February 2010 21:16

Para aqueles que trabalham com LINQ To SQL e querem expor as entidades geradas pela ferramenta via WCF, terá que tomar alguns cuidados. Quando essas entidades não mantém um relacionamento, algo que é bem díficil, você consegue retorná-las a partir das operações que o serviço irá disponibilizar.

O problema começa a aparecer quando você deseja enviar para os clientes, entidades que possuem relacionamentos com outras entidades. Um exemplo clássico e que ilustra bem o cenário, é quando temos categorias e produtos, onde cada produto deverá pertencer à somente uma única categoria. Neste caso, temos uma relação direta entre elas. Mas o ponto que torna isso difícil é que o LINQ To SQL cria uma relação bidirecional entra elas. Isso quer dizer que a entidade Categoria terá uma propriedade chamada Produtos, que como o próprio nome diz, retorna a coleção dos produtos daquela categoria; além disso, a classe Produto expõe uma propriedade chamada Categoria, que retorna a instância de uma categoria em qual o produto está contido.

Quando falamos em orientação à objetos, isso é perfeitamente válido e comum. O problema ocorre quando você tenta serializar essa estrutura a partir do WCF. Como eu comentei neste outro post, o WCF tem um comportamento diferente quando há referências circulares, e não conseguirá fazer a serialização porque ele ficará em uma espécie de loop, pois com há referência bidirecional, ao serializar uma categoria, ela serializa os respectivos produtos, e para cada produto a sua respectiva categoria, e para esta categoria os seus produtos, e por aí vai. O serviço roda sem maiores problemas, mas você terá uma exceção quando quando o cliente tentar acessá-lo.

Analisando a imagem abaixo, podemos visualizar a estrutura das classes que compõem o exemplo, e logo ao lado, você pode reparar nas propriedades do arquivo (superfície) DBML, verá que existe uma propriedade chamada Serialization, que pode receber apenas dois valores: None e Unidirectional. O primeiro deles permite que as propriedades das entidades sejam serializadas de acordo com as regras impostas pelo serializador padrão do WCF, que graças a possibilidade de serializar qualquer propriedade, mesmo que elas não estejam decoradas com os atributos DataContractAttribute e DataMemberAttribute (POCO). A segunda opção, Unidirectional, faz com que ele somente consiga serializar o relacionamento em uma única direção para evitar as referências circulares. No nosso caso, teremos a entidade Categoria com a propriedade Produtos, mas a classe Produto não terá uma propriedade que define a sua categoria.

Alterando a opção de serialização para Unidirectional, a forma que você efetua a consulta também terá que mudar. Isso fará com que o LINQ To SQL não consiga trazer os dados (produtos) relacionados aquela categoria, algo que é transparente quando estamos utilizando o LINQ To SQL diretamente. Para conseguir fazer com que os dados relacionados também sejam carregados, temos que recorrer a classe DataLoadOptions, como é mostrado abaixo:

public Categoria[] RecuperarCategorias()
{
    using (DBContextDataContext ctx = new DBContextDataContext())
    {
        DataLoadOptions opts = new DataLoadOptions();
        opts.LoadWith<Categoria>(c => c.Produtos);
        ctx.LoadOptions = opts;

        return (fromin ctx.Categorias select c).ToArray();
    }
}

Mas como disse acima, isso funcionará mas você perderá a navegação bidirecional. Felizmente, podemos recorrer à propriedade boleana IsReference, que é exposta pelo atributo DataContractAttribute, definindo isso na classe Categoria. Isso permitirá a criação da navegação bidirecional, mas há um trabalho manual a ser feito para que isso funcione. Quando você muda a propriedade Serialization para None, nenhuma das propriedades é decorada com o atributo DataContractAttribute/DataMemberAttribute; já se definir essa propriedade para Unidirectional, as propriedades que são problemáticas, não estarão decoradas com o atributo DataMemberAttribute.

Sendo assim, o exemplo final fica como é mostrado abaixo, conseguindo ter no cliente, a navegação bidirecional. Obviamente que alguns membros foram omitidos por questões de espaço.

[Table(Name = "dbo.Categoria")]
[DataContract(IsReference = true)]
public partial class Categoria
{
    [DataMember]
    [Column(...)]
    public int CategoriaId

    [DataMember]
    [Column(...)]
    public string Nome

    [DataMember]
    [Association(...)]
    public EntitySet<Produto> Produtos
}

[Table(Name = "dbo.Produto")]
public partial class Produto
{
    [Column(...)]
    public int ProdutoId

    [Column(...)]
    public int CategoriaId

    [Column(...)]
    public string Nome

    [Association(...)]
    public Categoria Categoria
}

Tags: , ,

Data | WCF

Serialização Circular no WCF

by Israel Aece 7. January 2010 06:18

Quando construímos as classes que atenderão à um sistema específico, é muito comum termos uma propriedade que expõe um outro objeto, e este, por sua vez, você gostaria de também ter uma propriedade que referencia o seu "pai". Isso é algo simples de se realizar, mas poderá haver problemas ao serializar essa classe, como por exemplo, quando precisar expor via WCF.

Levando em consideração a imagem acima, note que um cliente possui um endereço, e este endereço aponta para o cliente que o possui. Quando você tentar enviar o cliente através do WCF, você receberá uma exceção, já que ele ficará em loop infinito, tentando efetuar a serialização do cliente -> endereço -> cliente -> endereço -> cliente -> endereço e assim vai.

Para solucionar isso, podemos utilizar a propriedade IsReference na classe "pai", que no nosso casso é Cliente. A partir de agora, o processo de serialização gerencia a criação de um identificador, para assim conseguir refenciá-lo ao invés de tentar serializar novamente. É importante se atentar para também alterar no proxy construído do lado do cliente, caso o teu contrato permita enviar a instância da classe Cliente para o respectivo serviço.

[DataContract(IsReference = true)]
public class Cliente { }

Tags: ,

WCF

Serialização de Datasets

by Israel Aece 5. January 2010 11:02

Vira e mexe alguém entra em contato comigo para discutir sobre - os populares - DataSets. Como todos sabem, ele foi construído para ambientes desconectados, que permite criar uma espécie de "banco de dados em memória", possibilitando ao usuário chegar de manhã na empresa, carregar os dados que ele vai trabalhar durante todo o dia, e depois sair à campo.

Durante o tempo que ele estiver fora, dificilmente terá acesso à rede da empresa, o que obriga a persistir os dados fisicamente, para quando chegar no(s) cliente(s), conseguir restaurar essas informações e manipulá-las como se ele estivesse trabalhando localmente. Dependendo do volume de informações que é carregado neste DataSet, o arquivo final pode ser muito grande, e dependendo do tipo de dispositivo que está utilizando, isso pode ser prejudicial.

Hoje um ex-aluno me enviou um e-mail dizendo que passava por um problema semelhante, onde ele precisava diminuir o tamanho final do arquivo que continha os dados. O problema é que ele estava utilizando a serialização em Xml, que naturalmente é maior do que a serialização binária. Isso se deve-se ao fato de que Xml é o padrão para interoperabilidade, e se fosse trafegá-lo através de Web Services, então obrigatoriamente ele deve ser serializado desta forma.

Do contrário, você pode optar pelo padrão binário. Como neste caso a interoperabilidade não é necessária, já que a finalidade é apenas ter um arquivo menor salvo no disco, podemos adotar esta técnica. Para isso, a partir da versão 2.0 do ADO.NET, a Microsoft disponibilizou uma pequena funcionalidade no DataSet, que modifica-o durante o processo de serialização. Abaixo o exemplo de como podemos proceder:

DataSet ds = new DataSet();
CarregarDados(ds);

ds.RemotingFormat = SerializationFormat.Binary;

using (FileStream fs = File.Create("Dados.bin"))
    new BinaryFormatter().Serialize(fs, ds);

Com este exemplo, um DataSet que em Xml tem 1MB, caiu para 280KB. Você não é obrigado a utilizar a propriedade RemotingFormat, mas se omití-la, verá que o resultado não será tão expressivo como. Quando você define esta propriedade, ele customizará a serialização, transformando o schema e a instância do DataSet atual em um formato mais otimizado e comprimido. Atente-se aqui, pois se o DataSet for muito pequeno (quantidade de linhas/colunas), o resultado pode ser igual ou até mesmo maior que o Xml.

Mais uma vez, se possível reescreva e tente optar por alguma outra alternativa que não sejam os DataSets. Quando você persite-o, independentemente se for Xml ou binário, ele armazena muito mais informações do que o aquilo que realmente você precisa, pois lembre-se: ele é um "banco de dados em memória". Para mais informações sobre serialização, consulte o capítulo 06 deste livro.

Tags:

Data

Utilizando Exceptions como Faults

by Israel Aece 15. September 2009 08:39

Quando construímos um serviço WCF, podemos utilizar o atributo FaultContractAttribute um tipo, que representará o problema que ocorreu durante o processamento daquela mensagem. O tipo especificado ali vai estar disponível para o cliente através de uma FaultException<T>, onde T representará os detalhes do erro. O parâmetro genérico T não tem nenhuma constraint, o que permite colocar qualquer tipo (desde que ele seja serializável). Para maiores detalhes, consulte essas fontes.

Como o tipo informado através do atributo FaultContractAttribute está aberto, nada impede de colocarmos ali um tipo que herde direta ou indiretamente da classe Exception. Definindo exceções que foram criadas pelo .NET Framework, não haverá problemas, já que elas existem do outro lado. As dificuldades começam quando é preciso propagar exceções customizadas, aquelas que são herdadas a partir da classe Exception. O grande problema aqui é que o serializador padrão do WCF, que é o DataContractSerializer, não serializa tipos complexos por questões de interoperabilidade. Com essa "limitação", a classe Exception (ou uma de suas derivadas) sofrerá com isso, já que ela expõe uma propriedade chamada Data, que retorna a instância de um objeto que implementa a Interface IDictionary.

Para resolvermos isso, a boa prática é que sempre criar Faults. Dessa forma, você criará uma classe para detalhar o problema para os clientes, sem a necessidade de manipular exceções. Com isso, ao invés de disparar uma exceção customizada, você dispara uma FaultException<T>, onde T será essa classe que detalhará o problema ocorrido. Agora, se você tiver a possibilidade de compartilhar os tipos, então a exceção customizada funcionará, já que os clientes a conhecem, mas você pagará o preço da interoperabilidade.

Tags: , ,

WCF

Serialização de tipos internos

by Israel Aece 14. September 2009 10:27

O WCF permite expor tipos complexos (classes customizadas) através de um serviço. Essas classes precisam estar decoradas com o atributo DataContractAttribute/DataMemberAttribute ou SerializableAttribute, mas se estiver utilizando .NET 3.5 + SP1, você poderá omití-los (POCO).

Esses atributos somente podem ser descartados se a classe que está expondo, tiver seu modificador de acesso definido como public. Repare que no caso abaixo, estou optando por utilizar o modelo POCO, mas a classe está definida como internal, que quer dizer que a mesma somente pode ser acessada a partir do mesmo assembly onde ela foi criada.

[ServiceContract]
internal interface IData
{
    [OperationContract]
    Cliente Ping(Cliente cliente);
}

internal class Cliente
{
    public string Nome { get; set; }
}

Ao rodar a aplicação, resultará na seguinte exeção:

Unhandled Exception: System.Runtime.Serialization.InvalidDataContractException: Type 'Host.Cliente' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute.  See the Microsoft .NET Framework documentation for other supported types.

Para resolver isso, basta definir como public ou, se isso não for coerente, definindo explicitamente os atributos DataContractAttribute/DataMemberAttribute ou SerializableAttribute. Internamente, o WCF valida o tipo em um método chamado IsNonAttributedTypeValidForSerialization, que entre várias verificações, analisa se o tipo está ou não visível fora do assembly onde ele foi criado, recorrendo a propriedade IsVisible da classe Type. E como vimos, caso essa propriedade retorne False, a exceção acima será disparada. As validações para determinar se o tipo contém ou não os atributos de serialização suportados pelo WCF, também são realizadas dentro deste mesmo método.

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