We're looking to put in an rss feed aggregator on one of our SharePoint Publishing Site Collections. For now I'm looking at the code to produce the feed for the rss. I want the feed to take the top 20 items of anything that has changed in a site and all the subsites below it. I plan to expose the feed as a wcf service running on the SharePoint box and then use the service as a data source for a web part that we'll add either via SharePoint Designer or by C# Coded web part. The wcf service is not required you could put the code into a web part and then cache the web part. Whatever makes sense when we get there.
Some issues in the back of my mind that I forsee popping up:
- The query on the site for the data is quite a heavy call so I plan to cache the data. My current planned cache location will be in the wcf service. I hope that some wcf config setting will sort this out (about time those wcf configs gave me some love for all the pain I've put into them). Then get the cache to timeout every 30 min or so.
- What items do we show?
Two different users could have two very different views of data. Someone with admin rights can see alot more than someone else. To cache everyone's rss feed data is potentially quiet heavy. Resolution: only show items that have been published (I've not yet put this in the CAML sample below). - I build up a list using an item's Display Form. In Publishing Sites this is by default locked down and not accessible to the public.
Resolutions: make Display Forms public, or put a new url for the Display Form with some other url location when I build up the rss feed in the code below. - At runtime anything that has been modified will get returned. The items could be a doc lib item, a custom list item, a page, etc. What I get back as having been 'modified' might not be presentable.
Resolution: write code to ignore or redirect depending on what the item is, deal with this on an item type per item type basis.
I used this as my starting point:
http://darrinbishop.com/blog/archive/2007/04/08/47.aspx
I then modified the code to get the sample below.
It does the following:
- Get the latest 20 items in a site collection (will modify this to be sub site relative by editing "dt = w.GetSiteData(q)")
- Loops through all the returned items and looks for their Display Form ( see "where n.Type == PAGETYPE.PAGE_DISPLAYFORM")
- Builds up a string using the item's url, Display Form & ID.
NOTE:
This is where things could go wrong...
What if a custom Display Form is being used that is not set in the form.ServerRelativeUrl?
What if the Display Form is not actually what you want to show to the public (i.e. a Publishing Page's Display Form).
Resolution: Deal with these on an item type per item type basis.
What I have below is where I am now and I can proudly say that when I put a break on rssItems after the code has run and randomly browse to the urls that has been generated they all work.
If you want more info on this as I go along then drop a comment or contact me and I'll add more as I go along.
The code is done in ASP.Net 3.5. I opened Visual Studio 2008 created a new windows application and made a reference to Microsoft.SharePoint.dll. That was it. I will move this code into a class library and break it up into objects at a later date.
I apologise for the code not being styled, cut and paste it into Visual Studio and it'll sort itself out.
My Live Writer "Insert Code..." plugin is full of hate.
I'll clean it up at some point.
List<string> rssItems = new List<string>(); SPSiteDataQuery q = new SPSiteDataQuery(); q.ViewFields = "<FieldRef Name=\"Title\"/>" + "<FieldRef Name=\"Modified\"/>" + "<FieldRef Name=\"ID\"/>" + "<FieldRef Name=\"EncodedAbsUrl\"/>"; //q.Lists = "<Lists ServerTemplate=\"1100\"/>"; q.Webs = "<Webs Scope=\"SiteCollection\"/>"; q.Query = "<OrderBy><FieldRef Name=\"Modified\" Ascending=\"FALSE\" /></OrderBy>";// + q.RowLimit = 20; DataTable dt = null; using (SPSite site = new SPSite(http://MyServer)) { SPWeb w = site.OpenWeb(); dt = w.GetSiteData(q); w = null; Dictionary<Guid, SPWeb> webs = new Dictionary<Guid, SPWeb>(); Dictionary<Guid, SPList> lists = new Dictionary<Guid, SPList>(); try { foreach (DataRow row in dt.Rows) { Guid webId = new Guid(row["WebId"].ToString()); Guid listId = new Guid(row["ListId"].ToString()); if (!webs.ContainsKey(webId)) { SPWeb newWeb = site.OpenWeb(webId); webs.Add(webId, newWeb); } SPWeb web = webs[webId]; SPList list = web.Lists[listId]; IEnumerable<SPForm> forms = list.Forms.Cast<SPForm>(); SPForm form = (from n in forms where n.Type == PAGETYPE.PAGE_DISPLAYFORM select n ).FirstOrDefault(); if (form != null) { rssItems.Add(form.ServerRelativeUrl + "?ID=" + row["ID"].ToString()); } } } finally { // Dispose webs foreach (Guid webId in webs.Keys) { webs[webId].Dispose(); } } }
Update (1 Dec 2008):
Here's another rss aggregator created by Zlatan.(
http://www.codeplex.com/AJAXRSSAggregator) This aggregates already existing rss feeds into one feed. So one could expose your lists as rss feeds and then get this aggregator consolidate them.
You'd need to tweak the code a little to get the different feeds to order stuff by date.