The Search Is On: Part II

Creating a Custom SharePoint Web Part

LANGUAGES: C#

ASP.NET VERSIONS: 2.0

This two-part series guides you through the process of creating a SharePoint Web part. We began with the out-of-the-box configurations required to ensure that search is successful inside your Microsoft Office SharePoint Server portals, and explored where the search result and click-through data is kept (see Part I). Now the challenge is to use this knowledge to build our own custom Web part.

 

Building Your Query Report Control

With the system set up, you need end users to perform searches. As those searches are performed, you'll need a customized way to display them sorted by popularity. Luckily, this information can be extracted and displayed on a Web page or in a Web part. Because the information is simply in the database, there are a few options for accessing the data. You could access the data by connecting directly to the database, you could write a Web service, or you could even use the SharePoint classes to help build a component. It's important to keep in mind that these internal SharePoint stored procedures could change with any hot fix or service pack, and are not documented as interfaces to the system. As we'll briefly explore, each one of these options offers advantages and disadvantages.

If you connect to these stored procedures directly from code, you must write your own user and role security and database connection methods, provide the correct stored procedure, and provide the correct parameters. This can be a viable option, but you'll need to provide your data access code rights to the SharePoint database. If the SharePoint machine and the Web server on which you are working are in the same environment, this can usually be worked out with administration settings.

The second option, using Web services, works best with applications that need remote connections. For instance, you could display this information to end users in a smart client (like Microsoft Word or your own Windows Forms application). Although this helps distribute the data, the Web service itself would still need to make a connection to the database (either with direct access to SQL Server or through another method, such as using the SharePoint object model).

The third option, and the one we're going to take, is to use SharePoint s own classes to pull information from the Search Query Results. This method includes the requirement of having SharePoint installed on one of the machines, but does not require you to write the database calls or the security permission code.

To build this component, start by looking at the class hierarchy of Microsoft.SharePoint.Portal.Analytics.UI. Figure 1 shows how the classes are inherited. These are the classes that power the usage reports screens and provide the fundamental classes that are used for the component. You'll be creating a custom report control that would be at home on any of the usage report pages; however, this control is repurposing the data the report would use for display purposes. Having done this, you can take that data and use it in any Web page using simple ASP.NET controls.

 

TopQueriesReportControl

 

QueryTopLargeListReportControl (abstract)

 

QueryTopReportControl (abstract)

 

QueryReportControl (abstract)

 

ReportControl (abstract)

Figure 1: Report control hierarchy.

 

The component will derive from QueryTopLargeListReportControl to get the functionality provided to all report controls based on the search queries. When implementing QueryTopLargeListReportControl, you are required to implement certain properties that each class must implement; these properties are shown in Figure 2.

 

Property

Description

RdlFileName

When the report page is displayed this is the report definition file that will be used for formatting.

StoredProcedureName

The stored procedure that will be called to populate the data of the report.

Figure 2: Property definitions for QueryTopLargeListReportControl.

 

From your previous research, you might recall that the stored procedure used for this control is proc_MSS_QLog_TopQueries. This sample control has the string built in to the control, but you could easily make this a property that can be modified through the user interface in order to create a more generic TopQueries control. The RdlFileName will be left blank because you won't actually be using the Report Control portion of this component. If you want to use this control as a report control, or for testing purposes, simply set the RdlFileName to the report you want to use. The RdlFileName could be one of the existing Microsoft ones, or even a custom one you write. Once you've implemented the class, your code will start to look like that shown in Figure 3.

 

public class DevCowTopQueries: QueryTopLargeListReportControl

{

   protected override string StoredProcedureName

   {

       get

       {

           return "proc_MSS_QLog_TopQueries";

       }

   }

   protected override string RdlFileName

   {

       get { throw new Exception("The method or operation

             is not implemented."); }

   }    }

Figure 3: New control DevCowTopQueries.

 

Even though you are only required to implement two properties, the control also needs two other properties that are specific to this control: TitleText and StoredProcedureParameters (see Figure 4). The TitleText property is part of the reporting and control, and is only set for cases that the report control is used; the StoredProcedureParameters property is critical to the stored procedure you are using. From looking at the database stored procedure, we can tell there are three parameters required, one of which controls the number of results returned. The values used are inherited from the base classes, and only need to be implemented if you want to override them. For instance, the base value for TopResultCount is 10; if you want to have this variable, or if you want to increase this number, you can override your own property and set the value required.

 

protected override string TitleText

{

 get

 {

   return "DevCow Query Control";

 }

}

protected override Collection StoredProcedureParameters

{

 get

 {

   Collection storedProcedureParameters = new Collection();

   Guid guid = SPControl.GetContextWeb(this.Context).Site.ID;

   storedProcedureParameters.Add(new SqlParameter("@siteGuid", guid));

   storedProcedureParameters.Add(new SqlParameter("@isSspLevel", this.IsSspLevel));

   storedProcedureParameters.Add(new SqlParameter("@topResultsCount", this.TopResultsCount));

   return storedProcedureParameters;

 }

}

Figure 4: DevCowTopQueries properties.

 

Here's where your control begins to differ from the standard Microsoft controls. The Microsoft controls keep the report data internal to the control, but the entire idea of this control is to use that data on other Web pages. Luckily, one of the inherited method calls is LoadReportData, which doesn't take any parameters and returns a System.Data.DataTable. To allow this data to be exposed to the end user, you must create a new method, GetData:

 

public DataTable GetData()

{

   return this.LoadReportData();

}

 

You can see that the method has the identical signature to the LoadReportData method, and is, in fact, simply a wrapper method to allow other applications to access otherwise private data. Keep in mind that this class does not perform any security checks or audit logging on this method, but these security checks or audits might be required, depending on the environment. Now that you're returning the data in a DataTable to other Web parts, Web pages, or controls, there's no more mystery for the experienced developer using the control to take advantage of Search Query data.

 

Display Top Search Terms

Now that the search data is being retrieved from the SharePoint analytics classes, you need a way to display this data on a Web page. Because you are already using SharePoint to pull the data back, creating a few Web parts to make it easy to display the results and create the functionality to use the results to perform your own search is the best option. To set the values of the dropdown list, you first must know the names of each column of the DataTable. You could look at the return types of the stored procedure, but in some cases it's not obvious from the T-SQL. You could run the stored procedure and see the values, or you could opt to create a GridView-based Web part to dump the raw data to the screen. Here, a GridView Web part is a valuable exercise, perhaps to display the results for administrators, as illustrated in Figure 5. Administrators may want to see the entire view of the data that will be displayed to the end users in another format.

 


Figure 5: TopSearchTermsGrid Web part.

 

You can see that all the columns are information from the search pages discussed when you set up the page. These include the query string, the search scope, and the results URL, to name a few. Additionally, you can see information such as the number of times the query has been sought. Although this is a simple Web part, it can be placed on any page to see the results and for what users are searching. Once you know what the users are looking for, you can add best bets and improve the relevance of information that should show up based on actual user search patterns.

With all this information, you can now build a Web part that has a dropdown list of the top search queries run on the system. Remember, you are building components and Web parts to help the end users know the most popular search phrases on the site, and this is one way to help them expand their knowledge of the site without generating a lot of IT support calls. The salient field available for use is the queryString field, which is used for both the displayed text and the value for searching. Figure 6 shows the call to the control you just built to get the data, as well as using the data contained in the queryString column to populate the dropdown list values.

 

ddlTopChoicesClient = new DropDownList();

DevCowTopQueries tdd = new DevCowTopQueries();

ddlTopChoicesClient.DataSource = tdd.GetData();

ddlTopChoicesClient.DataTextField = "queryString";

ddlTopChoicesClient.DataBind();

this.Controls.Add(ddlTopChoicesClient);

Figure 6: Binding to usage data.

 

The dropdown list allows the Web part to present the user a pre-defined list of query string selections and also prevents them from modifying the list. This user experience lends itself to the collaborative nature of the Web part's intention. If the user wants to search for information not directly related to the available search options, they'll have to perform that search using the default search input control.

 

Build a GO Button

The GO button is the button that will perform the actual search action. There are many ways you can perform a search with SharePoint. You can type in the search URL with a query string, call the Web services API, use the object model to perform the search, and use the built-in JavaScript. For the purpose of demonstration, this Web part implements a pair of GO buttons, one using the JavaScript method and the other using the URL redirection method (both are valid methods that are easy to use for searching). The other methods might be used if you were writing your own search results page or wanted to change the way search results were returned.

With client-side JavaScript, you can use the built-in functions provided with the SharePoint JavaScript library. The function we want to use, GoSearch, is located in the search.js library. There are a number of parameters you can pass to the function. The only parameter the GO button uses is the queryString value retrieved from the option selected in the dropdown list. As you can see in Figure 7, there are a few hard-coded assumptions, such as the location of the Search Center page and that the desired search scope is All Sites .

 

searchResultsButton = new Button();

searchResultsButton.Text = " GO ClientSide";

searchResultsButton.OnClientClick =

 "GoSearch(null,'" + ddlTopChoicesClient.UniqueID

                   + "',null,true,false,null,'null',null"

                   + ",null,'\u002fsearchcenter\u002fPages\u002fResults.aspx'"

                   + ", 'This Site','This List', 'This Folder', 'Related Sites'"

                   + ", '\u002f_layouts\u002fOSSSearchResults.aspx');";

this.Controls.Add(searchResultsButton);

Figure 7: Create a client-side search button.

 

Once the JavaScript function is constructed, attach the string to the OnClientClick event of the button. This will perform the query search using the client redirect and will not require that the server have a postback for every request to the search query.

The second GO button performs the search on the server. Possible reasons for using this method would be to modify the URL or to log event information every time a user uses the Top Search Query functionality. To perform the server-side URL redirect, you must create a new dropdown list that has the URL pre-built from the data of the TopSearch control. To create the new dropdown control, loop through each row of the DataTable and construct the URL as {resultsUrl}?k={queryString}&s={scope}. The URL along with parameters for the queryString and scope are then passed to the correct URL for processing. As you can see, a single letter is used for the parameter locations: k is for queryString; s is for scope. Now when the user presses the button, a server-side request is made, processing can be performed, and the redirect to the URL occurs.

 

Go Directly to BestBet

We're sure that everyone has seen the functionality on Google that lets you go directly to the most relevant search result of the keyword entered. What if there was the same ability in a SharePoint site? Now there can be! Using the search results, keywords, and best bets, here's a look at how to create just such a Web part for the users.

The first step is connecting to the SharePoint Object Model and getting a reference to the Keywords class. The Keywords class contains a field named Term that can be matched to the search query. Note that with this method, all the words in the search string must also be in the Term value of the Keyword. Once you have a reference to the Keywords for the site, you can then perform standard operations like Add, View, Create, and Delete. Every Keyword can have a set of objects, called BestBets, associated with the Keyword term. These best bets provide a title and URL that can be used to navigate based on the Keyword term. This section of the article describes how to navigate directly to the top best bet if someone clicks the I might be Lucky! button.

Creating a new button is straightforward; simply create a new button that has a server-side event and when the user clicks the button, search through the keywords to find the right set and redirect the user to the top best bet. Keyword terms are not based on search scopes like the other query string results; for that reason, use the first dropdown list you created that has the query string as the value to make it easy to perform look-ups. Once you have the query string, there are built-in functions to help you narrow the collection of keyword results. The Keywords class has a function named FilterKeywords that takes three parameters. The first two are enumerations based on how you want the results returned and the last one is the keyword term you are looking for; in this case, it's the query string search.

Now that you have the keyword collection of terms that matched the search, you can perform a loop through each keyword. Each keyword may or may not have a set of best bets associated with it, so you might have to look at more than one keyword to find the first best bet. Once you find the keyword with a best bet, get the URL from the best bet and direct the user to the best bet location. If you don't find a best bet, you can take the user to another page or simply notify the user that no best bets are available for that term. Figure 8 shows the code that will perform the look-up of the best bet.

 

void bestBetButton_Click(object sender, EventArgs e)

{

 SearchContext searchContext =

SearchContext.GetContext(SPContext.Current.Site);

   Keywords keywords = new Keywords(searchContext, new Uri(SPContext.Current.Site.Url));

 KeywordCollection kwc = keywords.GetFilteredKeywords(KeywordView.AllKeywords,

                                                      KeywordFilter.Keyword,

                                                      ddlTopChoicesClient.SelectedValue);

 foreach (Keyword kw in kwc)

 {

     foreach (BestBet bb in kw.BestBets)

     {

         Page.Response.Redirect(bb.Url.ToString());

         return;

     }

 }

 lblUserMsg.Text = "No BestBet defined for search query.";

}

Figure 8: Redirect to SharePoint BestBet.

 

Conclusion

There are many ways to extend search with SharePoint. You can create custom search results with XSLT, connect to many other systems with Business Data Catalog (BDC), and search through files in your enterprise like file shares. But there is one constant users need a way to help them navigate through a system. Building tools that use the abilities of enterprise search can help direct users to the right information on your site, making it more valuable as a tool to all users. Building tools that use the usage data and search information provide a start to what you can add for your users. Using additional capabilities like keywords and best bets will help you, as content owner, direct your users to relevant information; now you can even see the areas that people are using the most.

Search doesn't have to be just building pages that return results, although the Web services provided by Microsoft will allow you to create these pages. The real advantage is using all the tools, such as JavaScript, URL navigation, the SharePoint object model, and more to create the solution that is right for your organization. You can even perform these actions from other pages or applications by creating an interface, such as a Web service. Be sure to configure security requirements on any data that you expose!

Building controls that take advantage of the work that has been done by the framework can help you make an impact quickly, but keep in mind that undocumented features can change in the future. For this reason, make sure to test your customizations when you apply service packs and perform upgrades.

This concludes the exploration of SharePoint Search. This series has demonstrated how to correctly configure SharePoint Search on a MOSS 2007 portal with a variety of content sources and search scopes. You've taken extra steps to ensure that your users are finding the most relevant details by identifying best-bet results for certain search terms. You know where to look to gain insight into how search is being used through a variety of built-in reporting pages. To further your understanding of how search data is stored and tracked, you've used Reflector to dive into the guts of SharePoint source code to discover its hidden secrets. Finally, you've applied this knowledge to create a custom Web part, which uses the portal itself to keep its contents fresh and relevant.

The source code accompanying this series is available for download.

Matthew S. Ranlett, a senior consultant with Intellinet s Information Worker team, is based out of Atlanta. A Microsoft SQL Server MVP, Matt is co-author of Professional SharePoint 2007 Development, and co-founder of the Atlanta .NET Regular Guys, hosted at DevCow (http://www.devcow.com).

Brendon Schwartz is a principal consultant with Slalom Consulting in Atlanta specializing in SharePoint 2007. Brendon is a Microsoft MVP, co-author of Professional SharePoint 2007 Development, and co-founder of the Atlanta .NET Regular Guys, hosted at DevCow (http://www.devcow.com).

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish