Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Windows Communication Foundation (WCF) innehåller en ny serialiseringsmotor, DataContractSerializer. DataContractSerializer översätts mellan .NET Framework-objekt och XML i båda riktningarna. I det här avsnittet beskrivs hur serialiseraren fungerar.
Vid serialisering av .NET Framework-objekt förstår serialiseraren en mängd olika serialiseringsprogrammeringsmodeller, inklusive den nya datakontraktet modell. 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 Using Data Contracts.
Vid deserialisering av XML använder serialiseraren klasserna XmlReader och XmlWriter. Den stöder också klasserna XmlDictionaryReader och XmlDictionaryWriter så att den kan producera optimerad XML i vissa fall, till exempel när du använder det binära XML-formatet WCF.
WCF innehåller även en tillhörande serialiserare, NetDataContractSerializer. Den NetDataContractSerializer:
- Är inte säker. Mer information finns i säkerhetsguiden för BinaryFormatter.
- Liknar de BinaryFormatter och SoapFormatter serialiserare eftersom det också genererar .NET Framework-typnamn som en del av serialiserade data.
- Används när samma typer delas på både serialiserings- och deserialiseringssidan.
Både DataContractSerializer och NetDataContractSerializer härledas från en gemensam basklass, XmlObjectSerializer.
Varning
DataContractSerializer serialiserar 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 skapa en instans av DataContractSerializer är ett viktigt steg. Efter konstruktionen kan du inte ändra någon av inställningarna.
Specifikation av rottyp
Den rottypen är den typ av instanser som serialiseras eller deserialiseras.
DataContractSerializer har många konstruktoröverlagringar, men som minst måste en rottyp anges med hjälp av 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 attributet KnownTypeAttribute 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 typ LibraryItem. De tredje och fyra klasserna (Book och Newspaper) ärver från klassen LibraryItem.
[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. I följande exempel anges 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
Om du serialiserar en instans av klassen Person skapas 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 XmlDictionaryString så att de kan optimeras med hjälp av det binära XML-formatet.
Ange maximal kvot för objekt
Vissa överlagringar av DataContractSerializer-konstruktorn 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 tur-och-retur- inträffar när ett objekt deserialiseras och serialiseras på nytt i en och samma operation. 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 på false 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 implementerar IExtensibleDataObject-gränssnittet. Anta till exempel att version 1 av Person-datakontraktet innehåller Name och PhoneNumber datamedlemmar, och version 2 lägger till en Nickname medlem. Om IExtensibleDataObject implementeras, när du skickar information från version 2 till version 1, lagras Nickname data och genereras sedan igen när data serialiseras igen. Därför går inga data förlorade under tur och retur. För mer information, se Forward-Compatible Datakontrakt och Versionshantering av datakontrakt.
Problem med säkerhet och giltighet av schema i rundresor
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 skickar ut Name och PhoneNumber, och om schemavalidering används, gör det extravärdet Nickname att valideringen misslyckas.
Aktivera och inaktivera rundresor
Om du vill inaktivera rundresor implementerar du inte IExtensibleDataObject-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 fälten billTo och shipTo anges till 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.)
Semantik. Ibland är det viktigt att bevara det faktum att två referenser är till samma objekt och inte till två identiska objekt.
Därför har vissa DataContractSerializer konstruktoröverlagringar en preserveObjectReferences parameter (standardvärdet är false). När den här parametern är inställd på trueanvänds en särskild metod för kodning av objektreferenser, som endast WCF förstår. När xml-kodexemplet är inställt på trueliknar 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:
DEN XML som
DataContractSerializerskapar medpreserveObjectReferencesinställd påtrueär inte kompatibel med andra tekniker och kan endast nås av en annanDataContractSerializer-instans, även medpreserveObjectReferencesinstä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.
Försiktighet
När preserveObjectReferences 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 ställas in på null. Annars kan du använda den för att ange en surrogat för datakontrakt, som ä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 Data Contract Surrogates.
Serialisering
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 metoden WriteObject. Det finns tre överbelastningar, en var för att skriva till en Stream, en XmlWritereller en XmlDictionaryWriter. Med Stream överbelastning är utdata XML i UTF-8 kodning. Med XmlDictionaryWriter överlagring optimerar serialiseraren sina utdata för binär XML.
När du använder metoden WriteObject 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>
Steg–By-Step Serialisering
Använd metoderna WriteStartObject, WriteObjectContentoch WriteEndObject för att skriva slutelementet, skriva objektinnehållet och stänga omslutningselementet.
Anmärkning
Det finns inga Stream överbelastningar 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>
Anmärkning
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 de ReadObject metodens överlagringar. Det finns tre överbelastningar, en för varje läsning med en XmlDictionaryReader, en XmlReadereller en Stream. Observera att Stream överlagring skapar en textbaserad XmlDictionaryReader som inte skyddas av några kvoter och endast ska användas för att läsa betrodda data.
Observera också att objektet som ReadObject-metoden returnerar måste kastas till lämplig typ.
Följande kod konstruerar en instans av DataContractSerializer och en XmlDictionaryReaderoch deserialiserar sedan 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 metoden ReadObject 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 metoden Read för XmlReader eller dess härledning och testa NodeType, enligt 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($"{p.Name} {p.Address} id:{2}");
}
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 på det här omslutningselementet innan du överför läsaren till ReadObject.
När du använder en av de enkla ReadObject överlagringar letar deserialiseraren efter standardnamnet och namnområdet i omslutningselementet (se föregående avsnitt, "Ange standardrotnamn och namnområde") och utlöser ett undantag om det hittar ett okänt element. I föregående exempel förväntas omslags-elementet <Person>. Metoden IsStartObject anropas för att verifiera att läsaren är placerad på ett element med det förväntade namnet.
Det finns ett sätt att inaktivera namnkontrollen för det här omslutningselementet. vissa överlagringar av metoden ReadObject tar den booleska parametern verifyObjectName, som är inställd på true som standard. När värdet är inställt på falseignoreras 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 främsta skillnaden mellan DataContractSerializer och NetDataContractSerializer är att DataContractSerializer använder namn på datakontrakt, medan NetDataContractSerializer matar ut fullständiga .NET Framework-sammansättnings- och typnamn i 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 typerna som ska deserialiseras alltid är kända.
Flera problem kan dock uppstå:
Säkerhet. Alla datatyper som finns i XML-strukturen som deserialiseras läses in. Detta kan utnyttjas för att tvinga inläsning av skadliga typer. Användning av
NetDataContractSerializermed obetrodda data bör endast ske om en Serialiseringsbinder 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 namnområdet System.Runtime.Serialization.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. Om du anger AssemblyFormat-egenskapen eller konstruktorparametern till Simple i stället för standardvärdet för Full kan du ändra sammansättningsversioner, men inte för allmänna 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 (särskilt BinaryFormatter och SoapFormatter).
Att använda NetDataContractSerializer liknar att använda DataContractSerializer, med följande skillnader:
Konstruktorerna kräver inte att du anger en rottyp. Du kan serialisera alla typer 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 surrogat för datakontrakt. I stället accepterar de en ISurrogateSelector parameter med namnet
surrogateSelector(som mappar till egenskapen SurrogateSelector). Det här är en äldre surrogatmekanism.Konstruktorerna accepterar en parameter som heter
assemblyFormatför 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 den FormatterAssemblyStyle mekanismen i binär- eller SOAP-serialisering.Konstruktörerna accepterar en StreamingContext-parameter med namnet
contextsom 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 metoderna WriteObject 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 och DataContractSerializer använder ä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 säga att det matas ut på den översta objektnivån och för alla polymorfa fall.