Imagination and versatility are a powerful combination. As the parent of a 2-year-old son, I’m reminded of that almost daily. He’s constantly innovating, and almost any object you hand him to play with will suddenly have a range of new uses you never imagined. If you’ve ever seen a hot dog become an airplane or watched an orange fly through a basketball net, you know exactly what I mean.
Although I would never dare compare Microsoft Office SharePoint Server’s Content Query Web Part (CQWP) with a hot dog flying through the air, I can’t ignore the fact that it, too, possesses a wide range of uses. Using a little imagination, I’ve found that the CQWP covers an amazing number of scenarios.
One such case came up on a project I was working on recently. We were building a corporate intranet with SharePoint Server 2010, and the client requested that we randomly display some items on the home page from one of their lists. Technically, it was a little more complicated than that, because there was a notion of active vs. inactive items and some other minor details, but the point is that we needed to randomly choose the items that got displayed. We knew the CQWP would be a good fit for querying the list items and formatting them for display, but we needed a way to randomize it. In this article, I discuss the approach I took to solve the problem, and I explain the reasoning behind some of the design decisions I made.
Solving the Problem with XSLT
To get started, I did a quick web search to see if anyone else had already done something similar. I didn’t find much that was useful except a couple of blog posts that solved a similar problem with Extensible Style Language Transformations (XSLT). I say “similar” because they were displaying only a single random item at a time (typically an image). I needed to display a set.
The approach in the blog posts was to create a custom XSLT extension function (similar to the ddwrt:FormatDate function that’s often used to format dates) and register it with the CQWP for use in Extensible Stylesheet Language (XSL). The function was a simple random number generator that returned a value between 1 and the number of query results being iterated in the XSL. When the function was called, the random number it returned was used as an index into the query results, and only that particular result was displayed.
If I had used that approach, registering an XSLT extension function with the CQWP isn’t difficult. Suppose, for example, that I wanted to create my own ToUpper function that let me convert a string to uppercase. First, I’d create a class with my extension function, as Listing 1 shows.
Next, I’d register an instance of that class with the CQWP as an XSLT extension object (i.e., a type of object that extends normal XSLT functionality). To do that, I’d override the ModifyXsltArgumentList() method of the ContentByQueryWebPart class, as Listing 2 shows. Within that method, I’d call AddExtensionObject() on the argList parameter that’s passed in and register my extension object with a custom namespace.
On the XSL side, I could access the extension object’s methods as extension functions by adding a namespace declaration, like so:
xmlns:custom=urn:my-xslt-extensions
Then, I could convert a string to uppercase by calling the function, like so:
<xsl:value-of select="custom:ToUpper(@Description)" />
As you can see, creating and using custom extension functions in XSLT isn’t all that difficult. And although I could’ve used this approach to generate random numbers and use them in XSL, as in the blog examples I’d seen, this solution wasn’t quite what I was looking for. I was looking for a more generic approach that would work even with the out-of-box XSL files provided by SharePoint. Custom extension functions would mean deploying custom XSL files, and I wanted to avoid doing so if possible—which left me with one other option: Randomly choose query results before rows are passed to the XSL transform for display.
Solving the Problem without XSLT
Solving the problem without XSLT meant intercepting the query results before they were passed to the XSL transform, randomly selecting some of them, and passing only those results to the transformation process. Intercepting query results in the CQWP means using the ProcessDataDelegate property. ProcessDataDelegate is a property on the ContentByQueryWebPart class that allows a method to be assigned that can intercept and process query results before they’re transformed.
I created a new class that inherited from ContentByQueryWebPart and assigned a method to the ProcessDataDelegate property in the constructor. Listing 3 shows the method I created. As you can see in the code, I didn’t bother creating a second DataTable and copying rows into it. I simply removed the rows I didn’t want from the original table and returned whatever was left. The key here was generating multiple, unique random numbers for selecting the rows to return. I wrote the GetUniqueRandomIndexes method to do just that, as Listing 4 shows.