Dela via


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 Namedu 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 XmlElementAttributeeller 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 returneras null inte och IsAny egenskapen för attributet lämnas till standardvärdet false. Det här är den vanligaste användningen av IXmlSerializable 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 till true eller returnerar null från metoden schemaprovider. Att ha en schemaprovidermetod är valfritt för elementtyper – du kan ange null i stället för metodnamnet i XmlSchemaProviderAttribute. Men om IsAny är true och en schemaprovidermetod har angetts måste metoden returnera null.

  • Äldre DataSet typer är IXmlSerializable typer som inte har markerats med attributet XmlSchemaProviderAttribute . I stället förlitar de sig på GetSchema metoden för schemagenerering. Det här mönstret används för DataSet 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 dina IXmlSerializable 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 XmlSchemaSeti . 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å IsAnytrue eller så returneras nullderas 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 som WriteXml 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 som WriteXml skriver, såvida inte ett rotnamn och namnområde uttryckligen anges när serialiseraren konstrueras i DataContractSerializer konstruktorerna eller NetDataContractSerializer . 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 vara null och kan inte tilldelas polymorft. Det går inte heller att aktivera konservering av objektdiagram och kan NetDataContractSerializer inte användas.

  • När du deserialiserar en elementtyp på den översta nivån utan att ange rotnamnet och namnområdet vid byggtiden returneras IsStartObjecttrue om det kan hitta början på något element. ReadObject med parametern verifyObjectName inställd true på fungerar på samma sätt som IsStartObject innan objektet faktiskt läss. ReadObject skickar sedan kontrollen till ReadXml 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 finns true i datakontraktsmodellen men inte med XmlSerializer.

  • XmlRootAttribute När attributet inte finns för innehålls- eller äldre datamängdstyper XmlSerializer 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 /importXmlTypestill 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änder XmlSerializer.

  • 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.

Se även