Exception while trying to use Anonymous Search Results Cache with User Segments
Anonymous Search Results Cache is a great way of optimizing the performance of your public-facing website that uses search-driven publishing. Unfortunately things look a little less optimistic when combined with User Segments.

Anonymous Search Results Cache in SharePoint 2013

In my recent post on Anonymous Search Results Cache I discussed how it can help you improve the performance of your public-facing website. By storing the results of search queries issued by anonymous users in cache, you can lower the load on your environment and handle more requests and handle them quicker.

Although Anonymous Search Results Cache is enabled for the Content Search Web Part (CSWP) by default since March PU, if you have a website built before that, you will need to enable it manually. While the approach recommended by Microsoft is to remove and re-add all instances of CSWP, you can enable Anonymous Search Results Cache on your existing Web Parts using a few simple steps I showed you in my previous article.

Personalizing search-driven content with User Segments

One of the great improvements in SharePoint 2013 is the ability of displaying personalized content using the Content Search Web Part. By passing User Segments to the search query, you can tailor the results to the particular visitor showing her more relevant content and increase the chance for conversion.

Assuming you have gathered User Segments, that describe the current visitor, passing them to SharePoint 2013 Search for personalizing content is pretty straight-forward, although it differs slightly depending on whether you’re using Client-Side or Server-Side Rendering.

If you’re using Client-Side Rendering, which is the default option in SharePoint 2013, following is how you would pass User Segments to the query of the Content Search Web Part:

public class TargetedContentSearch : ContentBySearchWebPart {
    protected override void OnLoad(EventArgs e) {
        if (this.AppManager != null) {
            if (this.AppManager.QueryGroups.ContainsKey(this.QueryGroupName) &&
                this.AppManager.QueryGroups[this.QueryGroupName].DataProvider != null) {
                this.AppManager.QueryGroups[this.QueryGroupName].DataProvider.BeforeSerializeToClient +=
                    new BeforeSerializeToClientEventHandler(AddUserSegments);
            }
        }

        base.OnLoad(e);
    }

    private void AddUserSegments(object sender, BeforeSerializeToClientEventArgs e) {
        DataProviderScriptWebPart dataProvider = sender as DataProviderScriptWebPart;
        if (dataProvider != null) {
            string[] userSegments = null;

            // your logic here for retrieving user segments for the current user

            dataProvider.Properties["UserSegmentTerms"] = userSegments;
        }
    }
}

When using XSLT-based rendering passing user segments into the query looks slightly different:

public class TargetedContentSearch : ContentBySearchWebPart {
    protected override void OnLoad(EventArgs e) {
        if (UseSharedDataProvider == false &&
            AppManager != null &&
            AppManager.QueryGroups.ContainsKey(QueryGroupName) &&
            AppManager.QueryGroups[QueryGroupName].DataProvider != null) {
            string[] userSegments = null;

            // your logic here for retrieving user segments for the current user

            AppManager.QueryGroups[QueryGroupName].DataProvider.Properties["UserSegmentTerms"] = userSegments;
        }

        base.OnLoad(e);
    }
}

In both cases User Segments must be passed as an array of string (string[]) where each value corresponds to an ID of a User Segment Term associated with Query Rules.

Inconvenient Anonymous Search Results Cache and User Segments

Although Anonymous Search Results Cache and User Segments work just fine on their own, as soon as you try to enable Anonymous Search Results Cache on a Content Search Web Part that uses User Segments you will get the following exception:

Specified method is not supported.

[NotSupportedException: Specified method is not supported.]
Microsoft.Office.Server.Search.Query.SearchCacheKeyGenerator.SerializeObject(Object value, StringBuilder& sb) +319
Microsoft.Office.Server.Search.Query.SearchCacheKeyGenerator.DeltaSerialize(IPropertyCollection propColl) +677
Microsoft.Office.Server.Search.Query.SearchCacheKeyGenerator.GetKey(QueryProperties queryProperties, String queryPrefix) +45
Microsoft.Office.Server.Search.Query.SearchResultsCacheWrapper.Get() +197
Microsoft.Office.Server.Search.Query.Query.ExecuteQuery() +582

Not Supported Exception when trying to use Anonymous Search Results Cache with Content Search Web Part that uses User Segments

Looking further, you will find out that the logic responsible for building the cache key for the query results doesn’t support string[] types for serializing values. Instead it supports StringCollection (System.Collections.Specialized.StringCollection) for values that contain multiple strings. Unfortunately, if you try to pass user segments as a StringCollection instead you will get another exception:

Type {0} is not valid. Only String, Int32, Boolean and String[] are supported.
Parameter name: System.Collections.Specialized.StringCollection

[ArgumentException: Type {0} is not valid. Only String, Int32, Boolean and String[] are supported.
Parameter name: System.Collections.Specialized.StringCollection]
Microsoft.Office.Server.Search.Query.QueryUtility.Create(String name, Object val) +460
Microsoft.Office.Server.Search.WebControls.ScriptApplicationManager.PrepareQueryForExecution(QueryGroup qg, Int32 lang) +2413
Microsoft.Office.Server.Search.WebControls.ScriptApplicationManager.GetSyncResult(String queryGroupName) +717
Microsoft.Office.Server.Search.WebControls.ContentBySearchWebPart.GetSingleResultRows() +122

Argument Exception when trying to pass user segments as String Collection instead of an array of string

This time it’s the process responsible for building the search query failing due to its lack of support for StringCollection-based properties.

As it’s impossible to create an object that would be of type string[] and StringCollection at the same time as well as to plug into the query creation and caching process to change the type of the object that contains User Segments it seems that at this moment it is not possible to use Anonymous Search Results Cache with User Segments.

Although the fix seems relatively easy, it may have impact on the underlying logic of SharePoint 2013 Search, so it’s hard to say how easy it is for Microsoft to fix this issue. One thing that we can hope for is, that this will be fixed in the future releases of SharePoint and that we will be able to optimize our public-facing websites for performance while leveraging the content personalization capabilities of search-driven publishing.

Summary

Anonymous Search Results Cache is a great way of optimizing public-facing websites that use search-driven publishing for performance. Unfortunately, at this moment, it cannot be used with Content Search Web Parts that use User Segments.