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.
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> ");
// ...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?