[转]A Templated ASP.NET RSS Feed Reader Control

Today I attended the Heros Happen event in Perth, and I learnt something very very cool, you see how I wrote my own classes and code to load the syndicated feed in, then used LINQ to XML to load that into my classes. During Dave Glovers presentation on WCF he had a sample in there that was building an RSS feed, and I could see he was using some classes to do so. What I then noticed is these classes are new in .NET 3.5 and will make creation of this control 100 times easier plug give us full support of the RSS 2.0 spec and allow us to access all feed proprties :) :) and will mean *removing* code and making things much simpler.

Firstly we need to add a reference to the System.ServiceModel.Web assembley, this is where the magic is. Then add a using System.ServiceModel.Syndication; to the RSSReader.cs class, we then have access to these helper classes which allow us to easily read and create RSS 2.0 and ATOM 1.0 feeds, as I am only supporting RSS I will only use the RSS formatter. To read our feed we need to do the below:

Firslty read the feed into an XMLReader:

XmlReader reader = XmlReader.Create("http://weblogs.asp.net/stefansedich/rss.aspx");

Then create an instance of the RSS20FeedFormatter and then make this read the data from the xmlreader:

Rss20FeedFormatter feedFormatter = new Rss20FeedFormatter();

feedFormatter.ReadFrom(reader);

We now can get access to the feed by using feedFormatter.Feed, this gives us an instance of a SyndicationFeed class which gives us access to all items and properties of our feed. The only difference now is that as some properties are a little different accessing them is different.So instead of feed.Title you use feed.Title.Text instead. The good thing with this is, 1. We have removed the need to do this manually and 2. We have all properties that belong to an RSS feed.

The updated RSSReader class is below with the code changes. You can now remove the Feed and FeedItem classes we created earlier as they are needed no more. It just goes to show we learn something new every day.....

Code:

///<summary>

/// An RSS Feed reader server control, it will basically aggregate rss feeds and display the content

///</summary>

public class RSSReader : CompositeControl, INamingContainer {

#region Properties

///<summary>

/// The URL of the feed to display

///</summary>

public string FeedURL {

get {

return ViewState["FeedURL"] != null ? ViewState["FeedURL"].ToString() : string.Empty;

}

set {

ViewState["FeedURL"] = value;

}

}

///<summary>

/// The number of items to show from the feed, 10 is the default.

///</summary>

public int FeedItemCount {

get {

return ViewState["FeedItemCount"] != null ? (int)ViewState["FeedItemCount"] : 10;

}

set {

ViewState["FeedItemCount"] = value;

}

}

///<summary>

/// The header template for this control

///</summary>

[TemplateContainer(typeof(RSSReaderDataItem)), PersistenceMode(PersistenceMode.InnerProperty)]

public ITemplate HeaderTemplate { get; set; }

///<summary>

/// The footer template for this control

///</summary>

[TemplateContainer(typeof(RSSReaderDataItem)), PersistenceMode(PersistenceMode.InnerProperty)]

public ITemplate FooterTemplate { get; set; }

///<summary>

/// The item template for this control

///</summary>

[TemplateContainer(typeof(RSSReaderDataItem)), PersistenceMode(PersistenceMode.InnerProperty)]

public ITemplate ItemTemplate { get; set; }

#endregion

#region Constructor

// Default constructor

public RSSReader() {

}

#endregion

#region Child Control Creation

///<summary>

/// Creates child controls for this control, first create the header

/// based on template, then the items, then finally the footer.

///</summary>

protected override void CreateChildControls() {

// Get our RSS feed data, this is returned as a collection

// of the RSSReaderDataItem controls.

SyndicationFeed feed = this.GetRSSData();

if (feed != null) {

// Create the header template, and add to the controls.

if (this.HeaderTemplate != null) {

// Create the dataitem control for the header

// and assign it the current feed as its dataitem.

RSSReaderDataItem header = new RSSReaderDataItem() {

DataItem = feed

};

// Instantiate template, add to controls and

// databind header.

this.HeaderTemplate.InstantiateIn(header);

this.Controls.Add(header);

header.DataBind();

}

if (this.ItemTemplate != null) {

foreach (SyndicationItem dataItem in feed.Items) {

// Create an item child control and

// assign the current feed item as its dataitem.

RSSReaderDataItem item = new RSSReaderDataItem() {

DataItem = dataItem

};

// Instantiate the item template, add to controls

// and databind the item.

this.ItemTemplate.InstantiateIn(item);

this.Controls.Add(item);

item.DataBind();

}

}

if (this.FooterTemplate != null) {

// Create the footer dataitem

// and assign it the current feed as its dataitem.

RSSReaderDataItem footer = new RSSReaderDataItem() {

DataItem = feed

};

// Instantiate the template, add to controls

// and databind the footer.

this.FooterTemplate.InstantiateIn(footer);

this.Controls.Add(footer);

footer.DataBind();

}

}

}

#endregion

#region RSS Data Retrieval

///<summary>

/// Fetched the RSS data from the current RSS Feed

///</summary>

///<returns>A collection of RSSReaderDataItems</returns>

private SyndicationFeed GetRSSData() {

SyndicationFeed feed = null;

// Only do if we have a feed url specified.

if (!string.IsNullOrEmpty(this.FeedURL)) {

// Get the current feed and load into an XMLReader

using (XmlReader reader = XmlReader.Create(this.FeedURL)) {

Rss20FeedFormatter feedFormatter = new Rss20FeedFormatter();

// Read the contents of the XMLReader into the FeedFormatter

feedFormatter.ReadFrom(reader);

// Get the current feed.

feed = feedFormatter.Feed;

}

}

return feed;

}

#endregion

}

And you would change the controls template on the page a little to be able to read the properties:

<cc2:RSSReader runat="server" FeedURL="http://weblogs.asp.net/stefansedich/rss.aspx">

<HeaderTemplate>

<div>

<b><%# Eval("Title.Text") %></b>

<br /><br />

</HeaderTemplate>

<ItemTemplate>

<%# Eval("Title.Text") %>

<br /><br />

<%# Eval("Summary.Text") %>

<hr />

</ItemTemplate>

<FooterTemplate>

</div>

</FooterTemplate>

</cc2:RSSReader>

Thanks

Stefan

---------------------------------------------------------------------------------------------------------------------------------------------------------------------

Hello All,

Bored again tonight thought I would have a play and write a Templated RSS feed reader control, using .NET 3.5. I created a control that you can control with templates, you just set the FeedURL, the FeedItemCount, Header and Footer templates if needed and the ItemTemplate and you are set. This post could get quite lengthy and will have lots of code examples. I kind of assume you know about templated controls in asp.net and do not go much into the details of their workings.

Loading an RSS Feed:

Thought I would make this a little better than just loading the feed in the control. So I ended up creating a Feed class and a FeedItem class, as the names suggest one is the feed and the other represents a feed item. In the Feed class there is a LoadFeed method, this takes the url and limit, then uses LINQ to XML to load the feed. If a limit > 0 is provided it will also limit the number of items in the feed. The code for the classes are below:

/// <summary>

/// Represents an RSS Feed

/// </summary>

public class Feed {

#region Properties

/// <summary>

/// The title of the feed

/// </summary>

public string Title { get; set; }

/// <summary>

/// A link to the site that hosts the feed

/// </summary>

public string Link { get; set; }

/// <summary>

/// A description of the feed

/// </summary>

public string Description { get; set; }

/// <summary>

/// The items that are contained within this feed

/// </summary>

public IEnumerable<FeedItem> Items { get; set; }

#endregion

#region Feed Loading

/// <summary>

/// Given a feed URL, loads that feed.

/// </summary>

/// <param name="feedURL"></param>

public void LoadFeed(string feedURL, int limit) {

// Load the feed from the URL we have set

// as a property on this control.

XDocument rssFeed = XDocument.Load(feedURL);

// Get the channel element.

var channel = rssFeed.Descendants("channel").First();

// Set the main feed values.

this.Title = channel.Element("title").Value;

this.Description = channel.Element("description").Value;

this.Link = channel.Element("link").Value;

// Process all the feed items and add them to the

// main feed class.

this.Items = (from item in rssFeed.Descendants("item")

select new FeedItem {

Title = item.Element("title").Value,

Description = item.Element("description").Value,

Link = item.Element("link").Value

});

if (limit > 0) {

// If the limit is > 0 then limit the

// number of items in our feed.

this.Items = this.Items.Take(limit);

}

}

#endregion

}

The feed class just has the feeds main properties and a collection of items, the LINQ to XML takes care of loading the items and relevant properties.

/// <summary>

/// Represents an RSS item

/// </summary>

public class FeedItem {

/// <summary>

/// The title of the item

/// </summary>

public string Title { get; set; }

/// <summary>

/// The items description

/// </summary>

public string Description { get; set; }

/// <summary>

/// A link to the item

/// </summary>

public string Link { get; set; }

}

The feeditem class just holds the properties for an item in our feed. One thing to note here is I did not implement all the properties according to RSS spec. I may do this at a future time thought and will update this post then.

The RSS Reader Control:

The control is where all the magic happens, basically to create a new templated control you need to create a control and add the template properties, in my case HeaderTemplate, FooterTemplate and ItemTemplate. All these template properties are an RSSReaderDataItem, this class internally just has a DataItem property which will either be the feed or the actual feed item incase of the ItemTemplate.

Basically my RSSReader control inherits from the CompositieControl class and then I override the CreateChildControls method, in here I get the RSS feed data and then begin to create the controls. First I create an instance of the header control and load the current HeaderTemplate into it, I set the dataitem to be the feed and then bind it. The same thing is done for the footer but using the FooterTemplate.

For each item in our feed I do the same thing just create an item, load the ItemTemplate, then set the DataItem to the current FeedItem, and databind. The controls code is listed below:

/// <summary>

/// An RSS Feed reader server control, it will basically aggregate rss feeds and display the content

/// </summary>

public class RSSReader : CompositeControl, INamingContainer {

#region Properties

/// <summary>

/// The URL of the feed to display

/// </summary>

public string FeedURL {

get {

return ViewState["FeedURL"] != null ? ViewState["FeedURL"].ToString() : string.Empty;

}

set {

ViewState["FeedURL"] = value;

}

}

/// <summary>

/// The number of items to show from the feed, 10 is the default.

/// </summary>

public int FeedItemCount {

get {

return ViewState["FeedItemCount"] != null ? (int)ViewState["FeedItemCount"] : 10;

}

set {

ViewState["FeedItemCount"] = value;

}

}

/// <summary>

/// The header template for this control

/// </summary>

[TemplateContainer(typeof(RSSReaderDataItem)), PersistenceMode(PersistenceMode.InnerProperty)]

public ITemplate HeaderTemplate { get; set; }

/// <summary>

/// The footer template for this control

/// </summary>

[TemplateContainer(typeof(RSSReaderDataItem)), PersistenceMode(PersistenceMode.InnerProperty)]

public ITemplate FooterTemplate { get; set; }

/// <summary>

/// The item template for this control

/// </summary>

[TemplateContainer(typeof(RSSReaderDataItem)), PersistenceMode(PersistenceMode.InnerProperty)]

public ITemplate ItemTemplate { get; set; }

#endregion

#region Constructor

// Default constructor

public RSSReader() {

}

#endregion

#region Child Control Creation

/// <summary>

/// Creates child controls for this control, first create the header

/// based on template, then the items, then finally the footer.

/// </summary>

protected override void CreateChildControls() {

// Get our RSS feed data, this is returned as a collection

// of the RSSReaderDataItem controls.

Feed feed = this.GetRSSData();

if (feed != null) {

// Create the header template, and add to the controls.

if (this.HeaderTemplate != null) {

// Create the dataitem control for the header

// and assign it the current feed as its dataitem.

RSSReaderDataItem header = new RSSReaderDataItem() {

DataItem = feed

};

// Instantiate template, add to controls and

// databind header.

this.HeaderTemplate.InstantiateIn(header);

this.Controls.Add(header);

header.DataBind();

}

if (this.ItemTemplate != null) {

foreach (FeedItem dataItem in feed.Items) {

// Create an item child control and

// assign the current feed item as its dataitem.

RSSReaderDataItem item = new RSSReaderDataItem() {

DataItem = dataItem

};

// Instantiate the item template, add to controls

// and databind the item.

this.ItemTemplate.InstantiateIn(item);

this.Controls.Add(item);

item.DataBind();

}

}

if (this.FooterTemplate != null) {

// Create the footer dataitem

// and assign it the current feed as its dataitem.

RSSReaderDataItem footer = new RSSReaderDataItem() {

DataItem = feed

};

// Instantiate the template, add to controls

// and databind the footer.

this.FooterTemplate.InstantiateIn(footer);

this.Controls.Add(footer);

footer.DataBind();

}

}

}

#endregion

#region RSS Data Retrieval

/// <summary>

/// Fetched the RSS data from the current RSS Feed

/// </summary>

/// <returns>A collection of RSSReaderDataItems</returns>

private Feed GetRSSData() {

Feed feed = new Feed();

// Only do if we have a feed url specified.

if (!string.IsNullOrEmpty(this.FeedURL)) {

// Load the feed.

feed.LoadFeed(this.FeedURL, this.FeedItemCount);

}

return feed;

}

#endregion

}

Also the code for the RSSFeedDataItem is below, this is the control that is used for our Templates, it just contains the DataItem.

/// <summary>

/// This is the item control template.

/// </summary>

public class RSSReaderDataItem : Control, INamingContainer {

#region Properties

/// <summary>

/// The RSS data item.

/// </summary>

public object DataItem { get; set; }

#endregion

#region Constructor

/// <summary>

/// Default, initialize properties.

/// </summary>

public RSSReaderDataItem() {

this.DataItem = null;

}

#endregion

}

Using the control:

Just drop the control on your page and set your templates, and in there you will have access to your feed properties, I am using Eval("NAME") to get the values.

For the header and footer you can get access to the feeds main properties plus items, so you could even show the count of items etc. In the ItemTemplate you only have access to the current feed item, so you can display any of the normal feed item properties.

<cc2:RSSReader runat="server" FeedURL="http://weblogs.asp.net/stefansedich/rss.aspx">

<HeaderTemplate>

<div>

<b><%# Eval("Title") %></b>

<br /><br />

</HeaderTemplate>

<ItemTemplate>

<%# Eval("Title") %>

<br /><br />

<%# Eval("Description") %>

<hr />

</ItemTemplate>

<FooterTemplate>

</div>

</FooterTemplate>

</cc2:RSSReader>

The End:

The control still has some things that could be done, but I will leave that up to the reader. It would be nice to implement the full RSS spec and use all properties, this could probably be done easier using a 3rd party library for the RSS loading. And even add the ability to add more filtering support to filter by date or even category.

The control shows how easy it is to make templated controls that let you modify the output very easily. This not only makes reuse of your controls better but it lets you fully customise the way your control renders. If you need a primer on templated controls maybe start with this link.

If you would like source code for this just email me and I will send you a copy of a sample project for you to use.

Thanks

Stefan