Jaa


Walkthrough: Create a simple Tag Cloud Web Part based on search results

Let's see how we can extend the SharePoint 2010 search user experience by programmatically implementing a Tag Cloud Web Part. We'll use the Federation OM, the object model (OM) Web Parts in SharePoint 2010 are based on, to create a Web Part like in the screen shot below. 

Tag Cloud Web Part

Background

The communication model between the search Web Parts changed from SharePoint 2007 to SharePoint 2010. Now the Web Parts use the Federation OM together with a public class called the SharedQueryManager that is shared by all synchronous Web Parts. There is one shared instance of the SharedQueryManager per search page, and through this class you can access the other classes that are part of the Federation OM (see also previous post for an overview of the query integration points). Using the Federation OM you can hook into the query path; you can e.g. fetch the search results (as in the sample code below) after the query has been executed, or you can modify the query before submitting it to the search backend (e.g. add query terms before request is submitted).

Setup and Design

We want the Tag Cloud (aka. Word Cloud) Web Part to visually display the most important terms in our result set. To keep it simple we'll leverage the document vectors of the first few results. These document vectors are created during document processing (before indexing), and are available in the managed property "docvector" when querying against FAST Search for SharePoint. A document vector indicates the most important terms/concepts in a document and the corresponding weight, i.e. like this: "[string1,weight1][string2,weight2]...[stringN,weightN]". Note also that these vectors are used for similarity search, a feature available with FAST Search on the object models.

Anyway, the goal of this walkthrough is to create a simple web part that will work nicely with the other out-of-box search web parts in SP2010. Here' are the setup steps.

To implement this new web part, I'm using Visual Studio 2010 RC1;

  • Open Visual Studio 2010 
    •  Download here first if needed
  • Create a new Visual Studio project and solution
    • Select e.g. the Visual Web Part or the Web Part template under SharePoint 2010 in VS2010, and click OK
    • Provide a URL to your FAST Search Center site, and click Finish.
  • Add a reference to the Microsoft.Office.Server.Search.dll in your newly created project
    • E.g. from C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI

You are ready to start coding!

Implementation 

I've included the code below that implements a TagCloudWebPart class. Here I access the SharedQueryManager in the OnInit() method, and keep a reference to the QueryManager. This is so we in the OnPreRender() method can access the result set, and collect the document vectors we want (assumption is that we will have one FAST Search Location available on this page). Here we also remove the surrounding brackets from these vectors, and store the terms and associated weight in a dictionary. The final part happens in the RenderContents()  method where we output the terms from the dictionary, sorted descending by weight (giving a larger fontsize to terms with highest weight).

Here's the code (note: code is for illustration purposes only, not meant to be production ready)

// Copyright © Microsoft Corporation. All Rights Reserved.

// This code released under the terms of the

// Microsoft Public License (MS-PL, https://opensource.org/licenses/ms-pl.html.)

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Linq;

using System.Web.UI;

using System.Web.UI.WebControls.WebParts;

using System.Xml.XPath;

using Microsoft.Office.Server.Search.Query;

using Microsoft.Office.Server.Search.WebControls;

namespace MyVisualWebPartProject.TagCloudWebPart

{

    [ToolboxItemAttribute(false)]

    public class TagCloudWebPart : WebPart

    {

        // Visual Studio might automatically update this path when you change the Visual Web Part project item.

        private const string _ascxPath = @"~/_CONTROLTEMPLATES/MyVisualWebPartProject/TagCloudWebPart/TagCloudWebPartUserControl.ascx";

        // To store the terms and weights used to render the tag cloud

        private Dictionary<string, double> dict = new Dictionary<string, double>();

        // To access the search results

        private QueryManager queryManager;

        protected override void OnInit(EventArgs e)

        {

            queryManager = SharedQueryManager.GetInstance(this.Page).QueryManager;

            base.OnInit(e);

        }

        protected override void CreateChildControls()

        {

            Control control = Page.LoadControl(_ascxPath);

            Controls.Add(control);

        }

        protected override void OnPreRender(EventArgs e)

        {

            LocationList locList = queryManager[0];

            if (locList == null)

                return;

            Location location = locList[0];

            dict.Clear();

            var nav = location.Result.CreateNavigator();

            XPathNodeIterator iterResults = nav.Select("All_Results/Result");

            string myContent = "";

            // concatenate document vectors

            foreach (XPathNavigator res in iterResults)

            {

                var docVectorNode = res.SelectSingleNode("docvector");

                if (null != docVectorNode)

                    myContent += docVectorNode.Value; // [term1, weight1]..[termN, weightN]

            }

            if (myContent.StartsWith("["))

            {

                // remove surrounding brackets, and split term and weight

                myContent = myContent.Remove(0, 1);

                myContent = myContent.Remove(myContent.Length - 1, 1);

                var array = myContent.Split(new string[] { "][" }, StringSplitOptions.RemoveEmptyEntries);

                for (int i = 0; i < array.Length; i++)

                {

                    string[] keyvalue = array[i].Split(',');

                    string key = keyvalue[0];

                    string val = keyvalue[1];

                    if (dict.ContainsKey(key))

                        dict[key] = dict[key] + Double.Parse(val);

                    else

                        dict.Add(key, Double.Parse(val));

                    // only keep 10 docvector items for each result

                    if (i >= 10)

                        break;

                }

            }

            base.OnPreRender(e);

        }

        protected override void RenderContents(HtmlTextWriter writer)

        {

            if (dict.Count > 0)

            {

                // order terms in dictionary by weight

                var words = from k in dict.Keys

                            orderby dict[k] descending

                            select k;

                int fontsize = 30;

                string color = "3333CC";

                int step = (30 - 8) / dict.Count;

                writer.Write("<p><center>");

                foreach(string word in words)

                {

                    // output one term...

writer.Write("<a href=\"results.aspx?k=" + word + "\" title=\"" + word + "\" style=\"color:#" + color + ";font-size:" + fontsize + "pt\">" + word + "</a> &nbsp;&nbsp; ");

                    // ...set smaller font for next term

                    fontsize = fontsize - step;

                    // ...and alternate color

                    if ("3333CC".Equals(color))

                        color = "9999FF";

                    else

                        color = "3333CC";

    }

                writer.Write("</center></p>");

                base.RenderContents(writer);

            }

        }

    }

}

Testing 

To test the web part you need a FAST Search Center on your SharePoint installation, plus some content indexed and searchable. Build and deploy the Web Part by hitting F5 in Visual Studio, and you are ready to start testing (and debugging!). Add the new Web Part to the result page in your search center; Click Edit Pag > Click Add Web Part in e.g. Bottom Zone > Select TagCloudWebPart from Custom category > Click Publish. Execute a query, and you will see a tag cloud like in the image at the top of this blog post.

Summary 

The key point with this exercise was to show how you can easily create new search Web Parts that play nicely together with other search Web Parts. With SharePoint 2010 this is done through the SharedQueryManager; here you can  hook in to get the results, or hook in to manipulate the query. Either way, the programming model is the same for FAST Search and SharePoint Search (and OpenSearch). You work with the QueryManager and the Locations in the Federation OM.

Links

See links below for more resources;
- The WebPart class on MSDN
Visual Studio 2010 support for SharePoint development

Comments

  • Anonymous
    February 16, 2010
    Is this code based on the RC release?I could not get it working on Beta2.(Result property is not available on Location object)
  • Anonymous
    February 23, 2010
    This was done using RC bits, but I believe the Result property should be available on beta 2. See. e.g. here: http://msdn.microsoft.com/en-us/library/microsoft.office.server.search.query.location.result(office.14).aspx. So make sure to reference the correct assembly in your VS2010 project.Anyway, a simple refactoring (that could also improve the code) is possible to avoid the Location.Result property; simply get the results directly from the query manager and create the xpath navigator on the resulting xml document;var results = queryManager.GetResults(locList);
  • Anonymous
    March 16, 2010
    Hi!Excellent blog. I wonder how one could compile with the Microsoft.Office.Server.Search.dll since its in the beta is in .net 2. So visual studio wont compile the code :-(. Do I need to wait for the RC or is there any way around?//Ludvig
  • Anonymous
    March 21, 2010
    The comment has been removed
  • Anonymous
    April 26, 2010
    I still can neither see the Result property of Location nor call this statement as arnts's suggestion. It will return 'null'. Any hint? I tried to work around by using this code block:var nav = location.GetResults(queryManager).CreateNavigator();               XPathNodeIterator iterResults = nav.Select("All_Results/Result");But the returned nav does not own any children. I debugged the location object and it contained 10 count of return results out of 36. However, I did not know how to get the result.
  • Anonymous
    November 11, 2010
    Hello,Please tell me how can i use tag cloud on term set. Please give guidelines..nice if u can give webpart..i am new in sharepoint...
  • Anonymous
    November 27, 2010
    Great article - This provides a great starting point to anyone wanting to exxtend the search UI in SharePoint 2010. It has been very helpful to me as a Microsoft FAST vTSP :-)
  • Anonymous
    December 30, 2010
    Can a similar concept be applied to SharePoint Server 2007. I am sure there should some mechanism other than XSLT to interpret and parse search result in MOSS 2007.
  • Anonymous
    February 22, 2011
    Hi,I have a requirement of creating a custom control for adding keywords to page level which is there in a content type in SharePoint 2010.Can anybody provide solution to this ?
  • Anonymous
    May 18, 2011
    Step by step to create a Sandbox Solution WebPart for SharePoint Online (Từng bước cách tạo một SharePoint WebPart sao cho có thể chạy trên SharePoint Online)sharepointtaskmaster.blogspot.com/.../step-by-step-to-create-sandbox-solution.html
  • Anonymous
    November 01, 2011
    I tried to create a web part using the abouve code. But getting some array out of boud error.Do i need to install the Microsoft FAST Search Server 2010 to get it working.Or can use the normal sharepoint page?
  • Anonymous
    June 19, 2012
    Has anyone gotten this idea to actually work?