Bekannte Typen in Datenverträgen

Die KnownTypeAttribute -Klasse ermöglicht es Ihnen, vorab die Typen anzugeben, die während der Deserialisierung in Betracht gezogen werden sollen. Ein Arbeitsbeispiel finden Sie unter Known Types .

Beim Übergeben von Parametern und Rückgabewerten zwischen einem Client und einem Dienst verwenden normalerweise beide Endpunkte sämtliche Datenverträge für die zu übertragenden Daten gemeinsam. Unter den folgenden Umständen ist dies allerdings nicht der Fall:

  • Der gesendete Datenvertrag wird vom erwarteten Datenvertrag abgeleitet. Weitere Informationen finden Sie im Abschnitt zur Vererbung unter Datenvertragsäquivalenz. In diesem Fall gilt für die übertragenen Daten nicht der Datenvertrag, der vom empfangenden Endpunkt erwartet wird.

  • Die zu übertragenden Informationen werden als zu einem Schnittstellentyp zugehörig deklariert, nicht als Klasse, Struktur oder Enumeration. Daher kann nicht im Vorhinein bekannt sein, welcher die Schnittstelle implementierende Typ tatsächlich gesendet wird, und folglich kann der empfangende Endpunkt nicht vorab den Datenvertrag für die übermittelten Daten bestimmen.

  • Für die zu sendenden Informationen wird der Typ Objectdeklariert. Weil jeder Typ von Objecterbt und nicht im Vorhinein bekannt sein kann, welcher die Schnittstelle implementierender Typ tatsächlich gesendet wird, und kann der empfangende Endpunkt nicht vorab den Datenvertrag für die übermittelten Daten bestimmen. Dies ist ein Sonderfall des ersten Punkts: Jeder Datenvertrag ist vom Standardvertrag abgeleitet, d. h. einem leeren Vertrag, der für Objectgeneriert wird.

  • Einige Typen, einschließlich .NET Framework-Typen, verfügen über Member, die einer der drei oben genannten Kategorien angehören. Zum Beispiel verwendet Hashtable den Typ Object , um die tatsächlichen Objekte in der Hashtabelle zu speichern. Beim Serialisieren dieser Typen kann die Empfängerseite nicht im Voraus den Datenvertrag für diese Member bestimmen.

Die KnownTypeAttribute-Klasse

Wenn die Daten beim empfangenden Endpunkt ankommen, versucht das WCF-Runtimemodul, die Daten in eine Instanz eines CLR-Typs (Common Language Runtime) zu deserialisieren. Zur Auswahl des Typs, der für die Deserialisierung instanziiert wird, wird zuerst die eingehende Nachricht überprüft, um den Datenvertrag zu ermitteln, dem der Inhalt der Nachricht entspricht. Die Deserialisierungs-Engine versucht dann, den CLR-Typ zu finden, der einen mit dem Nachrichteninhalt kompatiblen Datenvertrag implementiert. Die Gruppe potenzieller Typen, die die Deserialisierungs-Engine während dieses Prozesses zulässt, wird als die Gruppe "bekannter Typen" des Deserialisierers bezeichnet.

Eine Methode, die Deserialisierungs-Engine über einen Typ zu informieren, besteht im Einsatz von KnownTypeAttribute. Das Attribut kann nicht auf einzelne Datenmember, sondern nur auf gesamte Datenvertragstypen angewendet werden. Das Attribut wird auf einen äußeren Typ angewendet, der eine Klasse oder eine Struktur sein kann. Bei der einfachsten Verwendung wird durch Anwenden des Attributs ein Typ als „bekannter Typ“ festgelegt. Dies bewirkt, dass der bekannte Typ Teil der Menge bekannter Typen ist, wenn ein Objekt des äußeren Typs oder ein Objekt, auf das durch seine Member verwiesen wird, deserialisiert wird. Auf einen Typ können mehrere KnownTypeAttribute -Attribute angewendet werden.

Bekannte Typen und Primitive

Primitive Typen sowie bestimmte Typen, die als primitiv behandelt werden (z. B. DateTime und XmlElement) sind immer "bekannt" und müssen nie mithilfe dieses Mechanismus hinzugefügt werden. Arrays von primitiven Typen müssen allerdings explizit hinzugefügt werden. Die meisten Auflistungen werden als äquivalent mit Arrays betrachtet. (Nicht generische Auflistungen werden als äquivalent mit Arrays von Objectbetrachtet). Ein Beispiel für die Verwendung von Primitiven, primitiven Arrays und primitiven Auflistungen finden Sie in Beispiel 4.

Hinweis

Im Gegensatz zu anderen primitiven Typen ist die DateTimeOffset -Struktur standardmäßig kein bekannter Typ. Daher muss sie der Liste bekannter Typen manuell hinzugefügt werden.

Beispiele

In den folgenden Beispielen wird die Verwendung der KnownTypeAttribute -Klasse veranschaulicht.

Beispiel 1

Es sind drei Klassen mit einer Vererbungsbeziehung gegeben.

[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

Die folgende CompanyLogo -Klasse kann serialisiert werden. Sie kann jedoch nicht deserialisiert werden, wenn der ShapeOfLogo -Member auf ein CircleType -Objekt oder ein TriangleType -Objekt festgelegt wird, weil die Deserialisierungs-Engine keine Typen mit Datenverträgen namens "Circle" oder "Triangle" erkennt.

[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

Im folgenden Code wird gezeigt, wie der CompanyLogo -Typ richtig geschrieben wird.

[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

Jedes Mal, wenn der äußere Typ CompanyLogo2 deserialisiert wird, wird die Deserialisierungs-Engine über CircleType und TriangleType informiert und kann daher die passenden Typen für die Datenverträge namens "Circle" und "Triangle" finden.

Beispiel 2

Im folgenden Beispiel ist sowohl CustomerTypeA als auch CustomerTypeB der Datenvertrag Customer zugeordnet. Trotz dieser Tatsache wird jedes Mal eine Instanz von CustomerTypeB erstellt, wenn ein PurchaseOrder -Objekt deserialisiert wird, weil die Deserialisierungs-Engine nur CustomerTypeB kennt.

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

Beispiel 3

Im folgenden Beispiel speichert ein Hashtable seinen Inhalt intern als Object. Um eine Hashtabelle erfolgreich deserialisieren zu können, muss die Deserialisierungs-Engine die Gruppe möglicher Typen kennen, die hier vorkommen können. In diesem Fall wissen wir im Vorhinein, dass nur Book -Objekte und Magazine -Objekte im Cataloggespeichert werden. Daher werden sie mithilfe des KnownTypeAttribute -Attributs hinzugefügt.

[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

Beispiel 4

Im folgenden Beispiel wird eine Zahl und ein Vorgang, der mit der Zahl ausgeführt werden soll, in einem Datenvertrag gespeichert. Der Numbers -Datenmember kann eine ganze Zahl, ein Array von ganzen Zahlen oder ein Objekt des Typs List<T> sein, das ganze Zahlen enthält.

Achtung

Dies funktioniert nur auf Clientseite, wenn zum Generieren eines WCF-Proxys SVCUTIL.EXE verwendet wird. SVCUTIL.EXE ruft Metadaten vom Dienst ab, einschließlich aller bekannter Typen. Ohne diese Informationen kann ein Client die Typen nicht deserialisieren.

[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

Dies ist der Anwendungscode.

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

Bekannte Typen, Vererbung und Schnittstellen

Wenn ein bekannter Typ mithilfe des KnownTypeAttribute -Attributs mit einem bestimmten Typ verknüpft wird, wird der bekannte Typ auch mit allen von diesem Typ abgeleiteten Typen verknüpft. Beachten Sie beispielsweise folgenden Code.

[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

Die DoubleDrawing -Klasse erfordert nicht, dass das KnownTypeAttribute -Attribut mit Square und Circle im AdditionalShape -Feld verwendet wird, weil diese Attribute bereits auf die Basisklasse (Drawing) angewendet wurden.

Bekannte Typen können nur Klassen und Strukturen, nicht jedoch Schnittstellen, zugeordnet werden.

Bekannte Typen, die offene generische Methoden verwenden

Es ist möglicherweise notwendig, einen generischen Typ als bekannten Typ hinzuzufügen. Ein offener generischer Typ kann dem KnownTypeAttribute -Attribut nicht als Parameter übergeben werden.

Dieses Problem lässt sich mithilfe eines alternativen Mechanismus lösen: Schreiben Sie eine Methode, die eine Liste derjenigen Typen zurückgibt, welche der Auflistung bekannter Typen hinzugefügt werden können. Wegen einigen Beschränkungen wird der Name der Methode dann als Zeichenfolgenargument für das KnownTypeAttribute -Attribut angegeben.

Diese Methode muss für den Typ vorhanden sein, auf den das KnownTypeAttribute -Attribut angewendet wird. Sie muss statisch sein, darf keine Parameter annehmen und muss ein Objekt zurückgeben, das der IEnumerable -Schnittstelle von Typezugewiesen werden kann.

Es ist nicht möglich, das KnownTypeAttribute -Attribut mit einem Methodennamen und KnownTypeAttribute -Attribute mit tatsächlichen Typen für den gleichen Typ zu kombinieren. Weiterhin ist es nicht möglich, mehr als ein KnownTypeAttribute -Attribut mit einem Methodennamen auf den gleichen Typ anzuwenden.

Siehe folgende Klasse.

[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

Das theDrawing -Feld enthält Instanzen der generischen Klasse ColorDrawing und der generischen Klasse BlackAndWhiteDrawing, die beide von der generischen Klasse Drawingabgeleitet sind. Normalerweise müssen beide Klassen den bekannten Typen hinzugefügt werden, aber der folgende Code stellt keine für Attribute gültige Syntax dar.

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

Daher muss eine Methode erstellt werden, die diese Typen zurückgibt. Im folgenden Code wird gezeigt, wie dieser Typ richtig geschrieben wird.

[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

Weitere Verfahren zum Hinzufügen bekannter Typen

Darüber hinaus können bekannte Typen durch eine Konfigurationsdatei hinzugefügt werden. Dies ist hilfreich, wenn Sie keinen Einfluss auf den Typ haben, dessen Deserialisierung bekannte Typen erfordert, beispielsweise bei Verwendung von Typbibliotheken von Drittanbietern in Windows Communication Foundation (WCF).

Die folgende Konfigurationsdatei zeigt, wie in einer Konfigurationsdatei ein bekannter Typ angegeben wird.

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

In der vorangegangenen Konfigurationsdatei wird ein Datenvertragstyp mit der Bezeichnung MyCompany.Library.Shape deklariert, um MyCompany.Library.Circle als bekannten Typ zu erhalten.

Siehe auch