SharePoint 2013 Search Result Types

While trying to deploy SharePoint 2013 Search Result Types through a feature, I was a bit struck at the lack of examples or documentation in that area. After a few days of looking at the code via JustDecompile or Reflector, I found key hiding in the SearchServiceApplicationProxy.AddResultItemType method. It takes a ResultItemType object that can be created from the Microsoft.Office.Server.Search.Administration name. It seems convoluted but you have to take a “copy” of the built in result type and the change the properities of one you want to deploy and then add it back to the collection of result types;

Eventually I want to expand this into a detailed post and get some feedback but here is what works as part of feature activation. A few points:

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Office.Server.Search.Administration.Query;
using Microsoft.Office.Server.Search.Query.Rules;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.Office.Server.Search.Administration;
using KP.SP.Common;
using KP.SP.Common.Code;
using Microsoft.SharePoint.JSGrid;

namespace KP.SP.Search.Features.SearchEnhancement
{
/// <summary>
/// This class handles events raised during feature activation, deactivation, installation, uninstallation, and upgrade.
/// </summary>
/// <remarks>
/// The GUID attached to this class may be used during packaging and should not be modified.
/// </remarks>

[Guid("98362dff-c2b1-4cc8-b5b4-0133aec185bd")]
public class SearchEnhancementEventReceiver : SPFeatureReceiver
{
private const string LOCAL_SHAREPOINT_RESULTS = "Local SharePoint Results";
private const string LOCAL_PEOPLE_RESULTS = "Local People Results";
private const string EXISTING_PEOPLE_NAME_IN_SHAREPOINT_SEARCH = "People Name in Sharepoint Search";
private const string RESULT_TYPE_NAME = "Person KP";
private const string QUERY_RULE_NAME = "KP People";

private readonly string[] folderUrls = { "_catalogs/masterpage/Display Templates/Search" };

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
PrefixTrace.WriteLine("Begin");
try
{
var spSite = properties.Feature.Parent as SPSite;
if (spSite != null)
{
SPWeb rootWeb = spSite.RootWeb;
SPList gallery = spSite.GetCatalog(SPListTemplateType.MasterPageCatalog);
if (gallery != null)
{
SPListItemCollection folders = gallery.Folders;
string featureId = properties.Feature.Definition.Id.ToString();

foreach (string folderUrl in folderUrls)
{
SPFolder folder = FeatureHelpers.GetFolderByUrl(folders, folderUrl);
if (folder != null)
{
FeatureHelpers.PublishFiles(folder, featureId, "Published by Feature: KP Search Enhancements");
}
}
}

//http://dev.net.ua/blogs/ivanbilokon/archive/2012/11/22/11389.aspx
//http://mcnextpost.com/2013/10/30/les-conditions-dans-les-types-de-resultats-types-de-contenus-ou-types-de-contenus/
SecurityHelper.AnonymousSafeRunWithElevatedPrivileges(spSite.ID, rootWeb.ID, (elevatedSPSite, elevatedSPWeb) =>
{
DeployResultType(elevatedSPSite, elevatedSPWeb);

DeployQueryRule(elevatedSPSite, elevatedSPWeb);
});
}
}
catch (Exception ex)
{
PrefixTrace.WriteLine(ex.ToString());
throw new SPException(ex.ToString());
}
PrefixTrace.WriteLine("End");
base.FeatureActivated(properties);
}

private static void DeployResultType(SPSite spSite, SPWeb spWeb)
{
PrefixTrace.WriteLine("Begin deploy search result types");
try
{
SPServiceContext spServiceContext = null;
SPServiceApplicationProxy spServiceApplicationProxy = null;
SearchServiceApplicationProxy searchServiceApplicationProxy = null;

if (!ValidateAndGetSearchConnectors(spSite, out spServiceContext, out spServiceApplicationProxy, out searchServiceApplicationProxy))
{
throw new SPException("Unable to locate appropriate service and search connectors");
}

var resultItemTypeManager = new ResultItemTypeManager(searchServiceApplicationProxy);
var searchObjectOwner = new SearchObjectOwner(SearchObjectLevel.SPWeb, spWeb);
var resultItemTypes = resultItemTypeManager.GetResultItemTypes(searchObjectOwner, true);
var personResultType =
(from result in resultItemTypes
where result.BuiltIn
where result.Name == "Person"
select result).FirstOrDefault();

if (personResultType != null)
{
foreach (var resultType in searchServiceApplicationProxy.GetResultItemTypes(null, null, searchObjectOwner, false))
{
if (resultType.Name == RESULT_TYPE_NAME)
{
PrefixTrace.WriteLine(string.Format("Deleting existing result type named {0}...", RESULT_TYPE_NAME));
searchServiceApplicationProxy.DeleteResultItemType(resultType);
}
}
ResultItemType personKPResultItemType = new ResultItemType(personResultType, searchObjectOwner);
personKPResultItemType.Name = RESULT_TYPE_NAME;
personKPResultItemType.DisplayTemplateUrl = "~sitecollection/_catalogs/masterpage/Display Templates/Search/Item_Person_KP.js";
personKPResultItemType.RulePriority = 1;
searchServiceApplicationProxy.AddResultItemType(personKPResultItemType);
}
else
{
throw new ObjectNotFoundException("Unable to locate BuiltIn Person ResultItemType");
}
}

catch (Exception ex)
{
PrefixTrace.WriteLine(string.Format("Unable to deploy search template! {0}",ex));
throw;
}
}

private static void DeployQueryRule(SPSite spSite, SPWeb spWeb)
{
PrefixTrace.WriteLine("Begin deploy KP query rule");
try
{
SPServiceContext spServiceContext = null;
SPServiceApplicationProxy spServiceApplicationProxy = null;
SearchServiceApplicationProxy searchServiceApplicationProxy = null;

if (!ValidateAndGetSearchConnectors(spSite, out spServiceContext, out spServiceApplicationProxy, out searchServiceApplicationProxy))
{
throw new SPException("Unable to locate appropriate service and search connectors");
}

var qrm = new QueryRuleManager(searchServiceApplicationProxy);
var federationManager = new FederationManager(searchServiceApplicationProxy);
var searchObjectOwner = new SearchObjectOwner(SearchObjectLevel.SPWeb, spWeb);
var searchObjectFilter = new SearchObjectFilter(searchObjectOwner);
var queryRules = qrm.GetQueryRules(searchObjectFilter);

foreach (QueryRule rule in queryRules.Where(rule => rule.DisplayName == QUERY_RULE_NAME))
{
PrefixTrace.WriteLine(string.Format("Deleting existing query rule named {0}...", QUERY_RULE_NAME));
queryRules.RemoveQueryRule(rule);
break;
}

queryRules = qrm.GetQueryRules(searchObjectFilter);

// Create a new rule as a active one.
var queryRule = queryRules.CreateQueryRule(QUERY_RULE_NAME, null, null, true);
foreach (var qc in queryRule.QueryConditions)
{
queryRule.QueryConditions.Remove(qc);
}

var localSharePointResults = ValidateAndGetResultSource(federationManager, LOCAL_SHAREPOINT_RESULTS, searchObjectOwner);
var localPeopleResults = ValidateAndGetResultSource(federationManager, LOCAL_PEOPLE_RESULTS, searchObjectOwner);

queryRule.CreateSourceContextCondition(localSharePointResults);

queryRule.CreateQueryAction(QueryActionType.CreateResultBlock);
if (queryRule.CreateResultBlockActions != null && queryRule.CreateResultBlockActions.Count > 0)
{
var crba = queryRule.CreateResultBlockActions[0];
if (crba != null)
{
if (crba.QueryTransform.OverrideProperties == null)
{
crba.QueryTransform.OverrideProperties = new QueryTransformProperties();
}
crba.QueryTransform.OverrideProperties["RowLimit"] = 2;
crba.QueryTransform.QueryTemplate = "{subjectTerms}";
crba.QueryTransform.SourceId = localPeopleResults.Id;
if (!crba.ResultTitle.ContainsLCID(1033))
{
crba.ResultTitle.Add(1033, "KP People Results for \"{subjectTerms}\"");
}
crba.ResultTitleUrl = "peopleresults.aspx?k={subjectTerms}";
crba.GroupTemplateId = "~sitecollection/_catalogs/masterpage/Display Templates/Search/Group_Default.js";
crba.ItemTemplateId = "~sitecollection/_catalogs/masterpage/Display Templates/Search/Item_Person_KP.js";
crba.AlwaysShow = true;
crba.EnableOnAllPages = true;
crba.EnableOnRefinement = true;
}
}
queryRule.Update();

//deactive exiting people search query
UpdateRuleActiveFlag(qrm.GetQueryRules(searchObjectFilter), EXISTING_PEOPLE_NAME_IN_SHAREPOINT_SEARCH, false);
}
catch (Exception ex)
{
PrefixTrace.WriteLine(string.Format("Unable to KP query rule! {0}",ex));
throw;
}
PrefixTrace.WriteLine("End deploy KP query rule");
}

private static bool ValidateAndGetSearchConnectors(SPSite spSite, out SPServiceContext spServiceContext, out SPServiceApplicationProxy spServiceAppProxy, out SearchServiceApplicationProxy spSearchAppProxy)
{
SPServiceContext spsc = SPServiceContext.GetContext(spSite);
if (spsc == null)
{
throw new ObjectNotFoundException("Unable to locate Service Context");
}
SPServiceApplicationProxy proxy = spsc.GetDefaultProxy(typeof (SearchServiceApplicationProxy));
if (proxy == null)
{
throw new ObjectNotFoundException("Unable to locate Search Service");
}

SearchServiceApplicationProxy searchAppProxy = proxy as SearchServiceApplicationProxy;
if (searchAppProxy == null)
{
throw new ObjectNotFoundException("Unable to locate Search Service Proxy");
}

spServiceContext = spsc;
spServiceAppProxy = proxy;
spSearchAppProxy = searchAppProxy;
return true;
}

private static void UpdateRuleActiveFlag(IEnumerable<QueryRule> queryRules, string ruleName, bool ruleIsActive)
{
foreach (QueryRule rule in queryRules.Where(rule => rule.DisplayName == ruleName))
{
PrefixTrace.WriteLine(string.Format("Deactivating existing query rule named {0}...", EXISTING_PEOPLE_NAME_IN_SHAREPOINT_SEARCH));
rule.IsActive = ruleIsActive;
rule.UpdateActiveStatus();
break;
}
}

private static Source ValidateAndGetResultSource(FederationManager federationManager, string resultSourceName, SearchObjectOwner searchObjectOwner)
{
var localSharePointResults = federationManager.GetSourceByName(resultSourceName, searchObjectOwner);
if (localSharePointResults == null)
{
throw new Exception(string.Format("Unable to locate source {0}", resultSourceName));
}
return localSharePointResults;
}

}

}