Serialisering och deserialisering
Windows Communication Foundation (WCF) innehåller en ny serialiseringsmotor, DataContractSerializer. Översätter DataContractSerializer mellan .NET Framework-objekt och XML i båda riktningarna. I det här avsnittet beskrivs hur serialiseraren fungerar.
När .NET Framework-objekt serialiseras förstår serialiseraren en mängd olika serialiseringsprogrammeringsmodeller, inklusive den nya datakontraktsmodellen . En fullständig lista över typer som stöds finns i Typer som stöds av Data Contract Serializer. En introduktion till datakontrakt finns i Använda datakontrakt.
Vid deserialisering av XML använder serialiseraren klasserna XmlReader och XmlWriter . Den stöder också klasserna XmlDictionaryReader och XmlDictionaryWriter för att göra det möjligt för den att skapa optimerad XML i vissa fall, till exempel när du använder det binära XML-formatet WCF.
WCF innehåller också en tillhörande serialiserare, NetDataContractSerializer. NetDataContractSerializer:
- Är inte säker. Mer information finns i säkerhetsguiden för BinaryFormatter.
- Liknar serialiserarna BinaryFormatter och SoapFormatter eftersom den också genererar .NET Framework-typnamn som en del av serialiserade data.
- Används när samma typer delas på serialiseringen och deserialiseringen slutar.
Både DataContractSerializer och NetDataContractSerializer härleds från en gemensam basklass, XmlObjectSerializer.
Varning
Serialiserar DataContractSerializer strängar som innehåller kontrolltecken med ett hexadecimalt värde under 20 som XML-entiteter. Detta kan orsaka problem med en icke-WCF-klient när sådana data skickas till en WCF-tjänst.
Skapa en DataContractSerializer-instans
Att konstruera en instans av DataContractSerializer är ett viktigt steg. Efter konstruktionen kan du inte ändra någon av inställningarna.
Ange rottyp
Rottypen är den typ av instanser som serialiseras eller deserialiseras. Har DataContractSerializer många konstruktoröverlagringar, men minst måste en rottyp anges med parametern type
.
En serialiserare som skapats för en viss rottyp kan inte användas för att serialisera (eller deserialisera) en annan typ, såvida inte typen härleds från rottypen. I följande exempel visas två klasser.
[DataContract]
public class Person
{
// Code not shown.
}
[DataContract]
public class PurchaseOrder
{
// Code not shown.
}
<DataContract()> _
Public Class Person
' Code not shown.
End Class
<DataContract()> _
Public Class PurchaseOrder
' Code not shown.
End Class
Den här koden konstruerar en instans av DataContractSerializer
som endast kan användas för att serialisera eller deserialisera instanser av Person
klassen.
DataContractSerializer dcs = new DataContractSerializer(typeof(Person));
// This can now be used to serialize/deserialize Person but not PurchaseOrder.
Dim dcs As New DataContractSerializer(GetType(Person))
' This can now be used to serialize/deserialize Person but not PurchaseOrder.
Ange kända typer
Om polymorfism ingår i de typer som serialiseras som inte redan hanteras med hjälp av KnownTypeAttribute attributet eller någon annan mekanism, måste en lista över möjliga kända typer skickas till serialiserarens konstruktor med hjälp av parametern knownTypes
. Mer information om kända typer finns i Kända typer av datakontrakt.
I följande exempel visas en klass, LibraryPatron
, som innehåller en samling av en viss typ, LibraryItem
. Den andra klassen definierar LibraryItem
typen. Den tredje och fyra klasserna (Book
och Newspaper
) ärver från LibraryItem
klassen.
[DataContract]
public class LibraryPatron
{
[DataMember]
public LibraryItem[] borrowedItems;
}
[DataContract]
public class LibraryItem
{
// Code not shown.
}
[DataContract]
public class Book : LibraryItem
{
// Code not shown.
}
[DataContract]
public class Newspaper : LibraryItem
{
// Code not shown.
}
<DataContract()> _
Public Class LibraryPatron
<DataMember()> _
Public borrowedItems() As LibraryItem
End Class
<DataContract()> _
Public Class LibraryItem
' Code not shown.
End Class
<DataContract()> _
Public Class Book
Inherits LibraryItem
' Code not shown.
End Class
<DataContract()> _
Public Class Newspaper
Inherits LibraryItem
' Code not shown.
End Class
Följande kod konstruerar en instans av serialiseraren med hjälp av parametern knownTypes
.
// Create a serializer for the inherited types using the knownType parameter.
Type[] knownTypes = new Type[] { typeof(Book), typeof(Newspaper) };
DataContractSerializer dcs =
new DataContractSerializer(typeof(LibraryPatron), knownTypes);
// All types are known after construction.
' Create a serializer for the inherited types using the knownType parameter.
Dim knownTypes() As Type = {GetType(Book), GetType(Newspaper)}
Dim dcs As New DataContractSerializer(GetType(LibraryPatron), knownTypes)
' All types are known after construction.
Ange standardrotnamn och namnområde
När ett objekt serialiseras bestäms normalt standardnamnet och namnområdet för det yttersta XML-elementet enligt datakontraktets namn och namnområde. Namnen på alla inre element bestäms från datamedlemsnamn och deras namnområde är datakontraktets namnområde. Följande exempeluppsättningar Name
och Namespace
värden i konstruktorerna i klasserna DataContractAttribute och DataMemberAttribute .
[DataContract(Name = "PersonContract", Namespace = "http://schemas.contoso.com")]
public class Person2
{
[DataMember(Name = "AddressMember")]
public Address theAddress;
}
[DataContract(Name = "AddressContract", Namespace = "http://schemas.contoso.com")]
public class Address
{
[DataMember(Name = "StreetMember")]
public string street;
}
<DataContract(Name:="PersonContract", [Namespace]:="http://schemas.contoso.com")> _
Public Class Person2
<DataMember(Name:="AddressMember")> _
Public theAddress As Address
End Class
<DataContract(Name:="AddressContract", [Namespace]:="http://schemas.contoso.com")> _
Public Class Address
<DataMember(Name:="StreetMember")> _
Public street As String
End Class
Serialisering av en instans av Person
klassen ger XML som liknar följande.
<PersonContract xmlns="http://schemas.contoso.com">
<AddressMember>
<StreetMember>123 Main Street</StreetMember>
</AddressMember>
</PersonContract>
Du kan dock anpassa standardnamnet och namnområdet för rotelementet genom att skicka värdena för parametrarna rootName
och rootNamespace
till DataContractSerializer konstruktorn. Observera att rootNamespace
inte påverkar namnområdet för de inneslutna element som motsvarar datamedlemmar. Det påverkar endast namnområdet för det yttersta elementet.
Dessa värden kan skickas som strängar eller instanser av klassen för att tillåta deras optimering med hjälp av XmlDictionaryString det binära XML-formatet.
Ange maximal kvot för objekt
Vissa DataContractSerializer
konstruktoröverlagringar har en maxItemsInObjectGraph
parameter. Den här parametern avgör det maximala antalet objekt som serialiseraren serialiserar eller deserialiserar i ett enda ReadObject metodanrop. (Metoden läser alltid ett rotobjekt, men det här objektet kan ha andra objekt i sina datamedlemmar. Objekten kan ha andra objekt och så vidare.) Standardvärdet är 65536. Observera att när matriser serialiseras eller deserialiseras räknas varje matrispost som ett separat objekt. Observera också att vissa objekt kan ha en stor minnesrepresentation, så enbart den här kvoten kanske inte räcker för att förhindra en överbelastningsattack. Mer information finns i Säkerhetsöverväganden för data. Om du behöver öka den här kvoten utöver standardvärdet är det viktigt att göra det både på sidorna för att skicka (serialisera) och ta emot (deserialisera) eftersom det gäller både vid läsning och skrivning av data.
Tur och retur
En rundresa inträffar när ett objekt deserialiseras och serialiseras på nytt i en åtgärd. Det innebär att den går från XML till en objektinstans och tillbaka igen till en XML-dataström.
Vissa DataContractSerializer
konstruktoröverlagringar har en ignoreExtensionDataObject
parameter som är inställd false
på som standard. I det här standardläget kan data skickas tur och retur från en nyare version av ett datakontrakt via en äldre version och tillbaka till den nyare versionen utan förlust, så länge datakontraktet IExtensibleDataObject implementerar gränssnittet. Anta till exempel att version 1 av datakontraktet Person
innehåller Name
datamedlemmarna och PhoneNumber
och version 2 lägger till en Nickname
medlem. Om IExtensibleDataObject
implementeras, när du skickar information från version 2 till version 1, Nickname
lagras data och genereras sedan igen när data serialiseras igen. Därför går inga data förlorade under tur och retur. Mer information finns i Framåtkompatibla datakontrakt och Versionshantering av datakontrakt.
Problem med säkerhet och schema giltighet med tur och retur
Tur och retur kan ha säkerhetskonsekvenser. Till exempel kan det vara en säkerhetsrisk att deserialisera och lagra stora mängder onödiga data. Det kan finnas säkerhetsproblem vid återutsändande av dessa data som det inte finns något sätt att verifiera, särskilt om digitala signaturer är inblandade. I det föregående scenariot kan till exempel version 1-slutpunkten signera ett Nickname
värde som innehåller skadliga data. Slutligen kan det finnas problem med schemats giltighet: en slutpunkt kanske alltid vill generera data som strikt följer det angivna kontraktet och inte några extra värden. I föregående exempel säger version 1-slutpunktens kontrakt att det endast Name
genererar och PhoneNumber
, och om schemavalidering används genererar det extra Nickname
värdet att valideringen misslyckas.
Aktivera och inaktivera rundresor
Om du vill inaktivera rundresor implementerar IExtensibleDataObject du inte gränssnittet. Om du inte har någon kontroll över typerna anger du parametern ignoreExtensionDataObject
till true
för att uppnå samma effekt.
Bevarande av objektdiagram
Normalt bryr sig serialiseraren inte om objektidentitet, som i följande kod.
[DataContract]
public class PurchaseOrder
{
[DataMember]
public Address billTo;
[DataMember]
public Address shipTo;
}
[DataContract]
public class Address
{
[DataMember]
public string street;
}
<DataContract()> _
Public Class PurchaseOrder
<DataMember()> _
Public billTo As Address
<DataMember()> _
Public shipTo As Address
End Class
<DataContract()> _
Public Class Address
<DataMember()> _
Public street As String
End Class
Följande kod skapar en inköpsorder.
// Construct a purchase order:
Address adr = new Address();
adr.street = "123 Main St.";
PurchaseOrder po = new PurchaseOrder();
po.billTo = adr;
po.shipTo = adr;
' Construct a purchase order:
Dim adr As New Address()
adr.street = "123 Main St."
Dim po As New PurchaseOrder()
po.billTo = adr
po.shipTo = adr
Observera att billTo
fälten och shipTo
är inställda på samma objektinstans. Den genererade XML-koden duplicerar dock den duplicerade informationen och ser ut ungefär som följande XML.
<PurchaseOrder>
<billTo><street>123 Main St.</street></billTo>
<shipTo><street>123 Main St.</street></shipTo>
</PurchaseOrder>
Den här metoden har dock följande egenskaper, som kan vara oönskade:
Prestanda. Det är ineffektivt att replikera data.
Cirkelreferenser. Om objekt refererar till sig själva, även via andra objekt, resulterar serialisering efter replikering i en oändlig loop. (Serialiseraren genererar en SerializationException om detta händer.)
Semantics. Ibland är det viktigt att bevara det faktum att två referenser är till samma objekt och inte till två identiska objekt.
Av dessa skäl har vissa DataContractSerializer
konstruktoröverlagringar en preserveObjectReferences
parameter (standardvärdet är false
). När den här parametern är inställd true
på används en särskild metod för kodning av objektreferenser, som endast WCF förstår. När xml-kodexemplet är inställt true
på liknar det nu följande.
<PurchaseOrder ser:id="1">
<billTo ser:id="2"><street ser:id="3">123 Main St.</street></billTo>
<shipTo ser:ref="2"/>
</PurchaseOrder>
Namnområdet "ser" refererar till standardnamnområdet för serialisering, http://schemas.microsoft.com/2003/10/Serialization/
. Varje del av data serialiseras bara en gång och ges ett ID-nummer, och efterföljande användning resulterar i en referens till redan serialiserade data.
Viktigt!
Om både attributen "id" och "ref" finns i datakontraktet XMLElement
respekteras attributet "ref" och attributet "id" ignoreras.
Det är viktigt att förstå begränsningarna i det här läget:
XML-koden som
DataContractSerializer
skapas medpreserveObjectReferences
inställd påtrue
är inte kompatibel med andra tekniker och kan endast nås av en annanDataContractSerializer
instans, även medpreserveObjectReferences
inställd påtrue
.Det finns inget stöd för metadata (schema) för den här funktionen. Schemat som skapas är endast giltigt för fallet när
preserveObjectReferences
är inställt påfalse
.Den här funktionen kan göra att serialiserings- och deserialiseringsprocessen körs långsammare. Även om data inte behöver replikeras måste extra objektjämförelser utföras i det här läget.
Varning
preserveObjectReferences
När läget är aktiverat är det särskilt viktigt att ange maxItemsInObjectGraph
värdet till rätt kvot. På grund av hur matriser hanteras i det här läget är det enkelt för en angripare att skapa ett litet skadligt meddelande som resulterar i stor minnesförbrukning som endast begränsas av maxItemsInObjectGraph
kvoten.
Ange ett datakontraktssurrogat
Vissa DataContractSerializer
konstruktoröverlagringar har en dataContractSurrogate
parameter som kan vara inställd på null
. Annars kan du använda den för att ange en surrogat för datakontrakt, vilket är en typ som implementerar IDataContractSurrogate gränssnittet. Du kan sedan använda gränssnittet för att anpassa serialiserings- och deserialiseringsprocessen. Mer information finns i Surrogater för datakontrakt.
Serialization
Följande information gäller för alla klasser som ärver från XmlObjectSerializer, inklusive klasserna DataContractSerializer och NetDataContractSerializer .
Enkel serialisering
Det mest grundläggande sättet att serialisera ett objekt är att skicka det till WriteObject metoden. Det finns tre överlagringar, en vardera för att skriva till en Stream, en XmlWritereller en XmlDictionaryWriter. Med överlagringen Stream är utdata XML i UTF-8-kodningen. Med överlagringen XmlDictionaryWriter optimerar serialiseraren sina utdata för binär XML.
När du använder WriteObject metoden använder serialiseraren standardnamnet och namnområdet för omslutningselementet och skriver ut det tillsammans med innehållet (se föregående avsnitt "Ange standardrotnamn och namnområde").
I följande exempel visas hur du skriver med en XmlDictionaryWriter.
Person p = new Person();
DataContractSerializer dcs =
new DataContractSerializer(typeof(Person));
XmlDictionaryWriter xdw =
XmlDictionaryWriter.CreateTextWriter(someStream,Encoding.UTF8 );
dcs.WriteObject(xdw, p);
Dim p As New Person()
Dim dcs As New DataContractSerializer(GetType(Person))
Dim xdw As XmlDictionaryWriter = _
XmlDictionaryWriter.CreateTextWriter(someStream, Encoding.UTF8)
dcs.WriteObject(xdw, p)
Detta genererar XML som liknar följande.
<Person>
<Name>Jay Hamlin</Name>
<Address>123 Main St.</Address>
</Person>
Stegvis serialisering
WriteStartObjectAnvänd metoderna , WriteObjectContentoch WriteEndObject för att skriva slutelementet, skriva objektinnehållet och stänga omslutningselementet.
Kommentar
Det finns inga Stream överlagringar av dessa metoder.
Den här stegvisa serialiseringen har två vanliga användningsområden. Det ena är att infoga innehåll som attribut eller kommentarer mellan WriteStartObject
och WriteObjectContent
, som du ser i följande exempel.
dcs.WriteStartObject(xdw, p);
xdw.WriteAttributeString("serializedBy", "myCode");
dcs.WriteObjectContent(xdw, p);
dcs.WriteEndObject(xdw);
dcs.WriteStartObject(xdw, p)
xdw.WriteAttributeString("serializedBy", "myCode")
dcs.WriteObjectContent(xdw, p)
dcs.WriteEndObject(xdw)
Detta genererar XML som liknar följande.
<Person serializedBy="myCode">
<Name>Jay Hamlin</Name>
<Address>123 Main St.</Address>
</Person>
En annan vanlig användning är att undvika att använda WriteStartObject och WriteEndObject helt och hållet, och att skriva ett eget anpassat omslutningselement (eller till och med hoppa över att skriva en omslutning helt och hållet), som du ser i följande kod.
xdw.WriteStartElement("MyCustomWrapper");
dcs.WriteObjectContent(xdw, p);
xdw.WriteEndElement();
xdw.WriteStartElement("MyCustomWrapper")
dcs.WriteObjectContent(xdw, p)
xdw.WriteEndElement()
Detta genererar XML som liknar följande.
<MyCustomWrapper>
<Name>Jay Hamlin</Name>
<Address>123 Main St.</Address>
</MyCustomWrapper>
Kommentar
Om du använder stegvis serialisering kan det resultera i schema-ogiltig XML.
Deserialisering
Följande information gäller för alla klasser som ärver från XmlObjectSerializer, inklusive klasserna DataContractSerializer och NetDataContractSerializer .
Det mest grundläggande sättet att deserialisera ett objekt är att anropa en av ReadObject metodöverlagringarna. Det finns tre överlagringar, en var för läsning med en XmlDictionaryReader, en XmlReader
eller en Stream
. Observera att överlagringen Stream
skapar en text som XmlDictionaryReader inte skyddas av några kvoter och endast ska användas för att läsa betrodda data.
Observera också att objektet ReadObject
som metoden returnerar måste gjutas till lämplig typ.
Följande kod konstruerar en instans av DataContractSerializer och sedan XmlDictionaryReaderdeserialiserar en Person
instans.
DataContractSerializer dcs = new DataContractSerializer(typeof(Person));
FileStream fs = new FileStream(path, FileMode.Open);
XmlDictionaryReader reader =
XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas());
Person p = (Person)dcs.ReadObject(reader);
Dim dcs As New DataContractSerializer(GetType(Person))
Dim fs As New FileStream(path, FileMode.Open)
Dim reader As XmlDictionaryReader = _
XmlDictionaryReader.CreateTextReader(fs, New XmlDictionaryReaderQuotas())
Dim p As Person = CType(dcs.ReadObject(reader), Person)
Innan du anropar ReadObject metoden placerar du XML-läsaren på omslutningselementet eller på en nod som inte är innehåll som föregår omslutningselementet. Du kan göra detta genom att anropa Read metoden XmlReader för eller dess härledning och testa NodeType, som du ser i följande kod.
DataContractSerializer ser = new DataContractSerializer(typeof(Person),
"Customer", @"http://www.contoso.com");
FileStream fs = new FileStream(path, FileMode.Open);
XmlDictionaryReader reader =
XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas());
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
if (ser.IsStartObject(reader))
{
Console.WriteLine("Found the element");
Person p = (Person)ser.ReadObject(reader);
Console.WriteLine("{0} {1} id:{2}",
p.Name , p.Address);
}
Console.WriteLine(reader.Name);
break;
}
}
Dim ser As New DataContractSerializer(GetType(Person), "Customer", "http://www.contoso.com")
Dim fs As New FileStream(path, FileMode.Open)
Dim reader As XmlDictionaryReader = XmlDictionaryReader.CreateTextReader(fs, New XmlDictionaryReaderQuotas())
While reader.Read()
Select Case reader.NodeType
Case XmlNodeType.Element
If ser.IsStartObject(reader) Then
Console.WriteLine("Found the element")
Dim p As Person = CType(ser.ReadObject(reader), Person)
Console.WriteLine("{0} {1}", _
p.Name, p.Address)
End If
Console.WriteLine(reader.Name)
End Select
End While
Observera att du kan läsa attribut för det här omslutningselementet innan du ger läsaren till ReadObject
.
När du använder en av de enkla ReadObject
överlagringarna letar deserialiseraren efter standardnamnet och namnområdet på omslutningselementet (se föregående avsnitt, "Ange standardrotnamn och namnområde") och genererar ett undantag om det hittar ett okänt element. I föregående exempel förväntas omslutningselementet <Person>
. Metoden IsStartObject anropas för att verifiera att läsaren är placerad på ett element som heter som förväntat.
Det finns ett sätt att inaktivera namnkontrollen för det här omslutningselementet. vissa överlagringar av ReadObject
metoden tar den booleska parametern verifyObjectName
, som är inställd true
på som standard. När värdet false
är inställt på ignoreras namnet och namnområdet för omslutningselementet. Detta är användbart för att läsa XML som skrevs med hjälp av den stegvisa serialiseringsmekanismen som beskrevs tidigare.
Använda NetDataContractSerializer
Den primära skillnaden mellan DataContractSerializer
och är att DataContractSerializer
använder namn på datakontrakt, medan NetDataContractSerializer
utdata för fullständig .NET Framework-sammansättning och typnamn i NetDataContractSerializer serialiserad XML. Det innebär att exakt samma typer måste delas mellan serialiserings- och deserialiseringsslutpunkterna. Det innebär att mekanismen för kända typer inte krävs med NetDataContractSerializer
eftersom de exakta typer som ska deserialiseras alltid är kända.
Flera problem kan dock uppstå:
Säkerhet. Alla typer som finns i XML-koden som deserialiseras läses in. Detta kan utnyttjas för att tvinga inläsning av skadliga typer. Användning av
NetDataContractSerializer
ej betrodda data bör endast göras om en Serialiseringsbindning används (med egenskapen Binder eller konstruktorparametern). Pärmen tillåter endast att säkra typer läses in. Binder-mekanismen är identisk med den som används i System.Runtime.Serialization namnområdet.Versionshantering. Om du använder fullständiga typ- och sammansättningsnamn i XML begränsas allvarligt hur typer kan versionshanteras. Det går inte att ändra följande: typnamn, namnområden, sammansättningsnamn och sammansättningsversioner. AssemblyFormat Om du anger egenskapen eller konstruktorparametern till Simple i stället för standardvärdet för tillåts ändringar i Full sammansättningsversioner, men inte för generiska parametertyper.
Samverkan. Eftersom .NET Framework-typ- och sammansättningsnamn ingår i XML kan andra plattformar än .NET Framework inte komma åt resulterande data.
Prestanda. Om du skriver ut typ- och sammansättningsnamnen ökar storleken på den resulterande XML:en avsevärt.
Den här mekanismen liknar binär- eller SOAP-serialisering som används av .NET Framework-fjärrkommunikation (specifikt BinaryFormatter och SoapFormatter).
NetDataContractSerializer
Att använda liknar att använda DataContractSerializer
, med följande skillnader:
Konstruktorerna kräver inte att du anger en rottyp. Du kan serialisera vilken typ som helst med samma instans av
NetDataContractSerializer
.Konstruktorerna accepterar inte en lista över kända typer. Mekanismen för kända typer är onödig om typnamn serialiseras till XML.
Konstruktorerna accepterar inte ett datakontrakts surrogat. I stället accepterar de en ISurrogateSelector parameter som heter
surrogateSelector
(som mappar till egenskapen SurrogateSelector ). Det här är en äldre surrogatmekanism.Konstruktorerna accepterar en parameter som kallas
assemblyFormat
för den FormatterAssemblyStyle som mappar till egenskapen AssemblyFormat . Som tidigare nämnts kan detta användas för att förbättra serialiserarens versionsfunktioner. Detta är identiskt med mekanismen FormatterAssemblyStyle i binär- eller SOAP-serialisering.Konstruktörerna accepterar en StreamingContext parameter med namnet
context
som mappar till egenskapen Context . Du kan använda detta för att skicka information till typer som serialiseras. Den här användningen är identisk med den StreamingContext mekanism som används i andra System.Runtime.Serialization klasser.Metoderna Serialize och Deserialize är alias för WriteObject metoderna och ReadObject . Dessa finns för att ge en mer konsekvent programmeringsmodell med binär- eller SOAP-serialisering.
Mer information om dessa funktioner finns i Binär serialisering.
DE XML-format som NetDataContractSerializer
används och DataContractSerializer
är normalt inte kompatibla. Att försöka serialisera med en av dessa serialiserare och deserialisera med den andra är alltså inte ett scenario som stöds.
Observera också att NetDataContractSerializer
inte matar ut den fullständiga .NET Framework-typen och sammansättningsnamnet för varje nod i objektdiagrammet. Den matar ut den informationen endast där den är tvetydig. Det vill:et matas ut på rotobjektnivå och för alla polymorfa fall.