code it

Martins Tech Blog

RSS-Feeds an eigene Bedürfnisse anpassen

In meinem letzten Post hab ich eine kleine Einführung in das Thema RSS-Feeds mit .NET erzeugen gegeben. Nun sind aber nicht alle Inhalte, die man per RSS-Feed übertragen möchte in den Standard-Elementen unterzubringen, die die RSS-Spezifikation definiert. Typische Beispiele dafür sind zusätzliche Geodaten oder Daten zu Medieninhalten in Podcasts. Um auch dieser Anforderung gerecht zu werden, bieten die beiden vorgestellten Klassen SyndicationFeed und SyndicationItem noch die Möglichkeit eigene Attribute oder eigene Elemente zu definieren. Wie das geht, soll an einem kleinen Beispiel gezeigt werden:

Eigene Elemente zum Item hinzufügen

Möchte man einfach nur zusätzliche Elemente zu den Items hinzufügen, kommt man recht schnell ans Ziel. Schlüssel sind die ElementExtensions. Im Beispiel werden jedem Item noch die Elemente Latitude und Longitude zur Definition einer Geokoordinate hinzugefügt.

var item = new SyndicationItem
{
    Id = "http://myfeeds/2011/06/" + i,
    Title = SyndicationContent.CreatePlaintextContent("Wichtige Mitteilung"),
};

item.ElementExtensions.Add("Latitude", null, 34.2543);
item.ElementExtensions.Add("Longitude", null, 14.2545);

Das Resultat ist, dass wie gewünscht das Item-Element über die definierten Sub-Elemente verfügt.

<item>
    <guid isPermaLink="false">http://myfeeds/2011/06/0</guid>
    <title>Wichtige Mitteilung</title>
    <Latitude>34.2543</Latitude>
    <Longitude>14.2545</Longitude>
</item>

Den gleichen Weg kann man dann auch beim Einlesen der Daten wieder verwenden: In der Auflistung ElementExtensions befinden sich die hinzugefügten Elemente mit den jeweiligen Namen und Inhalten. Mit Hilfe der generischen Methode GetObject kann man dann auf die Inhalte zugreifen.

var feed2 = SyndicationFeed.Load(reader);
foreach (var item in feed2.Items)
{
    var latitudeElementExtension = item
        .ElementExtensions
        .Single(e => e.OuterName == "Latitude");
    var latitude = latitudeElementExtension.GetObject<double>();
}

Analog zu den ElementExtensions gibt es die AttributeExtensions, die (wie sollte es anders sein) keine Elemente sondern Attribute erzeugen.

Eigene komplexe Objekte zum Item hinzufügen

ElementExtensions erlauben nicht nur das Hinzufügen primitiver Datentypen sondern auch komplexer Objekte - Bedingung: Sie müssen xml-serialisierbar sein und es muss ein passender XML-Serializer existieren oder es muss sich um einen DataContract handeln. In meinem Fall definiere ich mir nun eine neue Klasse GeoCoordinate, die sich als Location serialisiert.

[XmlRoot("Location", Namespace = "http://ns.uniquesoftware.de/location")]
[Serializable]
public class GeoCoordinate
{
public GeoCoordinate()
{
}

 public GeoCoordinate(double lat, double lon)
 {
     Latitude = lat;
     Longitude = lon;
 }

 [XmlElement("Latitude")]
 public double Latitude { get; set; }
 [XmlElement("Longitude")]
 public double Longitude { get; set; }
}

Im Anschluss definiere ich wie vorhin auch eine ElementExtension und füge dieser der Auflistung des Elements hinzu.

var item = new SyndicationItem
{
    Id = "http://myfeeds/2011/06/" + i,
    Title = SyndicationContent.CreatePlaintextContent("Wichtige Mitteilung"),
};
item.ElementExtensions.Add(
    new GeoCoordinate(34.2543, 14.2545), 
    new XmlSerializer(typeof(GeoCoordinate)));

Das resultierende XML im Feed beinhaltet nun wie erwartet die eben definierten Tags, die über die definierten Namespaces verfügen.

<item>   
    <guid isPermaLink="false">http://myfeeds/2011/06/0</guid>
    <title>Wichtige Mitteilung</title>
    <Location 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns="http://ns.uniquesoftware.de/location">
        <Latitude>34.2543</Latitude>
        <Longitude>14.2545</Longitude>
    </Location>
</item>

Eigene Namespaces verwenden

Häufig trifft man auf Konstrukte, in denen der Namespace nicht explizit jedes Mal wieder als Attribut im Element definiert wird, sondern wo nur noch eine Namespace-Referenz existiert. Nichts einfacher als das: Dazu fügt man einfach die XML-Namespace-Deklaration der AttributeExtensions-Auflistung des SyndicationFeed-Objektes hinzu. Im folgenden Beispiel habe ich dafür den Namespace "my" deklariert.

// create new syndication feed 
var feed = new SyndicationFeed(
    "Mein Blog Feed",
    "Wissenwertes über ASP.NET, WPF und Silverlight",
    new Uri("http://feeds.feedburner.com/discoveringmicrosofttechnologies"))
    {
        Language = "de-DE",
        Copyright = new TextSyndicationContent("Unique Software"),
        Generator = "Unique Software News Generator"
    };
feed.AttributeExtensions.Add(
    new XmlQualifiedName("my", "http://www.w3.org/2000/xmlns/"), 
    "http://ns.uniquesoftware.de/location");

Der Effekt ist, dass nun der Namespace im XML-Element channel definiert und dann automatisch eine Namespace-Referenz aufgebaut wird.

<item>
    <guid isPermaLink="false">http://myfeeds/2011/06/0</guid>
    <title>Wichtige Mitteilung</title>
    <my:Location>
        <my:Latitude>34.2543</my:Latitude>
        <my:Longitude>14.2545</my:Longitude>
    </my:Location>
</item>

Zusammenfassung

ElementExtensions und AttributeExtensions sind eine gute Möglichkeit, RSS-Feeds flexibel auf die eigenen Bedürfnisse anzupassen und mit Hilfe des Objektmodells typsicher auf die Objekte beinhalteten Objekte zuzugreifen. Besonders bei XML-Strukturen, die nur noch sehr entfernt etwas mit dem ursprünglichen Feed-Objektmodell zu tun haben, wird es wohl immer wieder eine Abwägung von Aufwand und Nutzen sein, ob man ElementExtensions und die Syndication-Objekte verwendet oder ob man die XML-Struktur "schnell eben selbst" per Linq-To-Xml zusammenbaut. Ich persönlich finde die sich bietenden Möglichkeiten wieder recht umfangreich und finde es schade, dass die Klassen so ein unbeachtetes Dasein fristen. 

Englische Version Englische Version

RSS-Inhalte mit .NET erzeugen

Das Erzeugen einer XML-Struktur, die der RSS-Spezifikation entspricht ist gar nicht so kompliziert - erst recht nicht, seitdem man mit Linq-To-Xml recht einfach durch XML-Dokumente navigieren und diese auch modifizieren kann. Nachteil ist: Man muss die Spezifikation immer griffbereit haben, denn ganz schnell kommt man zu Fragen wie: "Wie hieß noch gleich das XML-Element für das Erstellungsdatum und in welchem Format muss es ausgegeben werden?" Was bisher aber nur wenige zu wissen scheinen: So viel Arbeit muss man sich gar nicht mehr machen. Seit dem .NET Framework 3.5 gibt es den Namespace System.ServiceModel.Syndication, dessen Klassen einen Großteil der Arbeit abnehmen. Die Erstellung ist ganz einfach: Zunächst erstellt man sich ein Objekt vom Typ SyndicationFeed. Dieses Objekt repräsentiert den eigentlichen Feed und dessen Eigenschaften.
 
// create new syndication feed  
var feed = new SyndicationFeed("Mein Blog Feed",
 "Wissenwertes über ASP.NET, WPF und Silverlight",
 new Uri("http://feeds.feedburner.com/discoveringmicrosofttechnologies"))
  {
     Language = "de-DE",
     Copyright = new TextSyndicationContent("Unique Software"),
     Generator = "Unique Software News Generator"
  };
 
Die Klasse SyndicationItem ermöglicht es dann, ebenfalls über das Objektmodell, die eigentlichen Einträge zu erzeugen, die mit Hilfe des Feeds übermittelt werden sollen. Diese werden dann dem Feed-Objekt nur noch zugewiesen.
 
// create syndication items which should be delivered
var items = new List<SyndicationItem>();
for (var i = 0; i < 5; i++)
{ 
 var item = new SyndicationItem
     {
         Id = "http://myfeeds/2011/06/" +  i,
         PublishDate = DateTime.Now,
         Title = SyndicationContent
             .CreatePlaintextContent("Wichtige Mitteilung"),
         Content = SyndicationContent
             .CreateHtmlContent("Meine wichtige Nachricht an alle Leser"),
         Summary = SyndicationContent
             .CreatePlaintextContent("Zusammenfassung meiner Mitteilung")
     };
     items.Add(item);
}
feed.Items = items;
 
Das war's auch schon fast: Letzter Schritt ist die Umwandlung in XML. Dafür gibt es auf dem Feed-Objekt die Methoden SaveAsAtom10 und SaveAsRss20, die diese Arbeit übernehmen.
 
var builder = new StringBuilder();
using (var writer = XmlWriter.Create(builder))
{
 feed.SaveAsRss20(writer);
 writer.Flush();
 writer.Close();
}
var xml = builder.ToString();
 
Im Hintergrund kommen hier je nach Methode die Atom10FeedFormatter-Klasse bzw. die Rss20FeedFormatter-Klasse zum Einsatz. Und mit diesem XML kann man nun das gleiche machen, was man sonst mit dem XML gemacht hätte, das man mit Linq-To-Xml zusammengebaut hätte: Man liefert es aus.

Englische Version Englische Version

back to basics: Generische XML-Serialisierung

Im heutigen btb-Post soll es um das Thema XML-Serialisierung gehen. Häufig sieht man im Alltag oder in Foren die Frage, wie ein Objekt in einen String serialisiert werden kann - z.B. um diese Daten dann in einem Datenspeicher abzulegen.

Im Grunde ist der Vorgang recht einfach: Man erzeugt einen XMLSerializer des zu (de-)serialisierenden Objekts und ruft dessen Serialize- bzw. Deserialize-Methode auf. Wenn man sich noch nie mit der Problemstellung beschäftigt hat, stellt sich dann aber die Frage, welche Parameter man der Methode übergibt, da hier String als Parametertyp nicht existiert. Verwendet man diese Methode häufiger, so ist es zudem noch nervig, am Ende der Deserialisierung den Cast zum Ziel-Typ zu machen.

Um die Sache zu vereinfachen kann man sich mit Hilfe von Generics recht einfach zwei Methoden schreiben, die diesen Task übernehmen.

static string SerializeObject<T>(T objectToSerialize)
{
  XmlSerializer serializer = new XmlSerializer(typeof(T));
  using (StringWriter writer = new StringWriter())
  {
      serializer.Serialize(writer, objectToSerialize);
      return writer.ToString();
  }
}

static T DeserializeObject<T>(string serializedData)
{
  XmlSerializer serializer = new XmlSerializer(typeof(T));
  using (StringReader reader = new StringReader(serializedData))
  {
      return (T)serializer.Deserialize(reader);
  }
}

Der Aufruf ist dann recht einfach:

Employee employee1 = new Employee { Name = "André Meyer" };
string serializedEmployee = SerializeObject(employee1);
Employee employee2 = DeserializeObject<Employee>(serializedEmployee);

Einen kleinen interessanten Nebeneffekt hat diese Art noch, wenn es auch nicht die beste Art ist, dieses Ziel zu erreichen: Durch die Serialisierung und Deserialisierung ist eine 1:1-Kopie des ursprünglichen Objekts entstanden.

LINQ to XML mit XML-Namespaces

Der Zugriff auf XML-Strukturen ist mit Linq-to-Xml recht einfach. Interessant wird es dann, wenn Namespaces mit im Spiel sind.

Nehmen wir mal diese XML-Datei an:

<library xmlns="http://schemas.mylibrary.com">
    <books>
        <book title="book 1" isbn="1234567890" />
        <book title="book 2" isbn="1234567891" />
        <book title="book 3" isbn="1234567892" />
    </books>
</library>

Ein Zugriffsversuch ohne Namespaceangabe bringt erwartungsgemäß keine Ergebnisse:

XDocument library = XDocument.Load("C:\\library.xml", LoadOptions.None);

var booktitles = from book in library.Descendants("book")
                 select book.Attribute("title").Value;

Lösung bietet die Angabe des Namespaces:

XDocument library = XDocument.Load("C:\\library.xml", LoadOptions.None);

var booktitles = from book in library.Descendants(XNamespace.Get("http://schemas.mylibrary.com") + "book")
                 select book.Attribute("title").Value;

Lesen und Schreiben von XML-Dateien mit LINQ to XML

Nachdem ich mich in den letzten Einträgen zum Thema LINQ eher mit dem Thema LINQ to Objects beschäftigt habe, möchte ich nun auch das Thema LINQ to XML anschneiden, denn auch beim Zugriff auf XML-Daten bietet LINQ den ein oder anderen Vorteil.

Ohne LINQ erfolgt der Zugriff auf XML-Daten zumeist mit den Klassen XmlDocument, XmlElement, XmlAttribute aus den Namespace System.Xml. Zum direkten Zugriff eignet sich XPath und zum Lesen und Schreiben der Dateien verwendet man häufig XmlReader- und XmlWriter-Objekte.

Mit LINQ ändern sich die verwendeten Objekte. Nun stehen die Klassen XDocument und XElement aus dem Namespace System.Xml.Linq zur Verfügung.

Grundlage für die Beispiele soll eine XML-Datei mit Adressdaten (aus der AdventureWorks-Beispieldatenbank) sein. Diese hat folgenden Aufbau:

<?xml version="1.0" encoding="utf-8"?>
<Adressen>
   <Adresse>
       <Id>1</Id>
       <Name>Guy Gilbert</Name>
       <Address>7726 Driftwood Drive</Address>
       <PostalCode>98272</PostalCode>
       <City>Monroe</City>
       <Country>United States</Country>
       <Phone>320-555-0195</Phone>
       <EmailAddress>guy1@adventure-works.com</EmailAddress>
   </Adresse>
   <Adresse>
       <Id>2</Id>
       <Name>Kevin Brown</Name>
       <Address>7883 Missing Canyon Court</Address>
       <PostalCode>98201</PostalCode>
       <City>Everett</City>
       <Country>United States</Country>
       <Phone>150-555-0189</Phone>
       <EmailAddress>kevin0@adventure-works.com</EmailAddress>
   </Adresse>
</Adressen>

Schreiben von XML-Daten

Schreiben von XML-Daten im .NET 2.0-Style

Zunächst soll die Beispieldatei ohne Verwendung von LINQ erzeugt werden. Damit der Code etwas übersichtlicher wird, hab ich die Erzeugung eines einfachen Elementknotens mit Text in eine separate Funktion ausgelagert.

 

Die eigentliche Routine sieht dann wie im folgenden Beispiel aus:

 

Zunächst wird ein neues XmlDocument erzeugt. Diesem wird zunächst die Xml-Deklaration hinzugefügt. Im Anschluss daran wird der Hauptknoten erzeugt und diesem die beiden Unterknoten hinzugefügt. Das letzte Statement speichert das Dokument.

Schreiben von XML-Dateien mit LINQ

Das Ganze funktioniert auch mit LINQ und - wie ich finde - übersichtlicher und einfacher.

 

Im Konstruktor der Klasse XElement können beliebig viele andere Objekte angegeben werden, so dass schon hier die komplette Dokumentenstruktur aufgebaut werden kann. Mit entsprechenden Einrückungen im Quellcode bleibt es trotzdem übersichtlich. 

Lesen von XML-Daten 

Lesen von XML-Daten im .NET 2.0-Style

Aus der Datei sollen nun die Namen der Personen nach Postleitzahl sortiert ausgegeben werden - der Einfachheit halber auf der Konsole. Auch hierzu zunächst der Quellcode aus .NET 2.0.

 

Zunächst wird die XML-Datei als XmlDocument geladen. Über GetElementsByTagName können alle Elemente mit dem Tag-Namen "PostalCode" ermittelt werden. Diese werden dann in eine generische Liste hinzugefügt, um einfacher mit Hilfe von einer anonymen Methode sortieren zu können.

Das Schema vorausgesetzt kann dann mittels Zugriff auf den ParentNode der Name der zugehörigen Person ausgelesen und ausgegeben werden.

Lesen von XML-Dateien mit LINQ

Auch hier geht alles etwas eleganter und lesbarer mit LINQ.

 

Es wird ein neues XDocument erzeugt, das mittels der statischen Methode Load auch gleich mit Daten befüllt wird. Dann übernimmt eine einzige Linq-Anweisung die komplette Erfüllung der Anforderung. Sie ermittelt alle Namen aus den Adress-Elementen und sortiert diese nach Postleitzahl.

Ähnlich wie LINQ to Objects ist auch LINQ to XML recht einfach in der Handhabung und bringt schnell die gewünschten Ergebnisse.

Entitäten in XML-Daten korrekt abfragen

Entschließt man sich, komplexe Datentypen in XML-Form im SQL-Server abzulegen, stolpert man früher oder später über die Tatsache, dass im Datentyp enthaltene Sonderzeichen wie < oder > in Entitäten umgewandelt werden. Nun sollen diese Entitäten bei der Abfrage natürlich wieder in deren korrekte Repräsentation umgewandelt werden. Hier hängt viel davon ab, welche Konvertierung man dazu verwendet.

Im folgenden Beispiel ist im Feld "XmlField" der Tabelle "table" ein XML-Wert gespeichert, der im Tag parent/child den Text "Dieses Beispiel enthält <." enthält.

Die Abfrage

SELECT CAST(XmlField.query('/parent/child/text()') as nvarchar(max)) FROM [table]

liefert das Ergebnis

Dieses Beispiel enthält &lt;.

Als Lösung verwendet man einfache eine andere Abfrage. So liefert die Abfrage 

SELECT XmlField.value('('/parent/child/text())[1]', 'nvarchar(max)') FROM [table]

das korrekte Ergebnis

Dieses Beispiel enthält <.