Sunday, July 13, 2014

Use custom search results page in Search box web part with contextual scopes in Sharepoint

As you probably know OTB Search box web part allows user to specify search keywords and perform search across configured search scopes. There are 2 types of scopes:

  • contextual – means that they depend on the current context, i.e. on where user is currently located on the site. Examples of contextual scopes are “This site” (search will be done in the current site and its sub sites) and “This list” (search will be done only in the current list or doclib);
  • custom – scopes configured in Central administration. Examples are “All sites” (search will be done across all Sharepoint sites in specified content source) and “People” (search will be done only across people).

Both contextual and custom scopes are shown in the scopes dropdown list near the text box in Search box web part. It is possible to control what scopes are shown there. E.g. in “Dropdown mode” property we may specify one of the following values:

  • Do not show scopes drop down
  • Show scopes drop down
  • Show, and default to ‘s’ URL parameter
  • Show and default to contextual scope
  • Show, do not include contextual scopes
  • Show, do not include contextual scopes, and default to ‘s’ URL parameter

They are quite self-explanative. The problem however is that using this property we may only hide all scopes or hide only contextual scopes. If we want to leave only contextual scopes and hide all custom scopes we need to set another web part property “Scope display group” to empty value. Scope display groups themselves are configured in Site settings > Search scopes and include custom scopes configured in Central administration.

By default search box web part redirects user to OTB OSSSearchResults.aspx application layout page which uses default application.master master page and as result has OTB look and feel. If we want to use custom look and feel we need either to use search center site collection or create new publishing page on the site and add Search core results web part on it. After that we need to specify URL of this page in the “Target search results page URL” property of Search box web part. The problem is that this property is used only for custom scopes, but not for contextual scopes. For the last ones it is ignored and user is anyway redirected to the OTB OSSSearchResults.aspx page.

In order to fix this issue we may use the following solution: create custom web part which inherits OTB SearchBoxEx control and sets its private variable “m_strOssSearchResultsUrl” via reflection (exactly this variable contains URL of the page used for search in contextual scopes):

   1: public class SearchBoxEx : Microsoft.SharePoint.Portal.WebControls.SearchBoxEx
   2: {
   3:     protected override void OnPreRender(EventArgs e)
   4:     {
   5:         if (!string.IsNullOrEmpty(this.SearchResultPageURL))
   6:         {
   7:             ReflectionHelper.SetPrivateField(this, this.GetType().BaseType,
   8:                 "m_strOssSearchResultsUrl", this.SearchResultPageURL);
   9:         }
  10:         base.OnPreRender(e);
  11:     }
  12: }

Here we used ReflectionHelper class:

   1: public static class ReflectionHelper
   2: {
   3:     public static object CallMethod(object obj, string name,
   4:         params object[] argv)
   5:     {
   6:         BindingFlags bf = 0 | BindingFlags.Instance | BindingFlags.Static |
   7:             BindingFlags.Public | BindingFlags.NonPublic;
   8:         MethodInfo mi = obj.GetType().FindMembers(MemberTypes.Method, bf,
   9:             Type.FilterName, name)[0] as MethodInfo;
  10:         return mi.Invoke(obj, argv);
  11:     }
  13:     public static void SetPrivateField(object obj, string name,
  14:         object val)
  15:     {
  16:         SetPrivateField(obj, obj.GetType(), name, val);
  17:     }
  19:     public static void SetPrivateField(object obj, Type type,
  20:         string name, object val)
  21:     {
  22:         BindingFlags bf = 0 | BindingFlags.Instance | BindingFlags.Static |
  23:             BindingFlags.Public | BindingFlags.NonPublic;
  24:         FieldInfo mi = type.FindMembers(MemberTypes.Field, bf, Type.FilterName,
  25:             name)[0] as FieldInfo;
  26:         mi.SetValue(obj, val);
  27:     }
  29:     public static object GetPrivateField(object obj, string name)
  30:     {
  31:         BindingFlags bf = 0 | BindingFlags.Instance | BindingFlags.Static |
  32:             BindingFlags.Public | BindingFlags.NonPublic;
  33:         FieldInfo mi = obj.GetType().FindMembers(MemberTypes.Field, bf,
  34:             Type.FilterName, name)[0] as FieldInfo;
  35:         return mi.GetValue(obj);
  36:     }
  38:     public static object CallStaticMethod(Type t, string name,
  39:         params object[] argv)
  40:     {
  41:         BindingFlags bf = 0 | BindingFlags.Instance | BindingFlags.Static |
  42:             BindingFlags.Public | BindingFlags.NonPublic;
  43:         MethodInfo mi = t.FindMembers(MemberTypes.Method, bf, Type.FilterName,
  44:             name)[0] as MethodInfo;
  45:         return mi.Invoke(null, argv);
  46:     }
  48:     public static object GetStaticData(Type t, string name)
  49:     {
  50:         BindingFlags bf = 0 | BindingFlags.Instance | BindingFlags.Static |
  51:             BindingFlags.Public | BindingFlags.NonPublic;
  52:         FieldInfo fi = t.FindMembers(MemberTypes.Field, bf, Type.FilterName,
  53:             name)[0] as FieldInfo;
  54:         return fi.GetValue(null);
  55:     }
  56: }

After that it will use custom search page URL also for contextual scopes.

No comments:

Post a Comment