Bewährte Methoden für die Darstellung von XML im .NET Framework

 

Dare Obasanjo
Microsoft Corporation

16. März 2004

Zusammenfassung: Dare Obasanjo untersucht die verfügbaren Optionen für die Darstellung VON XML-basierten Daten, die zwischen Komponenten innerhalb eines einzelnen Prozesses und in AppDomain gemeinsam genutzt werden, und erläutert die Entwurfskonflikten der einzelnen Ansätze. (10 gedruckte Seiten)

Einführung

Nach einer kürzlich durchgeführten Entwurfsüberprüfung fragte ein anderer PM, ob es Entwurfsrichtlinien für die Offenlegung von XML in einer API gibt, da er viele verschiedene Ansätze gesehen hatte und nicht sicher war, welche er wählen sollte. Ich sagte ihm, ich glaube, es gäbe einige Richtlinien auf MSDN, aber als ich überprüfte, stieß ich nur auf eine Episode von MSDN TV mit dem Titel Passing XML Data Inside the CLR, die solche Informationen enthält, aber nicht in einer leicht konsumierbaren Form. So entstand die Idee, eine druckerfreundliche Version der MSDN TV-Episode von Don Box bereitzustellen, zusammen mit einigen meiner eigenen Erfahrungen mit der Arbeit an XML-APIs bei Microsoft.

Ein erster Blick auf die Richtlinien

Es gibt drei primäre Situationen, in denen Entwickler berücksichtigen müssen, welche APIs zum Darstellen von XML verwendet werden sollen. Die Situationen und Richtlinien werden nachfolgend kurz beschrieben:

  • Klassen mit Feldern oder Eigenschaften, die XML enthalten: Wenn eine Klasse über ein Feld oder eine Eigenschaft verfügt, die ein XML-Dokument oder -Fragment ist, sollte sie Mechanismen zum Bearbeiten der Eigenschaft als Zeichenfolge und als XmlReader bereitstellen.
  • Methoden, die XML-Eingabe akzeptieren oder XML als Ausgabe zurückgeben: Methoden, die XML akzeptieren oder zurückgeben, sollten die Rückgabe von XmlReader oder XPathNavigator bevorzugen, es sei denn, der Benutzer wird erwartet, dass er die XML-Daten bearbeiten kann. In diesem Fall sollte XmlDocument verwendet werden.
  • Konvertieren eines Objekts in XML: Wenn ein Objekt zu Serialisierungszwecken eine XML-Darstellung von sich selbst bereitstellen möchte, sollte es den XmlWriter verwenden, wenn es mehr Kontrolle über den XML-Serialisierungsprozess benötigt, als vom XmlSerializer bereitgestellt wird. Wenn das Objekt eine XML-Darstellung von sich selbst bereitstellen möchte, die es ermöglicht, vollständig als Mitglied der XML-Welt teilzunehmen, z. B. XPath-Abfragen oder XSLT-Transformationen über das Objekt zulassen, sollte es die IXPathNavigable-Schnittstelle implementieren.

In den folgenden Abschnitten beschreibe ich die oben genannten Situationen ausführlicher und erkläre, wie ich zu den Richtlinien gekommen bin.

Die üblichen Verdächtigen

Wenn Sie sich entscheiden, XML von einer Methode oder Eigenschaft zurückzugeben, kommen eine Reihe von Klassen in der .NET Framework als für diese Aufgabe geeignet ein. Im Folgenden finden Sie eine Liste der fünf Klassen in der .NET Framework am besten für die Darstellung von XML-Eingaben oder -Ausgaben mit einer kurzen Beschreibung ihrer Vor- und Nachteile geeignet.

  1. System.Xml. XmlReader: Der XmlReader ist der XML-Parser des Pullmodells von .NET Framework . Während der Pullmodellverarbeitung steuert der Consumer des XML-Codes den Programmfluss, indem er bei Bedarf Ereignisse vom XML-Produzenten anfordert. Pull-Modell-XML-Parser, z . B. xmlReader, funktionieren nur vorwärts, streamingend, während zu einem bestimmten Zeitpunkt nur Informationen zu einem einzelnen Knoten angezeigt werden. Die Tatsache, dass ein XmlReader nicht erfordert, dass das gesamte XML-Dokument in den Arbeitsspeicher geladen werden muss und schreibgeschützt ist, macht es zu einer guten Wahl, um eine XML-Fassade über Nicht-XML-Datenquellen zu erstellen. Ein Beispiel für eine solche XML-Fassade gegenüber einer Nicht-XML-Datenquelle ist der XmlCsvReader. Einige sehen möglicherweise die vorwärtsgerichteten Merkmale des XmlReaders als Einschränkung, da benutzer daran gehindert werden, mehrere Durchläufe über Teile eines XML-Dokuments vorzunehmen.
  2. System.Xml. XPath.XPathNavigator: Der XPathNavigator ist ein schreibgeschützter Cursor für XML-Datenquellen. Ein XML-Cursor verhält sich wie eine Linse, die sich jeweils auf einen XML-Knoten konzentriert, aber im Gegensatz zu pullbasierten APIs wie xmlReader kann der Cursor jederzeit überall entlang des XML-Dokuments positioniert werden. Pullmodell-APIs sind in gewisser Weise nur Vorwärtsversionen eines Cursormodells. Der XPathNavigator ist auch ein guter Kandidat für die Implementierung einer XML-Fassade für Nicht-XML-Daten, da er es ermöglicht, die XML-Ansichten einer Datenquelle just-in-time zu erstellen, ohne die gesamte Datenquelle in eine XML-Struktur konvertieren zu müssen. Ein Beispiel für die Verwendung von XPathNavigator zum Erstellen einer XML-Ansicht von Nicht-XML-Daten ist ObjectXPathNavigator. Die Tatsache, dass der XPathNavigator schreibgeschützt ist und einige benutzerfreundliche Eigenschaften wie InnerXml und OuterXml fehlt, macht ihn weniger verträglich als die Verwendung von XmlDocument oder XmlNode in Fällen, die diese Features erfordern.
  3. System.Xml. XmlWriter: Der XmlWriter bietet einen generischen Mechanismus zum Pushen eines XML-Dokuments in einen zugrunde liegenden Speicher. Bei diesem zugrunde liegenden Speicher kann es sich um eine beliebige Datei handeln, wenn Sie den XmlTextWriter verwenden, um ein XmlDocument-Dokument zu verwenden, wenn Sie den XmlNodeWriter verwenden. Die Verwendung eines XmlWriter-Parameters für Methoden, die XML zurückgeben, bietet eine robuste Möglichkeit, eine vielzahl möglicher Rückgabetypen zu unterstützen, einschließlich Dateidatenströme, Zeichenfolgen und XmlDocument-Instanzen . Aus diesem Grund akzeptiert die XmlSerializer.Serialize()-Methode einen XmlWriter-Parameter für eine ihrer Überladungen. Wie der Name schon sagt, ist der XmlWriter nur zum Schreiben von XML nützlich und kann nicht zum Lesen oder Verarbeiten von XML verwendet werden.
  4. System.Xml. XmlDocument/XmlNode: Das XmlDocument ist eine Implementierung des W3C Document Object Model (DOM). Das DOM ist eine Im-Memory-Darstellung eines XML-Dokuments, das aus einer hierarchischen Struktur von XmlNode-Objekten besteht, die logische Komponenten des XML-Dokuments wie Elemente, Attribute und Textknoten darstellen. Das DOM ist die beliebteste API zum Bearbeiten von XML im .NET Framework, da es eine einfache Möglichkeit zum Laden, Verarbeiten und Speichern von XML-Dokumenten bietet. Der Standard Nachteil des DOM besteht darin, dass für seinen Entwurf das gesamte XML-Dokument in den Arbeitsspeicher geladen werden muss.
  5. System.String: XML ist ein textbasiertes Format und kann Text besser darstellen als die String-Klasse . Der Hauptvorteil der Verwendung einer Zeichenfolge als Art der Darstellung von XML besteht darin, dass sie der kleinste gemeinsame Nenner ist. Zeichenfolgen lassen sich einfach in Protokolldateien schreiben oder in der Konsole drucken. Wenn man den XML-Code tatsächlich mithilfe von XML-APIs verarbeiten möchte, kann man die Zeichenfolge in ein XmlDocument oder XPathDocument laden. Es gibt mehrere Probleme bei der Verwendung von Zeichenfolgen als primäre Eingabe oder Ausgabe in Methoden oder Eigenschaften, die XML verwenden. Das erste Problem mit Zeichenfolgen besteht darin, dass sie wie das DOM das gesamte XML-Dokument in den Arbeitsspeicher laden müssen. Zweitens ist die Darstellung von XML als Zeichenfolgen eine Belastung für den Produzenten, eine XML-Zeichenfolge zu generieren, was in einigen Fällen umständlich sein kann. Ein Beispiel für einen Fall, in dem das Generieren einer XML-Zeichenfolge umständlich sein könnte, ist, wenn die XML von einem XmlReader oder XPathNavigator abgerufen wird. Drittens kann die Darstellung von XML als Zeichenfolgen zu Verwirrung in Bezug auf Zeichencodierungen führen, da unabhängig davon, welche Codierungsdeklaration im XML-Code platziert ist, eine Zeichenfolge im .NET Framework immer in der UTF-16-Zeichencodierung ist. Schließlich macht die Darstellung von XML als Zeichenfolgen das Erstellen einer XML-Verarbeitungspipeline schwierig, da jede Ebene in der Pipeline das Dokument erneut analysieren muss.

Klassen mit Feldern oder Eigenschaften, die XML enthalten

In bestimmten Fällen kann ein Objekt über ein Feld oder eine Eigenschaft verfügen, bei dem es sich um ein XML-Dokument oder ein XML-Fragment handelt. Die folgende Beispielklasse stellt eine E-Mail-Nachricht dar, in der der Inhalt XHTML ist. Der XML-Inhalt der Nachricht wird als Zeichenfolge dargestellt und durch die Body-Eigenschaft der -Klasse verfügbar gemacht:

public class Email{  
  private string from;
  public string From{
    get { return from; }
    set {from = value; }
  }
  private string to;
  public string To{
    get { return to; }
    set {to = value; }
  }
  private string subject;
  public string Subject{
    get { return subject; }
    set {subject = value; }
  }
  private DateTime sent;
  public DateTime Sent{
    get { return sent; }
    set {sent = value; }
  }
  private XmlDocument body = new XmlDocument(); 
  public string Body{
    get { return body.OuterXml; }
    set { body.Load(new System.IO.StringReader(value));}
  }    
}

Die Verwendung einer Zeichenfolge als primäre Darstellung eines Felds oder einer Eigenschaft, die ein XML-Dokument darstellt, ist die benutzerfreundlichste Darstellung, da die System.String-Klasse dem durchschnittlichen Entwickler am vertrautsten ist. Dies stellt jedoch eine Belastung für Benutzer der Klasse dar, die jetzt möglicherweise mit den Kosten der zweimaligen Analyse des XML-Dokuments zu tun haben. Betrachten Sie beispielsweise eine Situation, in der eine solche Eigenschaft aus XML festgelegt wurde, die sich aus der SqlCommand.ExecuteXmlReader()-Methode oder der XslTransform.Transform()-Methode ergibt. In solchen Fällen müsste der Benutzer das Dokument zweimal analysieren, wie im folgenden Beispiel gezeigt:

     Email email   = new Email(); 
    email.From    = "dareo@example.com"; 
    email.To      = "michealb@example.org"; 
    email.Subject = "Hello World"; 

    XslTransform transform = new XslTransform(); 
    transform.Load("format-body.xsl"); 

    XmlDocument body = new XmlDocument(); 
    //1. XML is parsed by XmlDocument.Load()
    body.Load(transform.Transform(new XPathDocument("body.xml"), null));

    //2. The exact same XML is parsed again by XmlDocument.Load() in Email.Body property
    email.Body = body.OuterXml;
 

Im obigen Beispiel wird derselbe XML-Code zweimal analysiert, da er aus dem XmlReader in das XmlDocument geladen werden muss, bevor er in eine Zeichenfolge konvertiert werden kann. Dies wird dann verwendet, um die Body-Eigenschaft der Email-Klasse festzulegen, die den XML-Code selbst intern in ein XmlDocument analysiert. Eine gute Faustregel ist das Bereitstellen des Zugriffs auf eine XML-Eigenschaft als XmlReader und eine Zeichenfolge. Daher würden auch die folgenden Methoden der Email-Klasse hinzugefügt, um die maximale Flexibilität für Benutzer der -Klasse zu erhöhen:

public void SetBody(XmlReader reader){
    body.Load(reader); 
  }

  public XmlReader GetBody(){
    return new XmlNodeReader(body); 
  }

Dadurch erhalten Benutzer der Email-Klasse die Möglichkeit, XML-Daten innerhalb der Body-Eigenschaft bei Bedarf effizient zu übergeben, festzulegen und abzurufen.

**Richtlinie **Wenn eine Klasse über ein Feld oder eine Eigenschaft verfügt, die ein XML-Dokument oder -Fragment ist, sollte sie Mechanismen zum Bearbeiten der Eigenschaft als Zeichenfolge und als XmlReader bereitstellen.

Aufmerksame Leser bemerken möglicherweise, dass, wenn man ein XmlDocument direkt als Eigenschaft verfügbar macht, die Richtlinie erfüllt und benutzern der Eigenschaft auch die Möglichkeit gibt, fein abgestufte Änderungen am XML-Code vorzunehmen.

Methoden, die XML-Eingabe akzeptieren oder XML als Ausgabe zurückgeben

Beim Entwerfen von Methoden, die XML erzeugen oder nutzen, haben Entwickler die Verantwortung, diese Methoden in der von ihnen akzeptierten Eingabe flexibel zu gestalten. Bei Methoden, die XML als Eingabe akzeptieren, könnte man diese in Methoden unterteilen, die erwarten, die Daten an Ort und Stelle zu ändern, und in Methoden, die nur schreibgeschützten Zugriff auf den XML-Code erfordern. Die einzige der üblichen XML-Verdächtigen , die Lese-/Schreibzugriff unterstützt, ist das XmlDocument. Das folgende Codebeispiel zeigt eine solche Methode:

public void ApplyDiscount(XmlDocument priceList){
    foreach(XmlElement price in priceList.SelectNodes("//price")){
      price.InnerText = (Double.Parse(price.InnerText) * 0.85).ToString();      
    }
  }

Für Methoden, die schreibgeschützten Zugriff auf den XML-Code erfordern, gibt es zwei primäre Optionen:

  • XmlReader
  • Xpathnavigator

Der XmlReader bietet vorwärtsgerichteten Zugriff auf den XML-Code, während der XPathNavigator zufälligen Zugriff auf eine zugrunde liegende XML-Quelle sowie die Möglichkeit bietet, XPath-Abfragen für die Datenquelle auszuführen. In den folgenden Codebeispielen werden der Künstler und der Titel aus dem folgenden XML-Dokument gedruckt:

<items>
    <compact-disc>
      <price>16.95</price>
      <artist>Nelly</artist>
      <title>Nellyville</title>
    </compact-disc>
    <compact-disc>
      <price>17.55</price>
       <artist>Baby D</artist>
       <title>Lil Chopper Toy</title>
    </compact-disc>
  </items>
XmlReader: 
public static void PrintArtistAndPrice(XmlReader reader){
    reader.MoveToContent(); //move from root node to document element (items)
    /* keep reading until we get to the first <artist> element */
    while(reader.Read()){

      if((reader.NodeType == XmlNodeType.Element) && reader.Name.Equals("artist")){

        artist = reader.ReadElementString();
        title  = reader.ReadElementString(); 
        break; 
      }
    }
    Console.WriteLine("Artist={0}, Title={1}", artist, title);
  }
}

Xpathnavigator:

public static void PrintArtistAndPrice(XPathNavigator nav){

    XPathNodeIterator iterator = nav.Select("/items/compact-disc[1]/artist 
       | /items/compact-disc[1]/title");

    iterator.MoveNext();
    Console.WriteLine("Artist={0}", iterator.Current);

    iterator.MoveNext();
    Console.WriteLine("Title={0}", iterator.Current);
  }

Im Allgemeinen gelten ähnliche Regeln für Methoden, die XML zurückgeben. Wenn erwartet wird, dass der XML-Code vom Empfänger bearbeitet wird, sollte ein XmlDocument zurückgegeben werden. Andernfalls sollte ein XmlReader oder XPathNavigator zurückgegeben werden, je nachdem, ob die -Methode streamingbasierten Vorwärtszugriff auf die XML-Daten bereitstellen muss oder nicht.

**Richtlinie **Methoden, die XML akzeptieren oder zurückgeben, sollten die Rückgabe von XmlReader oder XPathNavigator bevorzugen, es sei denn, der Benutzer wird erwartet, dass er die XML-Daten bearbeiten kann. In diesem Fall sollte XmlDocument verwendet werden.

Die obige Richtlinie impliziert, dass Methoden, die XML zurückgeben, die Rückgabe des XmlReader bevorzugen sollten, da es mehr Benutzerfälle als jeder der anderen Typen erfüllt. In Fällen, in denen Aufrufer der -Methode mehr Funktionalität benötigen, können sie auch ein XmlDocument oder XPathDocument aus dem zurückgegebenen XmlReader laden.

Konvertieren eines Objekts in XML

Die Allgegenwärtigkeit von XML als gemeinsame Sprache für den Informationsaustausch macht es zur offensichtlichen Wahl für bestimmte Objekte, die sich selbst als XML darstellen möchten, entweder für Zwecke der Serialisierung oder um Zugriff auf andere XML-Technologien wie eine Abfrage mit XPath oder transformation mit XSLT zu erhalten.

Beim Konvertieren eines Objekts in XML zu Serialisierungszwecken besteht die offensichtliche Wahl darin, die XML-Serialisierungstechnologie im .NET Framework zu verwenden. In bestimmten Fällen kann jedoch eine größere Kontrolle über die generierte XML erforderlich sein, als vom XmlSerializer bereitgestellt wird. In solchen Fällen ist der XmlWriter eine praktische Klasse in Ihrem Toolkit, da sie Sie von der Anforderung befreit, dass zwischen der Struktur der Klasse und dem generierten XML eine 1:1-Zuordnung vorhanden ist. Das folgende Beispiel zeigt den XML-Code, der mithilfe eines XmlWriter generiert wurde, um die in den vorherigen Abschnitten erwähnte Email Klasse zu serialisieren.

public void Save(XmlWriter writer){

    writer.WriteStartDocument(); 
    writer.WriteStartElement("email"); 
    writer.WriteStartElement("headers");

    writer.WriteStartElement("header");
    writer.WriteElementString("name", "to"); 
    writer.WriteElementString("value", this.To); 
    writer.WriteEndElement(); //header

    writer.WriteStartElement("header");
    writer.WriteElementString("name", "from"); 
    writer.WriteElementString("value", this.From); 
    writer.WriteEndElement(); //header

    writer.WriteStartElement("header");
    writer.WriteElementString("name", "subject"); 
    writer.WriteElementString("value", this.Subject); 
    writer.WriteEndElement(); //header
    
    writer.WriteStartElement("header");
    writer.WriteElementString("name", "sent"); 
    writer.WriteElementString("value", XmlConvert.ToString(this.Sent)); 
    writer.WriteEndElement(); //header

    writer.WriteEndElement(); //headers;
    
    writer.WriteStartElement("body");
    writer.WriteRaw(this.Body); 

    writer.WriteEndDocument(); //closes all open tags
  }
Which generates the following XML document 
<email>
  <headers>
    <header>
      <name>to</name>
      <value>michealb@example.org</value>
    </header>
    <header>
      <name>from</name>
      <value>dareo@example.com</value>
    </header>
    <header>
      <name>subject</name>
      <value>Hello World</value>
    </header>
    <header>
      <name>sent</name>
      <value>2004-03-05T15:54:13.5446771-08:00</value>
    </header>
  </headers>
  <body><p>Hello World is my favorite sample application.</p></body>
</email>

Das obige XML-Dokument kann nicht nur mit den grundlegenden Funktionen des XmlSerializer generiert werden. Der andere Vorteil von XmlWriter besteht darin, dass er von dem zugrunde liegenden Ziel abstrahiert wird, in das die Daten geschrieben werden, sodass es sich um eine beliebige Datei auf dem Datenträger bis hin zu einer In-Memory-Zeichenfolge oder sogar um ein XmlDocument mit freundlicher Genehmigung des XmlNodeWriter handeln kann.

Wenn die Anforderung darin besteht, dass die Klasse eine Möglichkeit bietet, sich umfassender an der XML-Welt zu beteiligen, z. B. die Interaktion mit XML-Technologien wie XPath oder XSLT, besteht die beste Option darin, dass die Klasse die IXPathNavigable-Schnittstelle implementiert und einen XPathNavigator für die Klasse bereitstellt. Ein Beispiel hierfür ist der ObjectXPathNavigator, der eine XML-Ansicht über beliebige Objekte bereitstellt, die es ihnen ermöglicht, XPath-Abfragen auszuführen oder XSLT-Transformationen für diese Objekte auszuführen.

**Richtlinie **Wenn ein Objekt eine XML-Darstellung für Serialisierungszwecke bereitstellen möchte, sollte es den XmlWriter verwenden, wenn es mehr Kontrolle über den XML-Serialisierungsprozess benötigt, als vom XmlSerializer bereitgestellt wird. Wenn das Objekt eine XML-Darstellung von sich selbst bereitstellen möchte, die es ermöglicht, vollständig als Mitglied der XML-Welt teilzunehmen, z. B. XPath-Abfragen oder XSLT-Transformationen über das Objekt zulassen, sollte es die IXPathNavigable-Schnittstelle implementieren.

Zusammenfassung

In zukünftigen Versionen des .NET Framework wird mehr Wert auf cursorbasierte XML-APIs wie den XPathNavigator gelegt, der von der IXPathNavigable-Schnittstelle verfügbar gemacht wird. Solche Cursor sind die primären Mechanismen für die Interaktion mit XML im .NET Framework.

Dare Obasanjo ist Mitglied des WebData-Teams von Microsoft, das unter anderem die Komponenten innerhalb des System.Xml- und System.Data-Namespaces der .NET Framework, Microsoft XML Core Services (MSXML) und Microsoft Data Access Components (MDAC) entwickelt.

Sie können alle Fragen oder Kommentare zu diesem Artikel auf dem Extreme XML-Nachrichtenboard in GotDotNet posten.