Forget about writing Atom or RSS XML handling code ever again
A very welcome addition to .NET 3.5, which just went RTM for MSDN subscribers and trial for the rest before general availability early next year: System.ServiceModel.Syndication.
This namespace, which lives in the System.ServiceModel.Web.dll assembly which provides the WCF Syndication functionality, contains useful classes for working with feeds and items. I won’t go over the Architecture of Syndication, How the WCF Syndication Object Model Maps to Atom and RSS, How to: Create a Basic RSS Feed, How to: Create a Basic RSS Feed, How to: Expose a Feed as both Atom and RSS or the basics of Syndication Extensibility. All those links provide enough to get you started.
The typical usage is:
- Create XmlReader/XmlWriter of the feed you will be reading/writing.
- Create Atom10FeedFormatter/Rss20FeedFormatter which knows how to read/write the given format (there are Atom10ItemFormatter and Rss20ItemFormatter too)
- Read or write a SyndicationFeed or a SyndicationFeedItem.
The only thing I felt was missing was a factory that abstracts me from having to decide which formatter to create for a given source: the factory should be able to determine this automatically depending on the first element of the feed/item.
With the factory in place, reading a feed without caring for its format, is as simple as:
using (XmlReader reader = XmlReader.Create(atomFeedUrl))
{
** SyndicationFeedFormatter formatter = SyndicationFormatterFactory.CreateFeedFormatter(reader);
** formatter.ReadFrom(reader);
SyndicationFeed feed = formatter.Feed;
}
Note that there’s nothing in my code that knows about RSS or Atom.
Processing of the item and extensions is very friendly and quite flexible, making for a very nice platform to base syndication extension processing plugins:
foreach (SyndicationItem item in feed.Items)
{
// ... process item
foreach (SyndicationElementExtension extension in item.ElementExtensions)
{
// ... process extensions
// example: SSE sync
if (extension.OuterNamespace == Sync.NamespaceUri)
{
// NOTE: we don't need to pass an explicit XmlSerializer because we implement
// IXmlSerializable, so the library figures out it needs one :)
Sync sync = extension.GetObject<Sync>();
// ... do something with the extension
}
else if (extension.OuterName == "Mock")
{
// this extension does not implement IXmlSerializable neither is a WCF data
// contract, so we need to pass an XmlSerializer
Mock m = extension.GetObject<Mock>(new XmlSerializer(typeof(Mock)));
// ... do something with the extension.
}
}
}
So here goes such a factory:
/// <summary>
/// Creates formatters for RSS 2.0 and Atom 1.0 according to the input content.
/// </summary>
/// <remarks>
/// <see cref="http://www.clariusconsulting.net/kzu">Created by Daniel Cazzulino.</see>
/// </remarks>
public static class SyndicationFormatterFactory
{
static XmlReaderSettings settings;
static SyndicationFormatterFactory()
{
// Makes the processing faster for the readers we create.
settings = new XmlReaderSettings();
settings.IgnoreComments = true;
settings.IgnoreProcessingInstructions = true;
settings.IgnoreWhitespace = true;
settings.CheckCharacters = true;
settings.CloseInput = true;
}
/// <summary>
/// Creates a <see cref="SyndicationFeedFormatter"/> according to the
/// input format.
/// </summary>
/// <param name="uriString">Feed location</param>
/// <exception cref="NotSupportedException">The input does not contain a valid RSS 2.0 or Atom 1.0 feed.</exception>
public static SyndicationFeedFormatter CreateFeedFormatter(string uriString)
{
using (XmlReader reader = XmlReader.Create(uriString, settings))
return CreateFeedFormatter(reader);
}
/// <summary>
/// Creates a <see cref="SyndicationFeedFormatter"/> according to the
/// input format.
/// </summary>
/// <param name="uri">Feed location</param>
/// <exception cref="NotSupportedException">The input does not contain a valid RSS 2.0 or Atom 1.0 feed.</exception>
public static SyndicationFeedFormatter CreateFeedFormatter(Uri uri)
{
using (XmlReader reader = XmlReader.Create(uri.ToString(), settings))
return CreateFeedFormatter(reader);
}
/// <summary>
/// Creates a <see cref="SyndicationFeedFormatter"/> according to the
/// input format.
/// </summary>
/// <param name="reader">Feed source</param>
/// <exception cref="NotSupportedException">The input does not contain a valid RSS 2.0 or Atom 1.0 feed.</exception>
public static SyndicationFeedFormatter CreateFeedFormatter(XmlReader reader)
{
if (reader.ReadState == ReadState.Initial)
{
reader.MoveToContent();
}
Rss20FeedFormatter rss = new Rss20FeedFormatter();
if (rss.CanRead(reader))
{
return rss;
}
Atom10FeedFormatter atom = new Atom10FeedFormatter();
if (atom.CanRead(reader))
{
return atom;
}
throw new NotSupportedException("Invalid feed root element: " + reader.Name);
}
/// <summary>
/// Creates a <see cref="SyndicationItemFormatter"/> according to the
/// input format.
/// </summary>
/// <param name="uriString">Item location</param>
/// <exception cref="NotSupportedException">The input does not contain a valid RSS 2.0 or Atom 1.0 item.</exception>
public static SyndicationItemFormatter CreateItemFormatter(string uriString)
{
using (XmlReader reader = XmlReader.Create(uriString, settings))
return CreateItemFormatter(reader);
}
/// <summary>
/// Creates a <see cref="SyndicationItemFormatter"/> according to the
/// input format.
/// </summary>
/// <param name="uri">Item location</param>
/// <exception cref="NotSupportedException">The input does not contain a valid RSS 2.0 or Atom 1.0 item.</exception>
public static SyndicationItemFormatter CreateItemFormatter(Uri uri)
{
using (XmlReader reader = XmlReader.Create(uri.ToString(), settings))
return CreateItemFormatter(reader);
}
/// <summary>
/// Creates a <see cref="SyndicationItemFormatter"/> according to the
/// input format.
/// </summary>
/// <param name="reader">Item source</param>
/// <exception cref="NotSupportedException">The input does not contain a valid RSS 2.0 or Atom 1.0 item.</exception>
public static SyndicationItemFormatter CreateItemFormatter(XmlReader reader)
{
if (reader.ReadState == ReadState.Initial)
{
reader.MoveToContent();
}
Rss20ItemFormatter rss = new Rss20ItemFormatter();
if (rss.CanRead(reader))
{
return rss;
}
Atom10ItemFormatter atom = new Atom10ItemFormatter();
if (atom.CanRead(reader))
{
return atom;
}
throw new NotSupportedException("Invalid item element: " + reader.Name);
}
}
You can also download the plain text representation for easier copying.
This work is licensed under a Creative Commons Attribution 3.0 Unported License.
/kzu
/kzu dev↻d