Welcome to MSDN Blogs Sign in | Join | Help

Share Points

Development on the SharePoint Platform
Changing Microsoft Office SharePoint Portal Server 2003 Search Results
Overview

This article intends how to use the override functionality to deliver up your own rendered search results.

 

Typical Problem

I see lots of people asking questions on how to customise the Microsoft Office SharePoint Portal Server 2003 search results page with the typical question being “How can I edit the Search.ASPX file?

 

When trying to modify the Search Results rendered by SPS2003 there are some concepts and information that you should be aware of:

 

  • The Search Results page is actually made up of 2 web parts out of the box on a provisioned portal.
    • The first web part is the Search Constructor and is responsible for building the required query string for the user in order to be able to execute the query against the SPS index.
    • The second web part is called the Search Results web part and this is where the returned results from the index are rendered and displayed to the user. This web part also controls what grouping is available on the navigation bar.

 

To be able to view or manipulate these web parts, you need to enter the following command into your browser of choice (replacing <SPSserver> with your SPS server name and you must have the necessary permissions at the portal level)

 

http://<SPSserver>/search.aspx?mode=edit&PageView=Shared

 

This will expose the Modify Shared Page link at the top right of the page

Now by selecting “Design this page”, you will see the 2 default web parts that are deployed at portal provisioning time.

 

Displaying your own Search Results

To be able to change the way the search results are rendered to the user, you have to override the default Search Results web part.

 

Some sketchy information can be found at: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/spptsdk/html/cSearchResults.asp

 

This shows you the SearchClass that you can you programmatically override.

 

Sample

This sample is quite non-invasive into the Search Results. It just adds a new button as you can see below:

 

 

By selecting “Item parent” at 1, will display the parents’ site listing: (This is the only site collection in this instance of a portal)

 

By selecting “Item parent” at 2, will display the document library that the documents live in;

 

 

Show me the code

On lots of sites, I can normally find descriptive information on what I need to do but I always think that the best way to demonstrate a concept or approach is through the use of some sample code that you can take and modify for yourself.

 

Below is some code I wrote in C# that shows how to override the default search results and add a new button at the Actions level for a search result hit. The button enables the user to go to the parent level for particular items such as Documents (will go to the Documents Document Library), List Items (will go to the List Items List) and Sites (will go to the Sites parent Site)

 

using System;

using System.ComponentModel;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Xml.Serialization;

using Microsoft.SharePoint;

using Microsoft.SharePoint.Utilities;

using Microsoft.SharePoint.WebPartPages;

using System.Data;

using System.Text;

 

namespace SearchResultExtension

{

  // <summary>

  // This webpart has been developed to enable users to be able to navigate

  // easier to a particular items parent container.

  // Items that are supported by this functionality are:

  // . Documents within SharePoint

  // . Lists within SharePoint

  // . SharePoint Sites

  // . Files trawled from NTFS (file:// prefix'd)

  // </summary>

  [DefaultProperty("Text"),

    ToolboxData("<{0}:Override runat=server></{0}:Override>"),

    XmlRoot(Namespace="SearchResultExtension")]

 

  public class Override : Microsoft.SharePoint.Portal.WebControls.SearchResults

  {

    // Use the following constants to manage what pages are rendered for which result type

    const string c_DSP_SiteCollection = "/SiteDirectory/Lists/Sites/AllItems.aspx";

    const string c_DSP_SubWeb = "/_layouts/1033/mngsubwebs.aspx?view=sites";

    const string c_DSP_Lists = "/_layouts/1033/viewlsts.aspx";

    //

 

    // The following constant defines the default button name

    const string c_ButtonDefault = "Item parent";

    //

 

    private string _button;

 

    public Override()

    {

      _button = c_ButtonDefault; //Initialise private variables

    }

 

    [Category("Custom Properties")] //Create a custom category on the property sheet.

    [DefaultValue(c_ButtonDefault)] // Assign the default value.

    [WebPartStorage(Storage.Personal)] // Property is available in both Personalisation and Customisation mode.

    [FriendlyNameAttribute("Items' parent button display name.")] // The caption that appears in the property sheet

    [Description("Type the parent button name.")] // The tool tip that appears when pausing the mouse pointer

    [Browsable(true)] // Display the property in the property pane.

    [XmlElement(ElementName="Button")] // The accessor for this property.

 

    public string Button

    {

      get

      {

        return _button;

      }

      set

      {

        _button = value;

      }

    }

 

    //

    //********************************************************************************

    //Description:

    // This webpart is built to override the standard default out of the box Search

    // Results webpart functionality. The part adds a new option on the search

    // results page to be able to navigate to specific items parent folder.

    //

    //Author:

    //

    //

    //Date:

    // ??th October 2004

    //

    //Version:

    // V1.0.0.0

    //

    //Modifications:

    // Who When Why

    // NB **/**/** Initial webpart creation.

    //

    //********************************************************************************

    //

    protected override void GenerateHtmlOneRowForOneItem(

      System.Data.DataRow oneDataRow,

      System.Text.StringBuilder sbRenderRowHtml,

      int rowID,

      string strStyleClass,

      int iIndexOfItemInDataSet,

      int iIndexOfItemInGroup

    )

    {

      base.GenerateHtmlOneRowForOneItem(oneDataRow,

      sbRenderRowHtml,

      rowID,

      strStyleClass,

      iIndexOfItemInDataSet,

      iIndexOfItemInGroup);

 

      if (rowID == 3) //This is the row where the actions are!

      {

        string sHref = GetRowValue(oneDataRow, "DAV:href"); //Get the URL for the item

        string sFCC = GetRowValue(oneDataRow, "DAV:contentclass"); //See what sort of item it is

 

        if (sFCC.StartsWith("STS_")) //It is a SharePoint internal item

        {

          sHref = sHref.Substring(0, sHref.LastIndexOf("/")); //Format the URL to remove the filename

 

          switch (sFCC) //We need to do different things to different items to render the parent

          {

            case "STS_Site": //Site Collection

              sHref = sHref.Substring(0, sHref.LastIndexOf("/")) + c_DSP_SiteCollection;

              break;

 

            case "STS_ListItem_300": //Site collection but to be treated differently

              sHref = sHref.Substring(0, sHref.LastIndexOf("/")) + c_DSP_SiteCollection;

              break;

 

            case "STS_Web": //WSS Subweb

              sHref = sHref + c_DSP_SubWeb;

              break;

 

            case "STS_ListItem_DocumentLibrary": //List item within a Document Library

              break;

 

            case "STS_List_DocumentLibrary": //WSS Document Library

              string sListDisp = GetRowValue(oneDataRow, "DAV:displayname"); //Get the displayname

              sHref = sHref.Substring(0, sHref.LastIndexOf("/" + sListDisp + "/")) + c_DSP_Lists;

              break;

 

            case "STS_List_Announcements": //WSS List

              sHref = sHref.Substring(0, sHref.LastIndexOf("/Lists/")) + c_DSP_Lists; //Need to cut off /Lists also

              break;

 

            case "STS_Document": //A SharePoint specific file. Do not touch

              return;

 

            default: //Anything else, ignore

              return;   

          }

 

          if (sHref.Length > 7) //Bigger than "https://" i.e. Make sure we have something to replace with

          {

            string sSrcResPage = sbRenderRowHtml.ToString(); //HTML generated so far

            string sAnchor = " | <A href=\"" + SPEncode.UrlEncodeAsUrl(sHref) + "\">" + this._button + "</A>"; //Create new string

            int iInsert = sSrcResPage.LastIndexOf("</A>"); //Last occurrence of the string

 

            if (iInsert > 0) //if we've found the search string at all...

            {   

              //insert the new button into the HTML at the right place

              sbRenderRowHtml.Replace(sSrcResPage, sSrcResPage.Insert(iInsert + 4, sAnchor));

            }

          }

        }

        else if (sHref.StartsWith("file://")) //on an external file

        {

          sHref = sHref.Substring(0, sHref.LastIndexOf("/")); //Format the URL to remove the filename

 

          string sSrcResPage = sbRenderRowHtml.ToString(); //HTML generated so far

          string sAnchor = " | <A href=\"" + SPEncode.UrlEncodeAsUrl(sHref) + "\">" + this._button + "</A>"; //Create new string

          int iInsert = sSrcResPage.LastIndexOf("</A>"); //Last occurrence of the string

 

          if (iInsert > 0) //if we've found the search string at all...

          {   

            //insert the new button into the HTML at the right place

            sbRenderRowHtml.Replace(sSrcResPage, sSrcResPage.Insert(iInsert + 4, sAnchor));

          }

        }

      }

    }

 

    private string GetRowValue(DataRow dbRow, string strUri)

    {

      DataColumnCollection rowColums = dbRow.Table.Columns;

 

      if (rowColums.Contains(strUri)) return dbRow[strUri].ToString();

      else return "";

    }

  }

}

 

 

 

How the code works

The class is set to Override and class inherits from

 

Microsoft.SharePoint.Portal.WebControls.SearchResults.

 

Beneath the class definition, a number of constants are exposed. These constants can be used to direct what pages are displayed for particular items. For example, a WSS List can be returned in the search results and the parent container for this item is really just the site where it resides. However, SharePoint has a number of pages that render just the lists of the sites that presents a much more intuitive container.

 

const string c_DSP_SiteCollection = "/SiteDirectory/Lists/Sites/AllItems.aspx";

This is used when a site collection item is returned by the search. It directs the browser to the portals SiteDirectory listing view

 

const string c_DSP_SubWeb = "/_layouts/1033/mngsubwebs.aspx?view=sites";

This is used when a sub web is returned in the search results. Note that this site is not at the site collection level. The view that is used is for the parent site of the subweb and the subweb collection for the parent site is displayed.

 

const string c_DSP_Lists = "/_layouts/1033/viewlsts.aspx";

This is used when a list is found in the search results and points the browser to the list view for the parent site.

 

Then, the code looks to set up the custom property for the button name. The default string is maintained by the logical const string c_ButtonDefault = "Items parent";.

 

The custom button property is then set up by the following code snippet. This section controls the page where the control is displayed (Category property) and the button description, FriendlyNameAttribute:

private string _button;

public Override()

{

  _button = c_ButtonDefault;

}

 

[Category("Custom Properties")]

[DefaultValue(c_ButtonDefault)]

[WebPartStorage(Storage.Personal)]

[FriendlyNameAttribute("Items' parent button display name.")]

[Description("Type the parent button name.")]

[Browsable(true)]

[XmlElement(ElementName="Button")]

 

public string Button

{

  get

  {

    return _button;

  }

  set

  {

    _button = value;

  }

}

 

The code then defines the override function that is the main driver of the web part:

protected override void GenerateHtmlOneRowForOneItem(

  System.Data.DataRow oneDataRow,

  System.Text.StringBuilder sbRenderRowHtml,

  int rowID,

  string strStyleClass,

  int iIndexOfItemInDataSet,

  int iIndexOfItemInGroup

  )

 

This is the routine that is called by default by the SharePoint process on a result list of search items so that they can be rendered by the Search Results web part. The data row, oneDataRow contains the data that needs to be formatted.

 

The search page is built up from the hits that are returned, to a maximum of 40 items per page by default, and this data is organised by oneDataRow. For each of the hits, the GenerateHtmlOneRowForOneItem is called. When this is called, the string builder, sbRenderRowHtml, is appended to with the current row information from oneDataRow. So, we allow the base call to execute that would add the standard buttons such as Add to My Links | Alert Me | Item details. We then perform some tests to see if we are working on a known object type and should therefore be adding the Item Parent button.

The first test is just to see if we are actually on the correct row for the buttons. This is known as row 3 and contained within the integer rowID. If the row is anything but 3, we exit from the code and allow SharePoint to continue as normal.

If the rowID is 3, then we pull the items’ HRef property from oneDataRow. The HRef property is passed in and contains the full URL to the item that is currently being rendered. We must remember that all items known to SharePoint are URL addressable.

We extract the property from the data row by passing it, along with the property we are looking for, DAV:href, to another routine called GetRowValue

The routine passes back the property asked for, DAV:href, and it is assigned to the string sHref.

We also then use GetRowValue but request the value for DAV:contentclass. The contentclass of the object tell us whether or not we should be handling the item at all as we only want to act on known object types.

 

To see if we may need to add the button, we perform a test on the first 4 characters of the content class. If the item begins with STS_ then it is a SharePoint object and we may need to update with our new button. If the contentclass does not begin with STS_ then we may still need to add our button as long as the sHref string begins with file://. If neither of these conditions is met, then the routine is exited and normal SharePoint functionality continues.

 

Next we truncate sHref from the last “\”. This is in effect dropping the filename or object name from the string so that we have a more representative URL for the parent object.

Now we perform a switch test on the contentclass and we are looking for:

  • STS_Site: and STS_ListItem_300: Both of these content classes relate to a site collection level site and will be prefixed by /sites by default. We need to remove this part of the URL to get to the portal URL and so we again truncate from the last “/”. We strip off the /sites term and then append the string held by c_DSP_SiteCollection. Note that /sites is the default string but this can be some other configured string.
  • STS_Web: This is a normal WSS subweb. As we already have the parent URL in sHref, we just need to append to constant c_DSP_SubWeb to modify the URL to point to the site list for the parent.
  • STS_ListItem_DocumentLibrary: No requirement to modify the URL here as it is full and already correctly formatted in sHref.
  • STS_List_DocumentLibrary: This is a WSS Document Library listing and we need to perform more tests to ensure that we get the correct URL set. A call is made to GetRowValue and we ask for the DAV:displayname of the list. This display name will be contained within the URL. We then format the sHref, truncating from the appearance of the display name and append c_DSP_Lists
  • STS_List_Announcements: This is the same operation as above but this needs to work on a different list type and is always noted by /Lists
  • STS_Document: This is a SharePoint internal file that is used to display the portal and it is not sensible or appropriate to do anything with it, so we would return out of the routine on this condition.

 

Then, a check if made on the length of the formatted URL in sHref just to ensure that it contains a valid address.

 

At this stage, we have a formatted string that we want to insert into the string builder that will be used for rendering the results to the user. This string is stored in sbRenderRowHtml. To be able to perform string manipulations on it, we coerce the string builder type into a string called sSrcResPage

 

Another string is created called sAnchor, and this will contain the formatted HTML codes for our new button. The string is built up using normal HTML code and the sHref that has been built up as the target of the anchor tag. UrlEncodeAsUrl is a standard routine available in the SharePoint OM to replace particular characters with their printable form. this._button uses the button name that we have configured for it.

A search is then performed on the string version of the page being built stored in sSrcResPage for the particular </A> string which denotes the end of the last button. This will return to us the last position in the string for the current item, of the last button. We can then add our newly formatted button stored in sAnchor after it.

 

Sample DWP file

You can use this sample to build a DWP for the web part. (This assumes a strongly named assembly so you would have to update with your own projects details.)

 

<?xml version="1.0"?>

  <WebPart xmlns="http://schemas.microsoft.com/WebPart/v2">

    <Description>This webpart displays the ability to goto particular items parent container</Description>

    <Assembly>SearchOverride, Version=1.0.0.0, Culture=neutral, PublicKeyToken=…………….</Assembly>

    <TypeName>SearchResultExtension.Override</TypeName>

    <Title>Search Results Override</Title>

    <FrameType>None</FrameType>

    <ResultListID xmlns="urn:schemas-microsoft-com:sharepoint:DataResultBase">sch</ResultListID>

    <MaxMatchingItemsNumber xmlns="urn:schemas-microsoft-com:sharepoint:DataResultBase">40</MaxMatchingItemsNumber>

    <Button xmlns="SearchResultExtension">Items parent</Button>

  </WebPart>

 

 

Happy overriding!

Posted: Wednesday, October 27, 2004 12:47 PM by NigelBridport

Comments

Patrick Tisseghem's Blog said:

# October 27, 2004 8:12 AM

Addy Santo said:

Excellent post!
# October 27, 2004 3:04 PM

Point2Share said:

# October 29, 2004 11:31 AM

Martin said:

Can you explain how you are able to display your new version of the search control (not the results, but the actual control itself) on the target page?

I'm trying to replace the SPSWC:PageHeader control that is placed by default on several pages, but not having any luck getting my Custom User Controls to display on the pages.

Here's what I've tried so far:

Created a new ASP.Net solution and added a user control to the page.

Added the (very simple) user control to the web page.

Deleted the Web.Config and Global.asa files.

Added a reference in the Assembly file to a SNK file.

Built the solution.

Placed the assembled dll into the GAC on the target server.

Added a reference to the user control in this assembly with the following code on the aspx page in sharepoint that I want to display it on:
<%@ Register Tagprefix="uc1" Namespace="colorpallete" Assembly="colorpallete.WebForm1, Version=1.0.1769.24014, Culture=neutral, PublicKeyToken=e9edb25f62ae3ac2" %>

Added a reference to the assembly in the Web.Config file on the Target server with the following code:
<compilation>
<assemblies>
<add assembly="colorpallete.WebForm1, Version=1.0.1769.24014, Culture=neutral,PublicKeyToken=e9edb25f62ae3ac2"/>
</assemblies>
</compilation>

Reset IIS

tried to load the page, but no go...

Any Ideas?
# November 4, 2004 9:59 PM

Nigel said:

Martin,

I haven't changed or modified the Search Control. It is still plain vanilla on the page as you would get it ootb.

I have just overriden the set of results that are returned to the Search Results web part.

You may find this blog entry interesting though: http://blogs.msdn.com/roberdan/archive/2004/07/12/180434.aspx

Nigel.
# November 5, 2004 8:26 AM

Martin said:

Thanks for the tip Nigel,
Out of curiousity - how would you appoach that issue? Is it even possible to add a custom user control to a sharepoint aspx?

Let me give you a scenario:
You've build a kick-ass custom page header that you'd like to include in all site (including the portal home site).

In the old ASP 3.0 days, (or even ASP.Net) I'd put the code into a SSI or user control and add it as a 'module' into the pages...

Is this possible in SharePoint, or am I barking up the wrong tree ?

- Martin
# November 5, 2004 6:59 PM

Nigel said:

Martin,

I think you would have more joy trying to manage the style sheets used by SPS/WSS. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnanchor/html/Sharepoint.asp

But, there is no reason why you should not enter your own controls into the ASPX pages as long as you are aware of the cost in terms of maintenance, i.e. SPs overwiting. Also, you may have a real job trying to modify all of the portal pages as there are a few!!!

I personally think that you would have a good chance of achieving what you want in WSS but I am not so confident that you are going to get total satisfaction in SPS!

Just my opinion though ;-)

Nige.
# November 5, 2004 9:01 PM

Martin said:

Thanks for the advice Nigel,
Do you have any idea what the process would be (any examples?) of how to add a custom control into the Static Web Part area (where the "SPSWC:PageHeader" and others reside) ?

I've had nothing but problems trying to add a simple "Hello World" Label control that is inside a User Control into this area of the page.

Any ideas would be greatly appreciated.
# November 8, 2004 4:22 PM

Nigel said:

Martin,

You would need to create the server-side control/webpart, install it on the SharePoint server and then add a reference to the control in the appropriate ASPX files where you want to control to be displayed!.

Is this what you have already done?

I would suggest keeping an eye on SharePoint MSDN content as I know that there is some branding/customisation documentation coming online shortly.

Or, if you like, we can take this conversation offline! Give me your email address in a feedback then I'll mail you. (I'll delete the feedback after so you won't be exposing your email address :-) )

Nige.
# November 11, 2004 9:22 AM

Nick said:

Good day Nige and Martin,

I am quite curious upon how you managed to resolved Martin's dillema. Can I please join your conversation? I'm having a similar problem right now.

I'm using WSS 2.0 and is currently into customizing Sharepoint issue lists. I always do those thingies on msfrontpage, changing colors, layouts, etc. This applies to newform.aspx, dispform.aspx, and editform.aspx.

Now, my limitation is into adding user controls on those aspx pages; like for example, i want to put a 'tabbed' layout of the fill-up form, to separate the fields for each progress. I hope you can teach me how to add my user control onto the page. I did what Martin said he has done, but sharepoint just erases my "<%@ Register Tagprefix" tag from the code.

Any suggestion would really be helpful.
Thanks in advance.
# November 16, 2004 9:18 AM

Nigel said:

Nick,

If you leave me you email address, I'll ping you directly or if you can wait a week, I'll be blogging this next..

Let me know either way. (I will not expose your email in the feedback)

Nigel.
# November 16, 2004 9:34 AM

Olaf Hubel's Blog said:

# February 13, 2005 8:42 AM

Share Points said:

For the fully build CAB and source files, the Visual Studio Project directory, please go here.

Extract...
# July 6, 2005 5:08 AM

Share Points said:

For the fully built CAB and source files, the Visual Studio Project directory, please go here.

Extract...
# July 6, 2005 5:08 AM

Share Points said:

For the fully built CAB and source files, the Visual Studio Project directory, please go here.

Extract...
# July 6, 2005 5:10 AM

Share Points said:

For the fully built CAB and source files, the Visual Studio Project directory, please go here.

Extract...
# July 6, 2005 5:12 AM

Share Points said:

For the fully built CAB and source files, the Visual Studio Project directory, please go here.

Extract...
# July 6, 2005 5:16 AM

Share Points said:

For the fully built CAB and source files, the Visual Studio Project directory, please go here.

Extract...
# July 6, 2005 10:07 AM

spare thought said:

It's not a recommended approach (which is one of the reasons it's not in the box) but you'd probably...
# February 27, 2006 12:24 PM
Anonymous comments are disabled
Page view tracker