Saturday, November 22, 2014

Extend Sharepoint 2013 search queries with custom query variables populated dynamically in runtime

Sharepoint 2013 allows to extend search queries via several ways. The commonly used way is to do it via Search result sources:

image

In the query builder we may add more conditions to those which user enters in the search box. They are applied to query when user performs search and ResultScriptWebPart or ContentBySearchWebPart are configured to use appropriate search result source. It can be done via web part properties for specific web part or for all web parts if search result source is set as default. If you need to create custom result source and set it default programmatically you may check the following article: Create custom search result source programmatically in Sharepoint 2013. Note that there is one problem related with custom default result sources which I wrote about here: Problem with missing catalog connections when use customized search result source in Sharepoint 2013.

This is useful approach when we need to add more conditions to the search query. Using search query variables we may retrieve values from different site or site collection properties (e.g. id or url), current user’s properties, managed metadata term used for current navigation node and others. Full list of available standard query variables can be found here: Query variables in SharePoint Server 2013. However anyway with standard query variables the set of values which we may use in the queries is quite limited. But what if we need to add condition to the query with value which should be calculated dynamically in runtime, e.g. if we need to add value from cookie or value calculated by complex business logic?

One of the way is to use query string variable {QueryString.<ParameterName>}, where <ParameterName> should be replaced with the actual query string parameter name ,e.g. {QueryString.Country}. On practice however it is not always convenient or possible. E.g. by default search result web part (ResultScriptWebPart) uses client navigation (can be controlled via UpdateAjaxNavigate property), i.e. adds search phrase entered by user in search box on the search results page to the current url after hash “#” symbol. E.g. if user enters something in the search box on site’s front page and clicks search, user is redirected to the search result page with search phrase specified in “k” query string parameter:

http://example.com/search?k=foo

But if after that user adds “bar” phrase to the search box on the search results page, url will look like this:

http://example.com/search?k=foo#k=bar

and ResultScriptWebPart will use the last phrase defined after hash symbol. The problem however that part after hash tag is not considered by the standard as part of url and thus is not sent to the server (see the following forum thread for more details: How to get Url Hash (#) from server side). For our example it means that query string variable sent to the server will be empty (note that there is also one problem with empty query string variables described here: One problem with “Value of a parameter from URL” query variable in Sharepoint 2013 search queries) and search won’t work as we expect.

In order to add dynamically calculated values to the search query we may also try to use http request properties {Request.<PropertyName>}, but as people mentioned in the forums, it really works only with Url property (although I didn’t check it by myself), so you can use e.g. request’s property bag for that.

So what to do when we need to add dynamic value to the search query? In this case the following approach can be used:

1. add necessary condition to the search query builder with custom variable name:

MyManagedProperty={Country}

Here we added condition that managed property MyManagedProperty should be equal to {Country} variable. {Country} – is custom variable which will be added to the query processing pipeline by custom code (see below).

2. implement custom web part and put it on the page with ResultScriptWebPart or ContentBySearchWebPart and implement the following code there:

   1: // retrieve value from cookies or from any other place
   2: string country = ...;
   3:  
   4: var scriptApp = ScriptApplicationManager.GetCurrent(this.Page);
   5: var dataProvider = scriptApp.QueryGroups["Default"].DataProvider;
   6: dataProvider.BeforeSerializeToClient += (sender, args) =>
   7:     {
   8:         var dp = sender as DataProviderScriptWebPart;
   9:         if (dp == null)
  10:         {
  11:             return;
  12:         }
  13:  
  14:         dp.Properties["Country"] = country;
  15:     };

After that you search query will return only those content which has MyManagedProperty set to particular country, which is retrieved from the cookies or calculated using any other business logic. All credits should go to Mikael Svenson from which article I’ve got the idea of using this approach. Hope that this information will help you in your work.

No comments:

Post a Comment