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, http://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 (12)

  1. Joe says:

    Is this code based on the RC release?

    I could not get it working on Beta2.

    (Result property is not available on Location object)

  2. Garry Trinder says:

    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);

  3. Ludvig says:

    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

  4. mswin says:

    Hi,

    Excellent post on custom Search webpart using new Sharepoint object model classes. I am trying to extend Search Core Results web part to pass a custom build query. I have my custom webpart and federated webparts placed on a page and I don’t have Search Box webpart. How can I pass custom query that I build in Core results webpart to federated webparts. How can this be achieved using Shared Query Manager.

    Many Thanks in Advance for any inputs/suggestion on this.

  5. Hung Dang says:

    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.

  6. AB says:

    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…

  7. Paul Wojcik says:

    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 🙂

  8. Paresh Sharda says:

    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.

  9. Ankur says:

    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 ?

  10. Thomas Trung Vo says:

    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

  11. CW says:

    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?

  12. Taffy Lewis says:

    Has anyone gotten this idea to actually work?

Skip to main content