Chapter 8. Advanced Dishes

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.

Customizing the STSADM Command

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.

Recipe Type: STSADM Extension

Ingredients

Assembly References

  • System.Web .NET assembly

  • Windows SharePoint Services NET assembly

Special Considerations

  • 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.

Preparation

  1. 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.

    Note

    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.

  2. Create a new C# class library.

  3. Add references to the Windows SharePoint Services, System.Web and System.Xml .NET assemblies.

  4. Sign the assembly with a strong-name key file.

  5. Add the code shown in the following "Recipe—C#" section.

  6. Compile the class.

  7. Deploy the compiled .dll to the GAC.

Process Flow

Process Flow
  1. 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.

  2. 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.

  3. Add a function called GetHelpMessage() per the ISPStsadmCommand interface.

  4. Add help text for each of the commands referenced in the stsadmcommands.recipe.xml file.

  5. Add a function called Run() per the ISPStsadmCommand interface.

  6. Add calls to supporting methods for each of the commands referenced in the stsadmcommands.recipe.xml file.

  7. Write the support methods referenced in step 6.

Recipe—XML (See Project CustomizeSTSADM-CS, File stsadmcommands.recipes.xml)

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.

Note

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>

Warning

In the previous XML source, all text within the <command> elements should be entered on a single line.

Recipe—C# (See Project CustomizeSTSADM-CS, CustomizeSTSADM-CS.cs)

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. 
Recipe—C# (See Project CustomizeSTSADM-CS, CustomizeSTSADM-CS.cs)
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;
        }
    }
}

To Run

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:

  1. Open a console window.

  2. Navigate to the C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions12in folder.

  3. Type STSADM to display a list of all commands. Figure 8-1 shows the STSADM command output with the two new commands listed.

    STSADM command output listing new commands

    Figure 8.1. STSADM command output listing new commands

  4. 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!"

Running the STSADM addnoticetosite command

Figure 8.2. Running the STSADM addnoticetosite command

Figure 8-3 shows the result of running the addnoticetosite command.

Notice added to page by using the STSADM addnoticetosite command

Figure 8.3. Notice added to page by using the STSADM addnoticetosite command

Note

You can test the adddoclibtosite command in a similar fashion.

Variations

  • 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.

Crawling a Database Table by Using a BDC Schema

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.

Note

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.

Recipe Type: BDC Schema

Ingredients

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

    Warning

    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

Special Considerations

  • 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.

Preparation

First, create the data source:

  1. 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.

  2. Add a new table to the database named SharePointClasses with the columns shown in Figure 8-4.

    SharePointClasses table structure

    Figure 8.4. SharePointClasses table structure

    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]
  3. Save the table and then enter some sample data as shown in Figure 8-5.

    Sample data

    Figure 8.5. Sample data

Next, create an XML file that will describe the data source to SharePoint:

  1. If you haven't done so already, install the SharePoint Server 2007 SDK and follow the instructions for installing the BDC definition editor.

  2. Open the BDC definition editor and choose the option to Add LOB System (LOB stands for line-of-business).

  3. 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.

    Entering a connection string

    Figure 8.6. Entering a connection string

  4. Click the Add Table button to add the SharePointClasses table to the design surface, and then click OK ( Figure 8-7).

    BDC definition editor design surface with SharePointClasses table displayed

    Figure 8.7. BDC definition editor design surface with SharePointClasses table displayed

  5. Name the new schema MyDatabase and click OK.

  6. The BDC schema editor window should look similar to Figure 8-8.

  7. 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).

  8. When prompted, name the schema file MyDatabase.xml. (The XML file source is shown in the "Recipe—BDC Schema XML" section.)

  9. Now import the schema into MOSS and define a Search content source by using the schema.

    The completed BDC schema

    Figure 8.8. The completed BDC schema

    Exporting the BDC schema to XML

    Figure 8.9. Exporting the BDC schema to XML

  10. Open the SharePoint Central Administration Website (SCAW) and click the Shared Services Administration link as shown in Figure 8-10.

    Note

    By default, SharePoint names the shared services web application SharedServices1, but your installation may differ.

    Opening the Shared Services management page

    Figure 8.10. Opening the Shared Services management page

  11. Click the Import Application Definition link under the Business Data Catalog heading ( Figure 8-11).

    Navigating to the BDC schema import page

    Figure 8.11. Navigating to the BDC schema import page

  12. 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).

    Importing the BDC schema into SharePoint

    Figure 8.12. Importing the BDC schema into SharePoint

  13. 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).

    Navigating to the search settings main page

    Figure 8.13. Navigating to the search settings main page

  14. On the Configure Search Settings page, click the Content Sources and Crawl Schedules link ( Figure 8-14).

    Navigating to the content source management page in SCAW

    Figure 8.14. Navigating to the content source management page in SCAW

  15. 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).

    Defining the BDC content source for MyDatabase schema

    Figure 8.15. Defining the BDC content source for MyDatabase schema

  16. 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).

    Starting full crawl on BDC content source

    Figure 8.16. Starting full crawl on BDC content source

  17. You can verify that the database was crawled successfully by choosing the View Crawl Log option from the same context menu ( Figure 8-17).

Verifying that Search successfully crawled the BDC content source

Figure 8.17. Verifying that Search successfully crawled the BDC content source

The text of the SharePointClasses table rows is now indexed and ready to search from within SharePoint.

Recipe—BDC Schema XML

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" 
Recipe—BDC Schema XML
xsi:schemaLocation=
Recipe—BDC Schema XML
"http://schemas.microsoft.com/office/2006/03/
Recipe—BDC Schema XML
BusinessDataCatalogBDCMetadata.xsd"
Recipe—BDC Schema XML
Type="Database" Version="1.0.0.0" Name="MyDatabase"
Recipe—BDC Schema XML
xmlns="http://schemas.microsoft.com/office/2006/03/
Recipe—BDC Schema XML
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. 
Recipe—BDC Schema XML
SystemSpecific.Db.DbAccessProvider">SqlServer</Property> <Property Name="AuthenticationMode" Type="Microsoft.Office.Server.ApplicationRegistry.
Recipe—BDC Schema XML
SystemSpecific.Db.DbAuthenticationMode">
Recipe—BDC Schema XML
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
Recipe—BDC Schema XML
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  
Recipe—BDC Schema XML
SharePointClasses </Property> </Properties> <Parameters> <Parameter Direction="Return" Name="@SharePointClasses">

Note

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>

Note

The IdEnumerator() method instance tells SharePoint Search how to retrieve a list of primary keys for all rows in the source table.

To Run

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.

Entering search term

Figure 8.18. Entering search term

The resulting page displays all "documents" that include that term ( Figure 8-19).

SharePointClasses row found using SharePoint Search

Figure 8.19. SharePointClasses row found using SharePoint Search

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).

The default SharePointClasses viewer page

Figure 8.20. The default SharePointClasses viewer page

Variations

  • 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 Selects, 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>

    Note

    The use of LIKE rather than = in the Select command allows you to perform wildcard searches.

    <Property Name="RdbCommandText" Type="System.String">
           Select * from SharePointClasses  where Class LIKE 
    Variations
    @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" />

    Note

    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.

SharePointClasses row found using the BDC List web part

Figure 8.21. SharePointClasses row found using the BDC List web part

Related Recipes

Creating a Custom MOSS Search Page

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.

Recipe Type: ASP.NET Application

Ingredients

Assembly References

  • Microsoft.Office.Server .NET assembly

  • Microsoft.Office.Server.Search .NET assembly

  • Microsoft.SharePoint.Portal .NET assembly

Note

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.

Special Considerations

  • This page should run by using the same application pool as MOSS; typically that is the SharePoint—80 pool ( Figure 8-22).

    Assigning the application pool in IIS Manager

    Figure 8.22. Assigning the application pool in IIS Manager

Preparation

  1. Create a new ASP.NET web application

  2. Add references to the Windows SharePoint Services, Microsoft.Office.Server, Microsoft.Office.Server.Search, and Microsoft.Portal.Server .NET assemblies.

  3. 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.

  4. 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.

    Customized search page layout

    Figure 8.23. Customized search page layout

  5. Add the source code shown in the appropriate Recipe section.

Process Flow

Process Flow
  1. 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.

  2. 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.

  3. Execute the keyword query search, and use the DataTable.Load() method to copy the resulting data into a DataTable object for simplified processing.

  4. Loop through returned rows, finding the <c0> through <c3> tags embedded in the HitHighlightedSummary field.

  5. Replace those tags with <span> tags to bold and highlight search words in the summary text when output to the page.

  6. Load the formatted DataTable into a GridView web control for display on the page.

  7. 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.

Recipe—ASPX (See Project CustomizingSearch-CS, File CustomizingSearch.aspx)

<%@ Page Language="C#" AutoEventWireup="true" 
Recipe—ASPX (See Project CustomizingSearch-CS, File CustomizingSearch.aspx)
CodeFile="CustomizingSearch.aspx.cs" Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
Recipe—ASPX (See Project CustomizingSearch-CS, File CustomizingSearch.aspx)
"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>

Recipe—VB (See Project CustomizingSearch-VB, Class CustomizingSearch.aspx.vb)

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") = 
Recipe—VB (See Project CustomizingSearch-VB, Class CustomizingSearch.aspx.vb)
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; 
Recipe—VB (See Project CustomizingSearch-VB, Class CustomizingSearch.aspx.vb)
background-color:yellow'>") strSummary = strSummary.Replace("</c2>", "</span>") strSummary = strSummary.Replace( _ _ "<c3>", "<span style='font-weight:bold;
Recipe—VB (See Project CustomizingSearch-VB, Class CustomizingSearch.aspx.vb)
background-color:yellow'>") strSummary = strSummary.Replace("</c3>", "</span>") Return strSummary End Function End Class

Recipe—C# (See Project CustomizingSearch-CS, Class CustomizingSearch.aspx.cs)

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; 
Recipe—C# (See Project CustomizingSearch-CS, Class CustomizingSearch.aspx.cs)
background-color:yellow'>"); strSummary = strSummary.Replace("</c0>","</span>"); strSummary = strSummary.Replace( "<c1>", "<span style='font-weight:bold;
Recipe—C# (See Project CustomizingSearch-CS, Class CustomizingSearch.aspx.cs)
background-color:yellow'>"); strSummary = strSummary.Replace("</c1>", "</span>"); strSummary = strSummary.Replace( "<c2>", "<span style='font-weight:bold;
Recipe—C# (See Project CustomizingSearch-CS, Class CustomizingSearch.aspx.cs)
background-color:yellow'>"); strSummary = strSummary.Replace("</c2>", "</span>");
strSummary = strSummary.Replace(
           "<c3>",
           "<span style='font-weight:bold; 
Recipe—C# (See Project CustomizingSearch-CS, Class CustomizingSearch.aspx.cs)
background-color:yellow'>"); strSummary = strSummary.Replace("</c3>", "</span>"); return strSummary; } }

To Run

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.

Customized search with hit highlighting displayed

Figure 8.24. Customized search with hit highlighting displayed

This page will respond to the same search syntax you can use on a standard MOSS search page.

Variations

  • 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");

    Note

    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.

    Customized search page with ContentSource property displayed

    Figure 8.25. Customized search page with ContentSource property displayed

Related Recipes

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.161.228