Strongly typed extensions sample
The StronglyTypedExtensions sample uses the SyndicationFeed class for the purposes of the example. However, the patterns demonstrated in this sample can be used with all of the Syndication classes that support extension data.
The Syndication object model (SyndicationFeed, SyndicationItem, and related classes) supports loosely-typed access to extension data by using the AttributeExtensions and ElementExtensions properties. This sample shows how to provide strongly typed access to extension data by implementing custom derived classes of SyndicationFeed and SyndicationItem that make available certain application-specific extensions as strongly typed properties.
As an example, this sample shows how to implement an extension element defined in the proposed Atom Threading Extensions RFC. This is for demonstration purposes only and this sample is not intended to be a full implementation of the proposed specification.
Sample XML
The following XML example shows an Atom 1.0 entry with an additional <in-reply-to>
extension element.
<entry>
<id>tag:example.org,2005:1,2</id>
<title type="text">Another response to the original</title>
<summary type="text">
This is a response to the original entry</summary>
<updated>2006-03-01T12:12:13Z</updated>
<link href="http://www.example.org/entries/1/2" />
<in-reply-to p3:ref="tag:example.org,2005:1"
p3:href="http://www.example.org/entries/1"
p3:type="application/xhtml+xml"
xmlns:p3="http://contoso.org/syndication/thread/1.0"
xmlns="http://contoso.org/syndication/thread/1.0">
<anotherElement xmlns="http://www.w3.org/2005/Atom">
Some more data</anotherElement>
<aDifferentElement xmlns="http://www.w3.org/2005/Atom">
Even more data</aDifferentElement>
</in-reply-to>
</entry>
The <in-reply-to>
element specifies three required attributes (ref
, type
and href
) while also allowing for the presence of additional extension attributes and extension elements.
Modeling the In-Reply-To element
In this sample, the <in-reply-to>
element is modeled as CLR that implements IXmlSerializable, which enables its use with the DataContractSerializer. It also implements some methods and properties for accessing the element's data, as shown in the following sample code.
[XmlRoot(ElementName = "in-reply-to", Namespace = "http://contoso.org/syndication/thread/1.0")]
public class InReplyToElement : IXmlSerializable
{
internal const string ElementName = "in-reply-to";
internal const string NsUri =
"http://contoso.org/syndication/thread/1.0";
private Dictionary<XmlQualifiedName, string> extensionAttributes;
private Collection<XElement> extensionElements;
public InReplyToElement()
{
this.extensionElements = new Collection<XElement>();
this.extensionAttributes = new Dictionary<XmlQualifiedName,
string>();
}
public Dictionary<XmlQualifiedName, string> AttributeExtensions
{
get { return this.extensionAttributes; }
}
public Collection<XElement> ElementExtensions
{
get { return this.extensionElements; }
}
public Uri Href
{ get; set; }
public string MediaType
{ get; set; }
public string Ref
{ get; set; }
public Uri Source
{ get; set; }
}
The InReplyToElement
class implements properties for the required attribute (HRef
, MediaType
, and Source
) as well as collections to hold AttributeExtensions and ElementExtensions.
The InReplyToElement
class implements the IXmlSerializable interface, which allows direct control over how object instances are read from and written to XML. The ReadXml
method first reads the values for the Ref
, HRef
, Source
, and MediaType
properties from the XmlReader passed to it. Any unknown attributes are stored in the AttributeExtensions collection. When all the attributes have been read, ReadStartElement() is called to advance the reader to the next element. Because the element modeled by this class has no required children, the child elements get buffered into XElement
instances and stored in the ElementExtensions collection, as shown in the following code.
public void ReadXml(System.Xml.XmlReader reader)
{
bool isEmpty = reader.IsEmptyElement;
if (reader.HasAttributes)
{
for (int i = 0; i < reader.AttributeCount; i++)
{
reader.MoveToNextAttribute();
if (reader.NamespaceURI == "")
{
if (reader.LocalName == "ref")
{
this.Ref = reader.Value;
}
else if (reader.LocalName == "href")
{
this.Href = new Uri(reader.Value);
}
else if (reader.LocalName == "source")
{
this.Source = new Uri(reader.Value);
}
else if (reader.LocalName == "type")
{
this.MediaType = reader.Value;
}
else
{
this.AttributeExtensions.Add(new
XmlQualifiedName(reader.LocalName,
reader.NamespaceURI),
reader.Value);
}
}
}
}
reader.ReadStartElement();
if (!isEmpty)
{
while (reader.IsStartElement())
{
ElementExtensions.Add(
(XElement) XElement.ReadFrom(reader));
}
reader.ReadEndElement();
}
}
In WriteXml
, the InReplyToElement
method first writes out the values of the Ref
, HRef
, Source
, and MediaType
properties as XML attributes (WriteXml
is not responsible for writing the actual outer element itself, as that done by the caller of WriteXml
). It also writes the contents of the AttributeExtensions and ElementExtensions to the writer, as shown in the following code.
public void WriteXml(System.Xml.XmlWriter writer)
{
if (this.Ref != null)
{
writer.WriteAttributeString("ref", InReplyToElement.NsUri,
this.Ref);
}
if (this.Href != null)
{
writer.WriteAttributeString("href", InReplyToElement.NsUri,
this.Href.ToString());
}
if (this.Source != null)
{
writer.WriteAttributeString("source", InReplyToElement.NsUri,
this.Source.ToString());
}
if (this.MediaType != null)
{
writer.WriteAttributeString("type", InReplyToElement.NsUri,
this.MediaType);
}
foreach (KeyValuePair<XmlQualifiedName, string> kvp in
this.AttributeExtensions)
{
writer.WriteAttributeString(kvp.Key.Name, kvp.Key.Namespace,
kvp.Value);
}
foreach (XElement element in this.ElementExtensions)
{
element.WriteTo(writer);
}
}
ThreadedFeed and ThreadedItem
In the sample, SyndicationItems
with InReplyTo
extensions are modeled by the ThreadedItem
class. Similarly, the ThreadedFeed
class is a SyndicationFeed
whose items are all instances of ThreadedItem
.
The ThreadedFeed
class inherits from SyndicationFeed
and overrides OnCreateItem
to return a ThreadedItem
. It also implements a method for accessing the Items
collection as ThreadedItems
, as shown in the following code.
public class ThreadedFeed : SyndicationFeed
{
public ThreadedFeed()
{
}
public IEnumerable<ThreadedItem> ThreadedItems
{
get
{
return this.Items.Cast<ThreadedItem>();
}
}
protected override SyndicationItem CreateItem()
{
return new ThreadedItem();
}
}
The class ThreadedItem
inherits from SyndicationItem
and makes InReplyToElement
as a strongly typed property. This provides for convenient programmatic access to the InReplyTo
extension data. It also implements TryParseElement
and WriteElementExtensions
for reading and writing its extension data, as shown in the following code.
public class ThreadedItem : SyndicationItem
{
private InReplyToElement inReplyTo;
// Constructors
public ThreadedItem()
{
inReplyTo = new InReplyToElement();
}
public ThreadedItem(string title, string content, Uri itemAlternateLink, string id, DateTimeOffset lastUpdatedTime) : base(title, content, itemAlternateLink, id, lastUpdatedTime)
{
inReplyTo = new InReplyToElement();
}
public InReplyToElement InReplyTo
{
get { return this.inReplyTo; }
}
protected override bool TryParseElement(
System.Xml.XmlReader reader,
string version)
{
if (version == SyndicationVersions.Atom10 &&
reader.NamespaceURI == InReplyToElement.NsUri &&
reader.LocalName == InReplyToElement.ElementName)
{
this.inReplyTo = new InReplyToElement();
this.InReplyTo.ReadXml(reader);
return true;
}
else
{
return base.TryParseElement(reader, version);
}
}
protected override void WriteElementExtensions(XmlWriter writer,
string version)
{
if (this.InReplyTo != null &&
version == SyndicationVersions.Atom10)
{
writer.WriteStartElement(InReplyToElement.ElementName,
InReplyToElement.NsUri);
this.InReplyTo.WriteXml(writer);
writer.WriteEndElement();
}
base.WriteElementExtensions(writer, version);
}
}
To set up, build, and run the sample
Ensure that you have performed the One-Time Setup Procedure for the Windows Communication Foundation Samples.
To build the C# or Visual Basic .NET edition of the solution, follow the instructions in Building the Windows Communication Foundation Samples.
To run the sample in a single- or cross-machine configuration, follow the instructions in Running the Windows Communication Foundation Samples.