Issues creating SharePoint 2010 search scopes programmatically
Recap
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 globally at the search service application level so they are available to all site collections, or defined within a specific site collection.
Creating search scopes
Global 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. 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.
As there are problems with some of the methods that can be used to create search scopes and display groups programmatically I’ll detail these in this article. In a follow up article I’ll cover a method of creating scopes and scope display groups that avoids these problems.
The object model classes I will cover in this article are:
- SearchContext
- SearchServiceApplication
- SearchServiceApplicationProxy
SearchContext
This is only included as it is referenced in other articles and was a common way to create scopes in SharePoint 2007 such as in the code shown below.
SearchContext searchContext = SearchContext.GetContext(site);
Scopes scopes = new Scopes(searchContext);
// create custom search scope
Scope scope = scopes.AllScopes.Create("Custom Scope", "", new Uri(site.Url), true, "results.aspx", ScopeCompilationType.AlwaysCompile);
scope.Rules.CreateUrlRule(ScopeRuleFilterBehavior.Require, UrlScopeRuleType.Folder, site.Url);
// create custom display group
ScopeDisplayGroupCollection displayGroups = scopes.AllDisplayGroups;
ScopeDisplayGroup displayGroup = displayGroups.Create("Custom Display Group", "", new Uri(site.Url), true);
// add scopes to display group
displayGroup.Add(scope);
displayGroup.Default = scope;
displayGroup.Update();
In SharePoint 2010 this class is marked as obsolete and should not be used. If you use the SearchContext class in your code you will get the following warning at compile time:
“This class is obsolete now. Please use SearchServiceApplication and SearchServiceApplicationProxy instead.”
According to the article Microsoft SharePoint Server 2010: Deprecated Types and Methods:
Obsolete types and methods will continue to work in your custom code and solutions, but they will generate compiler warnings, and you should update your code to use the new types and methods as indicated by the compiler warnings.
So while this might work it may not always function as expected and there is no guarantee it will continue to work in the future. Next we will look at the alternatives suggested in the compiler warning message.
SearchServiceApplication
If you are updating SharePoint 2007 code the SearchServiceApplication class is an obvious choice as it is a suggested alternative to the SearchContext class. The code is slightly more complicated as you need to retrieve various classes relating to the search service application but a similar outcome can be achieved using the code below:
SPServiceContext serviceContext = SPServiceContext.GetContext(site);
SearchServiceApplicationProxy searchApplicationProxy = serviceContext.GetDefaultProxy(typeof(SearchServiceApplicationProxy)) as SearchServiceApplicationProxy;
SearchServiceApplicationInfo searchApplictionInfo = searchApplicationProxy.GetSearchServiceApplicationInfo();
SearchServiceApplication searchApplication = SearchService.Service.SearchApplications.GetValue<SearchServiceApplication>(searchApplictionInfo.SearchServiceApplicationId);
Scopes scopes = new Scopes(searchApplication);
// create custom search scope
Scope scope = scopes.AllScopes.Create("Custom Scope", "", new Uri(site.Url), true, "results.aspx", ScopeCompilationType.AlwaysCompile);
scope.Rules.CreateUrlRule(ScopeRuleFilterBehavior.Require, UrlScopeRuleType.Folder, site.Url);
// create custom display group
ScopeDisplayGroupCollection displayGroupColl = scopes.AllDisplayGroups;
ScopeDisplayGroup displayGroup = displayGroupColl.Create("Custom Display Group", "", new Uri(site.Url), true);
// add scopes to display group
displayGroup.Add(scope);
displayGroup.Default = scope;
displayGroup.Update();
There is a fundamental problem with this code, however (on top of the hardcoded strings and a complete lack of error handling). The code tries to access the search service application database directly without going through the search web service. In a least privilege installation this means that the application pool account of the web application containing the site collection will be used (even if you elevate privileges for this code) and will not have access to the search service application database by default (as least privilege requires separate accounts for search and content). The specific error message you get when running this code is:
System.Data.SqlClient.SqlException: Cannot open database “Search_Service_Application_DB_{…}” requested by the login. The login failed. Login failed for user ‘{0}’. at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
In the example above DEVDOMAIN\spcontent is the identity of the application pool of a web application that contains the content sites for which I want to create search scopes. Using the code above the spcontent account is trying to access the database directly as shown below:
This is something to watch out for as if your dev machines are not running a least privilege installation but your production servers are the issue may not manifest itself until it is difficult and costly to fix.
What we need to do is go through the web services so the account running the search web service (in this case an account named DEVDOMAIN\spservices) is used to connect to the database as shown below:
In the screen shot the intuitively named a168dee… application is the search web service.
This brings us on to the next method mentioned as an alternative to the SearchContext class – the SearchServiceApplicationProxy class.
SearchServiceApplicationProxy
This class requires the use of ScopeInfo and DisplayGroupInfo objects and requires some lower level code such as passing around the ID’s of the display groups. This does get around the database access issue metioned above, however, as it goes through the SearchServiceApplicationProxy class which uses the search web service instance.
SPServiceContext serviceContext = SPServiceContext.GetContext(site);
SearchServiceApplicationProxy searchProxy = serviceContext.GetDefaultProxy(typeof(SearchServiceApplicationProxy)) as SearchServiceApplicationProxy;
// create custom search scope
int statusCode = 0;
ScopeInfo scope = new ScopeInfo
{
Name = "Custom Search Scope",
SiteUrl = site.Url,
DisplayInAdminUI = true,
AlternateResultsPage = "results.aspx",
CompilationType = ScopeCompilationType.AlwaysCompile
};
int scopeId = searchProxy.AddScope(scope, out statusCode);
RuleInfo scopeRule = new RuleInfo
{
RuleType = ScopeRuleType.Url,
FilterBehavior = ScopeRuleFilterBehavior.Require,
UrlRuleType = UrlScopeRuleType.Folder,
UserValue = site.Url
};
searchProxy.AddRule(scopeRule, scopeId);
// create custom display group
DisplayGroupInfo displayGroup = new DisplayGroupInfo
{
Name = "Custom Display Group",
SiteUrl = site.Url,
DisplayInAdminUI = true
};
int displayGroupId = searchProxy.AddDisplayGroup(displayGroup, out statusCode);
// add scopes to display group
List<int> scopeIds = new List<int> { scopeId };
searchProxy.SetDisplayGroupListInfo(displayGroupId, scopeIds);
While this method works, it requires a lot more code than the methods above and isn’t as intuitive. What used to take 9 lines of code in SharePoint 2007 now takes 30 lines and requires us to pass round the Id’s of scopes and display groups and managing the list of display groups in our code.
So to summarise we have found that:
- SearchContext is now obsolete (and can also cause permission issues)
- SearchServiceApplication can lead to permission issues if called from within a feature receiver as it doesn’t use the search web service
- SearchServiceApplicationProxy uses the search web service but requires much more code and is difficult to work with
Surely there must be an easier way to add a search scope! The good news is there is – after some digging into the method that SharePoint uses internally I found another alternative – I’ll cover this in a subsequent article.
Update 10/4/11 – Read the following article on Creating SharePoint 2010 search scopes programmatically for details of the alternative method that avoids the problems mentioned above.
[…] 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 […]
Creating SharePoint 2010 search scopes programmatically at SharePoint Config
8 Apr 11 at 2:49 pm
Very nicely written, helped me a lot to solve my problem of creating scope from code completely except
If i need to create a RuleInfo with property query how to do it?
RuleInfo scopeRule = new RuleInfo
{
RuleType = ScopeRuleType.PropertyQuery,
FilterBehavior = ScopeRuleFilterBehavior.Require,
UrlRuleType = UrlScopeRuleType.Folder,
UserValue = site.Url
};
But how to specify the MangedProperty = SomeValue; ?
Waiting for your replay.
Mallikarjun
4 May 12 at 12:26 pm
@Mallikarjun if you are using the approach shown above you can use the Scope.Rules.CreatePropertyQueryRule(filterBehaviour, managedProperty, “propertyValue”) method rather than the RuleInfo method to create a scope rule based on a managed property.
Ari Bakker
7 May 12 at 11:55 am
Hi Ari baker, thanks for the replay
I am aware of using scope.Rules.CreatePropertyQueryRule(ScopeRuleFilterBehavior.Include, property, “STS_ListItem_DocumentLibrary”);
But the issue is with getting the property parameter object is it require some admin privileges on SSA as you mentioned in your previous post.
Currently am using below code:
SPServiceContext serviceContext = SPServiceContext.GetContext(site);
SearchServiceApplicationProxy searchApplicationProxy = serviceContext.GetDefaultProxy(typeof(SearchServiceApplicationProxy)) as SearchServiceApplicationProxy;
SearchServiceApplicationInfo searchApplictionInfo = searchApplicationProxy.GetSearchServiceApplicationInfo();
SearchServiceApplication searchApplication = SearchService.Service.SearchApplications.GetValue(searchApplictionInfo.SearchServiceApplicationId);
schema = new Schema(searchApplication);
property = schema.AllManagedProperties[“contentclass”];
scope.Rules.CreatePropertyQueryRule(ScopeRuleFilterBehavior.Include, property, “STS_ListItem_DocumentLibrary”);
And execution of the below code “schema.AllManagedProperties[“contentclass”];” throws access denied exception on SSA database, and i don’t want to add app pool account as SSA administrator. So i wanted perform the same using RuleInfo class.
Mallikarjun
8 May 12 at 10:05 am