So, thankyou Liam, I have been tagged, whatever that really means.
The task being to write 5 things about myself that others may not know.
- The most important one for me is that I am most definately a family man. I am married with 3 kids. Currently, the eldest is 8, then 3 and a little one at 1 (Who still insists on getting me and the wife up in the middle of the night :-( hence why it seems that I am permanently tired during the day and a bit grumpy). Immensly proud of my family and they are number 1.
- Been a computer games fan since I can remember (Well, late 70's anyway). I had an Grandstand Pong system which had some "different" games. As different as rearranging the position of your bat I suppose. This meant I could play Tennis, Squash, Target Shooting, Hockey against someone else. Then got an ATARI 2600 console. This was a real revolution at the time and discarded my Pong machine to a friend at school for a fiver. Then got a Texas Instruments TI 99-4A. This had some really cool games such as TI Invaders, Munchman,5 A-Side Soccer. I can remember spending many hours copying source code from some magazine only to find that I had mistyped something so then spending further hours tediously going over what I had done. Then some Sega consoles, Nintendo and of course Xbox and most recently Xbox 360 (My gamertag is "Baron Sparky") which I love, especially connected up wirelessly to my Media Centre.
- "Used" to play a lot of sports. Played a lot of cricket and football when I was young, then squash as I got older. Was in a good side for cricket but the football was not so successful. I was the captain of our local side and I think we won 1 game in 2 seasons :-( Also used to do a lot of Judo and a Korean martial art called Tang Soo Do
- I have been with Microsoft UK for nearly 9 years now. Joined from another American company called Digital Equipment Corporation where I used to part of the development team working on email backbones and gateways called MAILbus.
- Lifelong Liverpool FC supporter. I can still remember the heady days of the 70's where no side could touch them.
So, who to "tag" next. It has to be 5 of my friends and colleagues here at Microsoft UK who I know have blogs. (Thankfully, most of my friends are not technical apart from those I work with)
Graham Tyler
Martin Kearn
Mark Bower
Paul Holdaway
Jessica Gruber
I have enourmous respect for these guys! We all work for Microsoft in the UK and passionate about the products we work with.
The SharePoint Product team has recently launched their own blog at http://blogs.msdn.com/sharepoint/
This is a must read with articles initially from Kurt Delbene, Corporate VP of the Office Servers group, and Jeff Teper, General Manager of SharePoint Portal, Search and Content Management.
We are promised many more entries from the senior SharePoint development team in the near future.
A great add-on for SharePoint and it is free. Retention Server from 80-20 Software has just been formally released!
For organizations using Microsoft SharePoint that need to manage the retention lifecycle of electronic documents and email, 80-20 Retention Server is a free enterprise software solution that automates the retention process by applying information retention policies as part of everyday business processes.
80-20 Retention Server enables organizations to index, link, store, search, access and ultimately disposes of records stored in SharePoint over their complete lifecycle.
My previous post supplied a webpart to customise the Search Results.
Thanks to some feedback, we discovered that the search results for Documents stored in a Portal Area was not working correctly, so I have updated the ZIP with the new code that seems to work.
However, you may need to configure this one! The issue was found because the SPS index does not carry information, other than URLs, as to whether the result came from a WSS Site or a Portal Area. When the Document was in a Portal Area, the code would malform the URL for the Document Details page. So, the fix I have put in is to see whether the URL has "/sites/" after the Portal URL, i.e. http://portal/sites/Bridport. If it does not, then it assumes that the document is for a Portal Area. If it does, then it assumes it is a WSS Document.
Now, I realise that not everyone keeps the default "/sites/" container for their WSS sites so, this is configurable. If your WS Sites container was changed at install/configuration time, then update the new custom property for the webpart as shown below!
I was recently asked how to change the search results for SharePoint Portal Server 2003 so that when a user selected a document from the search results, instead of actually opening the document which is the current default behaviour of SPS, that the user should be directed to the documents' details page within the relevant WSS site. This is because typically some users actually want to view the documents details before deciding to open it. This also gives the user the ability to check out the document before opening it, etc.
So, the solution we came up with was to change the default search results webpart to look like below:

The code is pretty easy but got a bit messy with document libraries that has a folder structure within it. But it looks like it works!
As you can see, there is an option to "To open this document Click Here", instead the URL of the document which is normally displayed by default. On selecting this option, you are taken to the relevant WSS document details page as shown below: (This is also true if you select the document name)

For the fully built CAB and source files, and the Visual Studio Project directory, please go here.
- Extract the files to your SharePoint Portal Server, i.e. your C:\ root directory. (If you extract to another location, then you will have to modify the setup and removal batch files to point to the new location)
- Drill down through the /Deploy/Debug directory and there you will find the built CAB and batch files
- Run the SearchOverrideSetup.BAT file and run it or if you are familiar with the STSADM utility, just install the CAB
To see the enhanced results in your portal, follow the instructions from my previous thread http://blogs.msdn.com/nigelbridport/archive/2004/10/27/248439.aspx
But basically:
- http://<SPSserver>/search.aspx?mode=edit&PageView=Shared on the portal you want to effect
- Browse through your Virtual Server catalog looking for Search Results Document Detail web part
- Drag it to just below the default Search Results web part
- Close the default Search Results web part (otherwise you will get two sets of results and a script error)
- Go back to your Portals home page and do a search.
That's it!
I would be really interested to hear if this works for you and if you make any modifications to it.
I said previously that I would post the sample web parts that show the ICellProvider, ICellConsumer, IRowProvider, IRowConsumer, IFilterProvider and IFilterConsumer in action so here they are. Just click below for the appropriate Visual Studio Porject files.
Please note that these samples are written in VB.net
There is a bit of setup that you will have to go through that I will try to explain here.
- Firstly, install the CAB files from the CellConnections.ZIP and/or the RowConnections.ZIP
- If you only want to use the CellConnections sample, skip the rest of the setup instructions.
- If you also want to see the Row and Filter connections working:
- Download ConnectionResources.ZIP This is 5MB but is due to the .WMVs that are in there. (PLEASE NOTE: I have just copied the same smallish WMV with apprpriate names. So they are all the same. Just download the correct WMV from a site and replace this template - The file was just too big for me to be able to host otherwise)
- Create a folder, I created mine at C:\XBox.
- Drop the contents of XBox from ConnectionResources.ZIP into the new folder
- Create a new virtual server in IIS that points to that folder as the root. To get the parts working without having to tweak the code, I would put the Virtual Server on port 801, so you can refer the http://localhost:801 and get to the resource files. (Just a few images)
- Next, navigate to C:\Program Files\Common Files\Microsoft Shared\web server extensions\wpresources\RowConnections\1.0.0.0__38a6d1a988993080 because the web parts are strongly named and in the Global Assembly Cache, this folder should already be there.
- Drop the contents of 1.0.0.0_38a6d1a988993080 from the ConnectionResources.ZIP into that directory
With the CABS installed, you should be already to go!
CellConnections Sample
Go to the site you want to use and then select to add web parts. From the Virtual Server Gallery, arrange the web parts Demo21: Cell Provider, Demo22: Cell Consumer (Summary), Demo23: Cell Consumer (Fields) and Demo24: Cell Connections (Script) onto the page and arrange as below:
Wire them up and then when you click on the Cell Provider List and then CellReady, you will see the other parts change appropriately.
RowConnections Sample
Go to the site you want to use and then select to add web parts. From the Virtual Server Gallery, arrange the web parts Demo41: Filter Provider, Demo31: Row Provider, Demo32: Row Consumer (Summary), Demo33: Row Consumer (Video), Demo34: Row Consumer (Details) and Demo35: Row Consumer (Box Artwork) onto the page and arrange as below:
Wire them up and then when you click on the button on the Row Provider part, the others should respond appropriately.
For the fully built CAB and source files, the Visual Studio Project directory, please go here.
- Extract the files to your machine.
- Drill down through the "/Deploy/Debug" directory and there you will find the built CAB.
- Modify the BAT file and run it or if you are familiar with the STSADM utility, just install the CAB
To see the enhanced results in your portal, follow the instructions from my previous thread http://blogs.msdn.com/nigelbridport/archive/2004/10/27/248439.aspx
But basically:
- http://<SPSserver>/search.aspx?mode=edit&PageView=Shared on the portal you want to effect
- Browse through your Virtual Server catalog looking for Search Override web part
- Drag it to just below the default Search Results web part
- Close the default Search Results web part (otherwise you will get two sets of results
- Go back to your Portals home page and do a search.
That's it!
I would be really interested to hear if this works for you and if you make any modifications to it, what they are so I can get a sense of what you think is missing from the out-of-the-box results.
Recently, I presented at the UK Office Developers Conference at Heathrow. One of the subjects I spoke about was enhancing and extending the Microsoft Office SharePoint Portal Server 2003 Search and I did say that I would post some of that information, as well as the samples I used (including the web part connection samples), up on my blog.
So, this is the first of a few posts and is about explaining the format of the Search Results and the functions that you as a developer can use to override it to fix it up to look like YOU want.
The next post will be one of the custom Search Results web parts that I demonstrated that uses one of the override functions to add a new button (This will be the built version of a previous post here)
So...
The Search Results is split into a number of Rows and Columns and looks like:
You can see that each major functional area of the Search Results can be referred to uniquely.
Now, you can control what each element looks like by using the following functions:
| Name |
Description |
|
protected virtual string GenerateHtmlForItemIcon ( System.Data.DataRow objectDataRow, int iIndexOfItemInDataSet, int iIndexOfItemInGroup, string strElemIDPrefix) |
Generates the HTML that displays the icon for the specified DataRow object in the search result set.
- iIndexOfItemInDataSet Index of row in the result DataSet.
- iIndexOfItemInGroup Index of an item in a group when grouping is enabled. This index is used to expand and collapse groups of results.
- objectDataRow DataRow that represents a single item.
- strElemIDPrefix This element is used for HTML automation and is reserved for internal use only.
|
| protected virtual void GenerateHtmlForRowColumn( System.Data.DataRow oneDataRow, System.Text.StringBuilder strColumnHtml, int iColumn) |
Generates the HTML for the specified column of data for the specified DataRow object in the search result set.
- oneDataRow: Reference to the DataRow in the result set.
- strColumnHtml: StringBuilder that contains the HTML for the specified column. HTML is appended to this string.
- iColumn: Index of the column with HTML content to modify.
|
| protected virtual void GenerateHtmlOneRowForOneItem( System.Data.DataRow oneDataRow, class System.Text.StringBuilder sbRenderRowHtml, int rowID, string strStyleClass, int iIndexOfItemInDataSet, int iIndexOfItemInGroup) |
Generates the HTML for the specified column and row of data for the specified DataRow object in the search result set.
- oneDataRow: DataRow that represents the item in the result set.
- sbRenderRowHtml: StringBuilder that contains the HTML for the specified row.
- rowID: Index of the row of data to generate. 0 indicates that the first HTML table row.
- strStyleClass: Style from the stylesheet that applies to this row. This parameter is reserved for internal use.
- iIndexOfItemInDataSet: Index of this item in the result set to render. 0 represents the first item.
- iIndexOfItemInGroup: Index of this item in its group. This value is used to expand and collapse the result group in the browser.
|
| protected virtual string GenerateQueryString( string strKeyword, System.Collections.ArrayList rgScopeList, string strWhereAndPart, [out] string strSavedQuery) |
Generates the SQL Full-Text Search Syntax query that produces the current result set. • strKeyword: List of keywords specified for this query.
- rgScopeList :List of search scopes specified for this query.
- strWhereAndPart: WHERE clause for the query.
- strSavedQuery: Out parameter that receives the return value from this method.
| |
The next post will demonstrate how to use the GenerateHtmlOneRowForOneItem function in C#
Made some progress on SPUM2003 and I have now decided to release version X1.1 for you guys to look at.
Please be aware of the following:
Whats in over version X1.0
- Ability to add users to a WSS site
- Ability to delete users from a WSS site
- Ability to edit users on a WSS site
- Double-click on a node in the treeview will open the site in a new browser
- Some error handling gone in
- Should display some information on user alerts
- Render out all site roles, including custom ones
- Got some icons in the treeview to refelct what the site type is
Stuff that hasn't yet made it:
- SPS Object Model. Still working on giving the ability to perform user management on SPS Areas. (Version X1.2)
- Cross site groups are not yet showing up. Not absolutely sure why yet as I have dropped the code in but it never seems to be able to find them!
Stuff that is odd:
- If you delete a user from a site and then re-add that user, with different details such as user name, notes, etc, then the previous information is restored. Do not know why yet so the process to add a "new" user who has been on the site before is to add them account and give them a role and then select and edit them.
If you find any bugs with user management in WSS, then please leave information on how to reproduce as feedback to this post please! Also, any suggestions on how you would like to see the interface changed to make it more intuitive.
Download version X1.1
Dan has just managed to get a couple of new articles published on MSDN that are concerned with branding SharePoint Sites.
This is a must read for anyone who is serious about customising the look'n'feel of SharePoint.
http://msdn.microsoft.com/office/default.aspx?pull=/library/en-us/odc_SP2003_ta/html/Office_SharePointApplyingCorporateBrand.asp
Please go and check it out.
Just an update on this tool for people interested and also because I am off to New York for a few days :-)
I got laughed at by a good friend of mine due to its boring name so I had a think and now call the tool SPUM2003 Along the lines of SPIN/SPOUT for SharePoint. Well, that is my rationale for choosing the name.
I have done some more work and now the code operates on WSS site security by Adding, Deleting and Editing users and then displays their details, Alerts, Groups, Cross-Site groups. I have started to make the UI look a bit better and also in the process of enabling Site Group editing/management. Haven't got the SPS Area security bit working but it will shortly.
Not sure about other people, but I find it quite time consuming when trying to manage users inside of Windows SharePoint Services sites, especially when the sites in the hierarchy have their security inheritance broken. A number of customers end up breaking security inheritance at every opportunity and then hit this problem.
So, I am in the process of writing a SharePoint User Manager Windows Application in order to help out in this area!
Below is a screenshot of the utility to-date (Note: This is version X1.0 and as such has restricted functionality)

Note: The SharePoint Object model is not "remotable" so it has to be run on the SharePoint server itself.
In the Server textbox, you can control which portal is opened. i.e. If you leave the default, it will attempt to scan the default port (typically 80) and render out the sites from there. If you had a portal on a different port that you want to examine, just enter "<server name>:<port>". If I had a portal on port 100 on my development machine, I would enter "SPSNIGELBRI:100". Then select the "Go" button. Do not prefix with "HTTP://"
This should render out something like:

So, you can see all of the site collections on your portal. Now, just drill through them and you should see what users have what roles on each of the sites and also receive an indication if security for the selected site is inherited from the parent or not.

This utility is just really an exercise into the SPUser object and displaying what information is available for each user.
Please leave me any comments on the utility to date in terms of functionality that you think would be useful to see!
Currently, for the next version, the following is intended to be working:
- Edit selected user (able to change the name, email, site collection administrator, notes and roles)
- Add a new user to the selected site
- Delete a user from the selected site
- Manage the site groups and gross site groups
- View and manage the selected users configured Alerts
- Double click the site will open the site in IE
- Plus making the interface a bit nicer looking
Click HERE to download the X1.0 Version
or Download version X1.1
Please check back soon for an updated version!
Overview
This post tries to describe the process of writing a .NET server-side control for Microsoft Windows SharePoint Services
There are a number of ways to write add-in code for SharePoint Technologies, the most notable being web parts.
However, these do not always help in the customisation of a site. Sometimes, we need to be able to change the
SharePoint look'n'feel and web parts do not integrate seamlessly in an area we want to control.. If we consider that
SharePoint is an ASP.NET application, we are able to use other .NET techniques to deliver the look and customisation
we want.
One of those methods is via compiled controls. Most commonly used would be user controls but we can also write
server controls
Mini-Navigator
In this post, I will be describing how I wrote a server control that I call a Mini-Navigator. See below:

This is a server control written in VB.NET. It initially outputs the logged on user information, this is because when I am sometimes testing,
it is really easy for me to forget with IE session is representing which user. Then, it should give you the current site, the parent site to you
and the children site beneath you. So, it renders out 1 level above and 1 level below and displays the appropriate icon for the site.
Hovering on the site will display the sites' Title/Description information and clicking on the site will navigate the current window to it.
If you wanted to open the site in a new window, remember to hold the <Shift> key when clicking on the site.
In the above example, if I clicked on the site "bridport", you would get:

So that’s what the control looks like, so how can we put this into a SharePoint site?
Step 1 - Building the control
This is just using one of the default templates that comes with Visual Studio .NET 2003. The control that I will be building here
will be going into the Global Assembly Cache (GAC) on my SharePoint server so I need to strongly name it.
So, open Visual Studio .NET 2003, then "File/New/Project". In your language of choice, you should see a template called
"Windows Control Library". This is the template I will be using.
The control uses the WSS Object Model (OM), se we need to add a reference to "Windows SharePoint Services"

Note: This assumes you are developing on a machine that has WSS installed already. If
not, you will need to obtain the appropriate DLLs and add them.
You will need to set up your own appropriate namespace. My code looks like:
MiniNavigatorSC.VB
Imports System.ComponentModel
Imports System.Web.UI
Imports Microsoft.SharePoint
Imports Microsoft.SharePoint.Utilities
Imports Microsoft.SharePoint.WebControls
<DefaultProperty("Text"), ToolboxData("<{0}:MiniNavigator runat=server></{0}:MiniNavigator>")> Public Class MiniNavigator
Inherits System.Web.UI.WebControls.WebControl
Dim _text As String
<Bindable(True), Category("Appearance"), DefaultValue("")> Property [Text]() As String
Get
Return _text
End Get
Set(ByVal Value As String)
_text = Value
End Set
End Property
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Protected Overrides Sub Render(ByVal output As System.Web.UI.HtmlTextWriter)
Dim oSite As SPWeb = SPControl.GetContextWeb(Context)
Dim oSubWebs As SPWebCollection = oSite.Webs
Dim oSubWeb As SPWeb
Dim sOutput As String = ""
Dim sImg As String = ""
'Output the CurrentUser so that the user is aware of who they are logged in as.
sOutput = "<table><tr><td colspan=2 class='ms-tabselected' title='Logged in as:'>"
sOutput += oSite.CurrentUser.LoginName.ToString + "</TD></TR>"
'If at a site collection, the ParentWeb object will be nothing
If (oSite.IsRootWeb) Then
'Site Collection level. The parent maybe a portal
sOutput += "<tr><td valign='top'><img src='/_layouts/images/opx16.gif' alt=''></td><td width=90% class='ms-nav a' title='This is a SharePoint Portal site'" + oSite.PortalName + "><a href='" + oSite.PortalUrl.ToString + "'>" + oSite.PortalName.ToString + "</a></td></tr>"
Else
Dim sSiteName As String = ""
If (oSite.ParentWeb.Name.ToString.Length <> 0) Then
sSiteName = oSite.ParentWeb.Name.ToString
Else
sSiteName = Right(oSite.ParentWeb.Url.ToString, oSite.ParentWeb.Url.ToString.Length - oSite.ParentWeb.Url.ToString.LastIndexOf("/") - 1)
End If
sOutput += "<tr><td><img src='/_layouts/images/" + getGif(oSite.WebTemplate.ToString, oSite.Configuration) + ".gif' alt=''></td><td width=90% class='ms-nav a' title='Site Title: " + oSite.ParentWeb.Title.ToString + " / " + oSite.ParentWeb.Description.ToString + "'><a href='" + oSite.ParentWeb.Url.ToString + "'>" + SPEncode.HtmlDecode(sSiteName) + "</a></td></tr>"
End If
'The current site
sOutput += "<tr><td><img src='/_layouts/images/" + getGif(oSite.WebTemplate.ToString, oSite.Configuration) + ".gif' alt=''></td><td class='ms-selectednav' width=90% title='You are here'>" + oSite.Title.ToString + "</td></tr>"
'The child sites
For Each oSubWeb In oSubWebs
sOutput += "<tr><td valign='top'><img src='/_layouts/images/" + getGif(oSubWeb.WebTemplate.ToString, oSubWeb.Configuration) + ".gif' alt=''></td><td width=90% class='ms-nav a' title='Site Title: " + oSubWeb.Title.ToString + " / " + oSubWeb.Description.ToString + "'><a href='" + oSubWeb.Url.ToString + "'>" + oSubWeb.Name.ToString + "</a></td></tr>"
Next oSubWeb
sOutput += "</table>"
output.Write(sOutput)
End Sub
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Private Function getGif(ByVal sType As String, ByVal iConfig As Integer)
Select Case sType
Case "MPS"
Return "MTGICON"
Case Else
Select Case iConfig
Case 0
'Team Site
Return "STSICON"
Case 1
'Blank Site
Return "MSCNTVWL"
Case 2
'Document Workspace
Return "DOCICON"
Case Else
'Set as a team site
Return "STSICON"
End Select
End Select
End Function
End Class
AssemblyInfo.VB
Imports System
Imports System.Reflection
Imports System.Runtime.InteropServices
' General Information about an assembly is controlled through the following
' set of attributes. Change these attribute values to modify the information
' associated with an assembly.
' Review the values of the assembly attributes
<Assembly: AssemblyTitle("Mini-Navigator")>
<Assembly: AssemblyDescription("This is a server control that can be added to a SharePoint site to display the parent and children site information")>
<Assembly: AssemblyCompany("Bridport")>
<Assembly: AssemblyProduct("")>
<Assembly: AssemblyCopyright("")>
<Assembly: AssemblyTrademark("")>
<Assembly: CLSCompliant(True)>
'The following GUID is for the ID of the typelib if this project is exposed to COM
<Assembly: Guid("ED83C45C-4EBC-4a51-89AE-E95EB38C4AB1")>
' Version information for an assembly consists of the following four values:
'
'
Major Version
'
Minor Version
'
Build Number
'
Revision
'
' You can specify all the values or you can default the Build and Revision Numbers
' by using the '*' as shown below:
<Assembly: AssemblyVersion("1.0.0.0")>
<Assembly: AssemblyKeyFile("../../MiniNav.snk")>
Click here to download the project. The project contains the files previously mentioned plus the MiniNavigatorSCCS.cs which is the C# equivalent code for all you C#'ers out there!
Note: you will have to add your own KeyFile using SN -K called MiniNav.SNK and located in the
project directory at the same level as the .VB files
Now we have the control built and it is ready to deploy to the SharePoint Server
So, what does the code do?
Nothing hard or tricky really, just gets the site context from:
Dim oSite As SPWeb = SPControl.GetContextWeb(Context)
Then this context is used to get the Current User information from the WSS OM property
oSite.CurrentUser.LoginName.ToString
We then just work through the object model to get the parent and children site information.
To get the correct icons for the workspace, we use:
oSubWeb.WebTemplate and oSubWeb.Configuration
I do make an assumption here that you are using the default site definitions. If you have modified these,
you may need to change this piece of code
Step 2 - Deploy the control
As I may want to use the control on any site on my SharePoint server, over a number of portals,
I copy the built DLL, MiniNavigator.DLL, to the server GAC.
The control sits on a WSS default page, so we have to edit the sites' default.ASPX. There are a number of
ways to update this file using many editors such as FrontPage2003 but… I normally edit the page by
first mapping a web folder to the site

(REALLY IMPORTANT NOTE: Make sure you keep a copy of the original DEFAULT.ASPX file before
you edit it just in case!!!!)
I would then drag'n'drop the default.aspx file to my desktop and open it using NotePad and add the control
reference thus:

The line we are interested in is:
….
<%@ Register Tagprefix="NMB" Namespace="MiniNavigator" Assembly="MiniNavigator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7dd16f83bffd70b1" %>
….
With the reference in, we now need to place the control at the appropriate location within the ASPX file. As, this
control is to be displayed on the navigation area of the site, I put it as shown below:

Again, the lines I added are:
…
<TR>
<TD style="padding-left:0px;padding-right:0px">
<img width=1px src='/_layouts/images/blank.gif' ID='100' alt='Icon' border=0>
<NMB:MiniNavServerControl id="NigelSC1" runat="server" />
</TD>
</TR>
…
NOTE: You may find it easier to use FrontPage2003 to actually do this manipulation as it is easier to locate
the actual HTML where you want to drop the control
Now, save the file and drag it back to the WSS web folder where you originally dragged the file from, overwriting the
original file. (This will create a custom version of the file and it is handled slightly differently by SharePoint from
default sites without customisation. These files are more commonly called "one-off" or "unghosted")
The final part of the deployment is to update the appropriate web.config file to allow the control to
render. So, open the web.config and add the reference for the control into the <SafeControls> section:

The line is
…
<SafeControl Assembly="MiniNavigator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7dd16f83bffd70b1"
Namespace="MiniNavigator" TypeName="*" Safe="True" />
…
Now do an IISRESET and view the site!
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!
Microsoft Office Portal Server 2003 and Microsoft Windows SharePoint Services use a number of different databases for different tasks.
These databases are:
-
<...>_PROF
-
<...>_SITE
-
This is the site database and contains all of the Portal and WSS site information
-
At least one of these for each Portal or WSS instance. To help load balance, more can be created through the WSS Administration user interface.
-
<...>_SERV
-
This is the Service database and contains the alert definitions anf notifications, gatherer logs
-
One of these for each Portal
-
<...>_Config_DB