SharePoint Config

Ari Bakker's thoughts on customising and configuring SharePoint

Creating SharePoint 2010 search scopes programmatically

with 6 comments

Search scopes improve the SharePoint search experience by letting users search over a subset of information within an organisation. For example you can create a search scope for a specific project or a group such as Legal or Marketing. Search scopes can either be created at the search service application level and ‘shared’ by all site collections, or defined within a specific site collection.

Creating shared search scopes

Shared search scopes in SharePoint 2010 can easily be created using PowerShell commands such as New-SPEnterpriseSearchQueryScope. Corey Roth covers this in his article on Creating Enterprise Search Scopes with PowerShell. While the PowerShell method can also be used to create site collection related search scopes you might also want to do this programmatically in a feature receiver when the site collection is created. Another thing you might want to do programmatically is to associate a search scope with a display group such as the ‘Search Dropdown’ so it appears in options next to the search box. I’ll cover both these scenarios in this article.

custom-search-scope

Creating site collection level search scopes and display groups

As I mentioned in the previous article on Issues creating SharePoint 2010 search scopes programmatically there are several ways of doing creating scopes and display groups programmatically. To summarise the previous article the classes are:

  • SearchContext – this is now obsolete (and can also cause permission issues)
  • SearchServiceApplication – this can lead to permission issues if called from within a feature receiver as it doesn’t use the search web service
  • SearchServiceApplicationProxy – this uses the search web service but requires much more code and is difficult to work with
  • RemoteScopes – this uses the search web service and is easy to work with

So the RemoteScopes is the class that I’ve used in this article. To use this class to add both a site collection level scope and a display group that contains the scope you can use a feature receiver similar to the one shown below:

using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
using Microsoft.Office.Server.Search.Administration;

 

namespace SharePointProject.Features.CreateSearchScope
{
    /// <summary>
    /// This class handles events raised during feature activation, deactivation, installation, uninstallation, and upgrade.
    /// </summary>
    public class CreateSearchScopeEventReceiver : SPFeatureReceiver
    {
        private string scopeName = "Custom Scope";
        private string displayGroupName = "Custom Display Group";

 

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPSite site = properties.Feature.Parent as SPSite;

 

            // remotescopes class retrieves information via search web service so we run this as the search service account
            RemoteScopes remoteScopes = new RemoteScopes(SPServiceContext.GetContext(site));

 

            // see if there is an existing scope
            Scope scope = (from s
                           in remoteScopes.GetScopesForSite(new Uri(site.Url)).Cast<Scope>()
                           where s.Name == scopeName
                           select s).FirstOrDefault();

 

            // only add if the scope doesn't exist already
            if (scope == null)
            {
                scope = remoteScopes.AllScopes.Create(scopeName, "", new Uri(site.Url), true, "results.aspx", ScopeCompilationType.AlwaysCompile);
                scope.Rules.CreateUrlRule(ScopeRuleFilterBehavior.Require, UrlScopeRuleType.Folder, site.Url);
            }

 

            // see if there is an existing display group         
            ScopeDisplayGroup displayGroup = (from d
                                              in remoteScopes.GetDisplayGroupsForSite(new Uri(site.Url)).Cast<ScopeDisplayGroup>()
                                              where d.Name == displayGroupName
                                              select d).FirstOrDefault();

 

            // add if the display group doesn't exist
            if (displayGroup == null)
                displayGroup = remoteScopes.AllDisplayGroups.Create(displayGroupName, "", new Uri(site.Url), true);

 

            // add scope to display group if not already added
            if (!displayGroup.Contains(scope))
            {
                displayGroup.Add(scope);
                displayGroup.Default = scope;
                displayGroup.Update();
            }

 

            // optionally force a scope compilation so this is available immediately
            remoteScopes.StartCompilation();
        }

 

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPSite site = properties.Feature.Parent as SPSite;

 

            // remotescopes class retrieves information via search web service so we run this as the search service account
            RemoteScopes remoteScopes = new RemoteScopes(SPServiceContext.GetContext(site));

 

            // delete scope if found
            Scope scope = (from s
                           in remoteScopes.GetScopesForSite(new Uri(site.Url)).Cast<Scope>()
                           where s.Name == scopeName
                           select s).FirstOrDefault();
            if (scope != null)
                scope.Delete();

 

            // delete display group if found
            ScopeDisplayGroup displayGroup = (from d
                                              in remoteScopes.GetDisplayGroupsForSite(new Uri(site.Url)).Cast<ScopeDisplayGroup>()
                                              where d.Name == displayGroupName
                                              select d).FirstOrDefault();
            if (displayGroup != null)
                displayGroup.Delete();
        }

 

        // Uncomment the method below to handle the event raised after a feature has been installed.

 

        //public override void FeatureInstalled(SPFeatureReceiverProperties properties)
        //{
        //}

 

        // Uncomment the method below to handle the event raised before a feature is uninstalled.

 

        //public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
        //{
        //}

 

        // Uncomment the method below to handle the event raised when a feature is upgrading.

 

        //public override void FeatureUpgrading(SPFeatureReceiverProperties properties, string upgradeActionName, System.Collections.Generic.IDictionary<string, string> parameters)
        //{
        //}
    }
}

Note that when we check for an existing site collection level scope we use the GetScopesForSite method. This ensures that the scope relates to the site collection we are running under. We also pass in the URL to the site when creating the scope to create a site collection specific scope.

If you are creating search scope display groups one thing you might want to do is configure the search box to use your display group (to only show the scopes you define).

This can be done via the use of a delegate control as shown below:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Control
    Id="SmallSearchInputBox"
    Sequence="20"
    ControlAssembly="Microsoft.Office.Server.Search, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
    ControlClass="Microsoft.SharePoint.Portal.WebControls.SearchBoxEx">
      <Property Name="ScopeDisplayGroupName">Custom Display Group</Property>
      <Property Name="FrameType">None</Property>
  </Control>
</Elements>

Note that we can continue to use the same search control we just set the ScopeDisplayGroupName property on this control and give it a lower sequence number than the existing control. For more information about delegate controls have a look at the article Delegate Control (Control Templatization) on MSDN.

Adding a shared scope to a scope display group

Adding a shared scope to a scope display group is similar but we use the GetSharedScopes method of the RemoteScopes class as shown below:

using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
using Microsoft.Office.Server.Search.Administration;

 

namespace SharePointProject.Features.CreateSearchScope
{
    /// <summary>
    /// This class handles events raised during feature activation, deactivation, installation, uninstallation, and upgrade.
    /// </summary>
    public class CreateSearchScope : SPFeatureReceiver
    {
        private string scopeName = "Shared Scope";
        private string displayGroupName = "Search Dropdown";

 

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPSite site = properties.Feature.Parent as SPSite;

 

            // remotescopes class adds the scope via search web service so we run this as the search service account
            RemoteScopes remoteScopes = new RemoteScopes(SPServiceContext.GetContext(site));

 

            // see if there is an existing scope
            Scope scope = (from s
                           in remoteScopes.GetSharedScopes().Cast<Scope>()
                           where s.Name == scopeName
                           select s).FirstOrDefault();

 

            // see if there is an existing display group         
            ScopeDisplayGroup displayGroup = (from d
                                              in remoteScopes.GetDisplayGroupsForSite(new Uri(site.Url)).Cast<ScopeDisplayGroup>()
                                              where d.Name == displayGroupName
                                              select d).FirstOrDefault();

 

            // only add if the scope and display group exist
            if (scope != null && displayGroup != null && !displayGroup.Contains(scope))
            {
                displayGroup.Insert(0, scope);
                displayGroup.Default = scope;
                displayGroup.Update();
            }
            else
            {
                // optionally log warning or error message
            }
        }

 

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPSite site = properties.Feature.Parent as SPSite;

 

            // remotescopes class retrieves information via search web service so we run this as the search service account
            RemoteScopes remoteScopes = new RemoteScopes(SPServiceContext.GetContext(site));

 

            // see if there is an existing scope
            Scope scope = (from s
                           in remoteScopes.GetSharedScopes().Cast<Scope>()
                           where s.Name == scopeName
                           select s).FirstOrDefault();

 

            // see if there is an existing display group         
            ScopeDisplayGroup displayGroup = (from d
                                              in remoteScopes.GetDisplayGroupsForSite(new Uri(site.Url)).Cast<ScopeDisplayGroup>()
                                              where d.Name == displayGroupName
                                              select d).FirstOrDefault();

 

            // remove the scope from the display group
            if (scope != null && displayGroup != null && displayGroup.Contains(scope))
                displayGroup.Remove(scope);
        }

 

        // Uncomment the method below to handle the event raised after a feature has been installed.

 

        //public override void FeatureInstalled(SPFeatureReceiverProperties properties)
        //{
        //}

 

        // Uncomment the method below to handle the event raised before a feature is uninstalled.

 

        //public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
        //{
        //}

 

        // Uncomment the method below to handle the event raised when a feature is upgrading.

 

        //public override void FeatureUpgrading(SPFeatureReceiverProperties properties, string upgradeActionName, System.Collections.Generic.IDictionary<string, string> parameters)
        //{
        //}
    }
}

This code expects a shared scope named ‘Shared Scope’ to exist in the search service application, and a display group named ‘Search Dropdown’ to exist in the site collection we activate the feature on. If these are not present nothing is changed so you would probably want to throw an error or log a warning message depending on the situation.

As we used the Insert method to add our scope to the display group our scope now shows up at the top of the list, and because we used the default ‘Search Dropdown’ group we don’t need to use a delegate control – our scope will show up in the drop down automatically (although sometimes this takes a few minutes or an iisreset).

custom-search-scope-using-dropdown

Post to Twitter Post to Delicious Post to Digg Post to Reddit Post to StumbleUpon

Written by Ari Bakker

April 8th, 2011 at 2:39 pm

6 Responses to 'Creating SharePoint 2010 search scopes programmatically'

Subscribe to comments with RSS or TrackBack to 'Creating SharePoint 2010 search scopes programmatically'.

  1. Hello,

    good post guy. It help me to solve a annoying bug. Thanks a lot 😉

  2. Thank you Ari Bakker!

    Your article was very helpful for me .. 😉

    Att,
    Charles Lomboni.

    Charles Lomboni

    15 Mar 12 at 7:48 pm

  3. Hi,

    Nice article, thanks. I have some comments on: If you get the display group by name, you can be in trouble sometimes, because you use displayGroupName = “Search Dropdown” and in linq query you use: where d.Name == displayGroupName

    But the “Search Dropdown” title can be changed through central admin. So I recommend where d.Name.Contains(displayGroupName) and of course you should handle that case if “Search Dropdown” was not only extended but completely changed.

    Joe

    21 May 12 at 11:18 am

  4. @Joe good point, yes in a production situation you would need to either make the display groups configurable and/or handle the case when they were not present.

    Ari Bakker

    21 May 12 at 9:08 pm

  5. Thanks..very helpful article

    Ted

    Ted

    6 Sep 12 at 5:18 am

  6. vamshi

    9 Jan 14 at 6:11 am

Leave a Reply

*