Använda XmlSerializer-klassen
Windows Communication Foundation (WCF) kan använda två olika serialiseringstekniker för att omvandla data i ditt program till XML som överförs mellan klienter och tjänster: DataContractSerializer
och XmlSerializer
.
DataContractSerializer
Som standard använder DataContractSerializer WCF klassen för att serialisera datatyper. Den här serialiseraren stöder följande typer:
Primitiva typer (till exempel heltal, strängar och bytematriser) samt vissa specialtyper, till XmlElement exempel och DateTime, som behandlas som primitiver.
Datakontraktstyper (typer som har markerats med attributet DataContractAttribute ).
Typer som har markerats SerializableAttribute med attributet, som innehåller typer som implementerar ISerializable gränssnittet.
Typer som implementerar IXmlSerializable gränssnittet.
Många vanliga samlingstyper, som innehåller många generiska samlingstyper.
Många .NET Framework-typer ingår i de två senare kategorierna och kan därför serialiseras. Matriser med serialiserbara typer kan också serialiseras. En fullständig lista finns i Ange dataöverföring i tjänstkontrakt.
, DataContractSerializersom används tillsammans med datakontraktstyper, är det rekommenderade sättet att skriva nya WCF-tjänster. Mer information finns i Använda datakontrakt.
XmlSerializer
WCF har också stöd för XmlSerializer klassen. Klassen XmlSerializer är inte unik för WCF. Det är samma serialiseringsmotor som ASP.NET webbtjänster använder. Klassen XmlSerializer har stöd för en mycket smalare uppsättning typer än DataContractSerializer klassen, men ger mycket mer kontroll över den resulterande XML-koden och stöder mycket mer av XSD-standarden (XML Schema Definition Language). Det kräver inte heller några deklarativa attribut för de serialiserbara typerna. Mer information finns i xml-serialiseringsavsnittet i .NET Framework-dokumentationen. Klassen XmlSerializer stöder inte datakontraktstyper.
När du använder Svcutil.exe eller funktionen Lägg till tjänstreferens i Visual Studio för att generera klientkod för en tjänst från tredje part, eller för att få åtkomst till ett schema från tredje part, väljs automatiskt en lämplig serialiserare åt dig. Om schemat inte är kompatibelt med DataContractSerializer, är det XmlSerializer valt.
Växla till XmlSerializer
Ibland kan du behöva växla till XmlSerializer. Detta händer till exempel i följande fall:
När du migrerar ett program från ASP.NET webbtjänster till WCF kanske du vill återanvända befintliga, XmlSerializer-kompatibla typer i stället för att skapa nya datakontraktstyper.
När exakt kontroll över XML som visas i meddelanden är viktigt, men ett WSDL-dokument (Web Services Description Language) inte är tillgängligt, till exempel när du skapar en tjänst med typer som måste följa ett visst standardiserat, publicerat schema som inte är kompatibelt med DataContractSerializer.
När du skapar tjänster som följer den äldre SOAP-kodningsstandarden.
I dessa och andra fall kan du manuellt växla till XmlSerializer klassen genom att tillämpa XmlSerializerFormatAttribute
attributet på din tjänst, enligt följande kod.
[ServiceContract]
[XmlSerializerFormat]
public class BankingService
{
[OperationContract]
public void ProcessTransaction(BankingTransaction bt)
{
// Code not shown.
}
}
//BankingTransaction is not a data contract class,
//but is an XmlSerializer-compatible class instead.
public class BankingTransaction
{
[XmlAttribute]
public string Operation;
[XmlElement]
public Account fromAccount;
[XmlElement]
public Account toAccount;
[XmlElement]
public int amount;
}
//Notice that the Account class must also be XmlSerializer-compatible.
<ServiceContract(), XmlSerializerFormat()> _
Public Class BankingService
<OperationContract()> _
Public Sub ProcessTransaction(ByVal bt As BankingTransaction)
' Code not shown.
End Sub
End Class
' BankingTransaction is not a data contract class,
' but is an XmlSerializer-compatible class instead.
Public Class BankingTransaction
<XmlAttribute()> _
Public Operation As String
<XmlElement()> _
Public fromAccount As Account
<XmlElement()> _
Public toAccount As Account
<XmlElement()> _
Public amount As Integer
End Class
'Notice that the Account class must also be XmlSerializer-compatible.
Säkerhetsöverväganden
Kommentar
Det är viktigt att vara försiktig när du byter serialiseringsmotorer. Samma typ kan serialisera till XML på olika sätt beroende på vilken serialiserare som används. Om du av misstag använder fel serialiserare kanske du lämnar ut information från den typ som du inte har för avsikt att avslöja.
Klassen serialiserar till exempel DataContractSerializer endast medlemmar som markerats med DataMemberAttribute attributet vid serialisering av datakontraktstyper. Klassen XmlSerializer serialiserar alla offentliga medlemmar. Se typen i följande kod.
[DataContract]
public class Customer
{
[DataMember]
public string firstName;
[DataMember]
public string lastName;
public string creditCardNumber;
}
<DataContract()> _
Public Class Customer
<DataMember()> _
Public firstName As String
<DataMember()> _
Public lastName As String
Public creditCardNumber As String
End Class
Om typen oavsiktligt används i ett tjänstkontrakt där XmlSerializer klassen har valts creditCardNumber
serialiseras medlemmen, vilket förmodligen inte är avsett.
Även om DataContractSerializer klassen är standard kan du uttryckligen välja den för din tjänst (även om det aldrig bör krävas) genom att tillämpa DataContractFormatAttribute attributet på tjänstkontraktstypen.
Serialiseraren som används för tjänsten är en integrerad del av kontraktet och kan inte ändras genom att välja en annan bindning eller genom att ändra andra konfigurationsinställningar.
Andra viktiga säkerhetsöverväganden XmlSerializer gäller för klassen. För det första rekommenderar vi starkt att alla WCF-program som använder XmlSerializer klassen signeras med en nyckel som skyddas mot avslöjande. Den här rekommendationen XmlSerializer gäller både när en manuell växling till utförs och när en automatisk växel utförs (av Svcutil.exe, Lägg till tjänstreferens eller ett liknande verktyg). Det beror på att XmlSerializer serialiseringsmotorn stöder inläsning av förgenererade serialiseringssammansättningar så länge de har signerats med samma nyckel som programmet. Ett osignerat program är helt oskyddat från möjligheten att en skadlig sammansättning matchar det förväntade namnet på den förgenererade serialiseringssammansättningen som placeras i programmappen eller den globala sammansättningscacheminnet. Naturligtvis måste en angripare först få skrivåtkomst till någon av dessa två platser för att försöka utföra den här åtgärden.
Ett annat hot som finns när du använder XmlSerializer är relaterat till skrivåtkomst till systemets temporära mapp. Serialiseringsmotorn XmlSerializer skapar och använder tillfälliga serialiseringssammansättningar i den här mappen. Du bör vara medveten om att alla processer med skrivåtkomst till den temporära mappen kan skriva över dessa serialiseringssammansättningar med skadlig kod.
Stöd för Regler för XmlSerializer
Du kan inte tillämpa XmlSerializer-kompatibla attribut direkt på kontraktåtgärdsparametrar eller returvärden. De kan dock tillämpas på inskrivna meddelanden (brödtextdelar för meddelandekontrakt) enligt följande kod.
[ServiceContract]
[XmlSerializerFormat]
public class BankingService
{
[OperationContract]
public void ProcessTransaction(BankingTransaction bt)
{
//Code not shown.
}
}
[MessageContract]
public class BankingTransaction
{
[MessageHeader]
public string Operation;
[XmlElement, MessageBodyMember]
public Account fromAccount;
[XmlElement, MessageBodyMember]
public Account toAccount;
[XmlAttribute, MessageBodyMember]
public int amount;
}
<ServiceContract(), XmlSerializerFormat()> _
Public Class BankingService
<OperationContract()> _
Public Sub ProcessTransaction(ByVal bt As BankingTransaction)
'Code not shown.
End Sub
End Class
<MessageContract()> _
Public Class BankingTransaction
<MessageHeader()> _
Public Operation As String
<XmlElement(), MessageBodyMember()> _
Public fromAccount As Account
<XmlElement(), MessageBodyMember()> _
Public toAccount As Account
<XmlAttribute(), MessageBodyMember()> _
Public amount As Integer
End Class
När de tillämpas på inskrivna meddelandemedlemmar åsidosätter dessa attribut egenskaper som är i konflikt med de inskrivna meddelandeattributen. I följande kod åsidosätter Name
du ElementName
till exempel .
[MessageContract]
public class BankingTransaction
{
[MessageHeader] public string Operation;
//This element will be <fromAcct> and not <from>:
[XmlElement(ElementName="fromAcct"), MessageBodyMember(Name="from")]
public Account fromAccount;
[XmlElement, MessageBodyMember]
public Account toAccount;
[XmlAttribute, MessageBodyMember]
public int amount;
}
<MessageContract()> _
Public Class BankingTransaction
<MessageHeader()> _
Public Operation As String
'This element will be <fromAcct> and not <from>:
<XmlElement(ElementName:="fromAcct"), _
MessageBodyMember(Name:="from")> _
Public fromAccount As Account
<XmlElement(), MessageBodyMember()> _
Public toAccount As Account
<XmlAttribute(), MessageBodyMember()> _
Public amount As Integer
End Class
Attributet MessageHeaderArrayAttribute stöds inte när du använder XmlSerializer.
Kommentar
I det här fallet XmlSerializer genererar följande undantag, som släpps före WCF: "Ett element som deklareras på den översta nivån i ett schema kan inte ha maxOccurs
> 1. Ange ett omslutningselement för "mer" med hjälp XmlArray
av XmlElementAttribute
eller XmlArrayItem
i stället för , eller med hjälp av parameterformatet Wrapped."
Om du får ett sådant undantag ska du undersöka om den här situationen gäller.
WCF stöder inte attributen SoapIncludeAttribute och XmlIncludeAttribute i meddelandekontrakt och åtgärdskontrakt. Använd KnownTypeAttribute attributet i stället.
Typer som implementerar IXmlSerializable-gränssnittet
Typer som implementerar IXmlSerializable
gränssnittet stöds fullt ut av DataContractSerializer
. Attributet XmlSchemaProviderAttribute bör alltid tillämpas på dessa typer för att styra deras schema.
Varning
Om du serialiserar polymorfa typer måste du tillämpa på XmlSchemaProviderAttribute typen för att säkerställa att rätt typ serialiseras.
Det finns tre typer av typer som implementerar IXmlSerializable
: typer som representerar godtyckligt innehåll, typer som representerar ett enda element och äldre DataSet typer.
Innehållstyper använder en schemaprovidermetod som anges av attributet
XmlSchemaProviderAttribute
. Metoden returnerasnull
inte och IsAny egenskapen för attributet lämnas till standardvärdetfalse
. Det här är den vanligaste användningen avIXmlSerializable
typer.Elementtyper används när en
IXmlSerializable
typ måste styra sitt eget rotelementnamn. Om du vill markera en typ som en elementtyp anger du IsAny antingen egenskapen för XmlSchemaProviderAttribute attributet tilltrue
eller returnerarnull
från metoden schemaprovider. Att ha en schemaprovidermetod är valfritt för elementtyper – du kan angenull
i stället för metodnamnet iXmlSchemaProviderAttribute
. Men omIsAny
ärtrue
och en schemaprovidermetod har angetts måste metoden returneranull
.Äldre DataSet typer är
IXmlSerializable
typer som inte har markerats med attributetXmlSchemaProviderAttribute
. I stället förlitar de sig på GetSchema metoden för schemagenerering. Det här mönstret används förDataSet
typen och dess typade datauppsättning härleder en klass i tidigare versioner av .NET Framework, men är nu föråldrad och stöds endast av äldre skäl. Förlita dig inte på det här mönstret och tillämpa alltid påXmlSchemaProviderAttribute
dinaIXmlSerializable
typer.
IXmlSerializable-innehållstyper
När du serialiserar en datamedlem av en typ som implementerar IXmlSerializable
och är en innehållstyp som definierats tidigare skriver serialiseraren omslutningselementet för datamedlemmen och skickar kontrollen till WriteXml metoden. Implementeringen WriteXml kan skriva valfri XML, vilket innefattar att lägga till attribut i omslutningselementet. När WriteXml
är klar stänger serialiseraren elementet.
När deserialisera en datamedlem av en typ som implementerar IXmlSerializable
och är en innehållstyp som definierats tidigare placerar deserialiseraren XML-läsaren på omslutningselementet för datamedlemmen och skickar kontrollen till ReadXml metoden. Metoden måste läsa hela elementet, inklusive start- och sluttaggar. Kontrollera att koden ReadXml
hanterar fallet där elementet är tomt. Dessutom bör implementeringen ReadXml
inte förlita sig på att wrapper-elementet namnges på ett visst sätt. Namnet som väljs av serialiseraren kan variera.
Det är tillåtet att tilldela IXmlSerializable
innehållstyper polymorfiskt, till exempel till datamedlemmar av typen Object. Det är också tillåtet för typinstanserna att vara null. Slutligen är det möjligt att använda IXmlSerializable
typer med objektdiagrambevarande aktiverat och med NetDataContractSerializer. Alla dessa funktioner kräver att WCF-serialiseraren kopplar vissa attribut till wrapper-elementet ("nil" och "type" i XML-schemainstansens namnområde och "ID", "Ref", "Type" och "Assembly" i ett WCF-specifikt namnområde).
Attribut som ska ignoreras vid implementering av ReadXml
Innan kontrollen skickas till koden ReadXml
undersöker deserialiseraren XML-elementet, identifierar dessa speciella XML-attribut och agerar på dem. Om till exempel "nil" är true
, deserialiseras ett null-värde och ReadXml
anropas inte. Om polymorfism identifieras deserialiseras innehållet i elementet som om det var en annan typ. Den polymorfiskt tilldelade typens implementering av ReadXml
anropas. I vilket fall som helst bör en ReadXml
implementering ignorera dessa särskilda attribut eftersom de hanteras av deserialiseraren.
Schemaöverväganden för IXmlSerializable-innehållstyper
När du exporterar schema och en IXmlSerializable
innehållstyp anropas metoden schemaprovider. En XmlSchemaSet skickas till metoden schemaprovider. Metoden kan lägga till valfritt giltigt schema i schemauppsättningen. Schemauppsättningen innehåller det schema som redan är känt när schemaexport sker. När metoden schemaprovider måste lägga till ett objekt i schemauppsättningen måste den avgöra om en XmlSchema med rätt namnområde redan finns i uppsättningen. Om det gör det måste metoden schemaprovider lägga till det nya objektet i den befintliga XmlSchema
. Annars måste den skapa en ny XmlSchema
instans. Detta är viktigt om matriser av IXmlSerializable
typer används. Om du till exempel har en IXmlSerializable
typ som exporteras som typen "A" i namnområdet "B" är det möjligt att schemaprovidermetoden redan innehåller schemat för "B" för att lagra typen "ArrayOfA".
Förutom att lägga till typer i XmlSchemaSetmåste schemaprovidermetoden för innehållstyper returnera ett värde som inte är null. Den kan returnera ett XmlQualifiedName som anger namnet på den schematyp som ska användas för den angivna IXmlSerializable
typen. Det här kvalificerade namnet fungerar också som datakontraktets namn och namnområde för typen. Det är tillåtet att returnera en typ som inte finns i schemauppsättningen omedelbart när schemaprovidermetoden returneras. Det förväntas dock att när alla relaterade typer exporteras ( Export metoden anropas för alla relevanta typer på XsdDataContractExporter och Schemas egenskapen används) finns typen i schemauppsättningen. Åtkomst till Schemas
egenskapen innan alla relevanta Export
anrop har gjorts kan resultera i en XmlSchemaException. Mer information om exportprocessen finns i Exportera scheman från klasser.
Metoden schemaprovider kan också returnera för XmlSchemaType användning. Typen kan vara anonym eller inte. Om den är anonym exporteras schemat för IXmlSerializable
typen som en anonym typ varje gång IXmlSerializable
typen används som datamedlem. Typen IXmlSerializable
har fortfarande ett namn och namnområde för datakontraktet. (Detta fastställs enligt beskrivningen i Namn på datakontrakt förutom att DataContractAttribute attributet inte kan användas för att anpassa namnet.) Om den inte är anonym måste den vara en av typerna XmlSchemaSet
i . Det här fallet motsvarar att XmlQualifiedName
returnera typen.
Dessutom exporteras en global elementdeklaration för typen. Om typen inte har XmlRootAttribute attributet tillämpat på det, har elementet samma namn och namnområde som datakontraktet och dess egenskap "nillable" är true
. Det enda undantaget är schemanamnområdet (http://www.w3.org/2001/XMLSchema
) – om typens datakontrakt finns i det här namnområdet finns motsvarande globala element i det tomma namnområdet eftersom det är förbjudet att lägga till nya element i schemanamnområdet. Om typen har det XmlRootAttribute
attribut som tillämpas på den exporteras den globala elementdeklarationen med hjälp av följande: ElementNameoch NamespaceIsNullable egenskaper. Standardvärdena med XmlRootAttribute
tillämpad är namnet på datakontraktet, ett tomt namnområde och "nillable" är true
.
Samma regler för global elementdeklaration gäller för äldre datauppsättningstyper. Observera att det inte går att åsidosätta globala elementdeklarationer som XmlRootAttribute
lagts till via anpassad kod, antingen läggs till XmlSchemaSet
med hjälp av metoden schemaprovider eller via GetSchema
för äldre datamängdstyper.
IXmlSerializable-elementtyper
IXmlSerializable
elementtyper har antingen egenskapen inställd på IsAny
true
eller så returneras null
deras schemaprovidermetod .
Serialisering och deserialisering av en elementtyp liknar serialisering och deserialisering av en innehållstyp. Det finns dock några viktiga skillnader:
Implementeringen
WriteXml
förväntas skriva exakt ett element (som naturligtvis kan innehålla flera underordnade element). Det bör inte skriva attribut utanför det här enskilda elementet, flera syskonelement eller blandat innehåll. Elementet kan vara tomt.Implementeringen
ReadXml
bör inte läsa omslutningselementet. Det förväntas läsa det enda element somWriteXml
producerar.När du serialiserar en elementtyp regelbundet (till exempel som en datamedlem i ett datakontrakt) matar serialiseraren ut ett wrapper-element innan den anropar
WriteXml
, som med innehållstyper. Men när du serialiserar en elementtyp på den översta nivån matar serialiseraren normalt inte ut ett wrapper-element alls runt elementet somWriteXml
skriver, såvida inte ett rotnamn och namnområde uttryckligen anges när serialiseraren konstrueras iDataContractSerializer
konstruktorerna ellerNetDataContractSerializer
. Mer information finns i Serialisering och deserialisering.När du serialiserar en elementtyp på den översta nivån utan att ange rotnamnet och namnområdet vid byggtiden och WriteStartObjectWriteEndObject i princip inte gör någonting och WriteObjectContent anropar
WriteXml
. I det här läget kan objektet som serialiseras inte varanull
och kan inte tilldelas polymorft. Det går inte heller att aktivera konservering av objektdiagram och kanNetDataContractSerializer
inte användas.När du deserialiserar en elementtyp på den översta nivån utan att ange rotnamnet och namnområdet vid byggtiden returneras IsStartObject
true
om det kan hitta början på något element. ReadObject med parameternverifyObjectName
inställdtrue
på fungerar på samma sätt somIsStartObject
innan objektet faktiskt läss.ReadObject
skickar sedan kontrollen tillReadXml
metoden.
Schemat som exporteras för elementtyper är detsamma som för typen XmlElement
som beskrivs i ett tidigare avsnitt, förutom att metoden schemaprovider kan lägga till ytterligare scheman XmlSchemaSet som med innehållstyper. XmlRootAttribute
Det går inte att använda attributet med elementtyper och globala elementdeklarationer genereras aldrig för dessa typer.
Skillnader från XmlSerializer
Gränssnittet IXmlSerializable
och attributen XmlSchemaProviderAttribute
och XmlRootAttribute
förstås också av XmlSerializer . Det finns dock vissa skillnader i hur dessa behandlas i datakontraktsmodellen. De viktiga skillnaderna sammanfattas i följande lista:
Schemaprovidermetoden måste vara offentlig för att användas i
XmlSerializer
, men behöver inte vara offentlig för att kunna användas i datakontraktsmodellen.Metoden schemaprovider anropas när
IsAny
finnstrue
i datakontraktsmodellen men inte medXmlSerializer
.XmlRootAttribute
När attributet inte finns för innehålls- eller äldre datamängdstyperXmlSerializer
exporteras en global elementdeklaration i det tomma namnområdet. I datakontraktsmodellen är det namnområde som används normalt datakontraktets namnområde enligt beskrivningen tidigare.
Tänk på dessa skillnader när du skapar typer som används med båda serialiseringsteknikerna.
ImporteraR IXmlSerializable-schema
När du importerar ett schema som genererats från IXmlSerializable
typer finns det några möjligheter:
Det genererade schemat kan vara ett giltigt datakontraktsschema enligt beskrivningen i Schemareferens för datakontrakt. I det här fallet kan schemat importeras som vanligt och vanliga datakontraktstyper genereras.
Det genererade schemat kanske inte är ett giltigt datakontraktsschema. Till exempel kan din schemaprovidermetod generera ett schema som omfattar XML-attribut som inte stöds i datakontraktsmodellen. I det här fallet kan du importera schemat som
IXmlSerializable
typer. Det här importläget är inte aktiverat som standard, men kan enkelt aktiveras – till exempel med kommandoradsväxlingen/importXmlTypes
till Verktyget för ServiceModel-metadata (Svcutil.exe). Detta beskrivs i detalj i importschemat för att generera klasser. Observera att du måste arbeta direkt med XML för dina typinstanser. Du kan också överväga att använda en annan serialiseringsteknik som stöder ett bredare schemaintervall – se avsnittet om hur du använderXmlSerializer
.Du kanske vill återanvända dina befintliga
IXmlSerializable
typer i proxyn i stället för att generera nya. I det här fallet kan funktionen för refererade typer som beskrivs i avsnittet Importera schema för att generera typer användas för att ange vilken typ som ska återanvändas. Detta motsvarar att använda växeln/reference
på svcutil.exe, som anger den sammansättning som innehåller de typer som ska återanvändas.
Äldre XmlSerializer-beteende
I .NET Framework 4.0 och tidigare genererade XmlSerializer tillfälliga serialiseringssammansättningar genom att skriva C#-kod till en fil. Filen kompilerades sedan till en sammansättning. Det här beteendet fick vissa oönskade konsekvenser som att fördröja starttiden för serialiseraren. I .NET Framework 4.5 ändrades det här beteendet för att generera sammansättningarna utan att kompilatorn behövde användas. Vissa utvecklare kanske vill se den genererade C#-koden. Du kan ange att du vill använda det här äldre beteendet med följande konfiguration:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.xml.serialization>
<xmlSerializer tempFilesLocation='e:\temp\XmlSerializerBug' useLegacySerializerGeneration="true" />
</system.xml.serialization>
<system.diagnostics>
<switches>
<add name="XmlSerialization.Compilation" value="1" />
</switches>
</system.diagnostics>
</configuration>
Om du stöter på kompatibilitetsproblem, till exempel XmlSerializer
om det inte går att serialisera en härledd klass med en icke-offentlig ny åsidosättning, kan du växla tillbaka till det XMLSerializer
äldre beteendet med hjälp av följande konfiguration:
<configuration>
<appSettings>
<add key="System:Xml:Serialization:UseLegacySerializerGeneration" value="true" />
</appSettings>
</configuration>
Som ett alternativ till ovanstående konfiguration kan du använda följande konfiguration på en dator som kör .NET Framework 4.5 eller senare version:
<configuration>
<system.xml.serialization>
<xmlSerializer useLegacySerializerGeneration="true"/>
</system.xml.serialization>
</configuration>
Kommentar
Växeln <xmlSerializer useLegacySerializerGeneration="true"/>
fungerar bara på en dator som kör .NET Framework 4.5 eller senare. Ovanstående appSettings
metod fungerar på alla .NET Framework-versioner.