Ok so............. long time ago, in a city far far away (from Cape Town) our very own Willy (http://dotnet.org.za/willy) wanted to re-launch SAArchitect (http://www.saarchitect.net/) community and envisaged a need to aggregate all the feeds from SAArchitect community members and filter on a certain tag in the blog posts posted by those same members (in particular "SAArchitect" tag). Since http://www.saarchitect.net/ is running on a SharePoint 2007 platform, he needed a SharePoint kind of guy that can make this idea a reality. He searched high and low, and after being bounced around a bit he found me :), and considering the fact that at that time I thought I might have some free time between 3am and 4am every 4th day I thought, why the hell not. So Willy even blogged about it to make sure that my commitment stayed public (http://dotnet.org.za/willy/archive/2008/02/20/sa-architect-re-launching-the-community-why-how-and-where-next.aspx).
I wrote the Web Part and sent it to Willy, and although he successfully tested it, he still didn't get around to deploying it (hosting company issues I think).
After all this time I decided to share this with the community, as this is quite cool for community sites powered by SharePoint 2007, and I'm sure someone else might also find it quite useful.
I needed first to know how to get a simple, single RSS feed to display its data so I found this example from Sahil (http://blah.winsmarts.com/2006-7-Sharepoint_Webparts_AS_-_Writing_the_WebParts_-_The_RSS_Feed_WebPart.aspx) and used it as a starting point.
The way this works is that you can specify multiple feeds (separated by a character ";"), and the filter criteria as well the number of feeds displayed. This was my first shot at it, and it's quite simple, so feel free to take it apart, and I think this concept has quite a bit of potential so I think I'll revisit this once again (when I find time) with even more exciting technology.
Be careful as the example uses "saarchitect" as default value to filter on, make sure to change that when you deploy the web part, or you can even take out that whole concept out of the code.
It was written with Visual Studio 2005 SP1 with VSeWSS 1.1 (beta I think).
You can download the project files + source code with a wsp here (http://dotnet.org.za/blogs/zlatan/RSSWebPart.zip).
It used standard references (Microsoft.SharePoint, System, System.Web, System.XML)
Here's the code:
using System;
using System.Runtime.InteropServices;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;
using System.Collections.Generic;
using System.Xml;
using System.Text.RegularExpressions;
using System.ComponentModel;
namespace RSSWebPart
{
[Guid("c4cc9fd1-2865-4d28-9e33-6e2f80eb4868")]
public class RSSWebPart : System.Web.UI.WebControls.WebParts.WebPart
{
public RSSWebPart()
{
this.ExportMode = WebPartExportMode.All;
}
private string rssUrl;
private string feedName;
private const string constDefFeedCriteriaValue = "saarchitect";
private string filterCriteria = constDefFeedCriteriaValue;
private const string constDefFeedLimitValue = "5";
private string feedLimit = constDefFeedLimitValue;
private int feedLmt;
private string tagValue;
protected override void RenderContents(System.Web.UI.HtmlTextWriter writer)
{
try
{
feedLmt = Convert.ToInt32(feedLimit);
}
catch (Exception)
{
feedLmt = 0;
}
RssFeed feed = new RssFeed(rssUrl);
feed.Sort(delegate(RssItem r1, RssItem r2)
{
return r2.PubDate.CompareTo(r1.PubDate);
});
int i = 0;
foreach (RssItem singleRssItem in feed)
{
if (i < feedLmt)
{
if (filterCriteria != null && singleRssItem.Tags != null)
{
tagValue = singleRssItem.Tags.ToLower();
if (tagValue.Contains(filterCriteria) == true)
{
writer.Write("<div class=\"container\">");
writer.Write("<font size=\"4\" face=\"Verdana\"><b><a href =\"");
writer.Write(singleRssItem.Href);
writer.Write("\"><font size=\"2\" face=\"Verdana\">");
writer.Write(singleRssItem.Title);
writer.Write("</a></b></font>");
writer.Write("<div class=\"module-content\"><table width=\"100%\" border=\"0\"><tr><td><font size=\"2\" face=\"Verdana\">");
writer.Write(singleRssItem.Body);
writer.Write("</font></td></tr></table></div>");
writer.Write("</div>");
writer.Write("<div class=\"module-content\"><table width=\"100%\" border=\"0\"><tr><td><font size=\"2\" face=\"Verdana\">");
writer.Write(singleRssItem.Tags);
writer.Write("</font></td></tr></table></div>");
writer.Write("</div>");
writer.Write("<div class=\"module-content\"><table width=\"100%\" border=\"0\"><tr><td><font size=\"2\" face=\"Verdana\">");
writer.Write(singleRssItem.PubDate.ToString());
writer.Write("</font></td></tr></table></div>");
writer.Write("</div>");
i++;
}
}
}
}
}
public string FeedName
{
get
{
return feedName;
}
set
{
feedName = value;
}
}
[WebBrowsable(true),
Personalizable(true),
Category("RSS Aggregator Web Part"),
DisplayName("URLs of the Feed"),
WebDisplayName("URLs of the Feed"),
Description("Please enter the URLs of the feed separated by ';' character.")]
public string FeedURL
{
get
{
return rssUrl;
}
set
{
rssUrl = value;
}
}
[WebBrowsable(true),
Personalizable(true),
Category("RSS Aggregator Web Part"),
DisplayName("Filter Criteria"),
WebDisplayName("Filter Criteria"),
Description("Please enter the tag value you wish to filter on for desired RSS feeds."),
DefaultValue(constDefFeedCriteriaValue)]
public string FilterOn
{
get
{
return filterCriteria.ToLower();
}
set
{
filterCriteria = value.ToLower();
}
}
[WebBrowsable(true)]
[Personalizable(true),
Category("RSS Aggregator Web Part"),
DisplayName("Feed Limit"),
WebDisplayName("Feed Limit"),
Description("Defines the list the web part will read from."),
DefaultValue(constDefFeedLimitValue)]
public string FeedLimit
{
get
{
return feedLimit;
}
set
{
feedLimit = value;
}
}
}
internal class RssFeed : List<RssItem>
{
private XmlDocument rssDoc;
internal RssFeed(string RssURL)
{
if (RssURL == null)
{
this.Add(new RssItem());
}
else
{
string[] rssUrlsArray = RssURL.Split(new char[] { ';' });
foreach (string url in rssUrlsArray)
{
try
{
rssDoc = new XmlDocument();
XmlTextReader xRead = new XmlTextReader(url);
rssDoc.Load(xRead);
XmlNodeList xNodes = rssDoc.SelectNodes("./rss/channel/item");
foreach (XmlNode xNode in xNodes)
{
this.Add(new RssItem(xNode));
}
}
catch (Exception)
{
this.Add(new RssItem());
}
}
}
}
}
internal class RssItem
{
private string title;
private string href;
private string body;
private string tags;
private DateTime pubDate;
public string Href
{
get { return href; }
}
public string Title
{
get { return title; }
}
internal RssItem()
{
title = "Feed not available at this time";
href = "~";
}
public string Body
{
get { return body; }
set { body = value; }
}
public string Tags
{
get { return tags; }
set { tags = value; }
}
public DateTime PubDate
{
get { return pubDate; }
set { pubDate = value; }
}
internal RssItem(XmlNode xNode)
{
title = xNode.SelectSingleNode("./title").InnerText;
href = xNode.SelectSingleNode("./link").InnerText;
body = FixDesc(xNode.SelectSingleNode("./description").InnerText, href);
pubDate = Convert.ToDateTime(xNode.SelectSingleNode("./pubDate").InnerText);
XmlNodeList nodeList = xNode.SelectNodes("./category");
foreach (XmlNode node in nodeList)
{
tags = tags + " " + node.InnerText;
}
}
public string FixDesc(object desc, object link)
{
if (link == null || desc == null) return String.Empty;
string description = desc.ToString();
//Replace all HTML tags so none get cut-off and screw up the page
Regex reg = new Regex("<.*?>", RegexOptions.Compiled);
string stripDesc = reg.Replace(description, String.Empty);
if (stripDesc.Length > 250)
{
int startPos = 250;
char[] chars = stripDesc.ToCharArray();
char c = chars[startPos];
while (!Char.IsWhiteSpace(c) && startPos < chars.Length)
{
startPos++;
c = chars[startPos];
}
stripDesc = new String(chars, 0, startPos) + " ... <a href='" + link.ToString() + "'> More</a>";
}
return stripDesc;
}
}
}