A Templated ASP.NET RSS Feed Reader Control
Update 20070330:
By popular demand (well a
couple people) I have included a sample project with
everything needed to get started using this control.
Basically just a sample web project, click here
to get the zipped archive. You might notice things are done
a little different, I include the
System.ServiceModel.Syndication namespace on my page so that
I can case the Container.DataItem to a SyndicationItem and
access its properties that way, stops using Eval, but each
to their own.
Hope that makes it easier for
some, will be including full source in future too :).
Project
Files:
TemplatedRSSFeedReader.zip
Thanks
Stefan
Update 20070319:
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 ID="RSSReader2" 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
ID="RSSReader2" 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