Tipos conhecidos de contrato de dados

A KnownTypeAttribute classe 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 Tipos conhecidos .

Normalmente, ao passar parâmetros e valores de retorno 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, tal 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 de Contrato de Dados). Nesse caso, os dados transmitidos não têm o mesmo contrato de dados esperado pelo ponto de extremidade recetor.

  • 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 antecipadamente qual tipo que implementa a interface é realmente enviado e, portanto, o ponto de extremidade recetor não pode determinar antecipadamente o contrato de dados para os dados transmitidos.

  • O tipo declarado para a informação a transmitir é Object. Como cada tipo herda de , e não é possível saber antecipadamente qual tipo é realmente enviado, o ponto de extremidade recetor não pode determinar antecipadamente o contrato de Objectdados para os dados transmitidos. Este é 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 do .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 recetor 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 tempo de execução do WCF tenta desserializar os dados em uma instância de um tipo CLR (Common Language Runtime). O tipo que é instanciado para desserialização é escolhido inspecionando primeiro a mensagem de entrada para determinar o contrato de dados ao qual o conteúdo da mensagem está em conformidade. Em seguida, o mecanismo de desserialização tenta encontrar um tipo CLR que implemente um contrato de dados compatível com o conteúdo da mensagem. O conjunto de tipos candidatos que o mecanismo de desserialização permite durante esse processo é chamado de conjunto de "tipos conhecidos" do desserializador.

Uma maneira de informar o 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, a aplicação do 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 referido através de seus membros está sendo desserializado. Mais de um KnownTypeAttribute atributo 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 através deste 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 do uso de primitivas, matrizes primitivas e coleções primitivas, consulte o Exemplo 4.

Nota

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

Exemplos

Os exemplos a seguir mostram a KnownTypeAttribute classe em uso.

Exemplo 1

Existem 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 a seguir CompanyLogo pode ser serializada, mas não pode ser desserializada se o ShapeOfLogo membro estiver definido como um CircleType ou um TriangleType objeto, porque o mecanismo de desserialização não reconhece nenhum tipo com nomes de contrato de dados "Círculo" ou "Triângulo".

[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 escrever o CompanyLogo tipo é 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 CompanyLogo2 externo está sendo desserializado, o mecanismo de desserialização conhece CircleType e TriangleType , portanto, é capaz de encontrar tipos correspondentes para os contratos de dados "Círculo" e "Triângulo".

Exemplo 2

No exemplo a seguir, mesmo que ambos CustomerTypeA tenham CustomerTypeB o Customer contrato de dados, uma instância de é criada sempre que um PurchaseOrder é desserializado, porque apenas CustomerTypeB é conhecido pelo mecanismo de CustomerTypeB 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 lá. Neste caso, sabemos de antemão que apenas Book e Magazine objetos são armazenados no Catalog, então esses são adicionados usando o KnownTypeAttribute atributo.

[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 Numbers membro de dados pode ser um inteiro, uma matriz de inteiros ou um List<T> que contém inteiros.

Atenção

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 quaisquer 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, herança e interfaces conhecidos

Quando um tipo conhecido é associado a um tipo específico usando o KnownTypeAttribute atributo, o tipo conhecido também é associado a todos os tipos derivados desse tipo. Por exemplo, consulte 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 DoubleDrawing classe não requer o KnownTypeAttribute atributo para usar Square e Circle no AdditionalShape campo, porque a classe base (Drawing) já tem esses atributos aplicados.

Os tipos conhecidos podem ser associados apenas a classes e estruturas, não a interfaces.

Tipos conhecidos usando métodos genéricos abertos

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

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

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

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

Veja a aula a seguir.

[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 theDrawing campo contém instâncias de uma classe ColorDrawing genérica e uma classe BlackAndWhiteDrawinggenérica, ambas herdadas de uma classe Drawinggené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)))>  

Assim, um método deve ser criado para retornar esses tipos. A maneira correta de escrever esse tipo, então, é 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 a desserialização adequada, como ao usar bibliotecas de tipos de terceiros com o Windows Communication Foundation (WCF).

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 MyCompany.Library.Circle um tipo conhecido.

Consulte também