Creating a Site Context Search Box that Uses SharePoint Portal Server Search Results

 

Anthony Petro
Microsoft Corporation

Updated: March 2006

Applies to:
   Microsoft Windows SharePoint Services
   Microsoft Office SharePoint Portal Server 2003
   Web Part infrastructure

Summary: Customize the Microsoft Windows SharePoint Services site-level Search box to return Microsoft SharePoint Portal Server Search (SharePointPSSearch) results. Reviewdifferent methods to modify the search capabilities of Windows SharePoint Services. (18 printed pages)

Download ContosoSharePointSearchBox.EXE.

Contents

Introduction
Customize the Windows SharePoint Services Site-level Search Box to Return SharePointPSSearch Results
Creating the SiteContextSearchBox Web Control
Using the Site Context Search Box Web Control
Prototyped Options that Failed or were Less than Optimal
Conclusion
Additional Resources

Introduction

When you create Microsoft Windows SharePoint Services sites, Meeting Workspace sites, and Document Workspace sites that are linked to Microsoft Office SharePoint Portal Server 2003 portal sites, you may want to use the search engine of the portal site to search all your sites. When you use Microsoft SharePoint Portal Server Search service (SharePointPSSearch), you get the added benefits of a consistent user interface and the ability to search multiple levels of the site hierarchy with one request.

The following table lists and describes the items available for search when you use either the search capability in Microsoft SQL Server (on which Windows SharePoint Services is based) or the search capability in SharePoint Portal Server 2003.

Table 1. Search features for Windows SharePoint Services and SharePoint Portal Server 2003

Searches For Searched Using SQL Server Full-Text Search? Searched Using SharePointPSSearch?
List items Yes Yes
Document Yes Yes
Lists Yes Yes
Boolean searches (AND, OR, Near, NOT) No Yes by using custom code (NOT is unsupported)
File types other than .doc, .xls, .ppt, .txt, and .htm Not by default. You can install customized SQL Server search filters to search other file types. Yes. For more information, see the Microsoft Office SharePoint Portal Server 2003 Administrator's Guide.
Subsite content on a top-level Web site No. You must go to the subsite to perform the search. Yes
Non-text list fields (such as currency, number, lookup, Yes/No) No Yes
Attachments to lists No Yes
File properties (such as Author and Company) used by Microsoft Office System 2003 documents No Yes
Survey lists No Yes
Hidden lists No (by design) No (by design)
Site administrators, site groups, users, or cross-site groups No No
External Web sites, file shares, documents No Yes
Narrowed search results accomplished by searching through previous results No No
Displaying the total number of items matching a search string No Yes. You must use a custom query.

This article shows you an effective searching tool that you can add to any existing deployment. You can also use the sample code in the download that accompanies the article (see link at beginning of this article) to learn useful techniques that you can apply to a wide variety of your Web Part development efforts, as follows:

  • Interaction with search services
  • Working with SharePoint Portal Server objects from a team site
  • Client-side ECMAScript (Microsoft JScript, JavaScript) code
  • Supporting themes

Customize the Windows SharePoint Services Site-level Search Box to Return SharePointPSSearch Results

When you develop with Windows SharePoint Services and SharePoint Portal Server 2003, you focus your efforts using Web Parts. The Web Part infrastructure provides a concise approach to developing custom applications that use the underlying object models in Microsoft SharePoint Products and Technologies. However, simply creating a Web Part that executes searches on the portal site within the context of the team site is not enough. On Windows SharePoint Services sites that were configured to use SQL Server full-text search, the user would be presented with a confusing interface, as shown in Figure 1. As you can see, a user might not know which Search box to use.

Figure 1. Custom Search box as Web Part

You can take a better approach by replacing the default Search box that is provided with Windows SharePoint Services (Microsoft.SharePoint.WebControls.ViewSearchForm) completely on every site-level home page.

The default Search box is located in the navigation region of each SharePoint site's home page (default.aspx). This region does not live in a HTMLForm tag and cannot render Web Parts. To replace the default Search box, you need to make the custom Search box a Microsoft ASP.NET server control, otherwise known as a Web control.

Creating the SiteContextSearchBox Web Control

This section shows you how to build the SiteContextSearchBox Web control that mimics the functionality of the portal-level Search box, while providing the capability you need to use at the site level.

You can combine the functionality and look-and-feel of the site-level Search box (Figure 2) and the portal-level Search box (Figure 3) to create your own custom site context Search box (Figure 4).

Figure 2. Standard team site Search box

Figure 3. Standard portal Search box

Figure 4. Custom site context Search box

When you try to mimic a Web site's functionality, start by viewing the source in the browser. The source provides you with the HTML and ECMAScript (JScript, JavaScript) code that is present on the page after that page is rendered on the client. By viewing the source on a SharePoint site home page, you can learn about the table structure and cascading style sheet classes that you need to support when you build your custom Search box.

Few graphic design tools exist for developing ASP.NET server controls and Web Parts, but you can develop ASP.NET server controls using the most common of Web design layout elements: tables. By creating a multicolumn table layout for your Web control, you can more easily influence how controls are laid out on the page and rendered in the client browser.

By examining the SharePoint site and portal site Search boxes, you can quickly determine the base structure of the five-column table you will need:

  • Two columns to hold spacers (at the left and right of the table).
  • One column for the Advanced Search button.
  • One column for the scope****drop-down list box and Enter keyword(s) text box.
  • One column for the Go, or Execute Search, button.

As you begin to build your table structure, you first override the CreateChildControls() method of the Microsoft.SharePoint.WebControls.SPControl class. This method becomes code when you execute EnsureChildControls() in the other overridden method, Render().

Use the following code to create the first column of your table, a spacer. The only interesting code in this cell is the use of the CssClass property. We use the same classes that the SharePoint site Search box used to ensure that the Web control appears properly on a page that has a theme applied. You use this same technique on all table cells.

// Create first column – a simple spacer
_cellOne = new TableCell();
_cellOne.CssClass = "ms-searchform";
_cellOne.Wrap = false;
_cellOne.Height = Unit.Pixel(32);
_cellOne.Attributes.Add("style", "padding-left: 4px;");
Controls.Add(_cellOne);

The second column will contain the Advanced Search button. Clicking this button takes us to the Advanced Search page with our search term pre-filled.

Note   The href property setting is deferred at this time because the code for this property calls client-side ECMAScript (Jscript, JavaScript) code that needs to know the ClientID of the calling control. ClientIDs are not generated until a control is added to the Controls collection. Therefore, you set this property after you add all controls.

// Create second column - Advanced Search button
_cellTwo = new TableCell();
_cellTwo.CssClass = "ms-searchform";
_cellTwo.Wrap = false;
Controls.Add(_cellTwo);
// Advanced Search magnifying glass
HyperLink advanced = new HyperLink();
advanced.AccessKey = "S";
advanced.ToolTip = "Advanced Search";
advanced.Text = "Advanced Search";
// advanced.href --> added after all controls are in collection 
// to obtain proper ClientID
advanced.ImageUrl = "/_layouts/images/SPSSearch2.gif";
advanced.BorderWidth = 0;
advanced.Attributes.Add("style", "vertical-align: 2;cursor:hand;");
_cellTwo.Controls.Add(advanced);

The third column contains the scope drop-down list and the Enter keyword(s) text box. The scope list will contain two to three items depending on where your site is in the site hierarchy. The list is ordered from the broadest to the narrowest search:

  • All sources.   Performs a portal site search against the All sources content source.
  • Site collection.   Performs a portal site search against the top-level SharePoint site, or the site collection in Windows SharePoint Services terms, and all child team sites. This option is only present on a child site.
  • This site.   Performs a portal site search against the current SharePoint site and all subsites.

This column, like the one before, has several properties that need a ClientID. You defer setting those properties until the Controls collection is built. You also will find an interesting client-side script issue that you need to write code for. You will likely want the ENTER key to execute the search automatically if the user types a keyword in the text box and presses ENTER. Unfortunately, due to the different way the pages are rendered in SharePoint sites, Document Workspace sites, Meeting Workspace sites, and sites customized by Microsoft Office FrontPage, you need to determine if your text box control is in a <Form> tag and if not, emit one, as shown in the following code.

// Create third column - Scopes drop-down list box and 
// Enter keyword(s) text box
_cellThree = new TableCell();
_cellThree.CssClass = "ms-searchform";
_cellThree.Wrap = false;
_cellThree.Attributes.Add("style","padding-left: 4px;");
Controls.Add(_cellThree);
Control parent = this.Parent;
// Add Scopes drop-down list box
HtmlSelect scopes = new HtmlSelect();
scopes.ID = scopes.ClientID;
scopes.Attributes.Add("class", "ms-descriptiontext");
scopes.Attributes.Add("style", "vertical-align: 3");
ListItem item;
item = new ListItem("All Sources", "ALL");
scopes.Items.Add(item);
if(!_currentWeb.IsRootWeb)
{
   item = new ListItem("Site Collection", "ROOT");
   scopes.Items.Add(item);
}
item = new ListItem("This Site", "THIS");
item.Selected = true;
scopes.Items.Add(item);
// WORKAROUND NOTE: 
//     The Web Part infrastructure does not allow us to 
//     add a simple HtmlForm control like standard ASP.NET
//     does. The following two lines are the preferred 
//     method, but are unusable in a Web Part Page.
//          HtmlForm form = new HtmlForm();
//          form.ID = "frmSiteSearch";
//     Therefore, a literal control with the <Form> tag 
//     is rendered when appropriate
// Add Enter keyword(s) text box
HtmlInputText searchString = new HtmlInputText();
searchString.ID = searchString.ClientID;
searchString.Size = 25;
searchString.MaxLength = 300;
searchString.Attributes.Add("accesskey", "S");
searchString.Attributes.Add("class", "ms-searchbox ms-descriptiontext");
searchString.Attributes.Add("style", "width:170px;vertical-align: 3");
searchString.Attributes.Add("alt", "Search this Site via the Portal");
searchString.Attributes.Add("title", "Search this Site via the Portal");

// Crawl the control tree to see if we live in a form.
// If we don't have a form anywhere, we will create our own 
// to handle submit.
while ((parent != null) &&   !(parent is HtmlForm))
{
   parent = parent.Parent;
}
bool emitForm = (parent == null);

HtmlGenericControl form = new HtmlGenericControl("form");
if(emitForm)
{
   form.ID = "mcsCustomSiteSearch";
   form.Attributes.Add("method", "GET");
   // form.Attributes.Add("action",...) --> added after all controls
   // are in collection to obtain proper ClientID
   _cellThree.Controls.Add(form);
   form.Controls.Add(scopes);
   form.Controls.Add(searchString);
}
else
{
   _cellThree.Controls.Add(scopes);
   // searchString.Attributes.Add("onKeyDown",...) --> added after 
   // all controls are in collection to obtain proper ClientID
   _cellThree.Controls.Add(searchString);
}

The fourth column contains the Go button. This button uses the image hover techniques of the portal-level Search box to render a slightly different arrow image when the user pauses the mouse over the button. The RenderSPSFunctions() method handles creating the client-side ECMAScript (Jscript, JavaScript) code for changing our button image and ensuring it gets rendered to the page.

// Create fourth column - Go image button
_cellFour = new TableCell();
_cellFour.CssClass = "ms-searchform";
_cellFour.Wrap = false;
_cellFour.Attributes.Add("style","padding-left: 4px;");
Controls.Add(_cellFour);
// Add Hyperlink, then add image to hyperlink
HyperLink go = new HyperLink();
go.AccessKey = "S";
go.ToolTip = "Execute Search";
go.Text = "Execute Search";
// go.href --> added after all controls are in collection 
// to obtain proper ClientID
_cellFour.Controls.Add(go);
// Add the image control
Image imageGo = new Image();
imageGo.AlternateText = "Execute Search";
imageGo.ImageUrl = "/_layouts/images/icongo01.gif";
imageGo.BorderWidth = 0;
imageGo.Attributes.Add("ONMOUSEOVER", "changeImages(this, '\\/_layouts\\/images\\/icongo02.gif'); 
      return true;");
imageGo.Attributes.Add("ONMOUSEOUT", "changeImages(this, '\\/_layouts\\/images\\/icongo01.gif'); 
      return true;");
imageGo.Attributes.Add("ONMOUSEDOWN", "changeImages(this, '\\/_layouts\\/images\\/icongo03.gif'); 
      return true;");
imageGo.Attributes.Add("ONMOUSEUP", "changeImages(this, '\\/_layouts\\/images\\/icongo02.gif'); 
      return true;");
imageGo.Attributes.Add("style", "vertical-align: 2;cursor:hand;");
go.Controls.Add(imageGo);
// Add a spacer image
Image imageBlank = new Image();
imageBlank.ImageUrl = "/_layouts/images/blank.gif";
imageBlank.Width = Unit.Pixel(4);
imageBlank.AlternateText = "";
_cellFour.Controls.Add(imageBlank);

protected void RenderSPSFunctions()
{
   // Image hover function and image preload (directly from ViewSource 
   // of search.aspx)
   StringBuilder imageHover = new StringBuilder();

   imageHover.Append("<SCRIPT language=JavaScript>\n");
   imageHover.Append("<!--\n");
   imageHover.Append("function newImage(arg) {\n");
   imageHover.Append("   var   rslt = new Image();\n");
   imageHover.Append("   rslt.src = arg;\n");
   imageHover.Append("   return rslt;\n");
   imageHover.Append("}\n");
   imageHover.Append("function changeImages(a, b)   {\n");
   imageHover.Append("   a.src =   b;\n");
   imageHover.Append("}\n");
   imageHover.Append("newImage(\"\\/_layouts\\/images\\/icongo02.gif\");\n");
   imageHover.Append("newImage(\"\\/_layouts\\/images\\/icongo03.gif\");\n");
   imageHover.Append("// -->\n");
   imageHover.Append("</SCRIPT>");

   // Only register the script once
   if(!Page.IsClientScriptBlockRegistered("imageHover"))
      Page.RegisterClientScriptBlock("imageHover", imageHover.ToString());
}

The fifth and final column renders another simple spacer column identical to the first column.

The SPWeb object represents a SharePoint site. The member variable _currentWeb is set to the value of the SharePoint site that the Web control is hosted on using the following line of code found in the CreateChildControls() method.

_currentWeb = SPControl.GetContextWeb(Context);

**Note   **To access the SPWeb properties we need in our control, we must allow requests for the permission of type Microsoft.SharePoint.Security.SharePointPermission by using code access security. Deploying the control to the bin directory will require an adjustment to the trust element in the web.config file from WSS_Minimal to either WSS_Medium or WSS_Full.

For information about code access security, trust levels, and Web Parts, see the Code Access Security section in A Developer's Introduction to Web Parts.

The CreateChildControls() method ensures that a portal site is associated with the current SharePoint site before rendering any of our custom control. If the method cannot determine a portal association, it renders the original SharePoint site Search box (ViewSearchForm) instead.

if(_currentWeb.PortalMember)
{
// Render five-column table...
else
{
   // TODO: For full fidelity, the following properties should 
   // be implemented as properties on our search box
   //<SharePoint:ViewSearchForm ID="L_SearchView" 
   //Prompt="Search this site" Go="Go" Action="searchresults.aspx" 
   ///>
   viewSearchForm = new ViewSearchForm();
   viewSearchForm.ID = "L_SearchView";
   viewSearchForm.Prompt = "Search this site";
   viewSearchForm.Go = "Go";
   viewSearchForm.Action = "searchresults.aspx";
   Controls.Add(viewSearchForm);
}

Now that we have our table structure built, we need to generate our client-side ECMAScript (JScript, JavaScript) code to handle submitting the search criteria to the SharePointPSSearch engine. All the logic for creating our client-side script is contained in the RenderSearchOptions() method. This method uses the ServerRelativeUrl of either the current site or the site collection to create a where clause for our query. The GET parameters of the search.aspx page are used to send both the keywords and the where clause to that page on the associated portal site.

// Set the parameters for site context and SharePointPSSearch page
string allSourcesUrl="";
string siteContextWhere="";
string rootSiteContextWhere="";

// Construct the All Sources query
allSourcesUrl = _currentWeb.PortalUrl + "search.aspx?k="; 

//Where clause to construct:
//CONTAINS("DAV:href",'"[ServerRelativeUrl]"')AND("DAV:href"LIKE'%
//[ServerRelativeUrl]/%')
//We are combining CONTAINS and LIKE clauses for speed and accuracy.  
//The CONTAINS clause ignores "/" characters so there is no way
//to exclude sibling sites from the results.  
//The LIKE clause honors the trailing "/" but by itself would take 
//too long to execute.

// Construct the Site Context query
siteContextWhere =  "&w="
   // URL encode our query string 
   + SPEncode.UrlEncode("CONTAINS(\"DAV:href\",'\"" 
   // SQL encode our where clause
   + _currentWeb.ServerRelativeUrl.Replace("'","''") 
   + "\"')AND(\"DAV:href\"LIKE'%"
   //SQL encode our where clause
   + _currentWeb.ServerRelativeUrl.Replace("'","''") 
   + "/%')");

// Construct the Root Web query, if necessary
if(!_currentWeb.IsRootWeb)
{
   rootSiteContextWhere = "&w="
      // URL encode our query string
      + SPEncode.UrlEncode("CONTAINS(\"DAV:href\",'\"" 
      //SQL encode our where clause
      + _currentWeb.Site.RootWeb.ServerRelativeUrl.Replace("'","''") 
      + "\"')AND(\"DAV:href\"LIKE'%"
      //SQL encode our where clause
      + _currentWeb.Site.RootWeb.ServerRelativeUrl.Replace("'","''") 
      + "/%')");
}

The second part of the RenderSearchOptions() method handles emitting the client-side ECMAScript (JScript, JavaScript) on the page that reacts appropriately to the chosen scope value. An additional function is added to handle submitting the search query when the user presses the ENTER key from the Enter keyword(s) text box.

// Build the client-side search function
StringBuilder searchPortal = new StringBuilder();
searchPortal.Append("<SCRIPT language=JavaScript>\n");
searchPortal.Append("<!--\n");
// Determine the search keyword
searchPortal.Append("function SubmitPortalSearch(arg, scopesId, searchStringId) {\n");
searchPortal.Append("  var searchKeyword = document.getElementById(searchStringId).value;\n");
searchPortal.Append("  if(searchKeyword == '')\n");
searchPortal.Append("  {");
searchPortal.Append("    alert('Please enter one or more search words.');\n");
searchPortal.Append("       if(null != event) event.returnValue = false;\n");
searchPortal.Append("       return;\n");
searchPortal.Append("    }\n");
// Build base search string
searchPortal.Append("   var searchUrl = \"" + allSourcesUrl + "\" + escape(searchKeyword);\n");
// append advanced if clicked
searchPortal.Append(" if(arg=='advanced') searchUrl+='&pt=all';\n");
// append where clause if option selected from drop-down list
searchPortal.Append("  var dd = document.getElementById(scopesId);\n");
searchPortal.Append("  var scope = dd.options[dd.selectedIndex].value;\n");
searchPortal.Append("  switch(scope){\n");
searchPortal.Append("    case 'ALL':\n break;\n");
searchPortal.Append("    case 'THIS':\n searchUrl+=\"" + SPEncode.ScriptEncode(siteContextWhere) 
      + "\";\n break;\n");
searchPortal.Append("    case 'ROOT':\n searchUrl+=\"" + SPEncode.ScriptEncode(rootSiteContextWhere) 
      + "\";\n break;\n");
searchPortal.Append("    default:break;}\n");
searchPortal.Append("   window.top.window.location = searchUrl;\n");
searchPortal.Append(" if(null != event) event.returnValue = false;\n");
searchPortal.Append(" return;\n");
searchPortal.Append("}\n");
// handle ENTER key if in a page that already had a form
searchPortal.Append(" function CustomSearchKeyDown(event, scopesId, searchStringId){\n");
searchPortal.Append(" if (IsKeyDownSubmit(event)){\n");
searchPortal.Append("      SubmitPortalSearch('form',scopesId,searchStringId);\n");
searchPortal.Append("      return false;}\n");
searchPortal.Append("      return true;}\n");
searchPortal.Append("// -->\n");
searchPortal.Append("</SCRIPT>");

// Only register the script once
if(!Page.IsClientScriptBlockRegistered("searchPortal"))
   Page.RegisterClientScriptBlock("searchPortal", searchPortal.ToString());

Finally, you need to go back to the CreateChildControls() method and implement the hooks necessary to call the client-side script at the appropriate times.

// Now that all controls are added to the Controls collection, 
// they have the ClientID values we need for our client-side scripts
HyperLink tempAdvancedSearch = (HyperLink)_cellTwo.Controls[0];
tempAdvancedSearch.href = "javascript:SubmitPortalSearch('advanced', '" 
      + SPEncode.ScriptEncode(scopes.ClientID) + "', '" 
      + SPEncode.ScriptEncode(searchString.ClientID) + "');"; 
HyperLink tempGo = (HyperLink)_cellFour.Controls[0];
tempGo.href = "javascript:SubmitPortalSearch('go', '" 
      + SPEncode.ScriptEncode(scopes.ClientID) + "', '" 
      + SPEncode.ScriptEncode(searchString.ClientID) + "');"; 

// If we emit our own form, then simply hook the Submit function 
// to the form action because pressing ENTER in the text box
// causes the form to submit.
if(emitForm)
{
   HtmlGenericControl tempForm = (HtmlGenericControl)_cellThree.Controls[0];
   tempForm.Attributes.Add("action", "javascript:SubmitPortalSearch('form', '" 
      + SPEncode.ScriptEncode(scopes.ClientID) + "', '" 
      + SPEncode.ScriptEncode(searchString.ClientID) + "');"); 
}
// However, if we are on a page that already has a form, we need to hook
// Submit to the ENTER key keypress by intercepting the press event.
else
{   
   HtmlInputText tempText = (HtmlInputText)_cellThree.Controls[1];
   tempText.Attributes.Add("onKeyDown", "return CustomSearchKeyDown(event, '" 
      + SPEncode.ScriptEncode(scopes.ClientID) + "','" 
      + SPEncode.ScriptEncode(searchString.ClientID) + "');");
}

Using the Site Context Search Box Web Control

Now that we have our fully customized Search box, the SiteContextSearchBox Web control, we need to get it onto our SharePoint sites. You can accomplish this in at least three ways:

  1. Create a custom site definition that integrates the Search box. For more information, see Creating a Site Definition from an Existing Site Definition.

  2. Modify the default.aspx pages that have been unghosted or modified by using FrontPage, to contain the custom Search box.

  3. Wrap the custom Search box as a Web Part that can be inserted onto any Web Part Page by using the SharePoint UI.

    Warning   Modifications to default site definitions are not supported. For more information, see Supported and Unsupported Scenarios for Working with Custom Site Definitions and Custom Area Definitions in Windows SharePoint Services and in SharePoint Portal Server 2003.

Options 1 and 2 require the same changes to default.aspx. Navigate to the default.aspx page you want to modify, where C is the letter of the drive where Windows SharePoint Services was installed, and 1033 is the Locale ID (LCID) of the language pack installed. Make the following changes to the system path files using an editor such as Microsoft Visual Studio .NET or Notepad:

  • Custom site definition: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\60\TEMPLATE\1033\Custom Site Directory\default.aspx

  • FrontPage modified: Open the default.aspx page of the unghosted site in FrontPage.

    Note   You can also choose to modify a ghosted site using FrontPage, which makes the site unghosted following the change.

You must first register the new assembly on the page. Add this line to the top of the page under the <%@ Page. . . %> directives.

<%@ Register TagPrefix="Contoso" Namespace="Contoso.SharePoint.WebControls" 
      Assembly="Contoso.SharePoint, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b8ea74a908ff3588" %>

Now, you need to replace the existing ViewSearchForm control with your control. Find and replace the following tag. . .

<SharePoint:ViewSearchForm ID="L_SearchView" Prompt="Search this site"
Go="Go" Action="searchresults.aspx" />

With this tag:

<Contoso:SiteContextSearchBox  />

Save the default.aspx page and navigate to the changed site using the browser. You should now see the SiteContextSearchBox, shown in Figure 4, on the default.aspx home page.

Option 3 requires you to create another class in your assembly. This class inherits the Microsoft.SharePoint.WebPartPages.WebPart class and overrides the RenderWebPart() and CreateChildControls() methods. These methods simply create an instance of the SiteContextSearchBox Web control and render it by using the WebPart methods.

protected override void RenderWebPart(HtmlTextWriter output)
{
   EnsureChildControls();
   searchBox.RenderControl(output);
}

protected override void CreateChildControls()
{
   searchBox = new Contoso.SharePoint.WebControls.SiteContextSearchBox();
   Controls.Add(searchBox);
   base.CreateChildControls ();
}

For information about building and deploying your Web Part, see Installation Details in A Developer's Introduction to Web Parts.

Prototyped Options that Failed or were Less than Optimal

The following options were prototyped and proven to be either unworkable or less than optimal. We highlight them here to better illustrate the underlying architecture between SharePoint Portal Server and Windows SharePoint Services.

Inheriting Microsoft.SharePoint.Portal.WebControls.SearchBox

This option consisted of inheriting and extending the Microsoft.SharePoint.Portal.WebControls.SearchBox Web control. Unfortunately, the Microsoft.SharePoint.Portal.WebControls.SearchBox Web control cannot be used on any site that does not have a portal context. Although you can create a Windows SharePoint Services site, Meeting Workspace, or Document Workspace site by using the portal Site Directory, the underlying infrastructure simply maintains a link to that Windows SharePoint Services site. The site is created outside the portal context and as such cannot use the Microsoft.SharePoint.Portal.WebControls.SearchBox Web control.

Inheriting Microsoft.SharePoint.WebControls.ViewSearchForm

This option consisted of inheriting and extending the Microsoft.SharePoint.WebControls.ViewSearchForm Web control. This Web control renders a theme and Search box that is aware of FrontPage on Windows SharePoint Services sites, Meeting Workspace sites, and Document Workspace sites. To effectively use this control as a basis for our portal site Search box requires programmatic access to the composite controls collection. Unfortunately, this Web control does not provide the public interfaces needed to use it effectively as a base for your control.

This option consisted of modifying the display of the SearchResults.aspx page. SearchResults.aspx is the default destination page for all searches executed by the Microsoft.SharePoint.WebControls.ViewSearchForm Search box. This page has the logic needed to display the site-level, SQL Server full-text search results including list items, documents, paging, and searches within views. Additionally, for all site-level searches on sites that are linked to portal sites, this page displays a link that allows the user to execute the search on the portal site.

Figure 5. Standard team site search results

The link uses the GET parameters of the portal level Search.aspx page to execute the same search that was executed at the site level. Unfortunately, this link is not site-context aware; it always searches across all sources. You can modify this page to provide additional site-context aware links, as you did in your custom Search box. However, this change would require the users to always click twice to perform a search: once to perform the site-level search, and again to search the site using SharePointPSSearch. Because the links and ECMAScript (JScript or JavaScript) code emitted are nearly identical to those created by our custom Search box, this article does not cover them in detail.

Conclusion

As a Web Part developer, you have many different options for customizing the Search box functionality in Windows SharePoint Services. While some of these options may be easier to implement than others, only a custom Web control gives you the flexibility you need to provide a seamless experience to your users. With the SiteContextSearchBox, users can gain access to the power of SharePointPSSearch to effectively find information in their site hierarchy.

Additional Resources

Sample code on GotDotNet and an associated MSDN Webcast are available for an enhanced version of the site context Search box that adds a new Search box (HTC Menu and multiple portal configurations), and search results (wildcards, Boolean, and hit-highlighting). For more information, see the following: