Substituindo ASMX por WCF no servidor

by Israel Aece 3. September 2010 10:22

Os ASP.NET Web Services (ASMX) são baseados na especificação WS-I Basic Profile, gerando as mensagens na versão 1.1 do SOAP. Eles foram que foram introduzidos desde a primeira versão do .NET Framework, e mais recentemente foram substituídos pelo WCF.

Para manter a interoperabilidade, a Microsoft criou um binding chamado BasicHttpBinding, que possui as mesmas características dos serviços ASMX. Com isso, podemos substituir tanto o serviço quanto o cliente utilizando a API do WCF. Ao optar pela substituição do lado do serviço, precisamos nos preocupar com a SOAPAction.

A SOAPAction é um header que é incluído na requisição HTTP, e determina intenção da requisição SOAP. Esse header é representado por uma URI que não aponta necessariamente para um local válido na internet. O problema reside justamente aqui. Imagine que temos um serviço (ASMX), que disponibiliza apenas uma única operação:

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Servico : System.Web.Services.WebService
{
    [WebMethod]
    public string Ping()
    {
        return "ping!";
    }
}

Os clientes que o consomem devem enviar as requisições da seguinte forma:

POST /Servico.asmx HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 2.0.50727.4952)
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://tempuri.org/Ping"
Host: 127.0.0.1:51982
Content-Length: 288
Expect: 100-continue
Connection: Keep-Alive

<?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope ...>
        <soap:Body>
            <Ping xmlns="http://tempuri.org/" />
        </soap:Body>
</soap:Envelope>

Agora queremos substituir o serviço para o WCF. Optamos então por criar o contrato e, consequentemente, o implementamos em uma classe que representará o serviço. A interface do contrato está definida abaixo:

[ServiceContract]
public interface IContrato
{
    [OperationContract]
    string Ping();
}

O ponto mais importante é expor o serviço através do binding BasicHttpBinding, que não será abordado aqui. Se analisarmos o WSDL gerado pelo serviço, veremos que a SOAPAction gerada é: http://tempuri.org/IContrato/Ping. Como podemos perceber, ela leva o nome do contrato. Com isso, os clientes existentes não conseguirão mais enviar mais mensagens para o serviço, já que o WCF utiliza a SOAPAction para identificar a operação a ser disparada, e como ela é diferente, não a encontrará. Abaixo temos a descrição do erro que ocorre:

Unhandled Exception: System.Web.Services.Protocols.SoapHeaderException: The message with Action 'http://tempuri.org/Ping' cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver.  Check that sender and receiver have the same contract and the same binding (including security requirements, e.g. Message, Transport, None).

Para resolvermos este problema, temos que recorrer a propriedade Action que é definida no atributo OperationContractAttribute, e lá colocarmos a mesma SOAPAction que os clientes estão enviando, mantendo assim a compatibilidade e não sendo necessária qualquer mudança por parte daqueles que o consomem.

[ServiceContract]
public interface IContrato
{
    [OperationContract(Action = "http://tempuri.org/Ping")]
    string Ping();
}

Tomando este cuidado, podemos tranquilamente substituir serviços construídos em ASMX para WCF, sem afetar qualquer cliente que já consuma o mesmo. É importante dizer também que é possível consumir um serviço escrito em ASMX através da API do WCF do lado cliente, mas isso está fora do escopo deste artigo. E para finalizar, sempre se atente aos namespaces.

Tags: ,

Interoperabilidade | 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

Compartilhamento de Bindings

by Israel Aece 30. November 2009 08:25

Quando você possui múltiplos endpoints para expor um serviço, na maioria das vezes, esses endpoints possuem características diferentes, pois você poderá publicá-lo através de HTTP para consumo externo, enquanto as aplicações locais, consomem este mesmo serviço através de TCP, para uma melhor performance.

A configuração abaixo ilustra a exposição de um mesmo serviço e de um mesmo contrato através dos protocolos TCP e HTTP. Cada um dos protocolos possuem características diferentes de comunicação, e justamente por isso, exigem bindings diferentes.

<system.serviceModel>
  <services>
    <service name="App.Servico">
      <host>
        <baseAddresses>
          <add baseAddress="net.tcp://localhost:9838"/>
          <add baseAddress="http://localhost:8478"/>
        </baseAddresses>
      </host>
      <endpoint address="srv"
                contract="App.IContrato1"
                binding="netTcpBinding"/>
      <endpoint address="srv"
                contract="App.IContrato1"
                binding="basicHttpBinding"/>
    </service>
  </services>
</system.serviceModel>

Cada endpoint é composto por um endereço, binding e contrato, e cada endpoint possui seu próprio listener e channel stack, que varia de acordo com o binding escolhido. Em algumas situações, é comum ter mais do que um contrato para o mesmo serviço, onde queremos compartilhar o mesmo endereço para ambos contratos. Abaixo podemos ver um serviço que tem esse comportamento:

<system.serviceModel>
  <services>
    <service name="App.Servico">
      <host>
        <baseAddresses>
          <add baseAddress="net.tcp://localhost:9838"/>
        </baseAddresses>
      </host>
      <endpoint address="srv"
                contract="App.IContrato1"
                binding="netTcpBinding"/>
      <endpoint address="srv"
                contract="App.IContrato2"
                binding="netTcpBinding"/>
    </service>
  </services>
</system.serviceModel>

Repare que temos dois contratos (IContrato1 e IContrato2) expostos através de TCP e usando o mesmo endereço. Isso somente é possível porque o WCF reutiliza a mesma instância do binding para efetuar a comunicação, e difere a execução baseando-se nas Soap Actions. Se cada binding tiver sua configuração específica (definido através do atributo bindingConfiguration), então isso fará com que o WCF crie instâncias diferentes do mesmo binding, resultando em uma exceção tipo do InvalidOperationException sendo disparada, informando que não é permitido associar uma mesma instância binding à endereços diferentes.

Se você tiver endereços distintos, então duas instâncias distintas do binding correspondente serão criadas para atender cada um deles. Esse mesmo cuidado você precisa ter ao configurar o serviço de forma imperativa. Ao chamar o método AddServiceEndpoint da classe ServiceHost, você deverá se preocupar em passar a mesma instância do binding, como por exemplo:

using (ServiceHost host = 
       new ServiceHost(typeof(Servico), new Uri[] { new Uri("net.tcp://localhost:9838") }))
{
    NetTcpBinding b = new NetTcpBinding();

    host.AddServiceEndpoint(typeof(IContrato1), b, "srv");
    host.AddServiceEndpoint(typeof(IContrato2), b, "srv");

    host.Open();
    Console.ReadLine();
}

Tags: , , , ,

WCF

ASP.NET Session em serviços WCF

by Israel Aece 22. September 2009 10:46

Como foi visto aqui, podemos utilizar as variáveis de sessão do ASP.NET para manter o estado de um serviço WCF. Para isso, basta habilitar o modo de compatibilidade com ASP.NET e você já terá acesso ao HttpContext (HttpContext.Current) em seu serviço.

Você precisa ter um certo cuidado aqui com dois detalhes: o primeiro deles é com relação a dependência que o seu serviço terá com o protocolo HTTP, pois dentro da classe que representa o serviço, você mencionará classes que fazem parte do ASP.NET, assim como a HttpContext, e com isso, você não conseguirá acessá-lo através de TCP. Já o segundo detalhe, é com relação ao consumo deste serviço. Ao contrário do gerenciamento de instâncias do WCF, a sessão do ASP.NET é utilizada de forma diferente, mas com a mesma finalidade, ou seja, de manter o estado das informações durante as requisições.

Quando você faz uso do modelo PerSession, ao fechar o proxy o WCF descartará a instância da classe do lado do serviço. Como no caso do ASP.NET é ele quem controla as variáveis de sessão, você precisará explicitamente removê-las. Para que você consiga descartar tudo o que tem na memória, precisará chamar o método Abandon da classe HttpSessionState. Para automatizar essa tarefa e não poluir a Interface do contrato com métodos que "limpam" a sessão, você pode implementar na classe do serviço a Interface IDisposable, que será invocada quando o proxy for encerrado.

Como sabemos, a configuração padrão da sessão, é manter um cookie do lado do cliente com o "Id", para que ela consiga identificar o usuário. Para que isso funcione corretamente, precisamos habilitar o gerenciamento de cookies pelo binding, assim como é mostrado neste artigo. Com isso, o proxy do WCF encaminhará, automaticamente, os cookies nas futuras requisições. Mesmo que você utilize instâncias diferentes do proxy, devido ao recurso de caching, elas compartilharão as mesmas variáveis de sessão do lado do serviço.

Você pode consumir um serviço WCF na mesma aplicação em que ele está hospedado (comum em aplicações Silverlight). Mesmo que você utilize o mesmo diretório virtual/worker process, você não conseguirá dentro do serviço acessar as variáveis de sessão criadas no ASP.NET e vice-versa. Lembre-se de que o serviço nem sempre será consumido por essa mesma aplicação, e justamente por isso, é proibido. Se você precisar passar informações que estão armazenadas em variáveis de sessão do lado do cliente, tudo o que você precisa fazer, é enviá-las através do contrato.

Tags: , , ,

ASP.NET | WCF

Overloading de métodos no WCF

by Israel Aece 21. September 2009 08:16

Um outro detalhe que funciona perfeitamente bem no C# ou qualquer linguagem orientado a objetos, é o overloading de métodos, onde você tem várias métodos com o mesmo nome, variando o tipo ou a quantidade de parâmetros. Podemos aplicar essa técnica também em Interfaces, e quando ela for implementada nas classes, teremos tais métodos a nossa disposição.

A questão é que, assim como a implementação explícita, quando aplicado em uma Interface que servirá como contrato de um serviço WCF, isso não funcionará. Mas isso não é uma limitação da plataforma, mas sim do documento WSDL, que não suporta algumas características de programação orientada à objetos, e a sobrecarga de métodos é uma delas. Com essa "limitação", ao rodar um serviço (hosting) com algum método que possua algum overload, você receberá uma exceção do tipo InvalidOperationException, indicando que isso não é suportado.

Para resolver, basta recorrermos a propriedade Name do atributo OperationContractAttribute. Quando essa propriedade é omitida, o WCF assume o nome do método para publicar no documento WSDL. Eventualmente, você poderá customizar, escolhendo o nome que será exposto, já que você não está obrigado a manter o mesmo nome, que muitas vezes reflete algo intuitivo para a sua aplicação, mas incoerente com a operação que será publicada pelo serviço. O exemplo abaixo, ilustra como proceder para utilizar este atributo:

[ServiceContract]
public interface IContrato
{
   [OperationContract(Name = "RecuperarClientesPorEstado")]
   string[] RecuperarClientes(string estado);

   [OperationContract(Name = "RecuperarClientesPorCidade")]
   string[] RecuperarClientes(string estado, string cidade);
}

Ao referenciar esse serviço em um aplicação cliente, o mesmo enxergará dois métodos, com os respectivos nomes: "RecuperarClientesPorEstado" e "RecuperarClientesPorCidade", baseando-se no alias que demos ao criar o contrato. Apesar da ferramenta criar dois métodos, você poderá customizar o proxy gerado, recriando os overloads e tornando a programação do lado do cliente mais intuitiva. Ao gerar o proxy - automaticamente - do lado do cliente, o contrato é criado da seguinte maneira:

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[ServiceContractAttribute(ConfigurationName="Servico.IClientes")]
public interface IClientes {
    [
        OperationContract(
            Action="http://tempuri.org/IClientes/RecuperarClientesPorEstado", 
            ReplyAction="http://tempuri.org/IClientes/RecuperarClientesPorEstadoResponse")
    ]
    string[] RecuperarClientesPorEstado(string estado);

    [
        OperationContract(
            Action="http://tempuri.org/IClientes/RecuperarClientesPorCidade", 
            ReplyAction="http://tempuri.org/IClientes/RecuperarClientesPorCidadeResponse")
    ]
    string[] RecuperarClientesPorCidade(string estado, string cidade);
}

Para que você consiga refazer o overloading do lado do cliente, você precisa seguir a mesma técnica aplicada do lado do serviço, ou seja, adicionar a propriedade Name no atributo OperationContractAttribute, renomear as operações para "RecuperarClientes" e, finalmente, reimplementar o contrato na classe que representa o proxy. Desta forma, o cliente e o serviço trabalharão da forma tradicional (OO), enquanto em runtime, tudo será tratado com os "aliases".

Tags: ,

WCF

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