Serialisierung in Windows Communication Foundation

Veröffentlicht: 21. Aug 2006

Von Aaron Skonnard

Windows Communication Foundation baut von Anfang an auf den Grundsätzen der Dienstorientierung auf. Es unterstützt verschiedene Serialisierungsmechanismen, mit denen bestehende Typen weiterentwickelt werden können, und stellt eine einfache, interoperable Basis für zukünftige dienstorientierte Anwendungen zur Verfügung. Windows ® Communication Foundation (das sich noch im Betastadium befindet) enthält XML als zentrale Technologie. Mit WCF können zwar direkt Dienste erstellt werden, die XML unmittelbar verarbeiten, aber die meisten Entwickler bevorzugen Serialisierungsmechanismen, die den Wechsel zwischen Objekten von Microsoft ® .NET Framework und XML-Infosets automatisieren.

Laden Sie den Code zu diesem Artikel herunter: ServiceStation2006_08.exe (154 KB)

Zum Implementieren von Webdiensten stehen zwei grundsätzliche Ansätze zur Verfügung. Bei der ersten Methode werden XML und Programm direkt bei den Nachrichten zusammengebracht. Diese Lösung ist sehr flexibel, besonders wenn schwierige Problem wie die Versionierung gelöst werden müssen, bei denen zentrale XML-Technologien wie XPath, XSLT und XQuery unumgänglich sind. Vielen Entwicklern ist diese Methode jedoch zu aufwändig und zu unübersichtlich. Bei der zweiten Methode wird anfangs ein Mapping zwischen .NET und XML definiert, und anschließend kommen automatisierte Serialisierungsmechanismen zum Einsatz. Dadurch müssen sich Entwickler nicht mit den zahlreichen XML-Details beschäftigen. Windows Communication Foundation unterstützt beide Ansätze gleichermaßen.

In Windows Communication Foundation werden Nachrichten intern durch die Message-Klasse vom Typ System.ServiceModel.Channels dargestellt. Die Message-Klasse definiert eine SOAP-Nachricht, die allgemein als SOAP-Umschlag bezeichnet wird und aus einem Header- und einen Body-Abschnitt mit den eigentlichen Nutzdaten besteht. Die Message-Klasse verfügt über eine Schnittstelle für die Interaktion mit Header und Body über System.Xml-Klassen oder typbasierte Serialisierung.

Auf Nachrichtenebene können Sie die verwendete Methode explizit auswählen. Wie nachfolgend dargestellt, wird die Serialisierung in Windows Communication Foundation überwiegend zum Erstellen von Servicecontracts in Form serialisierbarer Typen genutzt:

[ServiceContract]
public interface IEchoService
{
    [OperationContract]
    Person EchoPerson(Person person);
}

Durch das Hinzufügen der .NET-Schnittstelle unter [ServiceContract] wird auch die .NET-Typdefinition als Servicecontract genutzt. Nehmen wir als Beispiel die Web Services Description Language (WSDL). Durch Hinzufügen der Methodensignatur unter [OperationContract] wird die Methode in den Servicecontract übernommen. Zur Laufzeit überträgt Windows Communication Foundation die Methodensignatur völlig automatisch auf ein Nachrichtenpaar, bei dem der SOAP-Body jeder Nachricht eine Person enthält. Mithilfe eines Serialisierers wird das Person-Objekt auf die Nachricht abgebildet. (Zur Steuerung aller Details im SOAP-Umschlag können die [MessageContract]-Typen der Signatur verwendet werden.)

Auf dieser Seite

Serialisierung und Kodierung Serialisierung und Kodierung
Hinter den Kulissen Hinter den Kulissen
Definieren des Serialisierungsmappings Definieren des Serialisierungsmappings
Arbeiten mit XmlSerializer Arbeiten mit XmlSerializer
Arbeiten mit DataContractSerializer Arbeiten mit DataContractSerializer
Arbeiten mit NetDataContractSerializer Arbeiten mit NetDataContractSerializer
Erweiterte Serialisierungskonzepte Erweiterte Serialisierungskonzepte
Schlussbemerkung Schlussbemerkung
Der Autor Der Autor

Serialisierung und Kodierung

Windows Communication Foundation unterstützt drei Serialisierer: XmlSerializer, DataContractSerializer und NetDataContractSerializer. Jeder verfügt über spezielle Mapping-Algorithmen und Anpassungsverfahren. (Siehe zum Vergleich Abbildung 1.) Trotzdem erfüllt jeder Serialisierer die gleiche grundlegende Aufgabe: das Mapping zwischen .NET-Objekten und XML-Infosets.

DataContractSerializer ist das Standardprogramm und wird immer verwendet, wenn nichts Gegenteiliges festgelegt wurde. Zur Verwendung eines anderen Serialisierers müssen Sie dem Servicecontract, wie nachfolgend dargestellt, ein Attribut hinzufügen:

[XmlSerializerFormat]
[ServiceContract]
public interface IEchoService
{
    [DataContractFormat]
    [OperationContract]
    Person EchoPerson(Person person);

    [OperationContract]
    Address EchoAddress(Address address);

    [OperationContract]
    Phone EchoPhone(Phone phone);
}

In diesem Beispiel wird unter [XmlSerializerFormat] XmlSerializer als Standardserialisierer für alle Methoden des Contracts ausgewählt. Dabei wird der Serialisierer für EchoPerson durch Hinzufügen der Methode unter [DataContractFormat] außer Kraft gesetzt. Es gibt zwar kein Attribut für NetDataContractSerializer, Sie können ggf. jedoch ein benutzerdefiniertes Attribut erstellen und genauso wie andere Attribute verwenden. Der Serialisierer wird als Teil des Servicecontracts betrachtet, weil er direkte Auswirkungen auf den Code besitzt.

In Windows Communication Foundation können Sie auch die verwendete Kodierung auswählen. Während die Serialisierung das Mapping von .NET-Objekten auf XML-Infosets definiert, legt die Kodierung fest, wie ein XML-Infoset als Bytestream ausgegeben wird. Windows Communication Foundation unterstützt gegenwärtig die folgenden Kodierungen: Text, binär und MTOM (Message Transmission Optimization Mechanism). Möglicherweise werden im Lauf der Zeit noch weitere Kodierungen, einschließlich benutzerdefinierter, hinzugefügt.

Wenn Sie einen XML-Infoset mit den drei Kodierungen ausgeben, erhalten Sie drei sehr unterschiedliche Bytestreams, die jedoch alle dieselben logischen Daten repräsentieren. Die Kodierung ist nicht Teil des Servicecontracts, sondern eher ein Konfigurationselement, da sie keine Auswirkungen auf den Code hat. Die Kodierung wird durch die Konfigurierung der Endpunktbindung gesteuert.

Durch die Trennung von Serialisierung und Kodierung können Anwendungen bei gleichzeitig flexibler Darstellung (Kodierung) auf Grundlage eines konsistenten Datenmodells (dem XML-Infoset) erstellt werden. Dies ist eine zentrale Funktion bei der Anwendung von Windows Communication Foundation auf verschiedenste reale Szenarios. Wenn Ihnen vor allem die Interoperabilität wichtig ist, sollten Sie die Textkodierung verwenden. Hat die Leistung größere Bedeutung, steht Ihnen die binäre Kodierung zur Verfügung. In beiden Fällen erfolgt die Kodierung unabhängig von den Serialisierungsmechanismen.

Auf den ersten Blick ähnelt die Serialisierung in Windows Communication Foundation ASMX. Bei einem Blick hinter die Schichten werden Sie jedoch verblüffend neue Möglichkeiten entdecken.

Hinter den Kulissen

Beim Erstellen von Nachrichten, die zur Laufzeit ausgegeben werden, wählt Windows Communication Foundation den Serialisierer anhand des Servicecontracts und die Kodierung anhand der Bindungskonfiguration aus. Befassen wir uns nun etwas näher mit der Art und Weise, wie das gewünschte Ergebnis erreicht wird. Abbildung 2 zeigt, wie Nachrichten, die ein Person-Objekt enthalten, erstellt und geschrieben werden.

Der letzte Parameter von CreateMessage wählt den Serialisierer zum Erstellen der Nachricht aus. Er steuert, wie das Person-Objekt als XML-Infoset zum Nachrichtenkörper (Body) hinzugefügt wird. Anschließend erzeugt er einen Nachrichtengenerator und leitet ihn an WriteMessage weiter. Der Generator wählt die Kodierung für den Ausgabebytestream der Nachricht aus. Mithilfe der neuen Klassen XmlDictionaryReader und XmlDictionaryWriter können Sie für alle unterstützten Kodierungen (Text, binär und MTOM) Leser und Generatoren erstellen.

Beim Beispiel in Abbildung 3 wurde ein binärer XmlDictionaryWriter verwendet. Beim Öffnen der erzeugten Datei in Visual Studio® wird deutlich, dass es sich um eine Binärdatei handelt, und die bei der herkömmlichen Textkodierung verwendeten viereckigen Klammern bzw. XML-Tags sind nicht vorhanden.

Binäre Kodierung
Abbildung 3:   Binäre Kodierung

Der Code in Abbildung 4 zeigt, wie die Umkehrung des Vorgangs, die Deserialisierung einer Person aus einer Binärdatei auf Datenträger, durchgeführt wird. Der von CreateMessage verwendete Leser wählt die Kodierung zum Lesen gespeicherter Bytes aus. In diesem Fall wird ein binärer XmlDictionaryReader benutzt. Der anschließende Aufruf von GetBody<Person> zeigt an, dass DataContractSerializer zur Deserialisierung des XML-Infosets zurück in ein Person-Objekt verwendet wird.

Soll keine Serialisierung durchgeführt werden, können Sie mithilfe der Message-Klasse unmittelbar mit dem XML-Infoset arbeiten. Statt GetBody<T> können Sie zur Deserialisierung des Body in ein .NET-Objekt auch GetReaderAtBodyContents aufrufen und den zurückgegebenen XmlReader zur Verarbeitung des Body verwenden (siehe Abbildung 5). Wenn Sie direkt mit dem XML-Infoset arbeiten, können Sie Ihre bevorzugte XML-Verarbeitungmethode verwenden. In Abbildung 5 werden lediglich das Namens- und Alterselement gesucht, und deren Werte werden auf der Konsole ausgegeben.

Definieren des Serialisierungsmappings

Sie können zwar auch direkt auf das XML-Infoset zugreifen, aber die Serialisierungskonzepte wurden speziell so gestaltet, dass sie einfach sind und dass über das Programmiermodell darauf zugegriffen werden kann. Bei der Serialisierung müssen Sie sich vor allem auf das Mapping zwischen .NET-Objekten und XML-Infosets konzentrieren.

Wenn Sie .NET-Typen bei der Serialisierung verwenden, müssen Sie zwei Contracts beachten. Der lokale .NET-Contract definiert die Datenstruktur und deren Verhalten. Dabei stehen Ihnen zur einfacheren Nutzung Konstruktoren, Eigenschaften und andere Hilfsmittel zur Verfügung. Der externe Datencontract (DataContract) legt dann exakt fest, welche Daten wie serialisiert werden. In Windows Communication Foundation wird dieser Contract als Datencontract eines .NET-Typs angesprochen.

Da ein Datencontract die Struktur eines XML-Infosets endgültig festlegt, ist es nur natürlich, diesen Datencontract mithilfe einer XML-Schemadefinition darzustellen. Tatsächlich werden immer XML-Schemas für die gemeinsame Verwendung von Datencontracts mit Windows Communication Foundation-fremden Anwendungen benutzt. Allerdings sind Datencontracts auch Teil von .NET-Typdefinitionen. Jeder Serialisierer verfügt über einen eigenen Standardalgorithmus, der die meisten Mappingdetails definiert, und gleichzeitig auch Attribute enthält, mit denen das Mapping angepasst werden kann. Diese Informationen werden gemeinsam mit dem .NET-Typ als Metadaten gespeichert, auf die die Serialisierer zur Laufzeit zugreifen können, um spezielle Mappingdetails zu bestimmen.

Windows Communication Foundation verfügt zum Wechseln zwischen den unterschiedlichen Datencontractdarstellungen über das neue Befehlszeilentool svcutil.exe. Wenn Sie mit XmlSerializer-Typen arbeiten, können Sie allerdings auch xsd.exe verwenden. Wird eine XML-Schemadefinition an svcutil.exe übergeben, erstellt dies automatisch die entsprechenden .NET-Serialisierungstypen mit den richtigen Attributen. Wird eine .NET-Assembly an svcutil.exe übergeben, erstellt dies automatisch die entsprechenden XML-Schemadefinitionen für alle Serialisierungstypen. Verwenden Sie dabei den /dataContractOnly-Schalter.

Diese Vorgehensweise gewährt Ihnen große Flexibilität. Sie können neue Datencontracts nach Belieben als XML-Schema oder .NET-Code definieren und sehr einfach in das andere Format konvertieren. Sind bereits XML-Schemadefinitionen vorhanden, die unterstützt werden müssen, können Sie einfach mit svcutil.exe beginnen. Wenn Sie schnell neue Contracts definieren müssen, schreiben Sie zuerst die Klassendefinitionen.

Arbeiten mit XmlSerializer

Wie Sie vermutlich wissen, ist XmlSerializer der gegenwärtig in ASMX verwendete Serialisierer (unter System.Xml.Serialization). Die Unterstützung von XmlSerializer durch Windows Communication Foundation wird sicherlich von allen mit Erleichterung aufgenommen, die mit der Zeit erheblich in .NET Web-Dienste investiert haben und davon auch in Zukunft profitieren möchten. Sie können vorhandene XmlSerializer-basierte Typen in neuen Servicecontracts verwenden, indem Sie, wie oben gezeigt, [XmlSerialzerFormat] auf den Servicecontract anwenden.

Beschäftigen wir uns nun etwas näher mit der grundlegenden Funktionsweise von XmlSerializer. Instanziieren Sie zunächst XmlSerializer, und wählen Sie den Typ aus, der serialisiert werden soll. Rufen Sie anschießend die Serialize- und Deserialize-Methoden auf, um zwischen den Instanzen des .NET-Typs und dem entsprechenden XML-Infoset zu wechseln. XmlSerializer verfügt über einen Standardalgorithmus zum Datencontractmapping zwischen diesen Darstellungen.

XmlSerializer arbeitet mit jedem öffentlichen Typ ohne spezielle Attribute. Mit XmlSerializer wird die öffentliche Datenschnittstelle eines Typs unmittelbar auf die XML-Infoset-Entsprechung abgebildet. Daher integriert XmlSerializer automatisch alle im Mapping enthaltenen öffentlichen Lese-/Schreibfelder und Eigenschaften und ignoriert alles, was privat, geschützt usw. ist. Der .NET-Klassenname wird auf das Stammelement und die öffentlichen Feld- und Eigenschaftsnamen werden auf lokale Elementnamen abgebildet. Die Elementenordnung ist die gleiche wie bei den Klassenmitgliedern. Zuerst kommen die Felder und anschließend die Eigenschaften. In der Standardeinstellung benutzt XmlSerializer keine XML-Namespaces.

Abbildung 6 zeigt eine einfache Klassendefinition. Da es sich um einen öffentlichen Typ handelt, kann die Serialisierung mit XmlSerializer ohne spezielle Attribute durchgeführt werden. Allerdings werden während des Mappings lediglich die öffentlichen Lese-/Schreibfelder und Eigenschaften einbezogen, was die sensitiveData- und spouse-Felder sowie die Name- und Age-Eigenschaften umfasst.

Abbildung 7 zeigt, wie eine Person mit XmlSerializer serialisiert wird. Beachten Sie, dass der Serialisierungsaufruf auch XmlWriter akzeptiert. Dadurch kann eine beliebige XmlDictionaryWriter-Implementation angegeben und die gewünschte Kodierung ausgewählt werden. Bei der Ausführung erzeugt dieser Code das folgende Dokument person.xml:

<Person>
  <sensitiveData>secret</sensitiveData>
  <spouse>
    <sensitiveData>secret</sensitiveData>
    <Name>Jane</Name>
    <Age>33</Age>
  </spouse>
  <Name>Bob</Name>
  <Age>34</Age>
</Person>

Beachten Sie, dass dabei der Name des Typs (Person) zum Namen des Stammelements wird und die Feld- und Eigenschaftsnamen zu lokalen Elementnamen werden. Beachten Sie weiter, dass die privaten Felder Name und Age nicht berücksichtigt werden, außer über die Eigenschaften, deren get-Methoden aufgerufen werden. Die Felder werden zuerst gruppiert, gefolgt von den Eigenschaften. Dabei besteht für jede Gruppe dieselbe Ordnung, die in der Klassendefinition festgelegt wurde. Die Elemente sind nicht Namespace-kompatibel.

In diesem Fall kann mithilfe des xsd.exe-Tools von .NET Framework ein Datencontract mit der Definition des tatsächlichen Mappings als XML-Schemadefinition veröffentlicht werden. Das Schema in Abbildung 8 beschreibt exakt die XML-Instanz, die mit dem Serialisierungscode in Abbildung 7 erzeugt wird und mit anderen gemeinsam genutzt werden kann. Wenn Sie dieses Schema xsd.exe /classes übergeben, wird eine Klassendefinition erzeugt, die unserer Ausgangsdefinition ähnelt und über einen entsprechenden Datencontract verfügt.

Abbildung 9 veranschaulicht, wie ein Person-Objekt von person.xml deserialisiert wird. Der Serialisierer liest die XML-Datei anhand des Datencontracts. Bei der Deserialisierung wird der Standardkonstruktor aufgerufen (wobei notwendige Initialisierungen durchgeführt werden können), und die set-Methoden werden wegen der Eigenschaften aufgerufen.

Zum Definieren dieses wechselseitigen Mappings müssen keine besonderen Schritte ausgeführt werden. Es wird automatisch aus der öffentlichen Datenschnittstelle des Typs abgeleitet. XmlSerializer verfügt über Anpassungsattribute (die auch in System.Xml.Serialization vorhanden sind), mit deren Hilfe Sie das Mapping für einen bestimmten Typ beeinflussen können. Abbildung 10 zeigt die Verwendung einiger dieser Attribute. In diesem Beispiel werden einige Änderungen am Standardmapping vorgenommen. So wird ein XML-Namespace für die Bestimmung des Stammelements festgelegt, die Elementenordnung wird geändert, die Age-Eigenschaft wird auf ein Attribut und nicht auf ein Element abgebildet, und der Serialisierer wird angewiesen, das sensitiveData-Feld zu ignorieren. Wird jetzt ein Objekt dieses Typs serialisiert, erhalten wir folgenden XML-Code:

<Person Age="34" xmlns="http://example.org/person">
  <Name>Bob</Name>
  <spouse Age="33">
    <Name>Jane</Name>
  </spouse>
</Person>

Wird das XML-Schema mithilfe von xsd.exe neu erzeugt, weist es alle diese Anpassungen auf. Sie können auch das Standardmapping komplett außer Kraft setzen, indem Sie den Typ mittels IXmlSerializable implementieren. In diesem Fall definieren Sie Ihren eigenen Mappingalgorithmus mit eigenem Leser- und Generatorcode.

XmlSerializer besitzt einige einzigartige Eigenschaften. XmlSerializer arbeitet ohne expliziten Datencontract, sondern leitet ihn implizit von der öffentlichen Datenschnittstelle ab, wodurch sich unerwartete Möglichkeiten ergeben (z. B. durch das sensitiveData-Feld). In diesen Situationen benötigen Sie Opt-out-Verfahren (wie [XmlIgnore]), um Einschränkungen vorzunehmen. In Bezug auf XML-Schemas bietet Ihnen XmlSerializer sehr viel Flexibilität. So wird im Beispiel statt eines Elements ein Attribut verwendet. Bei der Verwendung von XmlSerializer stehen noch weitere anspruchsvolle XML-Schemakonzepte zur Verfügung, deren Einsatz jedoch zu Interoperabilitätsproblemen in Frameworks führen kann.

Arbeiten mit DataContractSerializer

Beim Entwurf von Windows Communication Foundation wurde bewusst der Serialisierungstyp ausgewählt, der am meisten Sinn macht. Aus diesem Grund wurde Wert auf ein sehr explizites Datencontractmodell ("Grenzen sind explizit") gelegt, und Entwicklern steht nur eine Teilmenge der XML-Schemas Wert zur Verfügung, um die Interoperabilität zu verbessern. Da XmlSerializer nicht über diese Eigenschaften verfügte, mussten die Architekten einen neuen Serialisierer bereitstellen, den DataContractSerializer.

Der DataContractSerializer wurde für Windows Communication Foundation komplett neu entwickelt. Er basiert auf den praktischen Erfahrungen mit XmlSerializer und sollte ebenso einfach zu benutzen sein. Die Klasse befindet sich im System.Runtime.Serialization-Namespace, da die Implementierung dem IFormatter-Ansatz ähnelt, der in .NET Remoting verwendet wird. Tatsächlich soll dieser Mechanismus in der Zukunft ersetzt werden.

Die Handhabung des DataContractSerializer ähnelt der von XmlSerializer. Zunächst wird der DataContractSerializer instanziiert, und der zu serialisierende Typ wird ausgewählt. Anschießend rufen Sie die WriteObject- und ReadObject-Methoden auf, um zwischen den Instanzen des .NET-Typs und dem entsprechenden XML-Infoset zu wechseln. DataContractSerializer verfügt über einen Standardmappingalgorithmus für die beiden Darstellungen.

DataContractSerializer kann mit allen .NET-Typen mit [DataContract]- oder [Serializable]-Attribut arbeiten. Er ist rückwärts kompatibel zu [Serializable]-Typen, um .NET Remoting-Code auf einfache Weise nach Windows Communication Foundation zu migrieren. Mit [Serializable] sollten ganze Objekte anhand des Werts serialisiert werden, sodass diese Objekte auf der anderen Seite eines .NET Remoting-Anrufs wiederhergestellt werden konnten.

Im vorliegenden Beispiel wird die gleiche Person-Klasse wie oben verwendet, jedoch ohne XmlSerializer-Anpassungsattribute. Die Klasse wird unter [Serializable] hinzugefügt:

[Serializable]
public class Person
{
    private string name;
    private double age;
    private string sensitiveData;
    public Person spouse;
    ... // constructors and properties
}

Das Standardmapping für [Serializable] unterscheidet sich von dem für XmlSerializer. Hier werden alle Felder ins Mapping aufgenommen, ganz gleich ob öffentliche oder private, und Eigenschaften werden immer ignoriert. Der .NET-Klassenname wird auf das Stammelement und die Feldnamen werden auf lokale Elementnamen abgebildet. Die Elemente sind im Datencontract alphabetisch angeordnet, und der Namespace wird vom aktuell verwendeten .NET-Namespace abgeleitet.

Der Code in Abbildung 11 zeigt, wie ein Person-Objekt mit DataContractSerializer serialisiert wird. Aufgrund der Unterschiede beim [Serializable]-Mapping sieht die generierte XML-Datei nun etwas anders aus:

<Person xmlns=
  "http://schemas.datacontract.org/2004/07/DataContractSamples" 
   xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <age>34</age>
  <name>Bob</name>
  <sensitiveData>secret</sensitiveData>
  <spouse>
    <age>33</age>
    <name>Jane</name>
    <sensitiveData>secret</sensitiveData>
    <spouse i:nil="true"/>
  </spouse>
</Person>

Beachten Sie, dass nur Felder einbezogen werden, dass Elemente alphabetisch angeordnet sind und dass Elemente automatisch durch den XML-Namespace qualifiziert sind. (Dieser Namespace ist eine Kombination aus "http://schemas.datacontract.org/2004/07/" und dem .NET-Namespace, der den Typ enthält, bei dem es sich in diesem Fall um "DataContractSamples" handelt.) Mithilfe von svcutil.exe /dconly können Sie das XML-Schema erstellen, das dieses Format beschreibt. In Abbildung 12 ist der Code dargestellt, der für die Deserialisierung eines Person-Objekts verwendet wird.

Ein weiterer Unterschied zu XmlSerializer zeigt sich darin, dass Konstruktoren bei der Deserialisierung nicht aufgerufen werden. Muss jedoch eine Initialisierung durchgeführt werden, unterstützt DataContractSerializer [OnDeserializing]-, [OnDeserialized]-, [OnSerializing]- und [OnSerialized]-Rückrufe (Callbacks), die auch von den Binary/SoapFormatter-Klassen unterstützt werden.

Die einzige Änderung, die Sie am standardmäßigen [Serializable]-Mapping vornehmen können, ist das nachfolgend gezeigte Ausschließen eines Felds aus dem Datencontract mithilfe des [NonSerialized]-Attributs:

[Serializable]
public class Person
{
    private string name;
    private double age;
    [NonSerialized]
    private string sensitiveData;
    public Person spouse;
    ... // constructors and properties
}

Sie können jedoch den Mappingalgorithmus vollständig außer Kraft setzen, indem Sie den Typ mittels ISerializable implementieren. In diesem Fall ruft der DataContractSerializer Ihren Code zur Durchführung des Mappings auf und übergibt Ihnen so die volle Kontrolle.

DataContractSerializer unterstützt auch mithilfe der [DataContract]-, [DataMember]- und [EnumMember]-Attribute einen expliziteren Mappingmechanismus. Bei diesem Ansatz fügen Sie den Typ zu [DataContract] hinzu, um ihn serialisierbar zu machen, und anschließend fügen Sie die Felder oder Eigenschaften hinzu, die Sie mit [DataMember] abbilden möchten. Auf die gleiche Weise fügen Sie die enum-Werte hinzu, die Sie mit [EnumMember] abbilden möchten. Bei diesem Szenario definieren Sie den Datenkontrakt explizit, und nichts wird standardmäßig abgebildet.

Der in Abbildung 13 dargestellte Code repräsentiert eine Person-Klasse, die den [DataContract]-Mappingmechanismus verwendet. Wird eine Instanz dieses Typs mit dem im obigen Beispiel verwendeten Code serialisiert, erhalten wir folgenden XML-Code:

<Person xmlns=
  "http://schemas.datacontract.org/2004/07/DataContractSamples" 
   xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <Age>34</Age>
  <name>Bob</name>
  <spouse>
    <Age>33</Age>
    <name>Jane</name>
    <spouse i:nil="true"/>
  </spouse>
</Person>

Die Mappingdetails sind identisch mit [Serializable]. Der einzige Unterschied besteht in der Art, wie festgelegt wird, was in den Datencontract aufgenommen wird. Mit [Serializable] werden alle Felder Teil des Datencontracts, sofern sie nicht mit [NonSerialized] markiert sind. Bei [DataContract] werden nur Mitglieder, die mit [DataMember] markiert sind, berücksichtigt. Beachten Sie, dass für einen Typ, der sowohl [DataContract]- als auch [Serializable]-Attribute besitzt, das [DataContract]-Mapping verwendet wird.

Das [DataContract]-Mapping kann stärker angepasst werden als das durch [Serializable] zur Verfügung gestellte, es ist jedoch auch eingeschränkter als XmlSerializer. Der Code in Abbildung 14 zeigt die Verwendung einiger Anpassungseigenschaften von [DataContract] und [DataMember]. Im folgenden Beispiel sind der XML-Namespace, einige Elementnamen und die Elementenordnung angepasst. Durch die Einstellung IsRequired=true überprüft der Serialisierer, ob ein Element bei der Deserialisierung vorhanden ist. Dies wird im Schema als minOccurs="1" angezeigt. Der resultierende XML-Code sieht folgendermaßen aus:

<Person xmlns="http://example.org/person" 
    xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <Name>Bob</Name>
  <Age>34</Age>
  <Spouse>
    <Name>Jane</Name>
    <Age>33</Age>
    <Spouse i:nil="true"/>
  </Spouse>
</Person>

Anspruchsvollere Schema-Anpassungen sind über Attribute nicht möglich. So können beispielsweise keine Elemente in Attribute oder change- in sequence-Kompositoren umgewandelt werden. Wenn Sie das XML-Mapping komplett steuern möchten, können Sie z. B. ganz auf [DataContract]-Attribute verzichten und IXmlSerializable oder ISerializable implementieren (dabei besitzt IXmlSerializable die höhere Priorität). Dieser Ansatz erfordert jedoch mehr Aufwand.

Wenn Sie DataContractSerializer verwenden, stehen Ihnen ausschließlich XML-Schemadefinitionen zur Verfügung, die die [DataContract]-Mappingeinschränkungen erfüllen. Wird ein komplexeres XML-Schema an svcutil.exe übergeben, werden Sie benachrichtigt, wenn die [DataContract]-Einschränkungen nicht erfüllt sind, und aufgefordert, xsd.exe /XmlSerializer zu verwenden.

Trotz seiner Einschränkungen können Sie mit [DataMember] einige interessante Datenversionierungsheuristiken über den Order- oder IsRequired-Parameter implementieren. (Durch diese Vorgehensweise können Sie in zukünftigen Contractversionen problemlos neue Felder hinzufügen.) Wenn Sie IExtensibleData in Datencontracts implementieren, kann der Serialisierer für bei der Serialisierung nicht erkannte Daten mittels Roundtrip das Serialisierungsformat bestimmen, beispielsweise wenn eine neue Meldungsversion an eine alte Implementierung übergeben werden muss.

DataContractSerializer müssen Sie zunächst mit den Typinformationen versorgen, bevor Instanzen serialisiert bzw. deserialisiert werden können. Im Unterschied dazu werden bei .NET Remoting die Typinformationen zur Laufzeit identifiziert und die Nachrichten serialisiert, um für Typtreue im Netz zu sorgen. Wenn Sie für diesen Szenariotyp Unterstützung benötigen, sollten Sie NetDataContractSerializer verwenden.

Arbeiten mit NetDataContractSerializer

Der Hauptunterschied zwischen DataContractSerializer und NetDataContractSerializer besteht darin, dass NetDataContractSerializer .NET-Typinformationen in XML serialisiert. Ansonsten unterstützt NetDataContractSerializer [Serializable] und [DataContract], den gleichen Mappingalgorithmus und die gleichen Anpassungsattribute. Allerdings müssen Sie beim NetDataContractSerializer die Typinformationen, wie nachfolgend dargestellt, nicht vorab liefern:

NetDataContractSerializer serializer =
    new NetDataContractSerializer();  // no type specified
serializer.WriteObject(writer, p);

Wie nachfolgend dargestellt, enthält der erzeugte XML-Code .NET-Typinformationen in Attributen zum Stammelement (z:Type und z:Assembly):

<Person z:Id="1" z:Type="DataContractSamples.Person" 
    z:Assembly="DataContractSamples, Version=1.0.0.0, Culture=neutral, 
                PublicKeyToken=null" xmlns="http://example.org/person" 
    xmlns:i="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:z="https://schemas.microsoft.com/2003/10/Serialization/">
  <Name z:Id="2">Bob</Name>
  <Age>34</Age>
  <Spouse z:Id="3">
    <Name z:Id="4">Jane</Name>
    <Age>33</Age>
    <Spouse i:nil="true"/>
  </Spouse>
</Person>

Da die .NET-Typinformationen im XML-Code enthalten sind, muss der .NET-Typ beim Deserialisieren nicht angegeben werden.

Diese Möglichkeit wurde von Microsoft speziell für Situationen bereitgestellt, in denen Typtreue auch über Telefonleitungen benötigt wird. Überwiegend sollten Entwickler in Windows Communication Foundation jedoch den expliziteren DataContractSerializer-Ansatz wählen. Aus diesem Grund kann auch der NetDataContractSerializer erst dann mit einem Servicecontract verwendet werden, wenn Sie Verhalten und Attribute selbst definiert haben.

Erweiterte Serialisierungskonzepte

Bei der Verwendung von DataContractSerializer oder NetDataContractSerializer sind nur wenige Konzepte zu beachten. Bei der Serialisierung von Typen, die Vererbung verwenden, müssen Sie sich vergewissern, dass alle Typen in der Hierarchie serialisierbar sind (mittels [DataContract] oder [Serializable]). Trifft dies nicht zu, erhalten Sie die Ausnahmen zur Laufzeit. Ebenso müssen Sie sicherstellen, dass alle enthaltenen Typen auch serialisierbar sind.

Sollten u. U. nicht alle Typen serialisierbar gemacht werden können, können Sie ein so genanntes Serialisierungssurrogat verwenden. Dabei handelt es sich im Grunde um einen serialisierbaren Typ, der an die Stelle eines nicht-serialisierbaren Typs tritt, ohne dass die ursprüngliche Klassendefinition geändert werden muss. Dazu implementieren Sie IDataContractSurrogate und übergeben es an den Serialisierer. Dieser ruft das Surrogate beim Serialisierungsvorgang auf, woraufhin Sie das nicht-serialisierbare Objekt durch ein anderes Objekt ersetzen können.

Die Serialisierer von Windows Communication Foundation bieten auch eine Möglichkeit, bekannte Typsubstitutionen anzugeben, die vom Serialisierer zur Laufzeit erkannt werden. Dadurch können Sie ein serialisierbares Feld als Basistyp angeben und zur Laufzeit tatsächlich andere abgeleitete Typen serialisieren. Zu diesem Zweck weisen Sie Basistypen das [KnownType]-Attribut zu (so ähnlich wie mit [XmlInclude] in XmlSerializer). Entsprechend wird [ServiceKnownType] mit Servicecontractdefinitionen verwendet.

Die neuen Serialisierer können auch Objektverweise und Probleme wie Zirkelbezüge handhaben (siehe den Konstruktor mit einem preserveObjectReferences-Flag). Dies ist eine erfreuliche Verbesserung zu XmlSerializer, der Probleme mit solchen Objektgrafiken hatte. So kann beispielsweise ein spouse-Zirkelbezug in den bisherigen Beispielen serialisiert werden. Ein Beispiel dafür finden Sie im herunterladbaren Beispielcode.

Schlussbemerkung

Windows Communication Foundation basiert auf einem konsistenten Datenmodell (XML-Infoset) und flexiblen Darstellungen (Kodierungen). In WCF können Sie entweder direkt mit XML-Code arbeiten oder die integrierten Serialisierungstechniken zur Mappingautomatisierung verwenden. Dabei müssen Sie sehr selten Klassen für die Serialisierung programmieren, da Sie die Servicecontracts einfach so erweitern können, dass ein Serialisierer ausgewählt wird. Sie sollten jedoch unbedingt über die Arbeitsweise der einzelnen Serialisierer Bescheid wissen.

Sie sollten auch wissen, welcher Serialisierer sich am besten für ein bestimmtes Szenario eignet. Nach Möglichkeit sollten Sie in Windows Communication Foundation den DataContractSerializer verwenden. Sein eingeschränktes Mapping verbessert die Interoperabilität bei neuem Code. NetDataContractSerializer sollte nur verwendet werden, wenn Sie unbedingt Typtreue über Leitungen benötigen. Wenn Sie Rückwärtskompatibilität mit ASMX-Typen oder größere Flexibilität bei der XML-Darstellung benötigen bzw. von bestehenden Schemas ausgehen, die die [DataContract]-Mappingeinschränkungen nicht erfüllen, sollten Sie entsprechend XmlSerializer verwenden.

Senden Sie Fragen und Kommentare für Aaron Skonnard an sstation@microsoft.com (in englischer Sprache).

Der Autor

Aaron Skonnard ist Mitgründer von Pluralsight, einem Microsoft .NET-Schulungsanbieter. Er ist Verfasser der Pluralsight-Kurse "Applied Web Services 2.0", "Applied BizTalk Server 2006" und "Introducing Windows Communication Foundation". Seit mehreren Jahren entwickelt Aaron Skonnard Kurse, hält Vorträge auf Konferenzen und unterrichtet professionelle Entwickler. Sie erreichen ihn unter pluralsight.com/aaron (in englischer Sprache).

Aus der Ausgabe August 2006 des MSDN Magazine.