Tipi noti di contratto di dati

La classe KnownTypeAttribute consente di specificare, in anticipo, i tipi che devono essere presi in considerazione durante la deserializzazione. Per un esempio pratico, vedere l'esempio Known Types .

In genere, quando si passano parametri e valori restituiti tra un client e un servizio, entrambi gli endpoint condividono tutti i contratti dati dei dati da trasmettere. Nelle circostanze seguenti, tuttavia, la situazione è diversa:

  • Il contratto dati inviato deriva dal contratto dati previsto. Per altre informazioni, vedere la sezione sull'ereditarietà in Equivalenza del contratto di dati. In tale caso, i dati trasmessi non hanno lo stesso contratto dati previsto dall'endpoint di destinazione.

  • Il tipo dichiarato per le informazioni da trasmettere è un'interfaccia, anziché una classe, una struttura o un'enumerazione. Non è pertanto possibile conoscere in anticipo quale tipo che implementa l'interfaccia viene effettivamente inviato e, di conseguenza, l'endpoint di destinazione non è in grado di determinare, in anticipo, il contratto dati per i dati trasmessi.

  • Il tipo dichiarato per le informazioni da trasmettere è Object. Dato che ogni tipo eredita da Objecte che non è possibile sapere in anticipo qual è il tipo effettivamente inviato, l'endpoint di destinazione non è in grado di determinare in anticipo il contratto dati per i dati trasmessi. Questo è un caso speciale del primo elemento: ogni contratto dati deriva dall'impostazione predefinita, un contratto dati vuoto generato per Object.

  • Alcuni tipi, tra cui i tipi .NET Framework, dispongono di membri che rientrano in una delle tre categorie precedenti. Hashtable , ad esempio, utilizza Object per memorizzare gli oggetti effettivi nella tabella hash. Durante la serializzazione di questi tipi, il lato di destinazione non è in grado di determinare in anticipo il contratto dati per questi membri.

Classe KnownTypeAttribute

Quando i dati arrivano a un endpoint di destinazione, il runtime di WCF tenta di deserializzarli in un'istanza di un tipo Common Language Runtime (CLR). Il tipo di cui viene creata l'istanza per la deserializzazione viene scelto controllando innanzitutto il messaggio in arrivo per determinare il contratto dati al quale è compatibile con il contenuto del messaggio. Il motore di deserializzazione tenta quindi di trovare un tipo CLR che implementi un contratto dati conforme al contenuto del messaggio. Il set di tipi di candidato consentiti dal motore di deserializzazione durante questo processo viene chiamato set di "tipi noti" del deserializzatore.

Un modo per consentire al motore di deserializzazione di conoscere un tipo consiste nell'utilizzare KnownTypeAttribute. L'attributo non può essere applicato a membri dati singoli, ma solo a tutti i tipi di contratto dati. L'attributo viene applicato a un tipo esterno che può essere una classe o una struttura. Nell'utilizzo più elementare, l'applicazione dell'attributo specifica un tipo come "tipo conosciuto". Ciò fa sì che il tipo conosciuto sia parte di questo set di tipi noti ogni volta che viene deserializzato un oggetto del tipo esterno o un qualsiasi oggetto a cui si faccia riferimento tramite i suoi membri. Allo stesso tipo è possibile applicare più di un attributo KnownTypeAttribute .

Tipi conosciuti e primitivi

I tipi primitivi, così come certi tipi trattati come primitivi (ad esempio, DateTime e XmlElement) sono sempre "conosciuti" e non è mai necessario aggiungerli tramite questo meccanismo. Le matrici di tipi primitivi, tuttavia, devono essere aggiunte in modo esplicito. La maggior parte delle raccolte è considerata equivalente alle matrici. (La raccolte non generiche sono considerate equivalenti alle matrici di Object). Per un esempio di utilizzo di primitivi, matrici di primitivi e raccolte di primitivi, vedere l'esempio 4.

Nota

A differenza di altri tipi di primitivi, la struttura DateTimeOffset non è un tipo conosciuto per impostazione predefinita, pertanto deve essere aggiunta manualmente all'elenco dei tipi noti.

Esempi

Negli esempi seguenti viene illustrata la classe KnownTypeAttribute utilizzata.

Esempio 1

Esistono tre classi con una relazione di ereditarietà.

[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

La classe CompanyLogo seguente può essere serializzata, ma non può essere deserializzata se il membro ShapeOfLogo è impostato su un CircleType o su un oggetto TriangleType , perché il motore di deserializzazione non riconosce alcun tipo con i nomi del contratto dati "Circle" o "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

La modalità corretta per scrivere il tipo CompanyLogo è illustrata nel codice seguente.

[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

Ogni volta che il tipo esterno CompanyLogo2 viene deserializzato, il motore di deserializzazione viene a conoscenza di CircleType e TriangleType e, pertanto, è in grado di cercare i tipi corrispondenti per i contratti dati "Circle" e "Triangle".

Esempio 2

Nell'esempio seguente, anche se sia CustomerTypeA che CustomerTypeB hanno il contratto dati Customer , viene creata un'istanza di CustomerTypeB ogni volta che viene deserializzato un PurchaseOrder , perché solo CustomerTypeB è conosciuto al motore di deserializzazione.

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

Esempio 3

Nell'esempio seguente, un Hashtable memorizza internamente il contenuto come Object. Per deserializzare correttamente una tabella hash, il motore di deserializzazione deve conoscere il set dei tipi possibili che possono verificarsi. In questo caso, si sa in anticipo che solo gli oggetti Book e Magazine vengono memorizzati in Catalog, pertanto vengono aggiunti utilizzando l'attributo 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

Esempio 4

Nell'esempio seguente, un contratto dati memorizza un numero e un'operazione da eseguire sul numero. Il membro dati Numbers può essere un numero Integer, una matrice di numeri Integer o un oggetto List<T> che contiene numeri Integer.

Attenzione

Questo funzionerà sul lato client solo se SVCUTIL.EXE è utilizzato per generare un proxy WCF. SVCUTIL.EXE recupera metadati dal servizio inclusi tutti i tipi noti. Senza queste informazioni un client non sarà in grado di deserializzare i tipi.

[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

Questo è il codice dell'applicazione.

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

Tipi conosciuti, ereditarietà e interfacce

Quando un tipo conosciuto è associato a un particolare tipo utilizzando l'attributo KnownTypeAttribute , il tipo conosciuto viene associato anche a tutti i tipi derivati di quel tipo. Si consideri, ad esempio, il codice seguente.

[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

La classe DoubleDrawing non richiede l'attributo KnownTypeAttribute per utilizzare Square e Circle nel campo AdditionalShape , perché nella classe di base (Drawing) questi attributi sono già applicati.

I tipi noti possono essere associati solo a classi e strutture, non a interfacce.

Tipi conosciuti che utilizzano metodi generici aperti

Potrebbe essere necessario aggiungere un tipo generico come tipo conosciuto. Non è tuttavia possibile passare un tipo generico aperto come parametro all'attributo KnownTypeAttribute .

Questo problema può essere risolto utilizzando un meccanismo alternativo, ovvero scrivere un metodo che restituisca un elenco di tipi da aggiungere alla raccolta di tipi noti. Specificare quindi il nome del metodo come argomento di tipo stringa per l'attributo KnownTypeAttribute , per far fronte ad alcune restrizioni.

Il metodo deve esistere nel tipo al quale è applicato l'attributo KnownTypeAttribute , deve essere statico, non deve accettare parametri e deve restituire un oggetto che possa essere assegnato a IEnumerable di Type.

Non è possibile combinare l'attributo KnownTypeAttribute con un nome di metodo e attributi KnownTypeAttribute con i tipi effettivi sullo stesso tipo. Non è inoltre possibile applicare più di un KnownTypeAttribute con un nome di metodo allo stesso tipo.

Fare riferimento alla classe seguente.

[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

Il campo theDrawing contiene istanze di una classe ColorDrawing generica e una classe BlackAndWhiteDrawinggenerica, che ereditano entrambe da una classe Drawinggenerica. In genere, è necessario aggiungerle entrambe a tipi noti, ma quella che segue non è una sintassi valida per gli attributi.

// 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)))>  

Per restituire questi tipi, è pertanto necessario creare un metodo. Nel codice seguente viene illustrato il modo corretto per scrivere questo tipo.

[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

Altri modi per aggiungere tipi noti.

I tipi noti possono essere aggiunti anche tramite un file di configurazione. Ciò è utile quando non si controlla il tipo che richiede tipi noti per una deserializzazione corretta, ad esempio quando si utilizzano librerie dei tipi di terze parti con Windows Communication Foundation (WCF).

Nel file di configurazione seguente viene illustrato come specificare un tipo conosciuto in un file di configurazione.

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

Nel file di configurazione precedente è stato dichiarato che un tipo di contratto dati chiamato MyCompany.Library.Shape ha MyCompany.Library.Circle come tipo conosciuto.

Vedi anche