In Part 1 I used two Search Core Results Web Parts and a bit of jQuery magic to achive the look of blended search results.
This time we will create our own CoreResultsWebPart and inject the blended results into the result xml before it is transformed into html. In addition to blend in news results I decided to get some images as well. I did this by importing a “Federated Location” for Flickr. The location definition can be found at “Flickr Search Connector for SharePoint Server, Search Server, and FAST Search for SharePoint”.
I started off by creating an empty SharePoint 2010 project in VS2010 and added a new web part.
After deploying and activating the feature I replaced the Search Core Results Web Part with my own Search Blended Core Results Web Part.
Since I use FS4SP as the search back-end remember to copy the xslt from the original web part as the default xslt is for internal SharePoint search.
By overriding the GetXPathNavigator we have full control over the result xml. The blended results are retrieved asynchronous while we get the local results from the page’s query manager on the main thread.
protected override XPathNavigator GetXPathNavigator(string viewPath) { QueryManager queryManager = SharedQueryManager.GetInstance(Page).QueryManager; Func<QueryManager, StringBuilder> blend = GetBlendedResults; IAsyncResult asyncResult = blend.BeginInvoke(queryManager, null, null); // Get local results XmlDocument xmlDocument = queryManager.GetResults(queryManager[0]); // Get blended results StringBuilder sb = blend.EndInvoke(asyncResult); InsertBlendedResults(sb, xmlDocument); return xmlDocument.CreateNavigator(); }
The blended searches have been hard coded for Bing News and Flickr, but you could very well modify the web part to include settings where you choose which federated locations to use for your blended results. In order to execute the search we create a new QueryManager object and add both search locations to it. This enables the QueryManager to execute both the news and the images search at the same time, and the results are concatenated as two channels inside the returning rss feed. The rest of the code is parsing the rss and appending it into the main xml results.
private XmlDocument GetBlendedResultsXml(QueryManager queryManager) { SPServiceContext context = SPServiceContext.GetContext(SPServiceApplicationProxyGroup.Default, SPSiteSubscriptionIdentifier.Default); SearchServiceApplicationProxy searchProxy = context.GetDefaultProxy(typeof (SearchServiceApplicationProxy)) as SearchServiceApplicationProxy; QueryManager blendedQuery = new QueryManager {UserQuery = queryManager.UserQuery}; LocationList locList = new LocationList(); Location internetLocation = new Location("InternetSearchResults", searchProxy) {ItemsPerPage = 3}; locList.Add(internetLocation); Location flickrLocation = new Location("Flickr", searchProxy) {ItemsPerPage = 3}; locList.Add(flickrLocation); blendedQuery.Add(locList); blendedQuery.IsTriggered(locList); return blendedQuery.GetResults(locList); }
The complete code can be downloaded from my SkyDrive and includes result.xslt which is used for rendering the results. Click the icon below for the download.
The following regular expression is used to pull out the image url from the RSS summary.
private static Regex _imgExtract = new Regex("img src=\"(?<url>.*?)\"", RegexOptions.Compiled);
And the code to build the result xml for each item:
private void CreateResultXmlFragment(XmlWriter writer, SyndicationItem rssItem, string positionType) { writer.WriteStartElement("Result"); writer.WriteElementString("title", rssItem.Title.Text); writer.WriteElementString("description", rssItem.Summary.Text); writer.WriteElementString("write", rssItem.PublishDate.Date.ToString("MM/dd/yyyy")); writer.WriteElementString("url", rssItem.Links.First().Uri.OriginalString); writer.WriteStartElement("imageurl"); writer.WriteAttributeString("imageurldescription", "Web Page"); writer.WriteString("/_layouts/images/html16.png"); writer.WriteEndElement(); Match m = _imgExtract.Match(rssItem.Summary.Text); if (m.Success) { writer.WriteElementString("picturethumbnailurl", m.Groups["url"].Value); writer.WriteElementString("contentclass", "STS_ListItem_PictureLibrary"); writer.WriteElementString("blendtype", "Images"); } else { writer.WriteElementString("blendtype", "News"); } writer.WriteElementString("blended", positionType); writer.WriteEndElement(); }
XSLT modification for the rendering of blended sections:
<xsl:if test="blended = 'first'"> <div> <xsl:value-of select="blendtype" /> for <xsl:value-of select="$Keyword"/> </div> <xsl:text disable-output-escaping='yes'><div style="margin-left: 20px;background-color:#eee;font-size: 0.8em"></xsl:text> </xsl:if>
By using the same internal sample data as in Part 1, the result looks like this with both news and images blended in:
Right now they take up too much space for production quality layout, but that’s an exercise left to the xsl savvy out there . And you probably don´t want to blend both news and images after hit number three.
And I want to thank Corey Roth for his excellent posts on how to use the QueryManager class.
[Also postet at http://nuggets.comperio.no]
[Also postet at http://nuggets.comperio.no]