Prefetching SharePoint search results

Prefetch link rendered in HTML
Prefetching content allows users to access it more quickly what improves the user experience. Find out how to prefetch SharePoint search results and cut down your users’ waiting time.

Prefetching content

Whenever the user is searching for something the odds are high that he will visit at least the first search result. Depending on the quality of search results he might find just the thing he was looking for or he might go back and try another result. By prefetching content in the background you can minimize the time required to load the page and let your users use search on your site quicker.

How it works?

To prefetch a page (or any other file) all you have to do is to add a prefetch link to your page:

<link rel="prefetch" href="/pages/my-page.aspx"/>  

When the page and all its resources have been loaded the browser will process all prefetch links and store the content in its cache.

The following screenshot illustrates the result of prefetching.

Screenshot of Fiddler showing the working of prefetching

After entering a search query called Nieuws 1 the search results page (/zoek/paginas/default.aspx?k=Nieuws+1) is being requested (#1). After its HTML has been parsed the browser also requests all resources such as CSS, JavaScript and images (#2 – #16). After the page and all its resources have been loaded, the prefetching starts. The browser iterates through all prefetch links and requests in the background specified resources (#17).

When the user navigates then to the first search result only the assets specific to that page are loaded (#18 – #19). The page itself is retrieved from the browser’s cache.

Screenshot of Fiddler after navigating to the prefetched page

So how do you prefetch SharePoint search results?

Prefetching SharePoint search results

The following code snippet shows a custom control responsible for rendering prefetch links based on SharePoint search results:

Note: For this code to work your project will need a reference to the Microsoft.Office.Server.Search assembly.

using System;  
using System.Linq;  
using System.Web.UI;  
using System.Xml;  
using System.Xml.Linq;  
using Microsoft.Office.Server.Search.Query;  
using Microsoft.Office.Server.Search.WebControls;

namespace PrefetchSearchResults.Controls {  
    public class PrefetchSearchResults : Control {
        /// <summary>
        /// How many results should be prefetched
        /// </summary>
        public int NumResults { get; set; }
        public QueryId QueryId { get; set; }

        protected override void Render(HtmlTextWriter writer) {
            if (NumResults > 0) {
                QueryManager queryManager = SharedQueryManager.GetInstance(Page, QueryId).QueryManager;
                if (queryManager != null) {
                    LocationList list = null;

                    foreach (LocationList item in queryManager) {
                        if (item != null) {
                            list = item;
                            break;
                        }
                    }

                    if (list != null && list.ReturnedResults > 0) {
                        int numResults = Math.Min(NumResults, list.ReturnedResults);

                        XElement results = XElement.Parse(((XmlDocument)list[0].Result).InnerXml);
                        var resultsToPrefetch = (from XElement result
                                                 in results.Descendants("Result")
                                                 select result.Element("url").Value).Take(numResults);

                        foreach (string resultUrl in resultsToPrefetch) {
                            writer.AddAttribute(HtmlTextWriterAttribute.Rel, "prefetch");
                            writer.AddAttribute(HtmlTextWriterAttribute.Href, resultUrl);
                            writer.RenderBeginTag(HtmlTextWriterTag.Link);
                            writer.RenderEndTag();
                        }
                    }
                }
            }
        }
    }
}

The control contains two properties: NumResults (14) which defines how many results should be prefetched and QueryId (15) which determines the search query that should be used for retrieving the results from.

The first step is to retrieve a reference to the QueryManager which contains the results of the search query (19). Next, we retrieve the LocationList configured for the search results page (21-28). If the query returned results we can proceed with rendering prefetch links (30).

Because the search results are returned as XML we cannot access the URLs of the search results that should be prefetched directly but have to extract them from the XML instead. One approach to do this is to use LINQ to XML (33-36).

Once we have the URLs the last thing that we have to do is to render the prefetch links (38-43).

Prefetch link rendered in HTML

For the prefetch mechanism to work correctly the prefetch links must be rendered in the head section of your page. You can either do this by adding the control directly to the Master Page, filling a Content Placeholder from the search results page Page Layout or by creating a Delegate Control.

Inconvenient prefetching

Unfortunately at this moment Firefox is the only browser that supports prefetching natively. Internet Explorer does only DNS resolution for prefetch links which is of no use in context of search results and other browsers do nothing at all.

As a work around for other browsers you could render the following JavaScript code at the bottom of your HTML that would prefetch the search results using jQuery and AJAX:

<script>  
$(window).load(function() {
    var prefetch = ['Page-1.aspx','Page-2.aspx'];
    for (var i = 0; i < prefetch.length; i++) {
        $.get(prefetch[i]);
    }
});
</script>  

You can do this dynamically by using the following control:

using System;  
using System.Linq;  
using System.Web.UI;  
using System.Xml;  
using System.Xml.Linq;  
using Microsoft.Office.Server.Search.Query;  
using Microsoft.Office.Server.Search.WebControls;

namespace PrefetchSearchResults.Controls {  
    public class AjaxPrefetch : Control {
        /// <summary>
        /// How many results should be prefetched
        /// </summary>
        public int NumResults { get; set; }
        public QueryId QueryId { get; set; }

        protected override void Render(HtmlTextWriter writer) {
            if (NumResults > 0) {
                QueryManager queryManager = SharedQueryManager.GetInstance(Page, QueryId).QueryManager;
                if (queryManager != null) {
                    LocationList list = null;

                    foreach (LocationList item in queryManager) {
                        if (item != null) {
                            list = item;
                            break;
                        }
                    }

                    if (list != null && list.ReturnedResults > 0) {
                        int numResults = Math.Min(NumResults, list.ReturnedResults);

                        XElement results = XElement.Parse(((XmlDocument)list[0].Result).InnerXml);
                        var resultsToPrefetch = (from XElement result
                                                 in results.Descendants("Result")
                                                 select result.Element("url").Value).Take(numResults);

                        writer.RenderBeginTag(HtmlTextWriterTag.Script);

                        writer.Write(@"
$(window).load(function() {{
    var prefetch = ['{0}'];
    for (var i = 0; i < prefetch.length; i++) {{
        $.get(prefetch[i]);
    }}
}});", String.Join("','", resultsToPrefetch.ToArray()));

                        writer.RenderEndTag(); // script
                    }
                }
            }
        }
    }
}

Similarly to the previously discussed scenario we first retrieve the search results. Once we have them we render the prefetch JavaScript code. By using the jQuery load() function we ensure that the prefetch process won’t start before the complete page including its resources has been loaded (41). First we construct an array of all URLs that should be prefetched (42, 46). Then we loop through that array and for each URL we fire the jQuery get() function which will load the contents of the specified in the background using AJAX.

AJAX Prefetch JavaScript rendered in HTML

The results of this approach are similar to using the browser-native prefetching. The only downside is that it relies on JavaScript which is an optional capability and is not always enabled.

Summary

Prefetching content allows you to minimize the time your users have to wait while opening pages on your site. Page load time is an important criterion of a good user experience and can help you keep your visitors on your website. Although supported natively only by Firefox at this moment, alternative prefetching solutions are available what makes it possible to implement prefetching in all modern browsers.

Comments

comments powered by Disqus