Thursday, August 23, 2012

What's New in SharePoint 2013 Search (A Developers Perspective) Part One

Technorati Tags: ,,

Microsoft has made dramatic changes to serch in SharePoint 2013 Preview. There have been so many changes it is impossible to cover them all in one post. In this post I am going to talk about some of the changes you need to know about as a SharePoint Search developer. I will cover how the API has changed to accommodate the importance of Office 365. Secondly, I will show you how search scopes have been eliminated and replaced with result sources. Please remember that the findings in this post are based on the SharePoint 2013 Preview and are subject to change.

What happened to my Search Scopes?

In SharePoint 2013 search scopes are deprecated. Basically they are gone and not being used. You can access two system scope “All Sites” and “People” from the search service  application result sources page. However, you cannot create new search scopes nor can they be displayed in a dropdown list next to the standard search box. This is unfortunate because scopes could help users  find what they are looking for easier. SharePoint 2013 has replaced scopes with result sources. Result sources are SharePoint 2010 Federated Locations and Scopes combined with an easier way to define scoping rules.

Result sources make it easier to search remote sources including other SharePoint farms. Searching of remote SharePoint farms is easier than before because SharePoint 2013 relies on claims based authentication. No longer do you have to publish and subscribe to a remote service application and designate certain accounts for searching. Users can connect to the source using the default SharePoint authentication.

 

With SharePoint 2010 scopes you defined different types of rules which were limited to text properties and web addresses. In SharePoint 2013 there is a new query builder to help build a “Query Transformation”. You can now define any type of managed property inclusion or exclusion. The builder also gives you a large set of built in options to select from to help you build complex transformations, for example the name of the user who is running the query or a placeholder for a token from the request URL. Also, the query builder allows you to select from a list of all the managed properties and include property conditions similar to what you used in Advanced Search in SharePoint 2010. Here is an example of query text built by the builder to include content that has the user’s name running the query, a token from the URL and a ContentTypeID that starts with a certain value:

{searchTerms}{User.Name} {URLToken.1} ContentType=0x01*

You must include {searchTerms} to have the conditions appended to the query being submitted. The new query builder allows you to define much more complex conditions than the old search scoping rules. The builder also allows you to include custom sorting and gives you the ability to test the transformation and view the results it produces. There should be much more documentation on the capabilities of this builder and its syntax forthcoming from Microsoft.

 

 

How to use the new Result Source with your query

In SharePoint 2010 you appended scope names to your query text. In SharePoint 2013 you must set the KeywordQuery classes’ SourceID property to the GUID from a Microsoft.Office.Server.Search.Administration.Source. If you do not set this property to the source you want it will use the default source defined from the search service application. There seems to be no way from the UI to set the default result source for a search service application. So if you do not want to use the default result source you must retrieve the one you want. SharePoint 2013 is all about Office 365 and you can see evidence of this by the fact that you can define search managed properties, result sources, query rules and result types down to the site level. The SharePoint 2013 search API needed a way to retrieve these search objects easily, so the SearchObjectLevel and SearchObjectOwner classes were introduced to accomplish this. These two classes can be passed into various method calls to retrieve search objects, or used to create a SearchObjectFilter object to pass to the methods.

The SearchObjectFilter (Owners or Levels)

The SearchObjectFilter class is used in most cases to filter out the type of search objects you want returned. The SearchObjectFilter has two constructors one which takes a SearchObjectLevel enumeration and one that takes a SearchObjectOwner.

The SearchObjectLevel enumeration has the following levels:

public enum SearchObjectLevel 
{
        SPWeb = 0,
        SPSite = 1,
        SPSiteSubscription = 2,
        Ssa = 3, 
}

Using this enumeration in the constructor of the SearchObjectFilter you can filter based on Ssa = Search service application, SPSite = Site Collection, SPWeb = Site, and SPSiteSubscription = tenant.  Below is a code sample that uses a SearchObjectFilter based on a SearchObjectLevel to return a List of SourceRecords defined at the site collection level.

public static List<SourceRecord> GetSearchSourcesUsingLevel()
{
    using (SPSite site = new SPSite("
http://basesmc15"))
    {
        SPServiceContext serviceContext = SPServiceContext.GetContext(site);
        var searchProxy =
            serviceContext.GetDefaultProxy(typeof(SearchServiceApplicationProxy))
            as SearchServiceApplicationProxy;
        SearchObjectFilter filter = new SearchObjectFilter(SearchObjectLevel.SPWeb);

        //true is the default
        filter.IncludeHigherLevel = false;

        //false is the default
        filter.IncludeLowerLevels = true;

        List<SourceRecord> sources =
            searchProxy.GetResultSourceList(filter, false).ToList<SourceRecord>();

        return sources;

    }
}

This code will work only if there is an available SPContext. If you are running this in a console application or a timer job an error will be thrown stating a valid SPWeb could not be found on the SPContext. Hopefully, this will be fixed by release. The recommended approach is to create a SearchObjectOwner object using the appropriate SearchObjectLevel and a SPWeb. Then use the SearchObjectOwner to create the SearchObjectFilter. This enables the internal code to determine the scope of your search correctly.

public static List<SourceRecord> GetSearchSourcesUsingOwner()
{
    using (SPSite site = new SPSite("
http://basesmc15"))
    {
        using (SPWeb web = site.OpenWeb())
        {
            SPServiceContext serviceContext = SPServiceContext.GetContext(site);
            var searchProxy =
                serviceContext.GetDefaultProxy(typeof(SearchServiceApplicationProxy))
                 as SearchServiceApplicationProxy;
            SearchObjectOwner so = new SearchObjectOwner(SearchObjectLevel.SPSite, web);
            SearchObjectFilter filter = new SearchObjectFilter(so);

            //true is the default
            filter.IncludeHigherLevel = false;

            //false is the default
            filter.IncludeLowerLevels = true;

            List<SourceRecord> sources =
                searchProxy.GetResultSourceList(filter, false).ToList<SourceRecord>();
            return sources;

        }

    }
}

Notice the IncludeHigherLevel and IncludeLowerLevels properties. You can use these to adjust the filter to return sources above and below the SearchObjectLevel used to create the filter. For example if you wanted to return result sources from current site collection and the search service application then you would set the IncludeHigherLevel to true and IncludeLowerLevels to false. Below is example code getting a result source and using it in a query. The code uses the new SearchExecutor class to execute a query.

public static DataTable ExecuteKeyWordQuery()
{
    ResultTableCollection rtc = null;
    DataTable retResults = new DataTable();

    using (SPSite site = new SPSite("http://basesmc15"))
    {
        using (KeywordQuery query = new KeywordQuery(site))
        {
            query.QueryText = "microsoft isdocument:1";
            query.RowLimit = 500;
            query.SelectProperties.Clear();
            query.SelectProperties.Add("Path");
            query.SelectProperties.Add("IsDocument");
            query.SelectProperties.Add("Title");

            var source = from s in GetSearchSourcesUsingOwner()
                         where s.Name == "mysource" select s;

            if(source != null)
                query.SourceId = source.First().Id;
            SearchExecutor se = new SearchExecutor();
            rtc = se.ExecuteQuery(query);
            if (rtc.Count > 0)
            {
                var results = rtc.Filter("TableType", KnownTableTypes.RelevantResults);
                if (results != null && results.Count() == 1)
                    retResults.Load(results.First(), LoadOption.OverwriteChanges);

            }

        }

    }

    return retResults;

}

 

After result sources have been defined they can be used in any of the available search web parts that support sources. SharePoint 2013 search is focusing more on federation of search results rather than have users choose which scopes they want to search. The focus seems to be to create custom search result pages with multiple types of federated results.

More changes in SharePoint 2013 Search

In my next post I will talk about leveraging result sources and executing multiple queries with different sources and how SharePoint 2013 makes this easy. I will also talk about the changes in search syntax and enabling the use of FQL in your queries.

6 comments:

grayhill said...

we cannot create new search scopes nor can they be displayed in a dropdown list next to the standard search box. this is pretty difficult but great post

Sola Noah said...

hi, do we have to use claim based authentication in order to use "remote sharepoint"?

I have getting unauthroized exceptions.

Steve Curran said...

Sola, you must set up a trusted connection between the two farms in order to use remote result sources. You can follow the directions in the link below.

http://blogs.technet.com/b/speschka/archive/2012/07/23/setting-up-an-oauth-trust-between-farms-in-sharepoint-2013.aspx

Anonymous said...

do you know how to make the search box default to "everything" instead of this site.

Steve - SharePoint Developer said...

Thanks for the share, it is quite useful

Lei said...

Thanks Steve! Great article! One more question: What's the best practice to create query rule which sorts by rank and dynamic ordering
by URL exactly matches? Code sample is appreciate.

Post a Comment