There just never seems to be an end to the ways in which you can customize SharePoint 2007! In this chapter, you'll explore a few more. Specifically, you'll look at how to index a SQL Server database by using the Business Data Catalog (BDC) in MOSS, how to create a custom search page—also in MOSS, and last but not least, how to add your own commands to STSADM
(sure to win your SharePoint administrator's heart).
As with all of our recipes, you're sure to find plenty here to spark your imagination and set you on the path to creating your own unique variations. Let's dig in.
You're probably familiar with the STSADM
utility that can be used to perform a variety of SharePoint administrative functions from the Windows console. But you may not be aware that you can add your own commands to this utility with just a little bit of coding.
Given that you can perform almost every function that the STSADM
utility supports by using the SharePoint Central Administration Website, or through the standard Site Settings pages, why would you want to use STSADM
?
There are essentially two reasons. First, this utility may be more efficient for a system administrator who has limited understanding of SharePoint to master. So, if you want to provide your system administrator with quick and simple access to some basic custom utilities, customizing STSADM
might be just what you need. And because STSADM
provides a consistent interface across all commands, your customizations will be easier for the user to learn and remember.
Second, STSADM
commands can be easily scheduled by using the Windows Task Scheduler. Of course, you could simply write a console application, but customizing the STSADM
command enforces a consistency and simplicity that promotes not only ease of use but improved maintainability.
In this recipe, you'll add two custom commands to the STSADM
utility: one to add a new document library to a site, and the other to add an announcement to the top of a web site's default page. Through these two examples, you'll see how easy it is to extend the STSADM
command.
The required ISPStsadmCommand
interface throws an error when used in VB.NET because it does not recognize the signatures of the required functions GetHelpMessage()
and Run()
, so only the C# example is shown here.
Create an XML document called stsadmcommands.recipes.xml
in the C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions12Config
folder of your SharePoint server. See the following "Recipe—XML" section for the contents of this file.
The STSADM
customization XML file must take stsadmcommands.<some text>.xml
, where you supply the value for <some text>
. Microsoft recommends use of your organization's name and some indication of the purpose of the extensions, such as stsadmcommands.acme.corp.mycommands.xml
, but actual naming is up to you.
Create a new C# class library.
Add references to the Windows SharePoint Services, System.Web
and System.Xml
.NET assemblies.
Sign the assembly with a strong-name key file.
Add the code shown in the following "Recipe—C#" section.
Compile the class.
Deploy the compiled .dll
to the GAC.
Create the stsadmcommands.recipe.xml
file in the C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions12Config
folder. This file will be read by the STSADM
command at runtime to find our assembly containing the new commands.
Create a new C# .NET class library, referencing the Windows SharePoint Services, System.Web
, and System.Xml
.NET assemblies. Add using
statements as shown in the following source code and implement the ISPStsadmCommand
interface.
Add a function called GetHelpMessage()
per the ISPStsadmCommand
interface.
Add help text for each of the commands referenced in the stsadmcommands.recipe.xml
file.
Add a function called Run()
per the ISPStsadmCommand
interface.
Add calls to supporting methods for each of the commands referenced in the stsadmcommands.recipe.xml
file.
Write the support methods referenced in step 6.
The stsadmcommands.recipe.xml
file tells the STSADM
command the names of the custom commands we're adding, where to find the assemblies containing the executable code, and the names of the classes containing that executable code.
The class attribute of the <command>
element must be formatted correctly, or the STSADM
command will throw an error when you try to execute your custom commands. The format is class="<namespace>.<classname>, <assembly file name without .dll extension>, Version=<version – which will be 1.0.0.0 unless you change it>, Culture=neutral, PublicKeyToken=<strong key name assigned to the assembly>"
.
<?xml version="1.0" encoding="utf-8"?> <!-- Step 1: Add references to the commands to be added --> <commands> <command name="adddoclibtosite" class="CustomizeStsAdm.MyCustomizations, CustomizeSTSADM, Version=1.0.0.0, Culture=neutral, PublicKeyToken=61edc6796f256444" /> <command name="addnoticetosite" class="CustomizeStsAdm.MyCustomizations, CustomizeSTSADM, Version=1.0.0.0, Culture=neutral, PublicKeyToken=61edc6796f256444" /> </commands>
In the previous XML source, all text within the <command>
elements should be entered on a single line.
using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Text; using Microsoft.SharePoint; using Microsoft.SharePoint.StsAdmin; using System.Xml; using Microsoft.SharePoint.WebPartPages; using System.Web; using System.Web.UI.WebControls; namespace CustomizeStsAdm { // Step 2: Class must implement the ISPStsadmCommand // interface to allow it to be registered with // STSADM command public class MyCustomizations : ISPStsadmCommand { // Step 3: Implement the GetHelpMessage method // to provide a response when user runs the // new command with the -help flag public string GetHelpMessage(string command) { command = command.ToLower(); // Step 4: Provide as many cases as there are // commands supported by this class switch (command) { case "adddoclibtosite": return " -site <full url to a site collection " + "in SharePoint>" + " -web <web name to add doclib to" + " -libname <name to give new doclib>" + " -description <description for new " + "doclib>"; case "addnoticetosite": return " -site <full url to a site collection " + "in SharePoint>" + " -web <web name to add doclib to" + " -text <text of notice to add>"; default: throw new InvalidOperationException(); } }
// Step 5: Implement the Run method to // actually execute the commands defined in this // class public int Run(string command, StringDictionary keyValues, out string output) { command = command.ToLower(); // Step 6: Provide as many cases as there are // commands supported by this class switch (command) { case "adddoclibtosite": return addDoclibToSite(keyValues, out output); case "addnoticetosite": return addNoticeToSite(keyValues, out output); default: throw new InvalidOperationException(); } } // Step 7: Add the methods and code that will perfom // the operations for each command supported private int addNoticeToSite(StringDictionary keyValues, out string output) { // Get handle to target web site SPSite site = new SPSite(keyValues["site"]); // Get handle to web SPWeb web; if (keyValues["web"] == null) { web = site.RootWeb; } else { web = site.OpenWeb(keyValues["web"]); } // Get a handle to the default page of the target web // NOTE: we're assuming page called "default.aspx" exists web.AllowUnsafeUpdates = true; SPLimitedWebPartManager webParts = web.GetLimitedWebPartManager("default.aspx", System.Web.UI.WebControls.WebParts. PersonalizationScope.Shared); // Create XML element to hold notice text ContentEditorWebPart cewp = new ContentEditorWebPart(); XmlDocument xmlDoc = new XmlDocument(); XmlElement xmlElem = xmlDoc.CreateElement("xmlElem");
xmlElem.InnerXml = "<![CDATA[" + "<div align='center' " + " style='font-family: Arial; font-size: medium;" + " font-weight: bold; " + " background-color: red; color: white; border:" + " medium solid black; " + " width: 50%; padding: 10px; margin: 10px;'>" + keyValues["text"].ToString() + "</div>" + "]]>"; // Add the CEWP to the page // NOTE: we're assuming a zone called "Left" exists cewp.ChromeType = System.Web.UI.WebControls.WebParts.PartChromeType.None; cewp.Content = xmlElem; webParts.AddWebPart(cewp, "Left", 0); webParts.SaveChanges(cewp); web.Update(); web.Dispose(); site.Dispose(); output = ""; return 0; } private int addDoclibToSite(StringDictionary keyValues, out string output) { // Get handle to target web site SPSite site = new SPSite(keyValues["site"]); // Get handle to web SPWeb web; if (keyValues["web"] == null) { web = site.RootWeb; } else { web = site.OpenWeb(keyValues["web"]); } // Add a list to target site web.AllowUnsafeUpdates = true; web.Lists.Add(keyValues["listname"], keyValues["description"], SPListTemplateType.DocumentLibrary); SPList list = web.Lists[keyValues["libname"]]; list.OnQuickLaunch = true; list.Update(); web.Update();
web.Dispose(); site.Dispose(); output = ""; return 0; } } }
After the stsadmcommands.recipe.xml
file has been saved to C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions12Config
, and the class library has been compiled and deployed to the GAC, you're ready to test this recipe. To test the addnoticetosite
command, do the following:
Open a console window.
Navigate to the C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions12in
folder.
Type STSADM
to display a list of all commands. Figure 8-1 shows the STSADM
command output with the two new commands listed.
Enter the STSADM addnoticetosite
command with the following parameter values, as shown in Figure 8-2:
site = http://localhost
web = stsadm
text = "Now is the time for all good developers to come to the aid of their SharePoint administrator!"
Figure 8-3 shows the result of running the addnoticetosite
command.
You can test the adddoclibtosite
command in a similar fashion.
There are really as many opportunities to extend STSADM
as your needs dictate. The key limitation is that the STSADM
command must be run from the console so the user interface is very limited. Hence this technique is best suited to operations that take limited inputs and don't need to display much, if any, information back to the end user.
One of the really exciting enhancements to Office SharePoint Server is the inclusion of the BDC. The BDC enables you to define external data sources such as databases or web services to SharePoint, and make those sources available through web parts, list lookup fields, or Search.
This recipe focuses on one specific application of the BDC: configuring Search to index a SQL database. When a database is indexed in this way, your end users can search for database records in the same way as they would documents in document libraries or external web sites.
This recipe requires a version of Microsoft Office SharePoint Server (MOSS) that includes the Business Data Catalog feature. This recipe will not work with Windows SharePoint Services alone.
In this recipe, you will crawl a database named MyDatabase
, indexing a table named SharePointClasses
that contains a list of classes and associated information. You will need the following:
A database named MyDatabase
A table in MyDatabase
named SharePointClasses
A user in the MyDatabase
database mapped to the SharePoint administration account—in my example, MGEROW-MOSS-VPCAdministrator
SharePoint will attempt to crawl the database by logging on with the service account configured for all Search crawls. Whatever account you've configured in SharePoint for that purpose should have at least Select permissions for any tables you want to crawl.
An XML schema in BDC format defining the database connection and table we want to index
The BDC schema we'll create here is fairly limited and has just enough information to allow Search to index the database, but not enough to take full advantage of the BDC web parts that ship with MOSS. For an explanation of how to create a fully featured BDC schema, see the MOSS SDK, which you can download from www.microsoft.com/downloads/details.aspx?familyid=6D94E307-67D9-41AC-B2D6-0074D6286FA9&displaylang=en
.
First, create the data source:
Open the Microsoft SQL Server 2005 Management Studio (or Enterprise Manager if you're using SQL Server 2000) and create a new database named MyDatabase
.
Add a new table to the database named SharePointClasses
with the columns shown in Figure 8-4.
The SQL statement to create the preceding table is as follows:
CREATE TABLE [dbo].[SharePointClasses]( [Class] [varchar](50) COLLATE Latin1_General_CI_AI NOT NULL, [Namespace] [varchar](max) COLLATE Latin1_General_CI_AI NULL, [Comments] [text] COLLATE Latin1_General_CI_AI NULL, CONSTRAINT [PK_SharePointClasses] PRIMARY KEY CLUSTERED (
[Class] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
Save the table and then enter some sample data as shown in Figure 8-5.
Next, create an XML file that will describe the data source to SharePoint:
If you haven't done so already, install the SharePoint Server 2007 SDK and follow the instructions for installing the BDC definition editor.
Open the BDC definition editor and choose the option to Add LOB System (LOB stands for line-of-business).
Select the option to Connect to Database, as shown in Figure 8-6. Set the connection type to SqlServer, enter a valid connection string to your new database, and then click the Connect button.
Click the Add Table button to add the SharePointClasses
table to the design surface, and then click OK ( Figure 8-7).
Name the new schema MyDatabase
and click OK.
The BDC schema editor window should look similar to Figure 8-8.
Export the schema to an XML file by selecting the top-level node in the object outline (named MyDatabase
) and clicking the Export button ( Figure 8-9).
When prompted, name the schema file MyDatabase.xml
. (The XML file source is shown in the "Recipe—BDC Schema XML" section.)
Now import the schema into MOSS and define a Search content source by using the schema.
Open the SharePoint Central Administration Website (SCAW) and click the Shared Services Administration link as shown in Figure 8-10.
By default, SharePoint names the shared services web application SharedServices1
, but your installation may differ.
Click the Import Application Definition link under the Business Data Catalog heading ( Figure 8-11).
Enter the path to the XML schema file you saved earlier, select all the Resources to Import checkboxes, and then click the Import button ( Figure 8-12).
After the schema has been imported, return to the Shared Services Administration page and select Search Settings to define a new Search content source based on the BDC schema ( Figure 8-13).
On the Configure Search Settings page, click the Content Sources and Crawl Schedules link ( Figure 8-14).
Click the New Content Source button and name the new content source MyDatabase
. Select Business Data as the Content Source Type, select the Crawl Selected Applications radio button, and select the MyDatabase_Instance BDC schema checkbox. Then click OK ( Figure 8-15).
SharePoint will return you to the Manage Content Sources page. Mouse over the MyDatabase
content source and select Start Full Crawl from the context menu ( Figure 8-16).
You can verify that the database was crawled successfully by choosing the View Crawl Log option from the same context menu ( Figure 8-17).
The text of the SharePointClasses
table rows is now indexed and ready to search from within SharePoint.
The following is the output of the BDC definition editor, which you saved earlier. For the purposes of our recipe, you shouldn't need to edit the schema—it's provided here for your reference.
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <LobSystem xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://schemas.microsoft.com/office/2006/03/ BusinessDataCatalogBDCMetadata.xsd" Type="Database" Version="1.0.0.0" Name="MyDatabase" xmlns="http://schemas.microsoft.com/office/2006/03/ BusinessDataCatalog"> <LobSystemInstances> <LobSystemInstance Name="MyDatabase_Instance"> <Properties> <Property Name="rdbconnection Data Source" Type="System.String">MGEROW-MOSS-VPC</Property> <Property Name="rdbconnection Initial Catalog" Type="System.String">MyDatabase</Property> <Property Name="rdbconnection Integrated Security" Type="System.String">True</Property>
<Property Name="DatabaseAccessProvider" Type="Microsoft.Office.Server.ApplicationRegistry. SystemSpecific.Db.DbAccessProvider">SqlServer</Property> <Property Name="AuthenticationMode" Type="Microsoft.Office.Server.ApplicationRegistry. SystemSpecific.Db.DbAuthenticationMode"> PassThrough</Property> </Properties> </LobSystemInstance> </LobSystemInstances> <Entities> <Entity EstimatedInstanceCount="10000" Name="SharePointClasses"> <Identifiers> <Identifier TypeName="System.String" Name="Class" /> </Identifiers> <Methods> <Method Name="Find_SharePointClasses"> <Properties> <Property Name="RdbCommandType" Type="System.Data.CommandType, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">Text</Property> <Property Name="RdbCommandText" Type="System.String"> Select "Class","Namespace","Comments" from SharePointClasses where Class=@Class </Property> </Properties> <Parameters> <Parameter Direction="In" Name="@Class"> <TypeDescriptor TypeName="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" IdentifierName="Class" Name="Class" /> </Parameter> <Parameter Direction="Return" Name="@SharePointClasses"> <TypeDescriptor TypeName="System.Data.IDataReader, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" IsCollection="true" Name="Reader"> <TypeDescriptors> <TypeDescriptor TypeName="System.Data.IDataRecord, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" Name="Record">
<TypeDescriptors> <TypeDescriptor TypeName="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" IdentifierName="Class" Name="Class" /> <TypeDescriptor TypeName="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" Name="Namespace" /> <TypeDescriptor TypeName="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" Name="Comments" /> </TypeDescriptors> </TypeDescriptor> </TypeDescriptors> </TypeDescriptor> </Parameter> </Parameters> <MethodInstances> <MethodInstance Type="SpecificFinder" ReturnParameterName="@SharePointClasses" ReturnTypeDescriptorName="Reader" ReturnTypeDescriptorLevel="0" Name="Find_SharePointClasses_Instance" /> </MethodInstances> </Method> <Method Name="FindAll_SharePointClasses"> <Properties> <Property Name="RdbCommandType" Type="System.Data.CommandType, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">Text</Property> <Property Name="RdbCommandText" Type="System.String">Select "Class" from SharePointClasses </Property> </Properties> <Parameters> <Parameter Direction="Return" Name="@SharePointClasses">
The SpecificFinder
method instance tells SharePoint Search how to retrieve one row of the table in order to include its contents in the index.
<TypeDescriptor TypeName="System.Data.IDataReader, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" IsCollection="true" Name="Reader"> <TypeDescriptors> <TypeDescriptor TypeName="System.Data.IDataRecord, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" Name="Record"> <TypeDescriptors> <TypeDescriptor TypeName="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" IdentifierName="Class" Name="Class" /> </TypeDescriptors> </TypeDescriptor> </TypeDescriptors> </TypeDescriptor> </Parameter> </Parameters> <MethodInstances> <MethodInstance Type="IdEnumerator" ReturnParameterName="@SharePointClasses" ReturnTypeDescriptorName="Reader" ReturnTypeDescriptorLevel="0" Name="FindAll_SharePointClasses_Instance" /> </MethodInstances> </Method> </Methods> </Entity> </Entities> </LobSystem>
The IdEnumerator()
method instance tells SharePoint Search how to retrieve a list of primary keys for all rows in the source table.
You may search your new BDC content source in the same way you would any SharePoint content. To do so, navigate to your root MOSS page and enter a term that you'd expect to find in your database. In the example shown in Figure 8-18, I entered SPLimitedWebPartManager
.
The resulting page displays all "documents" that include that term ( Figure 8-19).
Note that the first entry is for a page named SharePointClasses.aspx
. SharePoint created this page to display data for a given row from our SharePointClasses
table. Clicking the link will display the default viewer page for the SharePointClasses
table ( Figure 8-20).
As you can see, enabling a search of a database table is fairly easy, although it does entail many steps. But after you've created a schema, creating additional schemas and installing them is pretty straightforward. In this example, we used a simple query from a single table, but you can just as easily query views, multitable Select
s, or stored procedures by using the same approach, simply by changing the RdbCommandText
property as shown here:
<Property Name="RdbCommandText" Type="System.String"> Select "Class","Namespace","Comments" from SharePointClasses where Class=@Class </Property>
We did not include an instance of a Finder
descriptor, but you can add one to allow your BDC schema to be used with the BDC web parts that ship with MOSS. The additional method descriptor for our example would look like the following:
<Method Name="Finder"> <Properties>
<Property Name="RdbCommandText" Type="System.String"> Select * from SharePointClasses where Class LIKE @Class</Property> <Property Name="RdbCommandType" Type="System.Data.CommandType, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">Text</Property> </Properties> <FilterDescriptors> <FilterDescriptor Type="Comparison" Name="Class"> <Properties> <Property Name="Comparator" Type="System.String">Equals</Property> </Properties> </FilterDescriptor> </FilterDescriptors> <Parameters> <Parameter Direction="In" Name="@Class"> <TypeDescriptor TypeName="System.String" IdentifierName="Class" AssociatedFilter="Class" Name="Class"> <DefaultValues> <DefaultValue MethodInstanceName="Finder" Type="System.String"></DefaultValue> </DefaultValues> </TypeDescriptor> </Parameter> <Parameter Direction="Return" Name="@SharePointClasses"> <TypeDescriptor TypeName="System.Data.IDataReader, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" IsCollection="true" Name="Reader"> <TypeDescriptors> <TypeDescriptor TypeName="System.Data.IDataRecord, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" Name="Record"> <TypeDescriptors> <TypeDescriptor TypeName="System.String" IdentifierName="Class" Name="Class" />
The <FilterDescriptor>
element enables you to define prompts that users can enter search strings into via the BDC web parts to query the database.
<TypeDescriptor TypeName="System.String" Name="Namespace" /> <TypeDescriptor TypeName="System.String" Name="Comments" /> </TypeDescriptors> </TypeDescriptor> </TypeDescriptors> </TypeDescriptor> </Parameter> </Parameters> <MethodInstances> <MethodInstance Type="Finder" ReturnParameterName="@SharePointClasses" ReturnTypeDescriptorName="Reader" ReturnTypeDescriptorLevel="0" Name="Finder" /> </MethodInstances> </Method>
Figure 8-21 shows an example of a BDC List web part for our schema.
As you've already seen, MOSS Search is a significantly richer product than was available in SharePoint Server 2003. In addition to the ability to index database content through the BDC, its performance and accuracy are much better. With all the potential of Search, it's not surprising that Microsoft has provided developers with access through the object model. By using this, it's possible to create a wide range of customized search solutions, such as the one we'll create here.
In out-of-the-box MOSS implementations, the search results are formatted by using an XSLT transform. This approach is incredibly flexible and requires no programming to perform many slick formatting operations. However, sometimes you'll want to get your hands on the result set to perform more-complex operations than those available via XSLT. In such a situation, it's helpful to load the results into a DataTable
object for further manipulation.
In this recipe, you'll create a custom ASP.NET web application to query the Search index and display the results. After the results are obtained, you'll perform some basic formatting and then display your results by using a GridView
control.
Most of the recipes in this book do not require MOSS and so use the Windows SharePoint Services .NET assembly. Because here we are performing operations that are available only in MOSS, we substitute that library for its MOSS equivalent.
This page should run by using the same application pool as MOSS; typically that is the SharePoint—80
pool ( Figure 8-22).
Create a new ASP.NET web application
Add references to the Windows SharePoint Services, Microsoft.Office.Server
, Microsoft.Office.Server.Search
, and Microsoft.Portal.Server
.NET assemblies.
Add using
or Imports
statements for the Microsoft.Office.Server
, Microsoft.Office.Server.Search
, Microsoft.Office.Server.Search.Query
, and Microsoft.Office.Server.Search.Administration
namespaces.
Add the following web controls to the page:
txtSearch
:A text box to hold the search string
cmdSearch
:A command button to execute the search
gridSearch
:A GridView
control to display the results
blMsg
:A Label
control to display any messages to the user
The completed web page will look similar to that shown in Figure 8-23.
Add the source code shown in the appropriate Recipe section.
If the user entered a search string, get a handle to the Search service on the current MOSS server by using the ServerContext.GetContext()
method; otherwise, display a message informing the user that a search string is required and exit.
Construct a new keyword query using the search text. In addition, set the time-out to 60 seconds, and the row limit to 1,000.
Execute the keyword query search, and use the DataTable.Load()
method to copy the resulting data into a DataTable
object for simplified processing.
Loop through returned rows, finding the <c0>
through <c3>
tags embedded in the HitHighlightedSummary
field.
Replace those tags with <span>
tags to bold and highlight search words in the summary text when output to the page.
Load the formatted DataTable
into a GridView
web control for display on the page.
Write the number of hits (that is, the number of rows in the DataTable
) to the bottom of the page and then display the results.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="CustomizingSearch.aspx.cs" Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Untitled Page</title> <link type="text/css" href="StyleSheet.css" /> </head> <body> <form id="form1" runat="server"> <div> <asp:TextBox ID="txtSearch" runat="server" Width="287px"></asp:TextBox> <asp:Button ID="cmdSearch" runat="server" OnClick="cmdSearch_Click" Text="Search" /><br /> <br /> <asp:GridView ID="gridSearch" runat="server" AutoGenerateColumns="False" CellPadding="4" Font-Size="Smaller" ForeColor="#333333" GridLines="None"> <FooterStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" /> <RowStyle BackColor="#F7F6F3" ForeColor="#333333" /> <Columns> <asp:HyperLinkField DataNavigateUrlFields="Path" DataTextField="Title" HeaderText="Title" Text="Title" />
<asp:BoundField DataField="Rank" HeaderText="Rank" /> <asp:BoundField /> <asp:BoundField DataField="Write" HeaderText="Edit Date" /> <asp:BoundField DataField="HitHighlightedSummary" HeaderText="Summary" HtmlEncode="False" HtmlEncodeFormatString="False" /> </Columns> <PagerStyle BackColor="#284775" ForeColor="White" HorizontalAlign="Center" /> <SelectedRowStyle BackColor="#E2DED6" Font-Bold="True" ForeColor="#333333" /> <HeaderStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" /> <EditRowStyle BackColor="#999999" /> <AlternatingRowStyle BackColor="White" ForeColor="#284775" /> </asp:GridView> <br /> <asp:Label ID="lblMsg" runat="server" Font-Bold="true"></asp:Label></div> </form> </body> </html>
Imports System Imports System.Data Imports System.Configuration Imports System.Web Imports System.Web.Security Imports System.Web.UI Imports System.Web.UI.WebControls Imports Microsoft.Office.Server Imports Microsoft.Office.Server.Search Imports Microsoft.Office.Server.Search.Query Imports Microsoft.Office.Server.Search.Administration Partial Public Class _Default Inherits System.Web.UI.Page Protected Sub cmdSearch_Click(ByVal sender As Object, ByVal e As EventArgs) Handles cmdSearch.Click If txtSearch.Text <> "" Then performSearch() Else lblMsg.Text = "Please enter a search string" End If End Sub
Private Sub performSearch() ' Step 1: Get a handle to the Shared Services Search context Dim context As ServerContext = ServerContext.GetContext("SharedServices1") ' Step 2: Construct a keyword search query Dim kwq As New KeywordQuery(context) kwq.ResultTypes = ResultType.RelevantResults kwq.EnableStemming = True kwq.TrimDuplicates = True kwq.QueryText = txtSearch.Text kwq.Timeout = 60000 kwq.RowLimit = 1000 kwq.KeywordInclusion = KeywordInclusion.AllKeywords ' Step 3: Get the results to a DataTable Dim results As ResultTableCollection = kwq.Execute() Dim resultTable As ResultTable = results(ResultType.RelevantResults) Dim dtResults As New DataTable() dtResults.Load(resultTable) ' Step 4: Format summary For Each drResult As DataRow In dtResults.Rows drResult("HitHighlightedSummary") = formatSummary(drResult("HitHighlightedSummary")) Next ' Step 6: Write out table of results gridSearch.DataSource = dtResults gridSearch.DataBind() ' Step 7: Inform the user how many hits were found lblMsg.Text = dtResults.Rows.Count.ToString() + " hits" End Sub ' Step 5: Highlight first 4 hits ' SharePoint Search places <c[#]> tags around the ' first 10 words in the summary that match ' a keyword search term. Here I just find ' the first four and replace them with ' a <SPAN> element to show the hits in ' bold with a yellow background Private Function formatSummary(ByVal strSummary As String) As String strSummary = strSummary.Replace( _ "<c0>", _ "<span style='font-weight:bold; background-color:yellow'>") strSummary = strSummary.Replace("</c0>", "</span>") strSummary = strSummary.Replace( _ "<c1>", "<span style='font-weight:bold; background-color:yellow'>") strSummary = strSummary.Replace("</c1>", "</span>") strSummary = strSummary.Replace( _ _
"<c2>", "<span style='font-weight:bold; background-color:yellow'>") strSummary = strSummary.Replace("</c2>", "</span>") strSummary = strSummary.Replace( _ _ "<c3>", "<span style='font-weight:bold; background-color:yellow'>") strSummary = strSummary.Replace("</c3>", "</span>") Return strSummary End Function End Class
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using Microsoft.Office.Server; using Microsoft.Office.Server.Search; using Microsoft.Office.Server.Search.Query; using Microsoft.Office.Server.Search.Administration; public partial class _Default : System.Web.UI.Page { protected void cmdSearch_Click(object sender, EventArgs e) { if (txtSearch.Text != "") { performSearch(); } else { lblMsg.Text = "Please enter a search string"; } } private void performSearch() { // Step 1: Get a handle to the Shared Services Search context ServerContext context = ServerContext.GetContext("SharedServices1"); // Step 2: Construct a keyword search query KeywordQuery kwq = new KeywordQuery(context); kwq.ResultTypes = ResultType.RelevantResults; kwq.EnableStemming = true; kwq.TrimDuplicates = true; kwq.QueryText = txtSearch.Text;
kwq.Timeout = 60000; kwq.RowLimit = 1000; kwq.KeywordInclusion = KeywordInclusion.AllKeywords; // Step 3: Get the results to a DataTable ResultTableCollection results = kwq.Execute(); ResultTable resultTable = results[ResultType.RelevantResults]; DataTable dtResults = new DataTable(); dtResults.Load(resultTable); // Step 4: Format summary foreach (DataRow drResult in dtResults.Rows) { drResult["HitHighlightedSummary"] = formatSummary( drResult["HitHighlightedSummary"].ToString()); } // Step 6: Write out table of results gridSearch.DataSource = dtResults; gridSearch.DataBind(); // Step 7: Inform the user how many hits were found lblMsg.Text = dtResults.Rows.Count.ToString() + " hits"; } // Step 5: Highlight first 4 hits // SharePoint Search places <c[#]> tags around the // first 10 words in the summary that match // a keyword search term. Here I just find // the first four and replace them with // a <SPAN> element to show the hits in // bold with a yellow background string formatSummary(string strSummary) { strSummary = strSummary.Replace( "<c0>", "<span style='font-weight:bold; background-color:yellow'>"); strSummary = strSummary.Replace("</c0>","</span>"); strSummary = strSummary.Replace( "<c1>", "<span style='font-weight:bold; background-color:yellow'>"); strSummary = strSummary.Replace("</c1>", "</span>"); strSummary = strSummary.Replace( "<c2>", "<span style='font-weight:bold; background-color:yellow'>"); strSummary = strSummary.Replace("</c2>", "</span>");
strSummary = strSummary.Replace( "<c3>", "<span style='font-weight:bold; background-color:yellow'>"); strSummary = strSummary.Replace("</c3>", "</span>"); return strSummary; } }
After you've created the ASP.NET web application and entered the code as shown, you're ready to run your application. In Visual Studio, click the Run button. Enter a search string and click the Search button; you should see a web page similar to that shown in Figure 8-24.
This page will respond to the same search syntax you can use on a standard MOSS search page.
In this recipe, we are returning only the default managed properties; however, you can return any managed property defined on your search server. For example, if you want to include the ContentSource
managed property in your result, you can use the KeywordQuery.SelectedProperties.Add()
method prior to executing your query. The following code snippet shows the lines that need to be added:
// Add the specific managed properties to output kwq.SelectProperties.Add("Title"); kwq.SelectProperties.Add("Rank"); kwq.SelectProperties.Add("Size"); kwq.SelectProperties.Add("Write"); kwq.SelectProperties.Add("Path"); kwq.SelectProperties.Add("HitHighlightedSummary"); kwq.SelectProperties.Add("ContentSource");
If you use this method, you will need to specify all properties you want included in the result table.
And Figure 8-25 shows the resulting output, including the ContentSource
property.
3.145.161.228