The SharePoint 2010
Enterprise Search API has changed from using the Shared Services
Provider architecture in SharePoint 2007 to using Search service
applications in SharePoint 2010. A lot of the engine under the hood is
still the same, however. This section focuses on how to do basic search
programming in SharePoint 2010. If the purpose is to migrate search code
from 2007 to 2010, this should be easy, as search has been abstracted
to support code for SP 2007.
Three ways of doing search programming are covered:
- Using the
KeywordQuery
- Using the
FullTextSqlQuery
- Using the search web service
Creating a KeywordQuery-Based Search
SharePoint 2010 now has a new way to get the object of the KeywordQuery
by using the SearchServiceApplicationProxy
. The name of the SearchServiceApplicationProxy
is required for creating the KeywordQuery
object. The name can be found by navigating to the SSA list. The proxy
name is normally the same as the SSA name. Per default this is simply
“Search Service Application”. If not, then go to Central Administration
and open Service Applications to find it.
Having the name of the SearchServiceApplicationProxy
, it is now possible to get the proper references required for creating the KeywordQuery
object. This is done as follows:
SearchQueryAndSiteSettingsServiceProxy settingsProxy =
SPFarm.Local.ServiceProxies.GetValue<SearchQueryAndSiteSettingsServiceProxy>();
SearchServiceApplicationProxy searchProxy =
settingsProxy.ApplicationProxies.GetValue<SearchServiceApplicationProxy>
("Search Service Application");
Using(KeywordQuery keywordQuery = new KeywordQuery(searchProxy))
{---}
Having the reference to the KeywordQuery
object, the search properties can now be configured. SharePoint 2010
introduces a significant number of new properties to configure. In the
example below, only the basic properties, that are needed to perform a
search, are configured.
keywordQuery.QueryText = "Your query text";
keywordQuery.ResultTypes = ResultType.RelevantResults;
keywordQuery.ResultsProvider = SearchProvider.Default;
ResultTableCollection keywordQueryResultsCollection = keywordQuery.Execute();
ResultTable keywordQueryResults = keywordQueryResultsCollection[ResultType.RelevantResults];
DataTable keywordQueryResultsTable = new DataTable();
keywordQueryResultsTable.TableName = "Results";
keywordQueryResultsTable.Load(keywordQueryResults, LoadOption.OverwriteChanges);
There are two properties that should receive special attention due to the new, enhanced people-finding abilities in SP 2010: EnableNicknames
and EnablePhonetic
.
The EnableNicknames
property allows
for people to be found by their, well, nicknames. This way is it
possible to find Andrew by his nickname, “Andy.” This is immensely
helpful if building custom people finder Web Parts.
EnablePhonetic
allows users to find
persons even if they do not know the exact spelling of a name. A search
for Kristian will return results for “Christian” as well.
One important point of
these properties is that they depend on the browser language setting.
For instance, the nickname Mike for Michael applies if browser language
is set to US or UK, but not if it is set to German.
Creating a FullTextSqlQuery-Based Search
Sometimes the programmer wants more control over the query. This can be achieved using the FullTextSqlQuery
class. This class allows the programmer to control the query using SQL
syntax, which is often a more familiar language and makes it easier to
understand why a query behaves a particular way.
The FROM target object in the SQL has to be set to the SCOPE()
function, as shown here. If further refinement is required, the scope
can be set in a WHERE clause. If the scope is not set, no scope is
applied. In the following query, the first name of all persons in the
People scope are returned.
SELECT FirstName FROM SCOPE() WHERE "scope" = 'People'
The SQL select statement can be expanded to
return more properties or have more property restrictions. For instance,
if the query should be limited to return the author for all documents
with a file type of .pdf
, the following code SQL can be used to perform that search. This assumes the code is executed with the SharePoint context.
The namespace where the SharePoint search types are defined is as follows:
using Microsoft.Office.Server.Search.Query;
using(FullTextSqlQuery fullTextSqlQuery = new FullTextSqlQuery(SPContext.Current.Site))
{
fullTextSqlQuery.QueryText = "SELECT Author FROM SCOPE() WHERE \"scope\" = 'All Sites'
AND CONTAINS(FileType, 'pdf')";
fullTextSqlQuery.ResultTypes = ResultType.RelevantResults;
ResultTable fullTextSqlQueryResults =
keywordQueryResultsCollection[ResultType.RelevantResults];
DataTable fullTextSqlQueryResultsTable = new DataTable();
fullTextSqlQueryQueryResultsTable.TableName = "Results";
fullTextSqlQueryQueryResultsTable.Load(fullTextSqlQueryQueryResults,
LoadOption.OverwriteChanges);
}
This is a very basic example of how to perform a
property search in SharePoint using the CONTAINS predicate.
If debugging in Visual Studio 2010, you can then
use the data visualizer to see the results. This can be a helpful way
of evaluating search results.
Searching Through the Search Web Service
In SharePoint it is possible to search the
SharePoint index not only using the API on a SharePoint server, but also
from external locations outside SharePoint, using the search web
service. Examples could be custom integrations of
search into locally deployed client applications or from other
non-SharePoint web sites hosted on other servers.
As opposed to using the API, which, as shown in
the previous examples, is straightforward, using the search web service
does introduce some new challenges, namely the binding context. Next, it
is described how to contact the web service through a console
application with the binding context setup from App.Config
.
The search web service can be called from any code, however, and the
binding can be specified directly from code too, where feasible.
Consuming the Search Web Service from a Console Application
First a reference to the search service soap
client is needed. The constructor can either be empty or receive a
string with the name of the service binding context to use.
SearchServices.WSSearch.QueryServiceSoapClient searchService =
new SearchServices.WSSearch.QueryServiceSoapClient("SearchServiceBinding");
It is good practice to verify that the service
is actually online before invoking it. This can be done by checking the
status message of the soap client.
if (searchService.Status().ToLower() != "ONLINE")
throw new Exception("The search service is not online.");
The App.Config
file in this example
also includes credentials to use when querying the search web service.
These credentials are passed on to the search service.
Although it is possible to use anonymous access
when searching, it is often a requirement in any corporation that some
level of access permissions is set.
searchService.ClientCredentials.Windows.AllowNtlm = true;
searchService.ClientCredentials.Windows.AllowedImpersonationLevel =
System.Security.Principal.TokenImpersonationLevel.Impersonation;
The service is configured to use NT LAN Manager (NTLM) credentials, and the allowed impersonation level is set.
String username = ConfigurationManager.AppSettings.Get("SearchUserName").ToString();
String password = ConfigurationManager.AppSettings.Get("SearchPassword").ToString();
String domainname = ConfigurationManager.AppSettings.Get("SearchDomainName").ToString();
searchService.ClientCredentials.Windows.ClientCredential =
new System.Net.NetworkCredential(username, password, domainname);
At this point, the search service soap client is
fully configured and ready to be queried. The web service exposes a
method called QueryEx
, which receives a XML-formatted query
request containing the textual expression to query for. Here the query
text is simply “SharePoint AND Search”.
String queryText = "SharePoint AND Search";
String queryRequestString = "<QueryPacket xmlns='urn:Microsoft.Search.Query'>" +
"<Query>" +
"<SupportedFormats>" +
"<Format revision='1'>" +
"urn:Microsoft.Search.Response.Document:Document" +
"</Format>" +
"</SupportedFormats>" +
"<Context>" +
"<QueryText language='en-US' type='STRING'>" +
queryText +
"</QueryText>" +
"</Context>" +
"</Query>" +
"</QueryPacket>";
The request package just shown is the simplest
possible request. With the search service soap client configured and the
query request ready, the actual query can be executed in order to get a
results data set back from the search web service. In real-life
applications, there should always be an error handling code and a check
on success when contacting the search web service as when calling any
other web service. This is left out here for clarity.
System.Data.DataSet searchServiceQueryResults = new System.Data.DataSet();
searchServiceQueryResults = searchService.QueryEx(queryRequestString);
Finally the results can be retrieved from the
search result data set by simply iterating the rows in the returned data
set. Each row contains the default properties returned by the search
result. Typical properties that are useful are Title, Path, Author, and a
Summary of the result.
String title, author, path, summary;
foreach (DataRow row in searchServiceQueryResults.Tables[0].Rows)
{
title = row["Title"];
author = row["Author"];
path = row["Path"];
summary = row["HitHighlightedSummary"];
}
App.Config Settings
The App.Config
for the application
is used to define the default binding. As mentioned, this can also be
defined directly in code on the search service soap client object using
the API, but it is suggested to use an App.Config
whenever
possible. This makes it easier to maintain and reconfigure if so needed.
As such, this example of the binding is sufficient for most practical
uses (Listing 1).
Listing 1. Application Configuration file for console application using search web service
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="SearchUserName" value="YourUserName"/>
<add key="SearchPassword" value="YourPassword"/>
<add key="SearchDomainName" value="YourDomainName"/>
</appSettings>
<system.serviceModel> <bindings> <basicHttpBinding>
<binding name="SearchServiceBinding"
closeTimeout="00:01:00"
openTimeout="00:01:00"
receiveTimeout="00:10:00"
sendTimeout="00:01:00"
allowCookies="false"
bypassProxyOnLocal="false"
hostNameComparisonMode="StrongWildcard"
maxBufferSize="500000000"
maxBufferPoolSize="500000000"
maxReceivedMessageSize="500000000"
messageEncoding="Text"
textEncoding="utf-8"
transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32"
maxStringContentLength="8192"
maxArrayLength="16384"
maxBytesPerRead="4096"
maxNameTableCharCount="16384"/>
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Ntlm" />
</security>
</binding>
</basicHttpBinding> </bindings>
<client>
<endpoint address=http://[servername]:[portnumber]/_vti_bin/search.asmx
binding="basicHttpBinding"
bindingConfiguration="QueryServiceSoap"
contract="WSSearch.QueryServiceSoap"
name="QueryServiceSoap"/>
</client>
</system.serviceModel>
</configuration>
As shown in this
section, it is fairly easy to use the search web service for performing
searches against the SharePoint index, from outside the SharePoint farm.
The drawback here is maintaining the correct binding context if the
name or location of the search web service were to change. One way of
handling this could be to create a proxy on the organization's primary
domain that internally refers to the search web service.