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 Bekannte Typen.
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 unter dem 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 Object deklariert. Weil jeder Typ von Object erbt 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 Object generiert wird.
Einige Typen, einschließlich der .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-Laufzeitmodul, die Daten in eine Instanz eines CLR (Common Language Runtime)-Typs 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. Das Deserialisierungsmodul versucht dann, den CLR-Typ zu finden, der einen mit dem Nachrichteninhalt kompatiblen Datenvertrag implementiert. Die Gruppe potenzieller Typen, die das Deserialisierungsmodul während dieses Prozesses zulässt, wird als die Gruppe "bekannter Typen" des Deserialisierers bezeichnet.
Eine Methode, das Deserialisierungsmodul ü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. Grundsätzlich wird durch die Anwendung des Attributs ein Typ als "bekannter Typ" angegeben. Dadurch wird der bekannte Typ Bestandteil der Gruppe bekannter Typen, wenn ein Objekt des äußeren Typs oder ein Objekt, auf das durch ein Member des äußeren Typs 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 Object betrachtet). 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
End Class
<DataContract(Name:="Circle")> _
Public Class CircleType
Inherits Shape
End Class
<DataContract(Name:="Triangle")> _
Public Class TriangleType
Inherits Shape
End Class
[DataContract]
public class Shape { }
[DataContract(Name = "Circle")]
public class CircleType : Shape { }
[DataContract(Name = "Triangle")]
public class TriangleType : Shape { }
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 das Deserialisierungsmodul keine Typen mit Datenverträgen namens "Circle" oder "Triangle" erkennt.
<DataContract()> _
Public Class CompanyLogo
<DataMember()> _
Private ShapeOfLogo As Shape
<DataMember()> _
Private ColorOfLogo As Integer
End Class
[DataContract]
public class CompanyLogo
{
[DataMember]
private Shape ShapeOfLogo;
[DataMember]
private int ColorOfLogo;
}
Im folgenden Code wird gezeigt, wie der CompanyLogo
-Typ richtig geschrieben wird.
<DataContract(), KnownType(GetType(CircleType)), KnownType(GetType(TriangleType))> _
Public Class CompanyLogo2
<DataMember()> _
Private ShapeOfLogo As Shape
<DataMember()> _
Private ColorOfLogo As Integer
End Class
[DataContract]
[KnownType(typeof(CircleType))]
[KnownType(typeof(TriangleType))]
public class CompanyLogo2
{
[DataMember]
private Shape ShapeOfLogo;
[DataMember]
private int ColorOfLogo;
}
Jedes Mal, wenn der äußere Typ CompanyLogo2
deserialisiert wird, wird das Deserialisierungsmodul ü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 das Deserialisierungsmodul nur CustomerTypeB
kennt.
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
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;
}
Beispiel 3
Im folgenden Beispiel speichert ein Hashtable seinen Inhalt intern als Object. Um eine Hashtabelle erfolgreich deserialisieren zu können, muss das Deserialisierungsmodul 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 Catalog
gespeichert werden. Daher werden sie mithilfe des KnownTypeAttribute-Attributs hinzugefügt.
<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
[DataContract]
public class Book { }
[DataContract]
public class Magazine { }
[DataContract]
[KnownType(typeof(Book))]
[KnownType(typeof(Magazine))]
public class LibraryCatalog
{
[DataMember]
System.Collections.Hashtable theCatalog;
}
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 sein, das ganze Zahlen enthält.
Vorsicht: |
---|
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(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
[DataContract]
[KnownType(typeof(int[]))]
public class MathOperationData
{
private object numberValue;
[DataMember]
public object Numbers
{
get { return numberValue; }
set { numberValue = value; }
}
//[DataMember]
//public Operation Operation;
}
Dies ist der Anwendungscode.
' 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
// 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;
}
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(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
[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;
}
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 Type zugewiesen 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(Of T)
<DataMember()> _
Private theData As T
<DataMember()> _
Private theDrawing As GenericDrawing(Of T)
End Class
[DataContract]
public class DrawingRecord<T>
{
[DataMember]
private T theData;
[DataMember]
private GenericDrawing<T> theDrawing;
}
Das theDrawing
-Feld enthält Instanzen der generischen Klasse ColorDrawing
und der generischen Klasse BlackAndWhiteDrawing
, die beide von der generischen Klasse Drawing
abgeleitet 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(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
[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;
}
}
Weitere Verfahren zum Hinzufügen bekannter Typen
Sie können Typen zudem der ReadOnlyCollection-Auflistung hinzufügen, auf die über die KnownTypes-Eigenschaft des DataContractSerializer-Objekts zugegriffen wird.
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
Aufgaben
Verweis
KnownTypeAttribute
Hashtable
Object
DataContractSerializer
KnownTypes