Tipos de contratos de dados conhecidos

A classe KnownTypeAttribute permite que você especifique, com antecedência, os tipos que devem ser incluídos para consideração durante a desserialização. Para obter um exemplo funcional, consulte o exemplo de Tipos conhecidos.

Normalmente, ao passar parâmetros e retornar valores entre um cliente e um serviço, ambos os pontos de extremidade compartilham todos os contratos de dados dos dados a serem transmitidos. No entanto, esse não é o caso nas seguintes circunstâncias:

  • O contrato de dados enviados é derivado do contrato de dados esperado. Para obter mais informações, consulte a seção sobre herança em Equivalência do Contrato de Dados). Nesse caso, os dados transmitidos não têm o mesmo contrato de dados esperado pelo ponto de extremidade de recebimento.

  • O tipo declarado para as informações a serem transmitidas é uma interface, em oposição a uma classe, estrutura ou enumeração. Portanto, não é possível saber com antecedência qual tipo que implementa a interface é realmente enviado e, portanto, o ponto de extremidade de recebimento não pode determinar antecipadamente o contrato de dados para os dados transmitidos.

  • O tipo declarado para as informações a serem transmitidas é Object. Como cada tipo é herdado de Object e não é possível saber com antecedência qual tipo é realmente enviado, o ponto de extremidade de recebimento não pode determinar antecipadamente o contrato de dados para os dados transmitidos. Esse é um caso especial do primeiro item: cada contrato de dados deriva do padrão, um contrato de dados em branco que é gerado para Object.

  • Alguns tipos, que incluem tipos .NET Framework, têm membros que estão em uma das três categorias anteriores. Por exemplo, Hashtable usa Object para armazenar os objetos reais na tabela de hash. Ao serializar esses tipos, o lado receptor não pode determinar antecipadamente o contrato de dados para esses membros.

A classe KnownTypeAttribute

Quando os dados chegam a um ponto de extremidade de recebimento, o runtime do WCF tenta desserializar os dados em uma instância de um tipo de CLR (Common Language Runtime). O tipo instanciado para desserialização é escolhido inspecionando primeiro a mensagem de entrada a fim de determinar o contrato de dados com o qual o conteúdo da mensagem está em conformidade. Em seguida, o mecanismo de desserialização tenta encontrar um tipo de CLR que implemente um contrato de dados compatível com o conteúdo da mensagem. O conjunto de tipos de candidatos que o mecanismo de desserialização permite durante esse processo é conhecido como o conjunto de "tipos conhecidos" do desserializador.

Uma maneira de informar ao mecanismo de desserialização sobre um tipo é usando o KnownTypeAttribute. O atributo não pode ser aplicado a membros de dados individuais, apenas a tipos de contrato de dados inteiros. O atributo é aplicado a um tipo externo que pode ser uma classe ou uma estrutura. Em seu uso mais básico, aplicar o atributo especifica um tipo como um "tipo conhecido". Isso faz com que o tipo conhecido faça parte do conjunto de tipos conhecidos sempre que um objeto do tipo externo ou qualquer objeto referenciado por meio de seus membros estiver sendo desserializado. Mais de um atributo KnownTypeAttribute pode ser aplicado ao mesmo tipo.

Tipos conhecidos e primitivos

Tipos primitivos, bem como certos tipos tratados como primitivos (por exemplo, DateTime e XmlElement) são sempre "conhecidos" e nunca precisam ser adicionados por meio desse mecanismo. No entanto, matrizes de tipos primitivos devem ser adicionadas explicitamente. A maioria das coleções é considerada equivalente a matrizes. (Coleções não genéricas são consideradas equivalentes a matrizes de Object). Para obter um exemplo de uso de primitivos, matrizes primitivas e coleções primitivas, consulte o Exemplo 4.

Observação

Ao contrário de outros tipos primitivos, a estrutura DateTimeOffset não é um tipo conhecido por padrão e, portanto, ela deve ser adicionada manualmente à lista de tipos conhecidos.

Exemplos

Os exemplos a seguir mostram a classe KnownTypeAttribute em uso.

Exemplo 1

Há três classes com uma relação de herança.

[DataContract]
public class Shape { }

[DataContract(Name = "Circle")]
public class CircleType : Shape { }

[DataContract(Name = "Triangle")]
public class TriangleType : Shape { }
<DataContract()> _
Public Class Shape
End Class

<DataContract(Name:="Circle")> _
Public Class CircleType
    Inherits Shape
End Class
<DataContract(Name:="Triangle")> _
Public Class TriangleType
    Inherits Shape
End Class

A classe CompanyLogo a seguir pode ser serializada, mas não pode ser desserializada se o membro ShapeOfLogo estiver definido como um objeto CircleType ou um TriangleType, pois o mecanismo de desserialização não reconhece nenhum tipo com nomes de contrato de dados "Circle" ou "Triangle".

[DataContract]
public class CompanyLogo
{
    [DataMember]
    private Shape ShapeOfLogo;
    [DataMember]
    private int ColorOfLogo;
}
<DataContract()> _
Public Class CompanyLogo
    <DataMember()> _
    Private ShapeOfLogo As Shape
    <DataMember()> _
    Private ColorOfLogo As Integer
End Class

A maneira correta de gravar o tipo CompanyLogo é mostrada no código a seguir.

[DataContract]
[KnownType(typeof(CircleType))]
[KnownType(typeof(TriangleType))]
public class CompanyLogo2
{
    [DataMember]
    private Shape ShapeOfLogo;
    [DataMember]
    private int ColorOfLogo;
}
<DataContract(), KnownType(GetType(CircleType)), KnownType(GetType(TriangleType))> _
Public Class CompanyLogo2
    <DataMember()> _
    Private ShapeOfLogo As Shape
    <DataMember()> _
    Private ColorOfLogo As Integer
End Class

Sempre que o tipo externo CompanyLogo2 está sendo desserializado, o mecanismo de desserialização sabe sobre CircleType e TriangleType e, portanto, é capaz de encontrar tipos correspondentes para os contratos de dados "Circle" e "Triangle".

Exemplo 2

No exemplo a seguir, embora tanto CustomerTypeA quanto CustomerTypeB tenham o contrato de dados Customer, uma instância de CustomerTypeB é criada sempre que um PurchaseOrder é desserializado, porque só CustomerTypeB é conhecido pelo mecanismo de desserialização.

public interface ICustomerInfo
{
    string ReturnCustomerName();
}

[DataContract(Name = "Customer")]
public class CustomerTypeA : ICustomerInfo
{
    public string ReturnCustomerName()
    {
        return "no name";
    }
}

[DataContract(Name = "Customer")]
public class CustomerTypeB : ICustomerInfo
{
    public string ReturnCustomerName()
    {
        return "no name";
    }
}

[DataContract]
[KnownType(typeof(CustomerTypeB))]
public class PurchaseOrder
{
    [DataMember]
    ICustomerInfo buyer;

    [DataMember]
    int amount;
}
Public Interface ICustomerInfo
    Function ReturnCustomerName() As String
End Interface

<DataContract(Name:="Customer")> _
Public Class CustomerTypeA
    Implements ICustomerInfo
    Public Function ReturnCustomerName() _
    As String Implements ICustomerInfo.ReturnCustomerName
        Return "no name"
    End Function
End Class

<DataContract(Name:="Customer")> _
Public Class CustomerTypeB
    Implements ICustomerInfo
    Public Function ReturnCustomerName() _
    As String Implements ICustomerInfo.ReturnCustomerName
        Return "no name"
    End Function
End Class

<DataContract(), KnownType(GetType(CustomerTypeB))> _
Public Class PurchaseOrder
    <DataMember()> _
    Private buyer As ICustomerInfo

    <DataMember()> _
    Private amount As Integer
End Class

Exemplo 3

No exemplo a seguir, um Hashtable armazena seu conteúdo internamente como Object. Para desserializar com êxito uma tabela de hash, o mecanismo de desserialização deve conhecer o conjunto de tipos possíveis que podem ocorrer ali. Nesse caso, sabemos, com antecedência, que somente os objetos Book e Magazine são armazenados no Catalog e, portanto, eles são adicionados usando o atributo KnownTypeAttribute.

[DataContract]
public class Book { }

[DataContract]
public class Magazine { }

[DataContract]
[KnownType(typeof(Book))]
[KnownType(typeof(Magazine))]
public class LibraryCatalog
{
    [DataMember]
    System.Collections.Hashtable theCatalog;
}
<DataContract()> _
Public Class Book
End Class

<DataContract()> _
Public Class Magazine
End Class

<DataContract(), KnownType(GetType(Book)), KnownType(GetType(Magazine))> _
Public Class LibraryCatalog
    <DataMember()> _
    Private theCatalog As System.Collections.Hashtable
End Class

Exemplo 4

No exemplo a seguir, um contrato de dados armazena um número e uma operação a ser executada no número. O membro de dados Numbers pode ser um inteiro, uma matriz de inteiros ou um List<T> que contenha inteiros.

Cuidado

Isso só funcionará no lado do cliente se SVCUTIL.EXE for usado para gerar um proxy WCF. SVCUTIL.EXE recupera metadados do serviço, incluindo todos os tipos conhecidos. Sem essas informações, um cliente não poderá desserializar os tipos.

[DataContract]
[KnownType(typeof(int[]))]
public class MathOperationData
{
    private object numberValue;
    [DataMember]
    public object Numbers
    {
        get { return numberValue; }
        set { numberValue = value; }
    }
    //[DataMember]
    //public Operation Operation;
}
<DataContract(), KnownType(GetType(Integer()))> _
Public Class MathOperationData
    Private numberValue As Object

    <DataMember()> _
    Public Property Numbers() As Object
        Get
            Return numberValue
        End Get
        Set(ByVal value As Object)
            numberValue = value
        End Set
    End Property
End Class

Este é o código do aplicativo.

// This is in the service application code:
static void Run()
{

    MathOperationData md = new MathOperationData();

    // This will serialize and deserialize successfully because primitive
    // types like int are always known.
    int a = 100;
    md.Numbers = a;

    // This will serialize and deserialize successfully because the array of
    // integers was added to known types.
    int[] b = new int[100];
    md.Numbers = b;

    // This will serialize and deserialize successfully because the generic
    // List<int> is equivalent to int[], which was added to known types.
    List<int> c = new List<int>();
    md.Numbers = c;
    // This will serialize but will not deserialize successfully because
    // ArrayList is a non-generic collection, which is equivalent to
    // an array of type object. To make it succeed, object[]
    // must be added to the known types.
    ArrayList d = new ArrayList();
    md.Numbers = d;
}
' This is in the service application code:
Shared Sub Run()
    Dim md As New MathOperationData()
    ' This will serialize and deserialize successfully because primitive 
    ' types like int are always known.
    Dim a As Integer = 100
    md.Numbers = a

    ' This will serialize and deserialize successfully because the array of 
    ' integers was added to known types.
    Dim b(99) As Integer
    md.Numbers = b

    ' This will serialize and deserialize successfully because the generic 
    ' List(Of Integer) is equivalent to Integer(), which was added to known types.
    Dim c As List(Of Integer) = New List(Of Integer)()
    md.Numbers = c
    ' This will serialize but will not deserialize successfully because 
    ' ArrayList is a non-generic collection, which is equivalent to 
    ' an array of type object. To make it succeed, object[]
    ' must be added to the known types.
    Dim d As New ArrayList()
    md.Numbers = d

End Sub

Tipos conhecidos, herança e interfaces

Quando um tipo conhecido é associado a um tipo específico usando o atributo KnownTypeAttribute, o tipo conhecido também é associado a todos os tipos derivados desse tipo. Por exemplo, confira o código a seguir.

[DataContract]
[KnownType(typeof(Square))]
[KnownType(typeof(Circle))]
public class MyDrawing
{
    [DataMember]
    private object Shape;
    [DataMember]
    private int Color;
}

[DataContract]
public class DoubleDrawing : MyDrawing
{
    [DataMember]
    private object additionalShape;
}
<DataContract(), KnownType(GetType(Square)), KnownType(GetType(Circle))> _
Public Class MyDrawing
    <DataMember()> _
    Private Shape As Object
    <DataMember()> _
    Private Color As Integer
End Class

<DataContract()> _
Public Class DoubleDrawing
    Inherits MyDrawing
    <DataMember()> _
    Private additionalShape As Object
End Class

A classe DoubleDrawing não exige que o atributo KnownTypeAttribute use Square e Circle no campo AdditionalShape, pois a classe base (Drawing) já tem esses atributos aplicados.

Os tipos conhecidos só podem ser associados a classes e estruturas e não a interfaces.

Tipos conhecidos usando métodos genéricos abertos

Pode ser necessário adicionar um tipo genérico como tipo conhecido. No entanto, um tipo genérico aberto não pode ser passado como um parâmetro para o atributo KnownTypeAttribute.

Esse problema pode ser resolvido usando um mecanismo alternativo: escrever um método que retorna uma lista de tipos a serem adicionados à coleção de tipos conhecidos. O nome do método é especificado como um argumento de cadeia de caracteres para o atributo KnownTypeAttribute devido a algumas restrições.

O método deve existir no tipo ao qual o atributo KnownTypeAttribute é aplicado, deve ser estático, não deve aceitar parâmetros e deve retornar um objeto que pode ser atribuído para IEnumerable de Type.

Não é possível combinar o atributo KnownTypeAttribute com um nome de método e atributos KnownTypeAttribute com tipos reais no mesmo tipo. Além disso, não é possível aplicar mais de um KnownTypeAttribute com um nome de método ao mesmo tipo.

Observe a seguinte classe.

[DataContract]
public class DrawingRecord<T>
{
    [DataMember]
    private T theData;
    [DataMember]
    private GenericDrawing<T> theDrawing;
}
<DataContract()> _
Public Class DrawingRecord(Of T)
    <DataMember()> _
    Private theData As T
    <DataMember()> _
    Private theDrawing As GenericDrawing(Of T)
End Class

O campo theDrawing contém instâncias de uma classe ColorDrawing genérica e uma classe BlackAndWhiteDrawing genérica, ambas herdadas de uma classe Drawing genérica. Normalmente, ambos devem ser adicionados a tipos conhecidos, mas a sintaxe a seguir não é válida para atributos.

// Invalid syntax for attributes:  
// [KnownType(typeof(ColorDrawing<T>))]  
// [KnownType(typeof(BlackAndWhiteDrawing<T>))]  
' Invalid syntax for attributes:  
' <KnownType(GetType(ColorDrawing(Of T))), _  
' KnownType(GetType(BlackAndWhiteDrawing(Of T)))>  

Portanto, um método deve ser criado para retornar esses tipos. A maneira correta de gravar o esse tipo é mostrada no código a seguir.

[DataContract]
[KnownType("GetKnownType")]
public class DrawingRecord2<T>
{
    [DataMember]
    private T TheData;
    [DataMember]
    private GenericDrawing<T> TheDrawing;

    private static Type[] GetKnownType()
    {
        Type[] t = new Type[2];
        t[0] = typeof(ColorDrawing<T>);
        t[1] = typeof(BlackAndWhiteDrawing<T>);
        return t;
    }
}
<DataContract(), KnownType("GetKnownType")> _
Public Class DrawingRecord2(Of T)
    Private TheData As T
    Private TheDrawing As GenericDrawing(Of T)

    Private Shared Function GetKnownType() As Type()
        Dim t(1) As Type
        t(0) = GetType(ColorDrawing(Of T))
        t(1) = GetType(BlackAndWhiteDrawing(Of T))
        Return t
    End Function
End Class

Maneiras adicionais de adicionar tipos conhecidos

Além disso, tipos conhecidos podem ser adicionados por meio de um arquivo de configuração. Isso é útil quando você não controla o tipo que requer tipos conhecidos para desserialização adequada, como ao usar bibliotecas de tipos de terceiros com o WCF (Windows Communication Foundation).

O arquivo de configuração a seguir mostra como especificar um tipo conhecido em um arquivo de configuração.

<configuration>

<system.runtime.serialization>

<dataContractSerializer>

<declaredTypes>

<add type="MyCompany.Library.Shape,

MyAssembly, Version=2.0.0.0, Culture=neutral,

PublicKeyToken=XXXXXX, processorArchitecture=MSIL">

<knownType type="MyCompany.Library.Circle,

MyAssembly, Version=2.0.0.0, Culture=neutral,

PublicKeyToken=XXXXXX, processorArchitecture=MSIL"/>

</add>

</declaredTypes>

</dataContractSerializer>

</system.runtime.serialization>

</configuration>

No arquivo de configuração anterior, um tipo de contrato de dados chamado MyCompany.Library.Shape é declarado como tendo MyCompany.Library.Circle como um tipo conhecido.

Confira também