Kända typer av datakontrakt
Med KnownTypeAttribute klassen kan du i förväg ange vilka typer som ska tas med för övervägande under deserialiseringen. Ett fungerande exempel finns i exemplet Kända typer .
När parametrar skickas och returneras mellan en klient och en tjänst delar normalt båda slutpunkterna alla datakontrakt för de data som ska överföras. Detta är dock inte fallet under följande omständigheter:
Det skickade datakontraktet härleds från det förväntade datakontraktet. Mer information finns i avsnittet om arv i Data Contract Equivalence). I så fall har de överförda data inte samma datakontrakt som förväntat av den mottagande slutpunkten.
Den deklarerade typen för den information som ska överföras är ett gränssnitt, till skillnad från en klass, struktur eller uppräkning. Det går därför inte att i förväg veta vilken typ som implementerar gränssnittet som faktiskt skickas och därför kan den mottagande slutpunkten inte i förväg fastställa datakontraktet för de överförda uppgifterna.
Den deklarerade typen för den information som ska överföras är Object. Eftersom varje typ ärver från Object, och det inte går att veta i förväg vilken typ som faktiskt skickas, kan den mottagande slutpunkten inte i förväg fastställa datakontraktet för de överförda data. Det här är ett specialfall för det första objektet: Varje datakontrakt härleds från standardvärdet, ett tomt datakontrakt som genereras för Object.
Vissa typer, som omfattar .NET Framework-typer, har medlemmar som finns i någon av de föregående tre kategorierna. Till exempel Hashtable används Object för att lagra de faktiska objekten i hash-tabellen. När du serialiserar dessa typer kan den mottagande sidan inte i förväg fastställa datakontraktet för dessa medlemmar.
Klassen KnownTypeAttribute
När data kommer till en mottagande slutpunkt försöker WCF-körningen deserialisera data till en instans av en CLR-typ (Common Language Runtime). Den typ som instansieras för deserialisering väljs genom att först inspektera det inkommande meddelandet för att fastställa det datakontrakt som innehållet i meddelandet överensstämmer med. Deserialiseringsmotorn försöker sedan hitta en CLR-typ som implementerar ett datakontrakt som är kompatibelt med meddelandeinnehållet. Den uppsättning kandidattyper som deserialiseringsmotorn tillåter under den här processen kallas deserialiserarens uppsättning av "kända typer".
Ett sätt att låta deserialiseringsmotorn veta om en typ är genom att använda KnownTypeAttribute. Attributet kan inte tillämpas på enskilda datamedlemmar, bara för hela datakontraktstyper. Attributet tillämpas på en yttre typ som kan vara en klass eller en struktur. I den mest grundläggande användningen anger användning av attributet en typ som en "känd typ". Detta gör att den kända typen är en del av uppsättningen kända typer när ett objekt av den yttre typen eller något objekt som hänvisas till via dess medlemmar deserialiseras. Mer än ett KnownTypeAttribute attribut kan tillämpas på samma typ.
Kända typer och primitiver
Primitiva typer, liksom vissa typer som behandlas som primitiver (till exempel DateTime och XmlElement) är alltid "kända" och behöver aldrig läggas till via den här mekanismen. Matriser med primitiva typer måste dock läggas till explicit. De flesta samlingar anses motsvara matriser. (Icke-generiska samlingar anses motsvara matriser med Object). Ett exempel på hur du använder primitiver, primitiva matriser och primitiva samlingar finns i Exempel 4.
Kommentar
Till skillnad från andra primitiva typer DateTimeOffset är strukturen inte en känd typ som standard, så den måste läggas till manuellt i listan över kända typer.
Exempel
I följande exempel visas den klass som KnownTypeAttribute används.
Exempel 1
Det finns tre klasser med en arvsrelation.
[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
Följande CompanyLogo
klass kan serialiseras, men kan inte deserialiseras om ShapeOfLogo
medlemmen är inställd på antingen ett CircleType
eller ett TriangleType
objekt, eftersom deserialiseringsmotorn inte känner igen några typer med datakontraktsnamnen "Circle" eller "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
Rätt sätt att skriva CompanyLogo
typen visas i följande kod.
[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
När den yttre typen CompanyLogo2
deserialiseras känner CircleType
deserialiseringsmotorn till och TriangleType
kan därför hitta matchande typer för datakontrakten "Circle" och "Triangle".
Exempel 2
I följande exempel, även om både CustomerTypeA
och CustomerTypeB
har datakontraktet Customer
, skapas en instans av CustomerTypeB
när en PurchaseOrder
deserialiseras, eftersom endast CustomerTypeB
är känd för deserialiseringsmotorn.
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
Exempel 3
I följande exempel lagrar en Hashtable dess innehåll internt som Object. Om du vill deserialisera en hash-tabell måste deserialiseringsmotorn känna till den uppsättning möjliga typer som kan inträffa där. I det här fallet vet vi i förväg att endast Book
och Magazine
objekt lagras i Catalog
, så de läggs till med hjälp av KnownTypeAttribute attributet.
[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
Exempel 4
I följande exempel lagrar ett datakontrakt ett tal och en åtgärd som ska utföras på talet. Datamedlemmen Numbers
kan vara ett heltal, en matris med heltal eller ett List<T> heltal.
Varning
Detta fungerar bara på klientsidan om SVCUTIL.EXE används för att generera en WCF-proxy. SVCUTIL.EXE hämtar metadata från tjänsten, inklusive alla kända typer. Utan den här informationen kan en klient inte deserialisera typerna.
[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
Det här är programkoden.
// 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
Kända typer, arv och gränssnitt
När en känd typ är associerad med en viss typ med hjälp av KnownTypeAttribute
attributet associeras även den kända typen med alla härledda typer av den typen. Se till exempel följande 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
Klassen DoubleDrawing
kräver KnownTypeAttribute
inte att attributet används Square
och Circle
i AdditionalShape
fältet eftersom basklassen (Drawing
) redan har dessa attribut tillämpade.
Kända typer kan endast associeras med klasser och strukturer, inte gränssnitt.
Kända typer med öppna allmänna metoder
Det kan vara nödvändigt att lägga till en generisk typ som en känd typ. En öppen allmän typ kan dock inte skickas som en parameter till attributet KnownTypeAttribute
.
Det här problemet kan lösas med hjälp av en alternativ mekanism: Skriv en metod som returnerar en lista med typer som ska läggas till i samlingen med kända typer. Namnet på metoden anges sedan som ett strängargument till KnownTypeAttribute
attributet på grund av vissa begränsningar.
Metoden måste finnas på den typ som KnownTypeAttribute
attributet tillämpas på, måste vara statiskt, får inte acceptera några parametrar och måste returnera ett objekt som kan tilldelas till IEnumerable .Type
Du kan inte kombinera KnownTypeAttribute
attributet med ett metodnamn och KnownTypeAttribute
attribut med faktiska typer av samma typ. Dessutom kan du inte tillämpa fler än en KnownTypeAttribute
med ett metodnamn på samma typ.
Se följande klass.
[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
Fältet theDrawing
innehåller instanser av en generisk klass ColorDrawing
och en generisk klass BlackAndWhiteDrawing
, som båda ärver från en generisk klass Drawing
. Normalt måste båda läggas till i kända typer, men följande är inte giltig syntax för attribut.
// 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)))>
Därför måste en metod skapas för att returnera dessa typer. Det rätta sättet att skriva den här typen visas sedan i följande kod.
[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
Ytterligare sätt att lägga till kända typer
Dessutom kan kända typer läggas till via en konfigurationsfil. Detta är användbart när du inte styr den typ som kräver kända typer för korrekt deserialisering, till exempel när du använder bibliotek av typen tredje part med Windows Communication Foundation (WCF).
Följande konfigurationsfil visar hur du anger en känd typ i en konfigurationsfil.
<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>
I den föregående konfigurationsfilen deklareras en datakontraktstyp som heter MyCompany.Library.Shape
ha MyCompany.Library.Circle
som en känd typ.