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:
E, além do contrato, temos a classe Pessoa que possui apenas uma propriedade do tipo string:
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:
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:
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:
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:
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:
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.
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.