Desenvolvimento - C#

Know Types em WCF

É muito comum em qualquer linguagem orientada a objetos, criarmos uma classe base e que, a partir dela, criar classes derivadas...

por Israel Aéce



function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i É muito comum em qualquer linguagem orientada a objetos, criarmos uma classe base e que, a partir dela, criar classes derivadas. Além disso, um dos grandes benefícios que temos com a orientação a objetos é a possibilidade de declararmos uma variável do tipo da classe base e atribuirmos a ela uma instância de uma classe concreta e, da mesma forma, podemos ter uma função em que em seus parâmetros os seus tipos são especificados com o tipo da classe base e, conseqüentemente, podemos também passar instâncias das classes derivadas.

Infelizmente não funciona da mesma forma quando falamos de serviços que são expostos a partir do WCF. Neste cenário, por padrão, você não pode usar uma classe derivada ao invés de uma classe base. Sendo assim, se quisermos utilizar esta classe base publicamente (parâmetros e retorno de métodos), precisamos nos atentar em algumas técnicas para permitir isso. Para exemplificar o problema, vamos analisar o código contrato abaixo:

using System;
using System.ServiceModel;

namespace DevMinds.Library
{
    [ServiceContract]
    public interface IGerenciadorDeContatos
    {
        [OperationContract]
        void AdicionarContato(Pessoa p);

        [OperationContract]
        Pessoa[] RecuperaContatos();
    }
}
Imports System.ServiceModel

<ServiceContract()> _
Public Interface IGerenciadorDeContatos

    <OperationContract()> _
    Sub AdicionarContato(p As Pessoa)

    <OperationContract()> _
    Function RecuperaContatos() As Pessoa()

End Interface
C# VB.NET

E, além do contrato, temos a classe Pessoa que possui apenas uma propriedade do tipo string:

using System;
using System.ServiceModel;
using System.Runtime.Serialization;

namespace DevMinds.Library
{
    [DataContract]
    public class Pessoa
    {
        private string _nome;
        
        [DataMember]
        public string Nome
        {
            get
            {
                return this._nome;
            }
            set
            {
                this._nome = value;
            }
        }
    }
}
Imports System
Imports System.ServiceModel
Imports System.Runtime.Serialization

Namespace DevMinds.Library
    <DataContract>
    Public Class Pessoa
        Private _nome As String
        
        <DataMember>
        Public Property Nome() As String
            Get
                Return Me._nome
            End Get
            Set (Value As String)
                Me._nome = value
            End Set
        End Property
    End Class
End Namespace
C# VB.NET

Supondo-se que o cliente defina uma classe chamada Fisica (de pessoa física) que herde diretamente da classe Pessoa e tente enviar a instância desta classe para o método AdicionarContato. Apesar de compilar, você terá uma exceção quando o código for executado, pelo fato de que quando você passar a classe Fisica ao invés de Pessoa o serviço não saberá como deserializar a "Pessoa" que é recebida. O mesmo vale para quando você tem uma coleção em seu serviço de uma classe derivada e tenta retorná-la para o cliente, expondo através do retorno do método uma coleção de tipos base.

Para suavizar este problema, o WCF introduziu um atributo chamado KnownType. Esse atributo recebe em seu construtor um Type, indicando à infra-estrutura do WCF que existe uma classe derivada do tipo onde o atributo é aplicado e que ela também pode ser aceita. Quando você define este atributo do lado do servidor, você permitirá que todos os contratos e operações que utilizem este tipo base possam aceitar o tipo especificado pelo atributo. O exemplo abaixo ilustra como devemos proceder para utilizar o atributo KnownType:

[DataContract]
[KnownType(typeof(Fisica))]
public class Pessoa
{
    //Implementação
}

[DataContract]
public class Fisica : Pessoa
{
    //Implementação
}
Imports System
Imports System.ServiceModel
Imports System.Runtime.Serialization

<DataContract, KnownType(GetType(Fisica))> _
Public Class Pessoa
    "Implementação
End Class

<DataContract>
Public Class Fisica
    Inherits Pessoa

    "Implementação
End Class
C# VB.NET

Uma vez aplicado este atributo, ele fará com que a classe derivada seja adicionada nos metadados do serviço e, conseqüentemente, o cliente terá a definição da mesma, podendo passá-la para o serviço ao invés da classe base. Desta forma, podemos tranquilamente executar o código que antes do atributo era impossível:

using (GerenciadorDeContatosClient proxy = new GerenciadorDeContatosClient())
{
    Fisica f = new Fisica();
    f.Nome = "Israel";
    f.Cpf = "00000000000";
    proxy.AdicionarContato(f);
}
Using proxy As New GerenciadorDeContatosClient()
    Dim f As New Fisica()
    f.Nome = "Israel"
    f.Cpf = "00000000000"
    proxy.AdicionarContato(f)
End Using
C# VB.NET

Como disse anteriormente, o ponto negativo deste atributo é com relação ao escopo, pois ele permite que todos os locais onde aceite uma classe base, aceitar um tipo derivado especificado no atributo. Se não quisermos isso, ou seja, se desejarmos habilitar este recurso somente para uma determinada operação, um contrato ou um serviço, podemos utilizar o atributo ServiceKnowType, que pode ser aplicado a um método, Interface ou classe. Com isso, o nosso código mudará ligeiramente, permitindo que somente um determinado método aceite instâncias da classe Fisica:

using System;
using System.ServiceModel;
using System.Runtime.Serialization;

namespace DevMinds.Library
{
    [ServiceContract]
    public interface IGerenciadorDeContatos
    {
        [OperationContract]
        [ServiceKnowType(typeof(Fisica))]
        void AdicionarContato(Pessoa p);

        //outros métodos
    }
}
Imports System
Imports System.ServiceModel
Imports System.Runtime.Serialization

<ServiceContract()> _
Public Interface IGerenciadorDeContatos

    <OperationContract(), ServiceKnowType(GetType(Fisica))> _
    Sub AdicionarContato(p As Pessoa)

    "outros métodos

End Interface
C# VB.NET

E ainda, se quiser permitir mais de uma classe derivada de um tipo base, então poderá adicionar múltiplos atributos (KnowType ou ServiceKnowType) para satisfazer a todos os tipos que a operação poderá aceitar/retornar. O trecho de código abaixo ilustra múltiplos atributos para mais de um tipo derivado:

using System;
using System.ServiceModel;
using System.Runtime.Serialization;

namespace DevMinds.Library
{
    [ServiceContract]
    public interface IGerenciadorDeContatos
    {
        [OperationContract]
        [ServiceKnowType(typeof(Fisica))]
        [ServiceKnowType(typeof(Juridica))]
        void AdicionarContato(Pessoa p);

        //outros métodos
    }
}
Imports System
Imports System.ServiceModel
Imports System.Runtime.Serialization

<ServiceContract()> _
Public Interface IGerenciadorDeContatos

    <OperationContract(), ServiceKnowType(GetType(Fisica)), ServiceKnowType(GetType(Juridica))> _
    Sub AdicionarContato(p As Pessoa)

    "outros métodos

End Interface
C# VB.NET

Para finalizar, ainda temos um detalhe importante quando falamos de know types que é em relação à recompilação do código cliente ou do serviço quando alguma nova classe derivada deve ser informada para que ela possa ser utilizada. Uma vez que um novo tipo precisa ser utilizado, implica em mudar o código, recompilá-lo e redistribuí-lo. Para amenizar isso, o WCF permite-nos fazer essa configuração, ou melhor, adição de novos tipos, a partir do arquivo de configuração, como é mostrado através do trecho de código abaixo:

<system.runtime.serialization>
  <dataContractSerializer>
    <declaredTypes>
      <add type="DevMinds.Library.Pessoa, DevMinds.Library, 
	     Version=1.0.0.0, PublicKeyToken=null">
        <knownType
          type="DevMinds.Client.Juridica, DevMinds.Client, Version=1.0.0.0, 
		  PublicKeyToken=null"/>
      </add>
    </declaredTypes>
  </dataContractSerializer>
</system.runtime.serialization>
*.config

Conclusão: Mais uma vez vimos através de uma pequena funcionalidade o quanto o WCF é flexível e permite de uma forma bem fácil e simples integrar o código e seus tipos que são desenhados e executados no cliente com o código que temos no servidor.
Israel Aéce

Israel Aéce - Especialista em tecnologias de desenvolvimento Microsoft, atua como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. Como instrutor Microsoft, leciona sobre o desenvolvimento de aplicações .NET. É palestrante em diversos eventos Microsoft no Brasil e autor de diversos artigos que podem ser lidos a partir de seu site http://www.israelaece.com/. Possui as seguintes credenciais: MVP (Connected System Developer), MCP, MCAD, MCTS (Web, Windows, Distributed, ASP.NET 3.5, ADO.NET 3.5, Windows Forms 3.5 e WCF), MCPD (Web, Windows, Enterprise, ASP.NET 3.5 e Windows 3.5) e MCT.