Chapter 3: Customizing and Extending Microsoft Office SharePoint 2007 Search (Part 1 of 2)

This article is an excerpt from Inside Microsoft Office SharePoint Server 2007 by Patrick Tisseghem, from Microsoft Press(ISBN 9780735623682, copyright Microsoft Press 2007, all rights reserved). No part of this chapter may be reproduced, stored in a retrieval system, or transmitted in any form or by any means—electronic, electrostatic, mechanical, photocopying, recording, or otherwise—without the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews. This excerpt is part 1 of 2.

In this chapter, I’ll start with a discussion of the architecture that makes it all happen behind the scenes and will continue with topics that will give you an understanding of how, as a developer, you can start customizing and extending the different features involved. I also refer to the Microsoft Office SharePoint Server 2007 Administrator’s Companion (Microsoft Press) authored by Bill English, for more detail of the topics related to the administration and configuration of the index and search engines.

Contents

  • Managing the Index and Search Engine

  • Customizing the Search Center

  • Architecture of the Search Center

  • Additional Resources

Managing the Index and Search Engine

Microsoft Office SharePoint Server 2007 introduces a new object model that enables developers to programmatically support many of the search administration tasks that are typically done in the browser. The search administration object model is physically contained within a new .NET assembly called the Microsoft.Office.Server.Search.dll, and all of the classes related to the search administration (the major ones are displayed in Figure 1) are defined in the Microsoft.Office.Server.Search.Administration namespace. I’ll review some of the major administration and configuration steps administrators can perform in the browser, and I’ll describe how you can use all of the classes to programmatically accomplish the same tasks.

Figure 3-1. The major classes in the Microsoft Office SharePoint Server 2007 Search Administration object model

Major classes in Search Adminstration object model

The SearchContext Class

To begin, let me explain something important. As discussed in Chapter 1, the indexing and search capabilities delivered with Microsoft Office SharePoint Server 2007 are under the control of the Shared Services Provider (SSP). As a result, whatever you do programmatically related to content crawling and search will have to begin with retrieving a reference to the context of the Shared Services Provider. The ServerContext class defined in the Microsoft.Office.Server namespace and part of the Microsoft.Office.Server.dll is designed to do this.

The ServerContext class has two static members to get the reference of the context of the Shared Services Provider. The first one, the Current property, can be used to get the context while already running in a SharePoint context. A second member is the GetContext method, which accepts the name of the Shared Services Provider. The latter is the member to use when your code runs outside of the context of SharePoint, such as the case in the sample application. The following line of code is executed after providing the name of the Shared Services Provider in the text box.

ServerContext context = ServerContext.GetContext(name)

The other reference is the SearchContext object part of the Microsoft.Office.Server.Search.Administration namespace. This is the entry object within the search administration object model, and it exposes nearly the same interface as the ServerContext object described previously. Again, two static members are important: the Current property and the GetContext method. The former can only be used when your code runs in the context of Office SharePoint Server 2007; the latter is the one to use outside of the context. The GetContext method accepts three possible arguments: One is the name of the search application (only if running in the context of one of the portals), a second one is a reference to the ServerContext object, and a final one is a reference to an SPSite object, used internally to retrieve the reference to the context of the Shared Services Provider with which the Internet Information Services (IIS) Web application hosting the site collection is associated. Here is a typical call to retrieve the SearchContext reference.

SearchContext searchcontext = SearchContext.GetContext(context);

Now that you have the instance, you have different methods you can use within the code. Once you have a reference to the SearchContext object, you’ll have access to the other classes in the Search Administration object model required for the different configuration tasks discussed in the following pages.

Working with Content Sources

As an administrator of a SharePoint server farm, one of your duties will probably be to create content sources and configure one or more crawl rules for them. These tasks can be done by navigating to the administration site of the Shared Services Provider. Also, there is full support in the object model to finalize the steps in a programmatic way.

Creating Content Sources Using the Browser

Creating a content source is a very clear-cut task. In the Search section of the Shared Services Provider administration site, there is an option to navigate to the Configure Search Settings page. Here, you’ll find the link to drill down to the Manage Content Sources page. By default, there is only one content source available. This content source, called Local Office SharePoint Server sites, indexes all of the SharePoint sites that are available after the installation or upgrade of the server.

Figure 3-2. The listing of content sources that are crawled and their status

Crawled content source list and status

Figure 3-2 displays the five possible types of content sources you can create in the browser: SharePoint sites, normal Web sites, file shares, Microsoft Exchange Server public folders, and any data store that is connected to the Shared Services Provider using the Business Data Catalog middle layer. There are specific settings to complete for each of the content source types. In addition to the settings, there is also the configuration of the crawl rules and the scheduling of the actual crawling.

Managing Content Sources Programmatically

Now, this is all great, but what if you want to programmatically perform all of these steps? Earlier, we discussed the necessary starter object, SearchContext. Let’s now have a look at the .NET types involved in the process of listing, creating, and configuring content sources. All of the classes reviewed are part of the Microsoft.Office.Server.Search.Administration namespace.

The first class to consider is the Content class, a required class before getting access to the different classes at the lower level that represents each of the possible types of content sources. The Content class is also the one to start with for the other types of configurations you want to perform, such as the search scopes, the crawl rules, and crawl settings. The argument for the constructor is a reference to the SearchContext object. The following code shows how to get to a reference to a Content object.

Content content = new Content(searchcontext);

You can, for example, after connecting to the Shared Services Provider, populate a treeview control with all of the content sources defined. After the creation of the instance of a Content type, the list of content sources is retrieved via the ContentSources property. The property gives you access to all of the defined content sources with, of course, all of the methods in place to manipulate this collection. Following is the code for listing all of the content sources and creating a node for them in the treeview with sub-nodes for the different addresses that are crawled (accessible via the StartAddresses property of the ContentSource type).

if (this.searchContext != null)
{
  Content content = new Content(this.searchContext);
  foreach (ContentSource contentsource in content.ContentSources)
  {
    TreeNode node = treeViewIndexSearch.Nodes.Add(contentsource.Name);
    node.Tag = contentsource;
    foreach (object startaddress in contentsource.StartAddresses)
    {
       node.Nodes.Add(startaddress.ToString());
    }
  }
}

The various types of content sources are represented by their own class in the Search Administration object model, as shown in Figure 3-3.

Figure 3-3. The classes in the Search Administration object model representing each of the possible types of content sources

Search Administration content source classes

The ContentSource class is the base class for a content source defined as an abstract class. It encapsulates the common operations inherited by each of the underlying child classes. One thing you can do is show the details of the crawl status for a selected content source. There is also the option of starting a full crawl or an incremental crawl.

Let’s return to the different types of content sources. The following are defined as direct children of the ContentSource class: the SharePointContentSource, the WebContentSource, and the BusinessDataContentSource types. Crawling SharePoint sites is done by creating a SharePoint content source, an object of type SharePointContentSource. There is one additional property exposed at this level: the SharePointCrawlBehavior property. It enables you to specify whether all of the sites on the IIS Web application need to be crawled (the CrawlVirtualServers value) or only particular site collections (the CrawlSites value). A content source for a normal Web site is exposed as a WebContentSource object. The class has two specific properties to control the number of site-hops the crawler can take from the start address to a content item: the MaxPageEnumerationDepth and the MaxSiteEnumerationDepth properties. Microsoft Office SharePoint Server 2007 also allows you to crawl business data when selecting the new Business Data Catalog protocol handler. All of the business applications are internally defined by the BusinessDataContentSource type. Two static methods are available at this level on top of the inherited members for detailing the business data application to be indexed: ConstructStartAddress and ParseStartAddress.

An additional intermediate layer, the HierarchicalContentSource class, is added under the ContentSource class to provide the necessary abstraction for the remaining types of content sources: the ExchangePublicFolderContentSource, the FileShareContentSource, and the LotusNotesContentSource class. All three of them inherit the FollowDirectories property as an extra member on top of the ones inherited from the base ContentSource class. The FollowDirectories property provides you with the means to tell the crawler whether it should include subdirectories in the crawl.

There is one final child class of the ContentSource class that we’ve not yet discussed: the CustomContentSource class. In addition to the six types of content sources that are immediately creatable out-of-the-box, you can basically let the crawler access any other type of location. The condition is that you provide the index engine with all of the required plumbing for accessing that location. The required information is made available and registered in the form of a custom protocol handler. Once done, additional content sources can be created that leverage the custom protocol handler.

Let’s have a look how to create a new content source of type SharePoint. The code is the following.

try
{
  Content content = new Content(this.searchContext);
  SharePointContentSource contentsource = 
      (SharePointContentSource)
           content.ContentSources.Create(typeof(SharePointContentSource),
                                         textBoxContentSourceName.Text);
  contentsource.StartAddresses.Add(new Uri(textBoxSharePointURL.Text));
  contentsource.Update();
  MessageBox.Show("Content source created!");
}
catch (Exception ex)
{
  MessageBox.Show(ex.ToString());
}

After you’ve created the content source, you can start the full crawl.

Working with Search Scopes

A second important concept when discussing the search administration is the whole notion of search scopes. From an administrator’s perspective, a search scope is a subdivision of the index built up by the crawler. The items in the index will be matched again to one or more rules created by the administrator. From a user’s perspective, a search scope is a means to limit the amount of search results returned after a search query to only those that match the rules contained within the search scope. Examples can be: get me only the search results for one specific content source, or get me only the documents authored by Brian Cox.

Managing Search Scopes in the Browser

Administrators have two levels where they can create the search scopes. A first level is the Shared Services Provider, also referred to as the global level. The search scope instances created at this level are called shared search scopes because, once defined, they are re-usable anywhere within the server farm—that is, for the SharePoint sites that are associated with the Shared Services Provider in question. The second level is the site-collection level. Site collection administrators can limit the search scope to be used only within one specific site collection.

For both types, however, the steps are the same. The View Scopes page for a site collection containing a collaboration portal is where you can start maintaining the search scopes and also create new ones.

Creating a new search scope starts by simply specifying a title and description. You also have to associate the search scope with one of the existing display groups. A display group is a layer between the actual search scopes and the drop-down control that displays the search scopes to the user. A search scope, defined either at the global or site-collection level, will not be displayed in the drop-down controls in the SharePoint pages unless they are associated to the corresponding display group.

The final step is to create one or more rules defining the conditions for the search results in the search scope. There are three types of rules possible: For the first, the scope is a URL. The second is an arbitrary property-based rule, and the third is a rule scoped to one of the content sources. Figure 3-4 displays how you can construct a rule defining that users can only see content or documents created by Mike.

Figure 3-4. Filtering the search results for only the content or documents created by Mike

Search results for only content created by Mike

Once the search scope is created, updated, and associated with one of the display groups, users will see it listed in the scopes drop-down list.

Working with Search Scopes in a Programmatic Way

Again, all of the administrative steps performed above in the browser can be done programmatically. Let’s have a look at some of the things you can do with search scopes.

This time, the entry object to start with is the Scopes object. Just as with the Content object discussed previously, the Scopes constructor needs an instance of the SearchContext class as a parameter.

Scopes scopes = new Scopes(searchcontext);

Numerous members are available to perform the various administrative operations. One example is the StartCompilation method. In Microsoft Office SharePoint Server 2007, search scopes are actually subsets of an index file and, therefore, when changes are done either at the level of the index file itself or the search scopes, you need to run a compilation of the search scopes to again mark the different subsets in the index file that match up with the active search scopes.

You can, for example, display all of the search scopes in a treeview control. The code behind the button to create the nodes looks like this.

//-- loop over all shared search scopes
TreeNode sharedScopesNode = treeViewIndexSearch.Nodes.Add("Shared Search Scopes");
foreach (Scope scope in scopes.GetSharedScopes())
{
    TreeNode node = sharedScopesNode.Nodes.Add(scope.Name);
    node.Tag = scope;
}

//-- loop over all scopes but filter on only local search scopes
TreeNode localScopesNode = treeViewIndexSearch.Nodes.Add("Local Search Scopes");
foreach (Scope scope in scopes.AllScopes)
{
    if (scope.OwningSite != null)
    {
        TreeNode node = localScopesNode.Nodes.Add(scope.Name);
        node.Text += " (" + scope.OwningSite.Url + ")";
        node.Tag = scope;
    }
}

It is also possible to loop over all of the display groups and show the individual scopes that are members.

//-- loop over all display groups
TreeNode displaygroupNode = treeViewIndexSearch.Nodes.Add("Display Groups");
foreach (ScopeDisplayGroup group in scopes.AllDisplayGroups)
{
    TreeNode node = displaygroupNode.Nodes.Add(group.Name);
    if (group.OwningSite != null)
        node.Text += " (" + group.OwningSite.Url + ")";
    node.Tag = group;

    foreach (Scope scope in group)
    {
        node.Nodes.Add(scope.Name);
    }
}

You’ll work with either an instance of a ScopeDisplayGroup class or an instance of the Scope class. The former is a type that internally maintains a collection of Scope instances that is of course maintainable using the traditional collection operations. The latter, the Scope class, represents an individual search scope with numerous methods to perform operations on the scope, such as the addition of one or more ScopeRule instances that make up the logic for creating the subset of matching items in the index.

Let’s have a look at the code that enables you to create a shared search scope.

Scopes scopes = new Scopes(this.searchContext);
scopes.AllScopes.Create
    (textBoxSharedSearchScopeName.Text,
     textBoxScopeDescription.Text, null, true,
     "results.aspx", ScopeCompilationType.AlwaysCompile);

The new Scope instance is created by calling the Create method at the level of the ScopeCollection that is returned by the AllScopes property. The first argument for which to provide a value is the name of the new scope. Secondly, you can give a value for the description and then the value for the OwningSite property. If you want to end up with a shared search scope, you have to pass the null value here. Of course, you can also provide a URL, but then you are creating a local search scope. The Boolean value indicates whether you want to have the scope displayed in the user interface for the administrators: yes or no. The next parameter is the page for showing the results of the query and the type of compilation you desire. There is an option to choose a conditional compilation, which would be used in the rare case that the number of scope rules exceeds 24.

But, simply creating the scope is not enough. You’ll have to create the rules too. Before doing that, review the following block of code.

private void PopulateWithRules(Scope scope, TreeNode node)
{
    foreach (ScopeRule rule in scope.Rules)
    {
        if (rule is PropertyQueryScopeRule)
        {
            PropertyQueryScopeRule prule = (PropertyQueryScopeRule)rule;
            TreeNode childnode = node.Nodes.Add("Property Query Rule: ");
            childnode.Text += prule.Property.Name + " =  " + prule.Value;        }

        if (rule is AllContentScopeRule)
        {
            AllContentScopeRule arule = (AllContentScopeRule)rule;
            node.Nodes.Add("All Content Rule");
        }

        if (rule is UrlScopeRule)
        {
            UrlScopeRule urule = (UrlScopeRule)rule;
            TreeNode childnode = node.Nodes.Add("URL Rule: ");
            childnode.Text += urule.MatchingString;
        }
    }
}

There are three different types of rules, each represented by its own class inheriting from the general ScopeRule class. The PropertyQueryScopeRule type is used when creating a rule that performs a property query or when the rule is associated with a specific content source. The AllContentScopeRule covers all of the content sources, so this rule type applies to all content in the index. The last type is AllContentScopeRule, which accepts a URL as the condition.

Once you’ve created a new search scope, you’ll create one or more rules. The Scope class exposes a Rules property with three methods you can call for the creation of the three different types of rules: the CreatePropertyQueryRule, the CreateAllContentRule, and the CreateUrlRule methods. Here is the code that creates a URL rule for one of the scopes.

scope.Rules.CreateUrlRule
    (ScopeRuleFilterBehavior.Include,
     UrlScopeRuleType.HostName, textBoxURLRule.Text);

The first parameter is the ScopeRuleFilterBehavior. Three options are valid here: Exclude, Include, and Required. Exclude means that any matching items are excluded from the search results. Include is of course the reverse, and Required means that the content items included in the search results must match the defined rule. Next you have the value for UrlScopeRuleType. Possibilities here are: Domain, Folder, or HostName. For a Web site, you select HostName. If you are going for a file share, you select Folder.

As previously mentioned, a search scope is actually a subset identified for the index, and it also becomes part of the index itself. That’s why you have to compile your search scopes so that this subset can be created. The start of the compilation is done by calling the StartCompilation method at the level of the Scopes type.

Scopes scopes = new Scopes(this.searchContext);
scopes.StartCompilation();

Schema Management

While crawling the content as defined by the different content sources, the crawler collects all of the metadata associated with the different resources found. The metadata is stored in the Search database, a database created by and under the control of the Shared Services Provider. Don’t underestimate the amount of information this can be. A typical crawl in a medium-sized company can involve hundreds of thousands of documents, and many, many different aggregated properties can end up in the database. It is the administrator’s job to organize all of this metadata and make properties available for the user in the advanced search user interface. This can be a very challenging task. However, Microsoft Office SharePoint Server 2007 has a number of improvements that will facilitate it. One important change is the introduction of a new layer called managed properties.

Creating and Using Managed Properties in the Browser

Managed properties map to one or more of the crawled properties. They group together the related crawled properties and expose them to the user. For example, after a crawl, you can end up with three different properties: Product Code, Product Code, and Product Number. All of them basically have the same meaning, but users responsible for creating and maintaining lists and document libraries do not always work in the same consistent manner. However, as an administrator, you can create managed properties and apply some level of consistency in the advanced search page for users who need to create more complex search queries involving the crawled metadata in the WHERE clauses.

The Search Settings in the administrative site of the Shared Services Provider has a Metadata Property Mappings link. Clicking on it brings you to the Metadata Property Mappings page, shown in Figure 3-5.

Figure 3-5. The list of managed properties, properties that map to one or more crawled properties

List of managed properties

Creating a new managed property involves two simple steps, shown in Figure 3-6. First, provide a name for the property, enter a description, and specify the type of the property.

Figure 3-6. Creating a new managed property and mapping it to the crawled properties, Product Code, ProductCode, and Product Number

Creating and mapping a managed property

The second step is to map to the crawled properties. The Add Mapping button opens a dialog box where you can navigate through all of the crawled properties and select the ones you want to map to the managed property. Figure 3-7 shows the three crawled properties: ows_Product_x0020_Code, ows_Product_x0020_Number, and ows_ProductCode. You have to add them one by one to the list of mapped properties.

Figure 3-7. The three crawled properties to use for mapping with the managed property Product Code

Three crawled properties to map to Product Code

But, how do you make the new managed property available in the advanced search page of the Search Center? That job cannot be done in the administration site of the Shared Services Provider. For this, you have to go to the Advanced Search page of the Search Center itself and switch the page to edit mode. The page contains the Advanced Search Web Part, and by using the properties tool pane, you can tweak and configure a lot of the configuration settings.

If you expand the Properties node in the tool pane, you’ll be able to modify the XML that defines the list of properties the Web Part knows about. In the XML, an element called PropertyDefs contains the entries for all of the managed properties that should be displayed to the user as possible criteria for the WHERE parts of their search queries.

<PropertyDefs>
  <PropertyDef Name="Path" DataType="text" DisplayName="URL"/>
  <PropertyDef Name="Size" DataType="integer" DisplayName="Size"/>
  <PropertyDef Name="Write" DataType="datetime" DisplayName="Last Modified Date"/>
  <PropertyDef Name="FileName" DataType="text" DisplayName="Name"/>
  <PropertyDef Name="Description" DataType="text" DisplayName="Description"/>
  <PropertyDef Name="Title" DataType="text" DisplayName="Title"/>
  <PropertyDef Name="Author" DataType="text" DisplayName="Author"/>
  <PropertyDef Name="DocSubject" DataType="text" DisplayName="Subject"/>
  <PropertyDef Name="DocKeywords" DataType="text" DisplayName="Keywords"/>
  <PropertyDef Name="DocComments" DataType="text" DisplayName="Comments"/>
  <PropertyDef Name="Manager" DataType="text" DisplayName="Manager"/>
  <PropertyDef Name="Company" DataType="text" DisplayName="Company"/>
  <PropertyDef Name="Created" DataType="datetime" DisplayName="Created Date"/>
  <PropertyDef Name="CreatedBy" DataType="text" DisplayName="Created By"/>
  <PropertyDef Name="ModifiedBy" DataType="text" DisplayName="Last Modified By"/>
</PropertyDefs>

Take the following PropertyDef element, for example. It defines an entry for the new ProductCode managed property.

<PropertyDef Name="ProductCode" DataType="text" DisplayName="Product Code"/>

Before this entry will appear in the drop-down menu in the Advanced Search Web Part, you’ll also have to add a PropertyRef element as a child to the ResultType element.

<ResultTypes>
  <ResultType DisplayName="All Results" Name="default">
    <Query/>
    <PropertyRef Name="Author" />
    <PropertyRef Name="Description" />
    <PropertyRef Name="FileName" />
    <PropertyRef Name="Size" />
    <PropertyRef Name="Path" />
    <PropertyRef Name="Created" />
    <PropertyRef Name="Write" />
    <PropertyRef Name="CreatedBy" />
    <PropertyRef Name="ModifiedBy" />
    <PropertyRef Name="ProductCode" />
  </ResultType>
<ResultTypes>

As Figure 3-8 shows, the Product Code property will be included in the list of properties to use for a selected result type only if you perform this last step.

Figure 3-8. The new managed property available in the drop-down menu in the Advanced Search box

Managed property in Advanced Search box

Managed Properties in the Object Model

How is all of this exposed in the search administration object model? Your entry point this time is the Schema object that is created with a SearchContext instance as the parameter.

Schema schema = new Schema(searchcontext);

The Schema class has an AllManagedProperties property, an instance of the ManagedPropertyCollection class. With this collection, you can loop over all of the managed properties, and you edit the settings one by one. The following code shows how a treeview is populated.

foreach (ManagedProperty prop in schema.AllManagedProperties)
{
    TreeNode node = treeViewManagedProperties.Nodes.Add(prop.Name);
    node.Tag = prop;
    foreach (Mapping mapping in prop.GetMappings())
    {
        TreeNode mappingnode = node.Nodes.Add(mapping.CrawledPropertyName);
    }
}

Creating a new managed property is done by calling the Create method at the level of the ManagedPropertyCollection collection and giving it the name of the property and then the type, as shown by the following lines of code.

Schema schema = new Schema(this.searchContext);
schema.AllManagedProperties.Create
    (textBoxManagedPropertyName.Text, ManagedDataType.Text);

The Schema class exposes a method, QueryCrawledProperties, that returns the list of crawled properties. The method accepts a number of parameters you can use to filter the possibly very large list of crawled properties. Here is the call that returns them and adds them to the list box in the sample application.

foreach (CrawledProperty cprop in schema.QueryCrawledProperties
    (string.Empty, 1000, Guid.NewGuid(), string.Empty, true))
{
    listBoxCrawledProperties.Items.Add(cprop);
}

Creating a mapping between one or more crawled properties and a managed property is not necessarily straightforward. Review the following code block with the different steps that have to be done.

ManagedProperty prop = 
    (ManagedProperty)treeViewManagedProperties.SelectedNode.Tag;
MappingCollection mappings = new MappingCollection();
foreach (CrawledProperty cprop in listBoxCrawledProperties.SelectedItems)
{
    mappings.Add(new Mapping(cprop.Propset, 
                             cprop.Name, cprop.VariantType, prop.PID));
}

prop.SetMappings(mappings);

To start, you have to create a new instance of the type MappingCollection. This collection has to be populated with instances of type Mapping, each of them representing one mapping with a crawled property. The information identifying the crawled property is something you provide during the creation of the instance with the constructor. After all of the crawled properties you intend to map are collected in the MappingCollection instance, you just have to call the SetMappings method of the ManagedProperty instance in question and assign it the new MappingCollection.

There is a lot more to discuss regarding the new search administration object model. I refer to the Microsoft SharePoint Server 2007 Software Developer Kit (SDK) that contains a full description of all of the classes that are part of the object model and some additional code samples.

Customizing the Search Center

The Search Center is a new type of site delivered with Microsoft Office SharePoint Server 2007 and is by default a subsite of the collaboration portal. Actually, you have two versions of the Search Center:

  • Search Center without Tabs is a version of the Search Center that does not require the publishing feature activated at the level of the site collection. You can therefore very quickly add Search Center Lite to a site collection that only contains team sites and provide a place within the site collection where users can execute a search query against the index file that’s under the control of the Shared Services Provider, with which the IIS Web application hosting the site collection is associated. Search Center Lite is a version of the Search Center without the tabs and thus does not allow full customization.

  • Search Center with Tabs is the full version of the Search Center. It requires the Microsoft Office SharePoint Server Publishing Infrastructure feature to be activated and comes with a tab-based interface that is fully customizable, as you’ll see in the next couple of pages.

Architecture of the Search Center

The Search Center is a normal SharePoint site built on the same fundamentals as any other site. Thus, we have a master page associated with the site that defines the overall look and feel, the chrome, the navigation, and other types of SharePoint controls you’ll see on many other types of sites. Content pages are displayed that are connected to this master page and, as discussed in Chapter 2, "Collaboration Portals and People Management," these pages are stored in the Pages document library. The pages are based on page layouts that are provisioned along with the site. After the site has been created, you have four of these page layouts available. Table 1 summarizes them:

Table 3-1. Search Center page layouts

Page Layout Description

Search Page

Pages based on this page layout are connected to the various tabs. By default, they contain the Search Box Web Part.

Search Results Page

This page layout defines the layout and functionality for the pages that show the search results. In addition to the Search Box Web Part, you’ll find about seven more Web Parts on the page that delivers the search results experience. I’ll detail them later.

Advanced Search Page

The page layout for the advanced search page with the Advanced Search Box as a Web Part on the page.

People Search Results Page

This is the page layout that defines the page for displaying the search results when making use of the People tab. It contains about eight Web Parts that display a part of the search results.

One major component of the Search Center with Tabs is the tab-based user interface. By default, you have two tabs: All Sites, which is the standard scope, and People, for a people-oriented search operation. Behind the scenes, the tabs are items stored in two lists. One is the Tabs in Search Pages list that contains the tabs for the pages based on the Search Page and the Advanced Search Page page layouts. The second list is the Tabs in Search Results, which contains the tabs for the Search Results Page and the People Search Results Page page layouts.

The tabs are actually rendered by the ListBoundTabStrip control contained within the Microsoft.SharePoint.Portal.dll and defined in the Microsoft.SharePoint.Portal.WebControls namespace. If you open one of the page layouts in Microsoft Office SharePoint Designer 2007, you’ll notice the following element representing this control.

<SPSWC:ListBoundTabStrip  id="Tab" persistQueryString="true" 
  cssClassNamePrefix="ms-sctab" 
  ListName="<%$Resources:sps,SearchCenterOnet_SearchResultsListName%>"  
  UnselectedTabTrimLength="-1" IgnoredQueryStringParameters="s,start1">
</SPSWC:ListBoundTabStrip>

So, everything is pretty much in place to customize the user’s experience when working within the Search Center. Let’s have a look at what you can accomplish.

Overview of the Search Web Parts

In total, you have nine Search Web Parts, and each of them exposes a wide variety of properties you can change to offer your users the search experience you have in mind. Many of these properties have to do with formatting, but there are also many options for configuring the actual execution of the search query itself. There is also the option of replacing the XSLT transformation used to transform the XML that contains the search results into the HTML presentation displayed in the Search Results page. All of the Search Web Parts are connected to one another, and there is a hidden control in charge of executing the search query, wrapping it into an XML block and sharing it with all of the other Web Parts available on the page.

The Search Box Web Part

The Search Box Web Part is used by the user to enter the query string. It can display the scopes drop-down list and the query text box, a button to execute the query, and a link to the Advanced Search page. Each of these components is customizable by opening the Properties tool pane for the Web Part. In addition to the display mode, you can also set a label for the scopes drop down list and set the width to a fixed size.

The query text box exposes properties that allow you to set a label, fix the width of the text box, and have additional query terms appended to the query string that the user inputs. It shows how you can limit the scope of the search results to only contain documents by automatically adding the query term isDocument:1 to every query.

A last group of exposed properties is under the Miscellaneous node. There are properties that allow you to replace any of the default images used in the Search Box Web Part with your own custom ones. There is also the option to specify a URL for the custom advanced search page and the search results page you may have created. A last important property is the name of the display group that is connected to the scopes dropdown and used to populate the dropdown with the available search scopes.

The Search Summary Web Part

This Web Part can show the summary of the search query and offer to correct the user if needed by displaying the Did You Mean… feature. The content of the summary can be displayed in compact or extended mode, and there is also the option to hide or display messages to the user.

The Search Action Links Web Part

With the Search Action Links Web Part, the user is able to perform certain actions on the returned search results. The operations supported are: sorting of the results based on the relevance ranking or the modified date, creating alerts on the search results, and navigating to the RSS feed generated. Each of these operations can be turned on or off. There is also the option to modify the XSLT transformation that is being used to display the different action links. However, there is no option here for adding custom action links.

The Search Best Bets Web Part

The Search Best Bets Web Part shows the results that match up with a keyword, a best bet, or high-confidence results. For all, you can configure properties that have an effect on how the results are displayed. For example, you can turn on or off the display of the title and the description, and you can limit the amount of results displayed by the Web Part. Again, the XSLT transformation that transforms the results in an HTML block displayed within the Web Part can be customized or replaced with your own custom XSLT.

The Search Statistics Web Part

The Search Statistics Web Part displays statistics such as the number of results shown on the page, the total amount of results, and the time it took to execute the search query. There’s nothing really exciting to configure other than the option of displaying the text on one line or two and another option for hiding or displaying individual items.

The Search Paging Web Part

The Search Paging Web Part shows the links for navigation between the different pages showing the search results. You can tune how many links are being shown before and after the current page link as well as what the link looks like. You can have plain text or replace the text with an image. The amount of results for a page is not defined in this Web Part.

The Search High Confidence Results Web Part

The Search High Confidence Results Web Part has pretty much the same goal as the Search Best Bets Web Part, and, as you’ll see, it even exposes the same type of properties. But this Web Part, by default, displays only the high confidence results, while the Search Best Bets Web Part only shows the results for the best bets and keywords.

The Search Core Results Web Part

This is definitely the most important Web Part on the Search Results page; it’s responsible for displaying results to the user. First, you have properties to configure the paging and the default sorting. Next, you have some options for tuning the results query. Duplicate results can be displayed or hidden from the user. By default, search term stemming is not enabled. Stemming is a method of mapping a linguistic stem to all matching words to increase the number of relevant results. For example, the stem "buy" matches "bought", "buying", and "buys". Another option is to enable or disable noise word queries. It is turned on by default, which means that users are able to input a search string containing only noise words, such as "the", "or", and "a".

An interesting property is Selected Columns, which contains an XML string that defines all of the columns that are to be displayed in the search results. The following is the default list of columns shown in the search results.

<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <Columns>
  <Column Name="WorkId"/>
  <Column Name="Rank"/>
  <Column Name="Title"/>
  <Column Name="Author"/>
  <Column Name="Size"/>
  <Column Name="Path"/>
  <Column Name="Description"/>
  <Column Name="Write"/>
  <Column Name="SiteName"/>
  <Column Name="CollapsingStatus"/>
  <Column Name="HitHighlightedSummary"/>
  <Column Name="HitHighlightedProperties"/>
  <Column Name="ContentClass"/>
  <Column Name="IsDocument"/>
  <Column Name="PictureThumbnailURL"/>
 </Columns>
</root>

Adding a column is simply adding a Column element with the proper name. But it does not mean that you’ll get to see it in the search results. There is the additional step of modifying the XSLT transformation associated with the Web Part and inserting the XSLT block to pick up the additional property and display it to the user.

Instead of displaying the search results based on a query string entered by the user in the Search text box, you also have the option of configuring the Search Core Results Web Part to show the results of a fixed query string.

This is an interesting process to follow if, for example, you need to display on a home page a list of the five most recent documents that contain the word "Business". Figure 3-9 shows the Search Core Results Web Part on the home page of a team site displaying just that. The configuration is pretty simple. Set the Fixed Query property to your query string, for example "Business -isDocument:0", and don’t forget to set the Cross-Web Part query ID to something other than User Query.

Figure 3-9. The Search Core Results Web Part configured with a fixed query on a home page of a SharePoint team site

Search Core Results Web Part with fixed query

The most important customization option for the Search Core Results Web Part is, of course, the XSLT transformation that you can either customize or completely replace with one of your one. Before we detail this further, let’s just review exactly how the search results end up in the body of the Web Part.

A query expressed as a string is passed to the Search Core Results Web Part by one of three ways: the user entering a query in the Search Box Web Part, a custom Web Part connected to it, or the internal fixed query property itself. An internal hidden object coordinates the execution of the query and returns the search results as an XML string. The XSLT transformation performed on the XML results in the view users see in the Web Part. The XML that is the input for the transformation looks like this.

<All_Results>
  <Result>
    <id>1</id> 
    <rank>713</rank> 
    <title>Microsoft.SharePoint Namespace</title> 
    <author /> 
    <size>39058</size> 
    <url>https://msdn.microsoft.com/library/SV01017995.asp</url> 
    <description>Microsoft.SharePoint Namespace</description> 
    <sitename> https://msdn.microsoft.com/library</sitename> 
    <collapsingstatus>0</collapsingstatus>
    <hithighlightedsummary>
      <…>
    </hithighlightedsummary>
    <hithighlightedproperties>
      <…>
    </hithighlightedproperties>
    <…>
   </Result>
   <Result>
    <…>
    <…>
   </Result>
</All_Results>  

It is possible to have additional properties included in this XML, for example, by adding more Column elements to the Selected Columns property as previously discussed.

One way to visualize the raw XML in the browser is by replacing the XSLT transformation with the following.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xmp><xsl:copy-of select="*"/></xmp>
</xsl:template>
</xsl:stylesheet>

With a full understanding of the structure of the raw XML, you’re ready to start creating your own custom XSLT transformation. You’ll work out an example in the walkthrough, but for now, let’s just have a look at the different sections within the XSLT that you should include in your custom XSLT.

A first block is the definition of various configuration parameters that can be passed to the transformation at run time. A lot of these are used to branch the XSLT at some point.

<xsl:param name="ResultsBy" />
<xsl:param name="ViewByUrl" />
<xsl:param name="ViewByValue" />
<xsl:param name="IsNoKeyword" />
<xsl:param name="IsFixedQuery" />
<…>

The first pattern-matching rule, found at the bottom of the XSLT, is the one that matches with the root-node of the XML to be transformed. This is the one that initiates the whole process.

<!-- XSLT transformation starts here -->
<xsl:template match="/">
  <xsl:if test="$AlertMeLink">
     <input type="hidden" name="P_Query" />
     <input type="hidden" name="P_LastNotificationTime" />
  </xsl:if>
  <xsl:choose>
     <xsl:when test="$IsNoKeyword = 'True'" >
         <xsl:call-template name="dvt_1.noKeyword" />
     </xsl:when>
     <xsl:when test="$ShowMessage = 'True'">
         <xsl:call-template name="dvt_1.empty" />
     </xsl:when>
     <xsl:otherwise>
         <xsl:call-template name="dvt_1.body"/>
     </xsl:otherwise>
  </xsl:choose>
</xsl:template>

In the template, the values of the incoming parameters are checked and used to jump to one of the other templates. If there is no keyword filled in by the user and the fixed query property is not set, the user will see a message indicating that something is wrong.

<!-- When there is keywory to issue the search -->
<xsl:template name="dvt_1.noKeyword">
  <span class="srch-description">
     <xsl:choose>
       <xsl:when test="$IsFixedQuery">
           Please set the 'Fixed Query' property for the Web Part.
      </xsl:when>
      <xsl:otherwise>
           Enter one or more words to search for in the search box.
      </xsl:otherwise>
     </xsl:choose>
  </span>
</xsl:template>

It is also possible that no search results are returned for the inputted search query string. In that scenario, the action links are displayed along with a message telling the user that nothing was found.

<!-- When empty result set is returned from search -->
<xsl:template name="dvt_1.empty">
  <div class="srch-sort">
     <xsl:if test="$AlertMeLink and $ShowActionLinks">
        <span class="srch-alertme" >
           <a href ="{$AlertMeLink}" id="CSR_AM1" title="{$AlertMeText}">
             <img style="vertical-align: middle;" 
                 src="/_layouts/images/bell.gif" alt="" border="0"/>
             <xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text>
             <xsl:value-of select="$AlertMeText" />
           </a>
         </span>
       </xsl:if>

       <xsl:if test="string-length($SrchRSSLink) &gt; 0 and $ShowActionLinks">
         <xsl:if test="$AlertMeLink">
            |
         </xsl:if>
         <a type="application/rss+xml" href ="{$SrchRSSLink}" 
            title="{$SrchRSSText}" id="SRCHRSSL">
            <img style="vertical-align: middle;" border="0" 
                 src="/_layouts/images/rss.gif" alt=""/>
               <xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text>
               <xsl:value-of select="$SrchRSSText"/>
         </a>
       </xsl:if>
     </div>
     <br/><br/>
     <span class="srch-description" id="CSR_NO_RESULTS">
     No results matching your search were found.
     <ol>
       <li>Check your spelling. Are the words in your query 
           spelled correctly?</li>
       <li>Try using synonyms. Maybe what you're looking for uses 
           slightly different words.</li>
       <li>Make your search more general. Try more general terms in 
           place of specific ones.</li>
       <li>Try your search in a different scope. Different scopes can 
           have different results.</li>
     </ol>
  </span>
</xsl:template>

Of course, the most interesting part of the XSLT is the part that is called when search services return search results. You’ll find a first block to render the action links.

<!-- Main body template. Sets the Results view (Relevance or date) options -->
<xsl:template name="dvt_1.body">
   <div class="srch-results">
     <…> XSLT for the action links – see above <…>
     <xsl:apply-templates />
   </div>
   <xsl:call-template name="DisplayMoreResultsAnchor" />
</xsl:template>

But then the real fun starts with the template that matches the Result node. One by one, the Result elements in the XML are transformed into SPAN and DIV elements. First, the icon is rendered.

<!-- This template is called for each result -->
<xsl:template match="Result">
  <xsl:variable name="id" select="id"/>
  <xsl:variable name="url" select="url"/>
  <span class="srch-Icon">
     <a href="{$url}" id="{concat('CSR_IMG_',$id)}" title="{$url}">
       <img align="absmiddle" src="{imageurl}" border="0" 
        alt="{imageurl/@imageurldescription}" />
     </a>
  </span>

Next, there is a block that transforms the title of the search result. This one is a bit more complicated because of highlighting of the possible keyword entered by the user in the query string.

  <span class="srch-Title">
    <a href="{$url}" id="{concat('CSR_',$id)}" title="{$url}">
      <xsl:choose>
        <xsl:when test="hithighlightedproperties/HHTitle[. != '']">
          <xsl:call-template name="HitHighlighting">
            <xsl:with-param name="hh" select="hithighlightedproperties/HHTitle" />
          </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="title"/>
        </xsl:otherwise>
      </xsl:choose>
    </a>
    <br/>
  </span>

The highlighting itself is delegated to a couple of additional templates that abstract the whole process. Continuing with processing the result, there is the rendering of the description, again calling the template for highlighting the keywords in the description if needed.

   <div class="srch-Description">
     <xsl:choose>
       <xsl:when test="hithighlightedsummary[. != '']">
          <xsl:call-template name="HitHighlighting">
             <xsl:with-param name="hh" select="hithighlightedsummary" />
          </xsl:call-template>
       </xsl:when>
       <xsl:when test="description[. != '']">
          <xsl:value-of select="description"/>
       </xsl:when>
     </xsl:choose>
   </div >

The same processing is basically applied to the URL of the search results.

   <p class="srch-Metadata">
     <span class="srch-URL">
       <a href="{$url}" id="{concat('CSR_U_',$id)}" title="{$url}" dir="ltr">
         <xsl:choose>
           <xsl:when test="hithighlightedproperties/HHUrl[. != '']">
              <xsl:call-template name="HitHighlighting">
                 <xsl:with-param name="hh"
                     select="hithighlightedproperties/HHUrl" />
              </xsl:call-template>
           </xsl:when>
           <xsl:otherwise>
              <xsl:value-of select="url"/>
           </xsl:otherwise>
         </xsl:choose>
       </a>
     </span>

To conclude, there is the processing of the other properties of the search result, the size, and the author.

     <xsl:call-template name="DisplaySize">
        <xsl:with-param name="size" select="size" />
     </xsl:call-template>
     <xsl:call-template name="DisplayString">
        <xsl:with-param name="str" select="author" />
     </xsl:call-template>
     <xsl:call-template name="DisplayString">
        <xsl:with-param name="str" select="write" />
     </xsl:call-template>
     <xsl:call-template name="DisplayCollapsingStatusLink">
        <xsl:with-param name="status" select="collapsingstatus"/>
           <xsl:with-param name="urlEncoded" select="urlEncoded"/>
           <xsl:with-param name="id" select="concat('CSR_CS_',$id)"/>
        </xsl:call-template>
    </p>
  </xsl:template>

If you have additional properties that need to be displayed, you can add them here following the same pattern and action. The rendering of the size and author is done with two generic templates.

<!-- The size attribute for each result is prepared here -->
<xsl:template name="DisplaySize">
  <xsl:param name="size" />
    <xsl:if test='string-length($size) &gt; 0'>
    <xsl:if test="number($size) &gt; 0">
     -
    <xsl:choose>
      <xsl:when test="round($size div 1024) &lt; 1">
           <xsl:value-of select="$size" /> Bytes
        </xsl:when>
        <xsl:when test="round($size div (1024 *1024)) &lt; 1">
            <xsl:value-of select="round($size div 1024)" />KB
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="round($size div (1024 * 1024))"/>MB
        </xsl:otherwise>
      </xsl:choose>
    </xsl:if>
  </xsl:if>
</xsl:template>

<!-- A generic template to display string with non 0 string length 
    (used for author and lastmodified time -->
<xsl:template name="DisplayString">
  <xsl:param name="str" />
    <xsl:if test='string-length($str) &gt; 0'>
      -
      <xsl:value-of select="$str" />
    </xsl:if>
</xsl:template>

There is more in the XSLT, but what I have described previously is enough to give you a basic understanding of how the transformation is organized, and a key message to take away is that all of this is, of course, very customizable. The template that matches with the Result element is a part in the XSLT you’ll probably focus on often.

The Advanced Search Box Web Part

The last Web Part we’ll discuss is the Advanced Search Box Web Part, which provides plenty of configuration options. First of all, instead of just one search text box, you have a few: the All Words, Exact Phrase, Any Words, and None Of These Words search boxes. For each of them, you can decide whether to display the Web Part to the user or not.

Next, there is the section for the scopes that is configured so that the "No Scopes" drop-down list is displayed by default. But, with a single click, you can display one for the user. When deciding to do that, you’ll also have to connect the scopes drop down to one of the existing display groups. Within the same section, there is also the language picker and the results type picker—again with the option to hide or show.

The property picker and the possible extension with custom additional managed properties have been discussed previously. In the Miscellaneous section, you’ll find the place to redirect the user to one of the custom search results pages you may have created.

Walkthrough: Customizing the Search Center

Let’s practice all of this with a walkthrough for three major goals: to show you how to add a custom tab in the Search Center, to demonstrate how to replace the search and the search results page with your own custom pages, and to show you a technique for replacing the XSLT that transforms the XML containing the search results with a custom one.

Assume that within the collaboration portal there is the need to deliver a customized search experience for the marketing department. The marketing people should be able to visit the Search Center, navigate to their own search page, and then enter a search query with the results containing only documents. The results page needs a different look and feel and some additional customizations. To follow the walkthrough, you’ll need a collaboration portal that is up-and-running.

Custom Tabs and Search Pages

Since there is the need for both a customized search and a search results page, let’s start by navigating to the Search Center of this collaboration portal. Using the Site Actions menu, open the Create Page page and select the (Welcome Page) Search Page page layout. Type SharePoint Training Search Page as the title and URL. Just publish the page for now. You’ll make some customizations later. Follow the same steps as before, but now create the SharePoint Training Search Results page based on the Search Results Page page layout. Again, just publish the page without any customizations.

You now have the two pages the users need for the customized search. Now, navigate back to the Search Center home page and use the Site Actions menu to switch to edit mode. The page is now ready to be customized.

Create a new tab by clicking the Add New Tab link. Remember that you are just creating a new list item with the details for the new tab. You use the New Item page where a tab name, such as Training, can be entered along with the name of the custom search page, SharePointTrainingSearch.aspx, which is associated with the new tab. Publish the changes you’ve made to the Search Center home page.

You can test your work by clicking on the Training tab. The SharePointTrainingSearch.aspx is now displayed. Your next step is to make sure that when users enter a search query and execute it, they’ll be directed to the custom Search Results page that was previously created. On the SharePointTrainingSearch.aspx, use the Site Actions menu to switch the page to edit mode. Now, you’ll configure a couple of the properties for the Search Box Web Part. Use the Modify Shared Web Part menu item to open the tool pane. Here, configure the option to show the scopes drop-down list. Expand the Query Text Box node and add the string -isDocument:0 as the value for the Additional Query Terms property. This string will be appended to every search query entered by the user and will apply an additional filter that forces only documents to be returned in the search results. The last configuration to finish the job is the specification of the URL to the custom Search Results page. Expand the Miscellaneous node and find the Target search results page URL property. Here you’ll enter the name of the custom page previously created: SharePointTrainingResults.aspx. This concludes the configuration, and you can save and publish everything.

If you have followed all of the steps up to now, you should be able to enter a query string. Click the Execute button and see the results for your custom Search Results page, as displayed in Figure 3-10.

Figure 3-10. The search results displayed in the custom search results page

Search results in custom search results page

Notice that the page displays both of the out-of-the-box tabs, but it does not display the Training tab you just created. What happened? The answer is very simple. The custom tab you created ended up in the SharePoint list that is not used by the pages that are based on the Search Results Page page layout. Those pages use a second list for the definition of the tabs. So, if you want to have the Training tab appear in the results pages, then you’ll have to add it to the Tabs in the Search Results list. There are a couple of ways to accomplish this: One way is to navigate to the Tabs in the Search Results list and just create a new item—no different from the steps you take to create items in other lists. You provide the same name and the same value for the search page as with the previously created list item for the first tab. Now, if you repeat the steps to test out your work, you’ll see the Training tab in the Search Results page.

Displaying the Search Results with Custom XSLT

Let’s focus our attention now on customizing the Search Results page. We’ll change some properties for the Search Core Results Web Part, and let’s say your company also needs to replace part of the XSLT with some custom rendering, such as grouping the results by the author of the document. It is possible to create the required XSLT from scratch in Notepad or another text editor if you are really into the XSLT language, but a better option might be to use a professional XSLT editor from a third-party provider. There are numerous products that can help you with this. For this book, I’ll stick with the Microsoft products, and I’ll show you how you can leverage the DataView Web Part and Microsoft Office SharePoint Designer 2007 to accomplish what your company needs for the search results customization.

The first thing you’ll need is the XML that is returned by the search engine. As previously discussed, a quick way to get these results is to replace the XSLT of the Search Core Results Web Part with the following.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xmp><xsl:copy-of select="*"/></xmp>
</xsl:template>
</xsl:stylesheet>

Switch the SharePointTrainingResults.aspx to edit mode, and choose Modify Shared Web Part from the Search Core Results Web Part menu to find the XSL Editor button in the tool pane. In the dialog box, replace the XSLT with the snippet that returns the raw XML. Apply all of your changes, and then test a search query. The Web Part now shows the XML with all of the search results. Isolate all of this XML in a file.

Next, open Microsoft Office SharePoint Designer 2007 and open one of your SharePoint sites. It really doesn’t matter which one—a SharePoint team site with a blank home page, for example. Create a new ASPX page and proceed with the steps for creating a data source that makes the XML for the search results available. Use the Data View menu and click on Manage Data Sources. The Data Source Library tool pane is opened as a result, and now you can expand the XML Files node, adding your XML file using the link available. Confirm that you want to include the XML file as a file in your SharePoint site, though it’s not important. Figure 3-11 shows what you should have in the tool pane at this point.

Figure 3-11. The XML containing the search results defined as data source in Microsoft Office SharePoint Designer 2007 and ready as input for the DataView Web Part

XML containing data source search results

Everything is in place now for creating the Data View Web Part. In the tool pane, use the context menu of the new data source and select the option to show the data. A new tab in the tool pane is opened with the list of nodes that are part of the XML data source. No need here to select all of the nodes. The users only have to see the file type image, the title, the description, and the size of the document. Therefore, select the following nodes in the list: title, size, write, and author. Drag and drop all of the selected nodes to one of the Web Part zones available on the page in the designer. The result will be a default rendering of the Data View Web Part as displayed in Figure 3-12.

Figure 3-12. The DataView Web Part showing the search results based on the selection of nodes from the XML data source using the default XSLT

DataView Web Part showing search results

There is some tuning to do now. If you move the cursor over the Data View Web Part, you’ll see a small button with an arrow giving you access to a pane displaying the configuration options. The first thing to do is click on the Edit Columns link. Then you have to rearrange the order of the columns. The first column is the title, followed by the author, write, and finally the size of the document.

Proceed again in the Common Data View Tasks dialog box by clicking on the Sort and Group link to define how you want the grouping to be done. The grouping is based on the author field. Therefore, move it to the Sort Order list box. Under the Group Properties section, select the option to show the group header and then click OK to save the configuration. Figure 3-13 shows the work process for the grouping.

Figure 3-13. Configuration of the grouping based on the author field

Configuration of grouping based on author field

In the designer, you should already see the results of your work. There is one last configuration you’ll have to do. The user should be able to click on the title of the search result and open the document with it. This is formatting you’ll have to do at the column level. Use the smart tag and select hyperlink as the format. A dialog box is shown where you can configure what the hyperlink needs to show as text and what the address is. For the text to be displayed, enter the value {title}, and for the address enter the value {url}.

There are a lot more things you can do for the configuration of the Data View Web Part, and I invite you to explore more options. But we must move on. If you switch to the page code, you’ll find the XSLT that is responsible for the view in the Web Part. It is contained as the body of the XSL element within the page. As a starter, you can simply drop this XSLT as the replacement of the default XSLT for the Search Core Results Web Part. Before you do this, copy the default XSLT to a new XML file in an editor such as Microsoft Visual Studio 2005. You’ll need it in a moment. After applying and publishing the page, the search results should be nicely grouped by author, as displayed in Figure 3-14.

Figure 3-14. The search results now displayed using the XSLT built up behind the Data View Web Part in Microsoft Office SharePoint Designer 2007

Search results grouped by author

This is a good start. Now, you need to finish it. The best thing to do is open your favorite XML editor again and load the generated XSLT in a new XML file. You should now have the two XSLT files in there: the custom one and the default XSLT of the Search Core Results Web Part. It is a good idea to copy all of the param elements from the latter and paste them at the top of your custom XSLT just before the root template element since you will need all of this for branching logic in the XSLT. Next, copy the templates named dvt_1.noKeyword and dvt_1.empty to the custom XSLT. In case there are no keyword or search results found, these templates will render the appropriate messages to the user.

The root template with the match attribute set to "/" has to be modified to make use of the copied templates. Again, you can just copy from the default XSLT and replace the body of root template. There is one change you’ll have to do: The call-template at the end must point to the dvt_1 instead of the dvt_1.body template. The full root template looks like this.

<xsl:template match="/">
    <xsl:if test="$AlertMeLink">
        <input type="hidden" name="P_Query" />
        <input type="hidden" name="P_LastNotificationTime" />
    </xsl:if>
    <xsl:choose>
        <xsl:when test="$IsNoKeyword = 'True'" >
            <xsl:call-template name="dvt_1.noKeyword" />
        </xsl:when>
        <xsl:when test="$ShowMessage = 'True'">
            <xsl:call-template name="dvt_1.empty" />
        </xsl:when>
        <xsl:otherwise>
            <xsl:call-template name="dvt_1"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

If there is a need for the action links to be displayed within the Search Core Results Web Part, you’ll have to copy and paste another block of XSLT. But, let’s ignore this and do one additional configuration. The size of the document has to be formatted appropriately. Copy the following template from the default XSLT to the custom XSLT.

<!-- The size attribute for each result is prepared here -->
<xsl:template name="DisplaySize">
    <xsl:param name="size" />
    <xsl:if test='string-length($size) &gt; 0'>
        <xsl:if test="number($size) &gt; 0">
            <xsl:choose>
                <xsl:when test="round($size div 1024) &lt; 1">
                    <xsl:value-of select="$size" /> Bytes
                </xsl:when>
                <xsl:when test="round($size div (1024 *1024)) &lt; 1">
                    <xsl:value-of select="round($size div 1024)" />KB
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="round($size div (1024 * 1024))"/>MB
                </xsl:otherwise>
            </xsl:choose>
        </xsl:if>
    </xsl:if>
</xsl:template>

The only thing to do now is to make sure that the size of the document is formatted using the algorithm defined in the body of the previous template. Look for the place in the custom XSLT where the size is rendered: the dvt_1.rowview template. At the bottom, you’ll find a <xsl:value-of … statement for inserting the value of the size field. Replace that one with the following.

<td class="ms-vb">
   <xsl:call-template name="DisplaySize">
      <xsl:with-param name="size" select="size" />
   </xsl:call-template>
</td>

There is a bit more work to do if you also want to include highlighting for the keywords in the title and the description. Again, this can be achieved by copying the corresponding XSLT snippets from the default XSLT into your custom XSLT and by calling the template at the places where the functionality is needed. For our walkthrough, however, what you’ve done is more than enough. Just save everything and publish it. Figure 3-15 displays the result for a typical query.

Figure 3-15. The search results displayed using a custom XSLT

Search results displayed using a custom XSLT

This concludes our walkthrough. Don’t forget that the technique for using the Data View Web Part to generate the custom XSLT is only one approach you can take—one that has to be used with care, but it can be a starter for those of you who are not familiar with XSLT language. You can also build your XSLT completely from scratch by using any of the professional XML/XSLT editors available in the market.

Next step: Chapter 3: Customizing and Extending Microsoft Office SharePoint 2007 Search (Part 2 of 2)

Additional Resources