Paging Content Query Web Part - How I did it - Part 1: Inside the CQWP


Recently I have published the extended version of the Content Query Web Part which supports paging the results. As promised I’m going to provide a series of ‘How I did it’ articles to provide you some more details about my approach. In this article I will focus on getting the paging done inside the Content Query Web Part.

After researching the ContentByQueryWebPart class I have found out that the best place to hook up the paging would be the ProcessItems method. This method provides a DataTable containing the data returned by the query and is supposed to return a DataTable upon processing it.

As I have decided to page the data using the DataTable.DefaultView.RowFilter property I needed to provide the row numbers first in order to pick the items from the requested page:

dt.Columns.Add("Number", typeof(int));
for (int i = 0; i < dt.Rows.Count; i++)
    dt.Rows[i]["Number"] = i;

After having the row numbers set, I have defined the RowFilter:

int startItemNumber = (currentPage - 1) * ItemsPerPage;
int endItemNumber = startItemNumber + (ItemsPerPage - 1);
dt.DefaultView.RowFilter = String.Format(
    CultureInfo.CurrentCulture,
    "Number >= {0} AND Number <= {1}",
    startItemNumber, endItemNumber);

Where ItemsPerPage is a Web Part Property which can be customized by the user and currentPage is a number provided by the visitor using a link with a QueryString variable parameter.

As I have already mentioned the ProcessItems method is supposed to return a DataTable as a result of some custom data processing routines. To leverage our paging filter in the results DataTable I have used the DefaultView.ToTable() method:

dataTable = dt.DefaultView.ToTable();

The complete ProcessItems method as overwritten by me:

protected DataTable ProcessItems(DataTable dt)
{
    if (ItemsPerPage > 0)
    {
        int currentPage = 1;
        numPages = (int)Math.Round(
            (double)dt.Rows.Count / ItemsPerPage);

        DataTable dataTable = null;

        if (!String.IsNullOrEmpty(PagingQueryStringVariable) &&
            !String.IsNullOrEmpty(
            Page.Request.QueryString[PagingQueryStringVariable]) &&
            Int32.TryParse(
            Page.Request.QueryString[PagingQueryStringVariable],
            out currentPage) &&
            (currentPage < 1 || currentPage > numPages))
            currentPage = 1;

        if (UseCaching)
            dataTable = PartCacheRead(Storage.Shared,
                "Page" + currentPage.ToString()) as DataTable;

        if (dataTable == null)
        {
            int startItemNumber = (currentPage - 1) * ItemsPerPage;
            int endItemNumber = startItemNumber + (ItemsPerPage - 1);

            dt.Columns.Add("Number", typeof(int));
            for (int i = 0; i < dt.Rows.Count; i++)
                dt.Rows[i]["Number"] = i;

            dt.DefaultView.RowFilter = String.Format(
                CultureInfo.CurrentCulture,
                "Number >= {0} AND Number <= {1}",
                startItemNumber, endItemNumber);

            dataTable = dt.DefaultView.ToTable();

            if (UseCaching)
                PartCacheWrite(Storage.Shared,
                    "Page" + currentPage.ToString(), dataTable,
                    TimeSpan.FromSeconds(CacheTime));
        }

        dt = dataTable;
    }

    return dt;
}

In order to leverage the great performance of the Content Query Web Part I have provided an option to cache the different pages. In situations when the CQWP returns many results spread across many pages, this possibility should improve the overall performance of the Extended CQWP.

In my next article I will focus on the Pager Web Part and its connection with the Extended CQWP using the ConnectionProvider and ConnectionConsumer attributes.

Technorati Tags: SharePoint, SharePoint 2007, MOSS 2007, WCM

Others found also helpful: