Znane typy kontraktów danych
Klasa KnownTypeAttribute umożliwia określenie z wyprzedzeniem typów, które należy uwzględnić podczas deserializacji. Aby zapoznać się z przykładem roboczym, zobacz przykład Znane typy .
Zwykle podczas przekazywania parametrów i zwracania wartości między klientem a usługą oba punkty końcowe współdzielą wszystkie kontrakty danych, które mają być przesyłane. Nie ma to jednak zastosowania w następujących okolicznościach:
Wysłany kontrakt danych pochodzi z oczekiwanego kontraktu danych. Aby uzyskać więcej informacji, zobacz sekcję dotyczącą dziedziczenia w artykule Równoważność kontraktu danych. W takim przypadku przesyłane dane nie mają takiego samego kontraktu danych, jak oczekiwano przez odbierający punkt końcowy.
Zadeklarowany typ przekazywanych informacji jest interfejsem, w przeciwieństwie do klasy, struktury lub wyliczenia. W związku z tym nie można znać z wyprzedzeniem, który typ implementujący interfejs jest rzeczywiście wysyłany, a zatem punkt końcowy odbierający nie może wcześniej określić kontraktu danych dla przesyłanych danych.
Zadeklarowany typ informacji, które mają być przesyłane, to Object. Ponieważ każdy typ dziedziczy z Objectelementu i nie może być wcześniej znany, który typ jest rzeczywiście wysyłany, punkt końcowy odbierający nie może określić z wyprzedzeniem kontraktu danych dla przesyłanych danych. Jest to szczególny przypadek pierwszego elementu: każdy kontrakt danych pochodzi od domyślnego, pustego kontraktu danych generowanego dla elementu Object.
Niektóre typy, które obejmują typy programu .NET Framework, mają elementy członkowskie należące do jednej z poprzednich trzech kategorii. Na przykład Hashtable używa Object metody do przechowywania rzeczywistych obiektów w tabeli skrótów. Podczas serializacji tych typów strona odbierania nie może określić z wyprzedzeniem kontraktu danych dla tych elementów członkowskich.
Klasa KnownTypeAttribute
Gdy dane docierają do odbierającego punktu końcowego, środowisko uruchomieniowe programu WCF próbuje deserializować dane w wystąpieniu typu środowiska uruchomieniowego języka wspólnego (CLR). Typ, który jest tworzone na potrzeby deserializacji, jest wybierany przez najpierw sprawdzenie komunikatu przychodzącego w celu określenia kontraktu danych, z którym jest zgodna zawartość komunikatu. Aparat deserializacji próbuje znaleźć typ CLR, który implementuje kontrakt danych zgodny z zawartością komunikatu. Zestaw typów kandydatów, które aparat deserializacji umożliwia w trakcie tego procesu, jest określany jako zestaw deserializatora "znanych typów".
Jednym ze sposobów, aby poinformować aparat deserializacji o typie, jest użycie klasy KnownTypeAttribute. Atrybutu nie można zastosować do poszczególnych składowych danych, tylko do całych typów kontraktów danych. Atrybut jest stosowany do typu zewnętrznego, który może być klasą lub strukturą. W najbardziej podstawowym użyciu zastosowanie atrybutu określa typ jako "znany typ". Powoduje to, że znany typ jest częścią zestawu znanych typów, gdy obiekt typu zewnętrznego lub dowolny obiekt, do którego odwołuje się jego składowe, jest deserializowany. Do tego samego typu można zastosować więcej niż jeden KnownTypeAttribute atrybut.
Znane typy i typy pierwotne
Typy pierwotne, a także niektóre typy traktowane jako typy pierwotne (na przykład DateTime i XmlElement) są zawsze "znane" i nigdy nie muszą być dodawane za pomocą tego mechanizmu. Należy jednak jawnie dodać tablice typów pierwotnych. Większość kolekcji jest uważana za równoważną tablicom. (Kolekcje inne niż ogólne są uważane za równoważne tablicom Object). Przykład użycia elementów pierwotnych, tablic pierwotnych i kolekcji pierwotnych można znaleźć w przykładzie 4.
Uwaga
W przeciwieństwie do innych typów pierwotnych DateTimeOffset struktura nie jest domyślnie znanym typem, dlatego należy ją ręcznie dodać do listy znanych typów.
Przykłady
W poniższych przykładach pokazano klasę używaną KnownTypeAttribute .
Przykład 1
Istnieją trzy klasy z relacją dziedziczenia.
[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
Poniższa CompanyLogo
klasa może być serializowana, ale nie można wykonać deserializacji, jeśli ShapeOfLogo
element członkowski jest ustawiony na CircleType
obiekt lub TriangleType
obiekt, ponieważ aparat deserializacji nie rozpoznaje żadnych typów z nazwami kontraktów danych "Circle" lub "Trójkąt".
[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
Prawidłowy sposób na napisanie CompanyLogo
typu jest wyświetlany w poniższym kodzie.
[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
Za każdym razem, gdy typ CompanyLogo2
zewnętrzny jest deserializowany, aparat deserializacji wie o CircleType
i TriangleType
, w związku z tym, jest w stanie znaleźć pasujące typy kontraktów danych "Circle" i "Trójkąt".
Przykład 2
W poniższym przykładzie, mimo że zarówno CustomerTypeA
kontrakt danych, jak i CustomerTypeB
mają Customer
kontrakt danych, wystąpienie CustomerTypeB
jest tworzone za każdym razem, gdy PurchaseOrder
element jest deserializowany, ponieważ jest znany tylko CustomerTypeB
aparatowi deserializacji.
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
Przykład 3
W poniższym przykładzie zawartość Hashtable jest przechowywana wewnętrznie jako Object. Aby pomyślnie wykonać deserializacji tabeli skrótów, aparat deserializacji musi znać zestaw możliwych typów, które mogą tam wystąpić. W tym przypadku wiemy z wyprzedzeniem, że tylko obiekty Book
i Magazine
są przechowywane w Catalog
obiekcie , więc są one dodawane przy użyciu atrybutu 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
Przykład 4
W poniższym przykładzie kontrakt danych przechowuje liczbę i operację do wykonania na liczbie. Element Numbers
członkowski danych może być liczbą całkowitą, tablicą liczb całkowitych lub zawierającą List<T> liczby całkowite.
Uwaga
Będzie to działać tylko po stronie klienta, jeśli SVCUTIL.EXE jest używany do generowania serwera proxy WCF. SVCUTIL.EXE pobiera metadane z usługi, w tym wszelkie znane typy. Bez tych informacji klient nie będzie mógł wykonać deserializacji typów.
[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
Jest to kod aplikacji.
// 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
Znane typy, dziedziczenie i interfejsy
Gdy znany typ jest skojarzony z określonym typem przy użyciu atrybutu KnownTypeAttribute
, znany typ jest również skojarzony ze wszystkimi typami pochodnymi tego typu. Zobacz na przykład następujący kod.
[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
Klasa DoubleDrawing
nie wymaga użycia atrybutu KnownTypeAttribute
Square
i Circle
w AdditionalShape
polu, ponieważ klasa podstawowa (Drawing
) ma już zastosowane te atrybuty.
Znane typy mogą być skojarzone tylko z klasami i strukturami, a nie z interfejsami.
Znane typy korzystające z otwartych metod ogólnych
Może być konieczne dodanie typu ogólnego jako znanego typu. Nie można jednak przekazać otwartego typu ogólnego jako parametru do atrybutu KnownTypeAttribute
.
Ten problem można rozwiązać przy użyciu alternatywnego mechanizmu: Napisz metodę zwracającą listę typów, które mają zostać dodane do znanej kolekcji typów. Nazwa metody jest następnie określana jako argument ciągu atrybutu KnownTypeAttribute
z powodu pewnych ograniczeń.
Metoda musi istnieć w typie, do którego KnownTypeAttribute
zastosowano atrybut, musi być statyczna, nie musi przyjmować żadnych parametrów i musi zwrócić obiekt, który można przypisać do TypeIEnumerable klasy .
Nie można połączyć atrybutu KnownTypeAttribute
z nazwą metody i KnownTypeAttribute
atrybutami z rzeczywistymi typami w tym samym typie. Ponadto nie można zastosować więcej niż jednego KnownTypeAttribute
z nazwą metody do tego samego typu.
Zobacz następującą klasę.
[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
Pole theDrawing
zawiera wystąpienia klasy ColorDrawing
ogólnej i klasy BlackAndWhiteDrawing
ogólnej , z których oba dziedziczą z klasy Drawing
ogólnej . Zwykle oba muszą być dodawane do znanych typów, ale poniższa składnia nie jest prawidłowa dla atrybutów.
// 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)))>
W związku z tym należy utworzyć metodę, aby zwrócić te typy. Prawidłowy sposób na napisanie tego typu, a następnie, jest wyświetlany w poniższym kodzie.
[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
Dodatkowe sposoby dodawania znanych typów
Ponadto znane typy można dodawać za pośrednictwem pliku konfiguracji. Jest to przydatne, gdy nie kontrolujesz typu, który wymaga znanych typów do właściwej deserializacji, na przykład w przypadku używania bibliotek typów innych firm z programem Windows Communication Foundation (WCF).
Poniższy plik konfiguracji przedstawia sposób określania znanego typu w pliku konfiguracji.
<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>
W poprzednim pliku konfiguracji typ kontraktu danych o nazwie MyCompany.Library.Shape
jest deklarowany MyCompany.Library.Circle
jako znany typ.