Friday, December 26, 2014

The 100% way of automatically updating Result Types (on-prem only)

I have previously written The 100% way of getting your custom managed properties to show in your Display Templates, where after updating a display template with new managed properties you have to go to the Result Type settings page and click the “Update” link in the yellow alert shown. That’s what this post is about -  how to automate the manual click step.

image

Clicking the “update” link configures SharePoint to bring back values from the specified managed properties in the display template. If not updated, the values will be blank. Clicking the “update” link during development or deployment to stage/test/production environments is a cumbersome manual step, very easily forgotten.

There are posts out there describing how you can use PowerShell and SSOM to execute this update on the Search Service Application, but the code used does not work directly when you have checked the “optimize for frequent use” checkbox.

Thus I decided to dig into what really happens when you click the “Update” link as I hate doing manual steps which should/could be automated. More automation means more time for coffee :) (as I don’t have to provide input and can walk over to the coffee machine while scripts are running).

And it’s actually not that complicated.

  1. For each custom result type SharePoint will load the associated display template
  2. From the display template SharePoint picks out the list of defined managed properties
  3. These managed properties is written back to the result types DisplayProperties field
  4. The result type is updated to the Search Service Application
  5. When a search is executed the values of the defined managed properties are returned since the Result Type and Display Template are now in sync

Unfortunately you cannot use this procedure in SharePoint Online, and have to use the UI, or instrument the UI (see the comments of http://www.justinkobel.com/post/2013/03/29/Updating-Display-Templates-in-SharePoint-2013-Search.aspx)

The below code is constructed from reflecting on the SharePoint assemblies and perform the same steps as outlined above. It uses the server-side object model (SSOM) and must be executed on a SharePoint server. There are currently no CSOM or REST API’s dealing with the update of Result Types.

// Library dependencies
//using System;
//using System.Collections.Generic;
//using Microsoft.SharePoint;
//using Microsoft.SharePoint.Administration;
//using Microsoft.Office.Server.Search.Administration;

// Field id which holds managed properties on a display template
static Guid _managedPropertiesId = new Guid("a0dd6c22-0988-453e-b3e2-77479dc9f014");

void UpdateResultTypes(string siteCollectionUrl)
{
using (SPSite site = new SPSite(string siteCollectionUrl))
{
using (SPWeb web = site.OpenWeb())
{
// Get the service context for the current site
SPServiceContext serviceContext = SPServiceContext.GetContext(site);
// Get the SearchApplicationProxy object from service context object
var searchApplicationProxy = (SearchServiceApplicationProxy)serviceContext.GetDefaultProxy(typeof (SearchServiceApplicationProxy))

var owner = new SearchObjectOwner(SearchObjectLevel.SPSite, web);

// Get a reference to the Master Page Gallery and create
// a map of URL to display template and the display template
SPList masterPageGallery = site.GetCatalog(SPListTemplateType.MasterPageCatalog);
var dict = new Dictionary<string, SPListItem>();
foreach (SPListItem item in GetDisplayTemplates(masterPageGallery))
{
string key = SPUtility.SiteRelativeUrlPrefix + item.Url;
dict[key] = item;
}

// Load all defined Result Types
// http://msdn.microsoft.com/en-us/library/microsoft.office.server.search.administration.searchserviceapplicationproxy.getresultitemtypes%28v=office.15%29.aspx
ICollection<ResultItemType> resultItemTypes = searchApplicationProxy.GetResultItemTypes(null, null, owner, true);
// Loop over the user defined result types
foreach (ResultItemType itemType in resultItemTypes.Where(itemType => !itemType.BuiltIn))
{
// Updating Result Type
SPListItem item = dict[itemType.DisplayTemplateUrl];
string propertyMappings = item[_managedPropertiesId] as string;
// If display template don't have any mp's defined, set string to empty
if (string.IsNullOrWhiteSpace(propertyMappings)) propertyMappings = string.Empty;
itemType.DisplayProperties = ParseManagePropertyMappings(propertyMappings);
// Update managed property settings on the result type
searchApplicationProxy.UpdateResultItemType(itemType, true);
}
}
}
}

// Helper function used to parse out mp's from the
// display template property settings string
string ParseManagePropertyMappings(string mappings)
{
string[] strArray = mappings.Replace("'", "").Replace("\"", "").Split(new[] {','});
for (int i = 0; i < strArray.Length; i++)
{
if (strArray[i].Contains(":"))
{
int pos = strArray[i].LastIndexOf(':');
if ((pos > 0) && (pos < (strArray[i].Length - 1)))
{
strArray[i] = strArray[i].Substring(pos + 1);
}
}
strArray[i] = strArray[i].Replace(";", ",");
}
return string.Join(",", strArray);
}