CQWP Frustrations - Introducing CARBURETUR

Posted on 3/30/2008 @ 5:10 PM in #SharePoint by | Feedback | 7052 views

____________________________________

Update: Based on the overwhelming feedback, I have polished this project and put it on codeplex. Details are here: http://blah.winsmarts.com/2008-3-Announcing_the_release_of_SPRSS.aspx

 ____________________________________

Note: If you guys think this idea is worth pursuing - I'll codeplex it, let me know who wishes to contribute and I'll set it up appropriately. See "Further room for improvement" for how specifically you could contribute.

 

 

SharePoint WCM gurus agree - the Content Query WebPart is awesome!

But the CQWP has a few serious limitations:

  1. The HTML ain't clean.
  2. It won't work across site collections.
  3. Changing the default rendering look & feel will involve a developer and going through a non-browser UI.

So, I decided to do something about it.

I wrote a bunch-a-code, and in competition with MSFT in coming up with terrible product names, I am going to name it -

Content Aggregation, Rendering, Binding, Unlimited Reuse, External conTent inclusive, Using RSS,

or CARBURETUR for short!

(Okay I just made that up - but seriously, a rose by any other name, is still a rose!)

Now, this is by no means a replacement of the CQWP, but it IMO can substitute for the CQWP in a vast number of situations especially if you need W3C compliant sites.

There are two major portions to how it works -

You generate RSS out of any given List or View.

The View, as you may be aware lets you set limitations using Filter critereon, specific columns you want, and even the number of items generated. I wrote a custom ASPX called "CleanRSS.aspx", which you are supposed to put inside of your _layouts directory. Whoaa whoaa!! Did I hear voice of dissensions, saying "Why bother, when MOSS can generate RSS OOTB?". Okay boys'n'girls, check this out.

    • The OOTB RSS generated won't validate as proper RSS.
    • Oh and specify a "View" - and you will find that your OOTB listFeed.aspx happily ignores your View information.
    • Even if the view worked, (which it doesn't) - it tends to collect all the columns being aggregated as RSS - as a single CDATA section. So you have no visibility/flexibility inside of the content actually being aggregated. That is not terribly useful in sharepoint.

So, I wrote my custom "CleanRSS.aspx" - just generate the RSS using listfeed.aspx (OOTB), and change "listfeed.aspx" - with "CleanRSS.aspx" - and thats about all you need to do.

The code is at the end of this blogpost.

You aggregate that RSS using a custom webpart, and render it in any way you want - using XSLT.

Okay the title was getting too long, but one thing I must mention is, that this webpart has the smarts to impersonate as the dude logged into the browser in case of Windows Auth, or as the AppPool in case of FBA.

It also exposes two properties - XSLT, and RSSURL. So using the UI, you can specify an RSS URL (which comes from cnn.com or CleanRSS.aspx - who cares!), a custom XSLT (no SPD required), and bingo - awesome rendering of your content.

The code is at the end of this blogpost.

Further room for improvement:

Now before the trolls hound me about further room for improvement, bad code quality, whatever - guys guys !! CALM DOWN! Seriously, stop crawling up my ass - for god's sake, I'm sweeter than Mrs. Bush (Sr.).

Here are further room(s) for improvement(s):

  • CleanRSS.aspx should use XmlTextWriter rather than StringBuilder, this will ensure much cleaner RSS, and error free behavior.
  • RSS Render webpart really needs a custom editor. The custom editor should read from a document library with a bunch of XSLTs, and give the end user a small image preview of how the rendered XML(RSS) will look like.
  • A nice and clean installation for the above, which also replaces the OOTB listFeed.aspx with CleanRSS.aspx. (I don't see why anyone would mind getting their RSS fixed).
  • Write a second webpart that has the smarts to aggregate multiple feeds, and convert them to a single feed - and send it to the RSSRender Webpart over a webpart connection.
  • Misc. nitpicky stuff - Error handling, cleaner code - I'll do those when I get some time. Seriously those comments are like "Clean this car" - I say, if you find it filthy - "You do it!"

So, Ladies and Gentlemen and others, without much further ado -

Presenting:

Carburetur - CleanRSS.aspx (Put this in _layouts).

 

<%@ Page Language="C#" %>

<%@ Import Namespace="Microsoft.SharePoint" %>

<%@ Import Namespace="System.Text" %>

<script runat="server">

    public void Page_Load()

    {

        Response.Clear();

        Response.ContentType = "text/xml";

        string blah = Request.QueryString["View"];

        try

        {

            Response.Write(

                GetRSSFromListView(

                Request.QueryString["List"],

                Request.QueryString["View"])

                );

        }

        catch (Exception ex)

        {

            Response.Write("<pre>");

            Response.Write(ex.ToString());

            Response.Write("</pre>");

        }

    }

   

    private static string GetRSSFromListView(string strListGUID, string strViewGUID)

    {

        StringBuilder sb = new StringBuilder();

        sb.Append("<?xml version=\"1.0\"?>") ;

        sb.Append("<rss version=\"2.0\">");

       

        Guid listGuid = new Guid(

            HttpContext.Current.Server.UrlDecode(strListGUID)) ;

       

        SPSite site = SPContext.Current.Site;

        SPWeb web = site.OpenWeb();

        SPList list = web.Lists[listGuid];

        SPView view = null;

        if (strViewGUID != null)

        {

            Guid viewGuid = new Guid(

                HttpContext.Current.Server.UrlDecode(strViewGUID));

            view = list.Views[viewGuid];

        }

        else

        {

            view = list.DefaultView;

        }

          sb.Append("<channel>");

          AddTag("title", list.Title, sb);

          AddTag("description", list.Description, sb);

          //// I have the view, now lets start spewing out the RSS

          SPListItemCollection items = list.GetItems(view);

          foreach (SPListItem item in items)

          {

              sb.Append("<item>");

              AddTag("title", item["LinkTitle"].ToString(), sb);

              AddTag("link", list.RootFolder.Url + "/DispForm.aspx?ID=" + item.ID, sb);

              sb.Append("<description>");

              foreach (string viewField in view.ViewFields)

              {

                  if (viewField != "LinkTitle")

                  {

                      AddTag(

                          viewField.Replace("_x0020_", " "),

                          HttpContext.Current.Server.HtmlEncode(item.GetFormattedValue(viewField)),

                          sb);

                  }

              }

              sb.Append("</description>");

              sb.Append("</item>");

          }

       

        sb.Append("</channel>");

        sb.Append("</rss>");

        return sb.ToString();

    }

    private static void AddTag(string tagText, string tagValue, StringBuilder sb)

    {

        sb.Append("<");

        sb.Append(tagText.Replace(" ", "_"));

        sb.Append(">");

        sb.Append(HttpContext.Current.Server.HtmlEncode(tagValue));

        sb.Append("</");

        sb.Append(tagText.Replace(" ", "_"));

        sb.Append(">");       

    }

</script>

 

 

RSSRender WebPart -

(The OOTBXSLT.xml is an embedded resource that gives it some default rendering OOTB).

using System;

using System.Runtime.InteropServices;

using System.Web.UI;

using System.Web.UI.WebControls.WebParts;

using System.Xml;

using System.Xml.Serialization;

using Microsoft.SharePoint;

using Microsoft.SharePoint.WebControls;

using Microsoft.SharePoint.WebPartPages;

using System.Xml.Xsl;

using System.IO;

using System.Reflection;

using System.Xml.XPath;

namespace RSSRender

{

    [Guid("ab18a8de-2765-4c1e-b99a-82f93a27f667")]

    public class RSSRender : System.Web.UI.WebControls.WebParts.WebPart

    {

        private string rssUrl;

        private string xslTransform;

        [WebBrowsable(true)]

        [Personalizable(true)]

        public string XSLTransform

        {

            get { return xslTransform; }

            set { xslTransform = value; }

        }

 

        [WebBrowsable(true)]

        [Personalizable(true)]

        public string RssUrl

        {

            get { return rssUrl; }

            set { rssUrl = value; }

        }

 

        public RSSRender()

        {

            this.ExportMode = WebPartExportMode.All;

            TextReader ootbXSLTStream =

                new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("RSSRender.OOTBXSLT.xml"));

            xslTransform = ootbXSLTStream.ReadToEnd();

        }

        protected override void Render(HtmlTextWriter writer)

        {

            XmlDocument rssDoc = new XmlDocument();

            try

            {

                System.Net.WebClient wc = new System.Net.WebClient();

                wc.Credentials = System.Net.CredentialCache.DefaultCredentials;

                byte[] rssBytes = wc.DownloadData(rssUrl);

                string rssText = System.Text.ASCIIEncoding.ASCII.GetString(rssBytes, 3, rssBytes.Length - 3);

                rssDoc.LoadXml(rssText);

            }

            catch (Exception ex)

            {

                writer.Write("Feed not available at this time! Error message:<pre>" + ex.ToString() + "</pre>");

                writer.Write(System.Security.Principal.WindowsIdentity.GetCurrent().Name);

            }

 

            XmlDocument xsltDoc = new XmlDocument();

            xsltDoc.LoadXml(xslTransform);

 

            writer.Write(Transform(rssDoc, xsltDoc));

        }

 

        private static string Transform(IXPathNavigable input, IXPathNavigable xslt)

        {

            XslCompiledTransform xsltProcessor = new XslCompiledTransform();

            xsltProcessor.Load(xslt);

            Stream results = new MemoryStream();

            if (input is XmlDocument)

            {

                xsltProcessor.Transform(input, null, results);

            }

            results.Position = 0;

            StreamReader rdr = new StreamReader(results);

            return rdr.ReadToEnd();

        }

    }

}

 

Enjoy!! 

(BTW - if you better-ify the above code, please send me a copy (even though you are not obligated to do so), so I can share it for everyone's benefit here).

Sound off but keep it civil:

Older comments..


On 10/27/2007 6:20:29 PM donal said ..
I thin this is worth persuing. I"ve been beating my head against the CQWP to use for a blog (seen as the rss reader does not work against authenticated feeds).


Talk about a rock and a hard place.

Codeplex it, i'll try and help.


On 12/8/2007 5:52:00 AM Martin said ..
I would love to see this get the CodePlex treatment as there are numerous uses for this kind of "application".

On 12/12/2007 4:40:12 PM Jessica said ..
This is totally worth pursuing! I will try to help as well, but my programming skills are rusty. I can't even get your code running... :(

On 12/19/2007 12:49:03 AM darren said ..
can you go into more detail on how to implement this code? i have copied the CleanRSS.aspx file to _layouts and i can view the xml passing in a list guid, and i have built the webpart RSSRender but when i go to add the web part to my page i get an error: "unable to add selected web part(s). rssrender: cannot import this web part". i think this is an awesome idea but i cant seem to get it working.


On 3/3/2008 8:53:19 PM Chris said ..
Dude.... rockin code!


On 3/4/2008 10:40:22 AM Sahil Malik said ..
This will be on codeplex, in a much more polished way, in 2 more weeks.


On 3/7/2008 10:31:16 AM David said ..
Can't wait!


On 11/5/2008 2:02:40 PM georg said ..
Hey Sahil did you put it up on codeplex?


Please let us know, by the way your code rocks! Great job sharing it with community.


On 11/5/2008 5:39:07 PM Sahil Malik said ..
Thanks man .. yeah it's on codeplex -


http://blah.winsmarts.com/2008-3-Announcing_the_release_of_SPRSS.aspx


On 2/14/2012 10:27:04 AM Surya said ..
Hi,

According to my requirement, when i click on any feed it should redirect to custom page instead of list item edit page. for ex

actual url


============


AddTag("link", list.RootFolder.Url + "/DispForm.aspx?ID=" + item.ID, sb);

my custom url


======================


AddTag("link", mycustompage.spx?ID=" + item.ID, sb);

I copied CleanRSS.aspx into layouts folder and when i tried to access this page its not rendering properly. it looks like it is missing xslt file.

when i see viewsource it showing like this.

out of box


============

<!--RSS generated by Windows SharePoint Services V3 RSS Generator on 2/14/2012 9:43:10 AM-->


<?xml-stylesheet type="text/xsl" href="/ExecutiveNews/_layouts/RssXslt.aspx?List=ab257e8b-f8d4-4fc3-8c84-3f32b2c91807" version="1.0"?>


<rss version="2.0">

Custom


=======

<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:cf="http://www.microsoft.com/schemas/rss/core/2005">;

this is my custom feed


<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:cf="http://www.microsoft.com/schemas/rss/core/2005">;

Please let me know how to refer stylesheet in CleanRSS.aspx file.

send me your email id so that i can send you the screenshot

Thanks,


Surya.


On 2/14/2012 12:28:28 PM Surya said ..
it is workig now when I changed below line

AddTag(viewField.Replace("_x0020_", " "),item.GetFormattedValue(viewField), sb);

Thank you for posting.