Microsoft Office 2003 Editions add a powerful new tool to the Office task pane: the Research Library.

By default, the Research Library allows you to type in a word or phrase and search for the term using several built-in resources. The Research Library is also extensible: you can add your own research service simply by creating a Web service that follows schemas defined by Microsoft for research services.

In this article, you'll look at the nuts and bolts of the Research Library and see how you can use Microsoft Visual Studio .NET to create your own research service. If you are new to Microsoft Visual Basic .NET (VB.NET), you will become familiar with some of the coding techniques needed to create research services in VB.NET. You'll also learn how to use various formatting tags to change the appearance of the result and how to provide a custom search form for your research service.

Working with the Research Library in the Task Pane

The Research Library is available from the task pane in Microsoft Office Excel 2003, Microsoft Office PowerPoint 2003, Microsoft Office Word 2003, and Microsoft Office Outlook 2003. You can display the Research task pane by selecting Research from the Tools menu, or by pressing Ctrl+F1 and choosing Research from the Other Task Panes drop-down list. As you can see in Figure 1, the Research task pane provides a text box for you to enter the word or phrase on which to search, a drop-down list where you can choose to limit the search to a single resource, and an area for displaying the results.

A custom research service could provide a way for users to query corporate data or it could be a front-end to a publicly available service such as Google's Web APIs.

Figure 1 shows the Research task pane after the user has queried the thesaurus for a particular word; the user could have selected any of the resources shown in Figure 2 or searched all resources for the same term. You can customize the Research task pane to show other resources by clicking Research Options at the bottom of the pane. The Research Options dialog box, shown in Figure 3, lists additional resources. These resources are mostly international versions of the default resources. Although these resources may be useful, they are not as intriguing as the command button in the bottom left-hand corner of the dialog box: the Add Services button. It's this button that separates the Research Library from other useful features available in interactive Office 2003.

Figure 1: The Research Library is available from the task pane.
Figure 1: The Research Library is available from the task pane.

Although you could parse the XML string with string functions, it is far easier to load the XML string as an XmlDocument object.

Figure 2: The Research Library includes several built-in and online resources by default.
Figure 2: The Research Library includes several built-in and online resources by default.
Figure 3: Office 2003 provides a long list of choices for customizing the Research task pane's list of resources.
Figure 3: Office 2003 provides a long list of choices for customizing the Research task pane's list of resources.

The ability to add services means that developers can create research services of their own for end users to use in Excel 2003, Word 2003, or PowerPoint 2003. A custom research service could provide a way for users to query corporate data, or it could be a front-end to a publicly available service such as Google's Web APIs (www.google.com/apis).

Querying a Corporate Database: A Contacts Sample

The steps you're about to walk through are needed to create a simple research service that allows the end user to search for contacts in a database. The source data is based on Microsoft's Northwind Traders database from Microsoft SQL Server or Microsoft Office Access 2003. The Employees and Customers tables were combined to make the Contacts table, shown in Figure 4.

Figure 4: The sample uses ADO.NET to open and query an Access 2003 table.
Figure 4: The sample uses ADO.NET to open and query an Access 2003 table.

To use the Northwind Contacts sample, enter a first or last name in the Search For text box. Figure 5 shows the result of a search for a particular name. In addition to the list of contacts, the search result includes a Search by country hyperlink at the bottom of the result list. When you select the hyperlink, the research service returns a list of all the countries in the Contacts table (Figure 6). Select a country and the research service returns a list of contacts in that country.

Figure 5: You can search by first or last name.
Figure 5: You can search by first or last name.
Figure 6: You can choose a country from a list generated by the research service.
Figure 6: You can choose a country from a list generated by the research service.

A Quick Guide to Web Services

A research service is simply a Web service that complies with certain specifications. A Web service is, as the name implies, an application that runs on a Web server. The sample Web service used in this article was developed using VB.NET, but it could easily have been created with a different language. Web services communicate using open standards like Extensible Markup Language (XML), so they are not tied to a particular language or operating system.

A Web service receives information from a client as an XML packet, processes that information, and returns a response, also in XML format, to the client. Typically, the schemas for the call and response are determined by the developer of the Web service. To call a Web service, the developer of the client application must know what XML to send to the Web service and how to read and process the XML returned to the client.

If the Web service is built to be a part of the Research Library, the client is the Research task pane. All the code to send a request to the research service and process the returned XML packet has already been written. When you develop a research service, you don't get to choose the schemas you use; the schemas have already been determined by Microsoft.

This article does not delve deeply into every element defined in each schema; rather it discusses those elements that are required and points out features that are available. The schemas used by the Research Library are all included in the Research SDK. The Research SDK will be available for download from the MSDN.Microsoft.com/Office Web site after Office 2003 ships.

Creating a Research Service in Visual Studio .NET

To create a research service in Visual Studio .NET, you will need:

  • One of the Microsoft® Office 2003 Editions
  • Microsoft Windows® XP Professional, Microsoft Windows 2000 Server with Service Pack 2, Microsoft Windows 2000 Professional with Service Pack 2, or Microsoft Windows Server 2003
  • Microsoft .NET Framework 1.1
  • Internet Information Services 5.1, or access to a remote Web server
  • Microsoft Visual Studio .NET 2003

Visual Studio .NET 2003's installation process walks you through installing and setting up the Microsoft .NET Framework and IIS. If you didn't install them when you installed Visual Studio .NET, check out the Visual Studio .NET product documentation on MSDN.Microsoft.com for help.

The family of schemas used to send the response packet provides a set of HTML-like elements that allow you to format a result for the Research task pane.

Visual Studio NET makes it easy for you to build projects of different types by providing templates for those project types. When you choose to create a new project, you'll see the dialog box shown in Figure 7. To create a research service, choose the ASP.NET Web Service template and supply a name for your service in the Location text box.

Figure 7: Use the ASP.NET Web service to build a research service.
Figure 7: Use the ASP.NET Web service to build a research service.

Visual Studio .NET creates your project with several files already in it, including the stub of a Web service module. Most likely, the only file you will ever need to edit is the Web service module, named Service1.asmx by default. You can add other modules as needed.

A Research Service's Two Methods

A research service must supply two Web methods with specific names. The Registration Web method is called when you use the Research Options dialog box to add your research service to the Research Library. The Query Web method is called by the Research task pane when you use your research service to search for a term. Each method takes a string argument and returns a string value. In all cases, the strings are valid XML packets.

You can see the complete Registration and Query methods for the sample in Listing 1 and Listing 2.

The Search Namespace

You can put the Registration and Query methods in the same Web service module or in two separate modules. The sample uses two separate modules. Either way, the module or modules that contain the Registration and Query methods must belong to the namespace urn:Microsoft.Search. If that namespace isn't found, the Research Library won't be able to register and use the service.

To change the namespace in a Web service module, open the module and find the WebService declaration. Change the Namespace parameter as shown in the following bit of code:

<WebService(Namespace:="urn:Microsoft.Search")> _
Public Class ContactReg
    Inherits System.Web.Services.WebService

Note that the class name and the name of the ASMX file itself are up to you. You can use whatever names make sense to you.

Helpful Coding Techniques

If you have worked with VBA but are new to Visual Studio .NET, the thought of moving to VB.NET can be a bit daunting. Have no fear: most of the familiar VBA functions still work just fine in VB.NET. But because you are working in Visual Studio .NET, you can also use a whole range of new and powerful objects and methods supplied by the .NET Framework. The sample research service makes use of the Framework's capabilities whenever possible.

The next sections are designed to bring the neophyte VB.NET developer up to speed in two areas of particular importance when working with Web services: working with XML and using ADO.NET to query data.

Working with XML

To communicate with the client, all Web service methods must include code that reads and writes XML. The .NET Framework provides many objects and methods for working with XML and the listings in this article use several of them.

To work with XML objects, it's best to import the namespaces to which the objects belong at the top of the module. The sample modules use members of the System.Xml and System.IO namespaces to read and write XML:

Imports System.Xml
Imports System.IO

Reading XML

The XML packets sent as arguments to the Registration and Query methods include information you need to read in order to determine how your code should proceed. For example, the schema for the XML packet sent to the Query method includes a QueryText element. Its value is the term for which the user is searching.

ADO.NET's DataReader object provides efficient read-only, forward-only access to a query result.

Although you could parse the XML string with string functions, it is far easier to load the XML string as an XmlDocument object, as this code demonstrates:

<WebMethod()> Public Function Query( _
 ByVal queryXml As String) As String
   Dim requestXML As New XmlDocument
   requestXML.LoadXml(queryXml)

The XML packet passed into the Query and Registration Web methods include elements from more than one namespace. For example, the QueryText element belongs to the urn:Microsoft.Search.Query namespace. It's easier to find the element you are looking for if you add the namespaces you need to the XmlDocument object's name table:


 Dim nsmRequest As _
  New XmlNamespaceManager(requestXML.NameTable)
 nsmRequest.AddNamespace("ns", _
  "urn:Microsoft.Search.Query")

Finally, you can use the SelectSingleNode method of the XmlDocument to read the QueryText node.

 Dim queryTerm As String = _
  requestXML.SelectSingleNode( _
  "//ns:QueryText", nsmRequest).InnerText

Once you know what the user is searching for, you can take appropriate actions in the rest of the code.

Writing XML

The Registration and Query methods must both return XML packets. The return values are strings, but like reading XML, it is easier to write valid XML using the XML-specific objects and methods supplied by the .NET Framework.

To create an XML packet that can be returned as a string, begin by setting up an XmlTextWriter object that writes XML to a memory stream:

Dim ioMemStream As New MemoryStream
Dim myXMLwriter As New _
 XmlTextWriter(ioMemStream, Nothing)

You can then use methods of the XmlTextWriter object to create the XML document.

With myXMLwriter
   .WriteStartDocument()
   .WriteStartElement("ProviderUpdate", _
    ns:="urn:Microsoft.Search." & _
    Registration.Response")
   .WriteElementString("Status", "SUCCESS")
   .WriteStartElement("Providers")
   .WriteStartElement("Provider")
   .WriteElementString("Name", _
    "Contact Research Service")
   .WriteEndElement()  ' Provider
   .WriteEndElement()  ' Providers
   .WriteEndElement()  ' ProviderUpdate
   .WriteEndDocument()
End With

This code fragment creates an XML document that looks like this:

<?xml version="1.0"?>
<ProviderUpdate xmlns=
"urn:Microsoft.Search.Registration.Response">
   <Status>SUCCESS</Status>
   <Providers>
      <Provider>
         <Name>Contact Research Service</Name>
      </Provider>
   </Providers>
</ProviderUpdate>

Note that each start method (WriteStartDocument, WriteStartElement) has a corresponding end method (WriteEndElement, WriteEndDocument). It is good coding practice to include a comment with the WriteEndElement methods specifying which element the method is ending. If you forget to end an element, the resulting XML packet will not be well-formed. You won't get a result when you try to use the research service if either Web method returns an invalid XML packet.

Once the XML packet has been created in memory, it needs to be written to a string so it can supply the method's return value. The StreamReader object reads the XML packet from memory:

myXMLwriter.Flush()
ioMemStream.Flush()
ioMemStream.Position = 0
Dim iostReader As StreamReader
iostReader = New StreamReader(ioMemStream)
Registration = iostReader.ReadToEnd.ToString

Reading Data with ADO.NET

A typical research service, like the Northwind Contacts sample, searches a database for the terms entered by the user. The sample research service's Query method uses ADO.NET to perform a Select query against data in a Microsoft Access 2003 database and scans through the result. The Query method has no need to change data in the database or to move both backward and forward through the result. ADO.NET's DataReader object provides efficient read-only, forward-only access to a query result.

To query an Access 2003 database, use the OleDbConnection and OleDbCommand objects. The OleDbCommand object provides an ExecuteReader method to return a DataReader object. In this code snippet, the strSQL variable contains a SQL Select statement:

Dim oledbConnectionString As String = _
 "Provider=Microsoft.Jet.OLEDB.4.0;" & _
 "Data Source=" & _
 Server.MapPath(".") & "\Data\Contacts.mdb"
Dim odConnection As New _
 OleDbConnection(oledbConnectionString)
Dim odCommand As OleDbCommand
Dim dr As OleDbDataReader

odConnection = New _
       OleDbConnection(oledbConnectionString)
odCommand = New _
 OleDbCommand(strSQL, odConnection)
odConnection.Open()
dr = odCommand.ExecuteReader()

The sample uses OleDb to work with an Access 2003 database. The .NET Framework also provides analogous objects such as SqlConnection that are optimized for working with SQL Server data.

Once you have a data reader open, you can use the HasRows method to see if any rows were returned. If the query did return data, use the data reader's Read method to scan through it:

If dr.HasRows Then
   While dr.Read()
      With myXMLwriter
         .WriteStartElement("Heading")
         .WriteElementString("Text", _
          dr("FirstName") & " " & _
          dr("LastName"))

         .WriteEndElement()  ' Heading
      End With
   End While
End If

ADO.NET provides many more objects and methods for working with data. For the purposes of most research services, though, the data reader and its parents and members are all you need.

The Registration Web Method

The Registration Web method is called when the end user chooses to install your research service. The method returns all of the information the Research Library needs to install the research service, show an entry for it in the Research task pane, and call the research service's Query Web method. The information is returned as an XML packet that conforms to the registration response schema. Refer to the schema definition in the Research SDK for a complete description of each element and attribute.

Most often, the Registration Web method is nearly boilerplate: you can copy and paste the Registration Web method from one research service to another and make only a few changes to the values of the elements.

The registration response schema has two main nodes: Providers and Services. There are a few values in each node that you should always change.

The All-Important GUID

The Registration Web method returns globally unique identifiers (GUIDs) for two elements: the ID child of the Provider node and the ID child of the Service node. As the name implies, these GUIDs uniquely identify the research service and tie together the Registration and the Query Web methods.

Whenever you create a new research service, you should generate new GUIDs for both its Provider ID and its Service ID. To generate a GUID, select Create GUID from the Tools menu. You can see the Create GUID dialog box in Figure 8. Choose the Registry format option and click the Copy button to copy the new GUID to the clipboard. Close the dialog box and paste the GUID from the clipboard to the proper location in the code.

Figure 8: Visual Studio .NET provides a handy GUID generator.
Figure 8: Visual Studio .NET provides a handy GUID generator.

The Service node's ID value is also used in the Query method, so you may want to create a public constant to hold that value rather than using the value directly when the ID element is written.

Address Information

The Registration response packet must provide information about where to find both the Query method and the Registration Web method itself (so that the Research Library can periodically run the Registration Web method in case any updates have occurred). The values, found in the Providers node, are paths that point to the ASMX files containing the Registration and Query Web methods:

<QueryPath> http://localhost/ContactResearchService/
Query.asmx
</QueryPath>

<RegistrationPath> http://localhost/ContactResearchService/
Registration.asmx
</RegistrationPath>

The code in the sample research service simply specifies a hard-coded address on the localhost server. In a production research service, you may want to write code that determines the location of the research service at runtime, or store the values of the paths in the Web.config file where they can be changed without re-compiling the research service.

Descriptive Information

There are several values that don't strictly need to be changed for each research service, but should be changed to avoid confusing the end user.

Provider node children that should be changed include:

  • Message: The message the user sees when successfully adding the research service to the Research Library
  • Name: The name that appears as the Provider name when you choose to update or remove services, and in the Service Properties dialog box

Service node children that should be changed include:

  • Name: The name that appears in the Research task pane's Research Library drop-down menu
  • Description: The description that appears in the Service Properties dialog box
  • Copyright: Copyright information shown in the Service Properties dialog box

Most Registration Web methods require no more than those few changes to properly register your research service.

The Query Web Method

The Query Web method is called whenever the end user chooses to search using your research service. When you create a research service, you spend the bulk of your time working on the Query Web method. The code in this method uses the query packet sent by the Research task pane to determine what the user wants to do and then creates a result packet that can be rendered into a result by the Research task pane.

The family of schemas used to send the response packet provides a set of HTML-like elements allowing you to format a result for the Research task pane. The content you can provide in the result includes:

  • Headings that can be expanded to show more information
  • Simple text, optionally with bold, italic, or other font attributes
  • Images
  • Buttons to copy specified text to the clipboard or insert text to the Office document
  • Hyperlinks to open the browser to a particular address
  • Hyperlinks to send a new query (with the query term specified by the hyperlink itself) to the research service
  • Form elements such as list boxes, check boxes, and radio groups to provide a richer interface specific to the research service

The sample research service provides two types of responses. The first response type is shown in Figure 5. The contact name appears as a heading (expanded by default) with the address and phone number as text elements within the contact's heading. At the bottom of the list is a hyperlink that calls the research service again with a query term of Search by country.

The second response is shown in Figure 6. This response is sent when the user clicks the Search by country hyperlink. A list of countries found in the Contacts table is displayed in a drop-down. When the end user makes a selection, the research service is called.

Finding Out What the User Wants

The query packet sent to the Query Web method provides two types of information. Refer to Listing 3 for a sample query packet.

The first type of information tells the Web method about the client application that is calling the Web method. The information is found mainly in the OfficeContext node of the packet, and includes data such as the name, version, and language of the application in which the end user is working. You can use the information in the OfficeContext node to determine what data to display and whether to show buttons allowing the user to copy and paste data from the Research task pane to the application.

The second type of information tells the research service what criteria to use to determine what data should be returned.

The Keywords node of the query packet contains an element named QueryTerm. For simple research services, you can use that element's value to get the criteria for your data query. The Keywords node also breaks up multi-word terms so that you can search for each word separately. When the research service is called because a user typed a term into the Search For text box, the QueryTerm element is also sent as a part of the Context node.

If the research service is called by a form element, the Context node includes different children. The following XML fragment is sent when the user selects Finland from the country list:

<Requery>
   <ServiceParameters
    xmlns="urn:Microsoft.Search.
    Office.ServiceParameters">
      <Action>SelectCountry</Action>
         <Parameters>
         <SelectCountry>Finland</SelectCountry>
      </Parameters>
   </ServiceParameters>
</Requery>

The first part of the sample's Query Web method checks for a SelectCountry element in the query packet. If it exists, the code continues by querying the Contacts table with the value of the SelectCountry element as the criteria.

If the SelectCountry element doesn't exist, the request is either a query by name or a request for the Country list. If the user has clicked the hyperlink to request the Country list, the QueryTerm element's value will be “Select a country.” The code checks for this value and returns the list box instead of running a query.

Returning a Response Packet

You can refer to Listing 4 to see the response packet returned by the sample research service for a query result. Note that the Response element's domain attribute value is the same as the ID element's value in the Service node of the Registration response.

After running a query on the Contacts table, the sample code creates a Heading element for each contact the query returned. The Heading element's children include a Text element for the contact's name and P elements for the address, city and region, and phone number.

The code also adds a NewQuery element to the XML. The node looks like this:

<NewQuery query="Search by country">
<Text>Search by country...</Text>
</NewQuery>

The NewQuery element appears as a hyperlink when it is rendered by the Research task pane. The Text element's value is used for the text of the hyperlink. The value of the query attribute is sent as the QueryTerm element of the query packet.

If you want the Research task pane to display a form for the user, you add a Form node to the response packet. The XML fragment shown below describes a list box named SelectCountry with two options:

<Form xmlns="urn:Microsoft.Search.Response.Form">
<Listbox dropDown="true" 
 id="SelectCountry" action="requery">
<Text>Select a country: </Text>
<Option id="Argentina">
<Text>Argentina</Text>
</Option><Option id="Austria">
<Text>Austria</Text>
</Option>
</Listbox>
</Form>

The Listbox element's action attribute is set to requery so that when the user selects a country from the list, the Research task pane makes another call to the research service. This time, the query packet includes a SelectCountry element with a value equal to the country the user selected.

Installing Your Research Service

Once you have written the Registration and Query Web methods for your research service, you can install it in Office 2003. To do so, open the Research task pane in Excel 2003, PowerPoint 2003, or Word 2003. Click the Research Options hyperlink at the bottom of the task pane. From the Research Options dialog box, click the Add Services command button. You can see the Add Services dialog box shown in Figure 9. Type in the full path and name of the ASMX file containing the Registration Web method. When you click the Add button, the Registration method is called. If it returns a valid response packet, the research service is installed. When you return to the Research task pane, your research service is listed in the resource drop-down list.

Figure 9: Add research services with the Add Services dialog box.
Figure 9: Add research services with the Add Services dialog box.

Summary

If your user community needs to do quick searches of corporate data without adding yet another full-fledged application to each desktop, the Research Library is just the tool you need. The sample in this article demonstrates a simple but powerful research service. To see other examples of research services, check out the Research SDK on msdn.microsoft.com.

Listing 1: The Registration Web method in its module

Imports System.Web.Services ' automatically added
Imports System.Xml ' needed to read and create XML packets
Imports System.IO  ' needed to construct the XML packet in memory


<WebService(Namespace:="urn:Microsoft.Search")> _
Public Class ContactReg
    Inherits System.Web.Services.WebService

#Region " Web Services Designer Generated Code "

' Note: Code for this region not reproduced in the listing

#End Region

    <WebMethod()> Public Function Registration( _
     ByVal regXML As String) As String
        Dim ioMemStream As New MemoryStream
        Dim myXMLwriter As New XmlTextWriter(ioMemStream, Nothing)
        Dim iostReader As IO.StreamReader
        Try

            With myXMLwriter
                .Indentation = 4
                .IndentChar = " "
                .WriteStartDocument()
                .WriteStartElement("ProviderUpdate", _
                 ns:="urn:Microsoft.Search.Registration.Response")
                .WriteElementString("Status", "SUCCESS")
                .WriteStartElement("Providers")
                .WriteStartElement("Provider")

                .WriteElementString("Message", _
                 "Congratulations! You've registered the " & _
                 "Northwind Contact Research Service!")
                .WriteElementString("Id", _
                 "{35DAB457-F2E1-425e-95B1-95188455A62D}")
                .WriteElementString("Name", _
                 "Contact Research Service")
                .WriteElementString("QueryPath", _
                 "http://localhost/ContactResearchService/" & _
                 "Query.asmx")
                .WriteElementString("RegistrationPath", _
                 "http://localhost/ContactResearchService/" & _
                 "Registration.asmx")
                .WriteElementString("Type", "SOAP")
                .WriteStartElement("Services")
                .WriteStartElement("Service")
                ' This GUID will be needed in the 
                ' Query method as well
                .WriteElementString("Id", _
                 "{302E491F-6221-4977-B179-F33F6678BC44}")

                .WriteElementString("Name", "Northwind Contacts")
                .WriteElementString("Description", _
                 "The Northwind Contacts Research Service " & _
                 "provides the ability to search for contacts " & _
                 "from within Office 2003 products.")
                .WriteElementString("Copyright", _
                 "All content Copyright (c) 2003.")
                .WriteElementString("Display", "On")
                .WriteElementString("Category", "INTRANET_GENERAL")

                .WriteEndElement()  ' Service
                .WriteEndElement()  ' Services
                .WriteEndElement()  ' Provider
                .WriteEndElement()  ' Providers
                .WriteEndElement()  ' ProviderUpdate
                .WriteEndDocument()
            End With

            myXMLwriter.Flush()

            ioMemStream.Flush()
            ioMemStream.Position = 0
            iostReader = New IO.StreamReader(ioMemStream)
            Registration = iostReader.ReadToEnd.ToString
        Catch ex As Exception
            Console.Write(ex.ToString)
        Finally
            myXMLwriter = Nothing
            ioMemStream = Nothing
            iostReader = Nothing
        End Try

    End Function
End Class

Listing 2: The Query Web method in its module

Imports System.Web.Services ' automatically added
Imports System.Xml  ' needed to read and create XML packets
Imports System.IO   ' needed to construct the XML packet in memory
Imports System.Data
Imports System.Data.OleDb

<WebService(Namespace:="urn:Microsoft.Search")> _
Public Class ContactQuery
    Inherits System.Web.Services.WebService

#Region " Web Services Designer Generated Code "
        'Note: Code in this region not reproduced in listing
#End Region

    <WebMethod()> Public Function Query( _
     ByVal queryXml As String) As String

        Dim queryTerm As String
        Dim ioMemStream As New MemoryStream
        Dim myXMLwriter As New XmlTextWriter(ioMemStream, Nothing)

        Dim iostReader As StreamReader
        Dim xWrite As XmlTextWriter
        Dim oledbConnectionString As String = _
         "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & _
         Server.MapPath(".") & "\Data\Contacts.mdb"
        Dim odConnection As New _
         OleDbConnection(oledbConnectionString)
        Dim odCommand As OleDbCommand
        Dim dr As OleDbDataReader
        Dim strResult As String

        Try
            Dim requestXML As New XmlDocument
            requestXML.LoadXml(queryXml)

            Dim nsmRequest As _
             New XmlNamespaceManager(requestXML.NameTable)
            With nsmRequest
                .AddNamespace("ns", _
                 "urn:Microsoft.Search.Query")
                .AddNamespace("sp", _
                 "urn:Microsoft.Search.Office.ServiceParameters")
                .AddNamespace("oc", _
                 "urn:Microsoft.Search.Query.Office.Context")
            End With

            queryTerm = requestXML.SelectSingleNode( _
             "//ns:QueryText", nsmRequest).InnerText

            ' The Query method is called to fill three 
            '  kinds of requests:
            ' 1. User types in a term and method queries Contacts 
            '    table for first and last names that contain
            '    the term entered by the user
            '    
            ' 2. User clicks Search by country hyperlink and
            '    method responds with a box listing countries
            '    (queryTerm will be Search by country)
            '
            ' 3. User selects a country from listbox and method
            '    queries Contacts table for countries that
            '    match user's selection
            '   (SelectCountry element will be present in 
            '   queryXML)

            'If this is a request from the Country listbox,  
            ' overwrite the search term with the text 
            ' SelectCountry
            ' (If statement later in the code will get the actual 
            ' query term)

            If Not requestXML.SelectSingleNode( _
             "//sp:SelectCountry", nsmRequest) Is Nothing Then
                queryTerm = "SelectCountry"
            End If

            ' First xml is the same regardless of query type
            With myXMLwriter
                .Indentation = 4
                .IndentChar = " "
                .WriteStartDocument()

                .WriteStartElement("ResponsePacket", _
                 ns:="urn:Microsoft.Search.Response")
                .WriteAttributeString("revision", value:=1)

                .WriteStartElement("Response")

                ' this GUID must match the Registration Service ID
                .WriteAttributeString("domain", _
                 value:="{302E491F-6221-4977-B179-F33F6678BC44}")

                .WriteElementString("QueryID", _
                 "{690EF6D8-575D-4897-8F30-293E175C1B99}")
                .WriteStartElement("Range")
                .WriteStartElement("Results")
            End With

            If queryTerm = "Search by country" Then
                ' return a form with a country dropdown
                odConnection = New _
                 OleDbConnection(oledbConnectionString)
                odCommand = New _
                 OleDbCommand("SELECT DISTINCT Country " & _
                 "FROM Contacts", odConnection)
                odConnection.Open()
                dr = odCommand.ExecuteReader()

                With myXMLwriter
                    'Form Heading
                    .WriteStartElement("Form", _
                     "urn:Microsoft.Search.Response.Form")

                    'Root Listbox 
                    .WriteStartElement("Listbox")
                    .WriteAttributeString("dropDown", "true")
                    .WriteAttributeString("id", "SelectCountry")
                    .WriteAttributeString("action", "requery")
                    .WriteElementString("Text", _
                     "Select a country: ")

                    While dr.Read
                        .WriteStartElement("Option")
                        .WriteAttributeString("id", dr("Country"))
                        .WriteElementString("Text", dr("Country"))
                        .WriteEndElement()
                    End While

                    .WriteEndElement() 'Listbox
                    .WriteEndElement() 'Form
                    .WriteStartElement("Content", _
                     ns:="urn:Microsoft.Search.Response.Content")
                End With
            Else
                Dim strSQL As String
                If queryTerm = "SelectCountry" Then
                    ' Return all contacts in specified country
                    ' Find out what country was requested
                    Dim strCountry As String
                    strCountry = requestXML.SelectSingleNode( _
                     "//sp:SelectCountry", nsmRequest).InnerText

                    ' Replace is used for the case where
                    '  criteria has its own single quote
                    strSQL = _
                     "SELECT * FROM Contacts " & _
                     "WHERE [Country] = '" & _
                     strCountry.Replace("'", "''") & "'"
                Else   ' Search for matching first or last names
                    strSQL = _
                     "SELECT * FROM Contacts " & _
                     "WHERE [FirstName] = '" & _
                     queryTerm.Replace("'", "''") & "'" & _
                     " OR [LastName] = '" & _
                     queryTerm.Replace("'", "''") & "'"
                End If
                odConnection = New _
                 OleDbConnection(oledbConnectionString)
                odCommand = New _
                 OleDbCommand(strSQL, odConnection)
                odConnection.Open()

                dr = odCommand.ExecuteReader()

                myXMLwriter.WriteStartElement("Content", _
                 ns:="urn:Microsoft.Search.Response.Content")
                If dr.HasRows Then
                    While dr.Read()
                        With myXMLwriter
                            .WriteStartElement("Heading")
                            .WriteElementString("Text", _
                             dr("FirstName") & " " & _
                             dr("LastName"))

                            ' The following string manipulation
                            '  takes care of the case where
                            '  the address has an embedded
                            '  carriage return-line feed (which
                            '  would be ignored by the Research
                            '  task pane when it renders result).
                            Dim strAddress As String = _
                             dr("Address")
                            Dim delimStr As String = Chr(13)
                            Dim delimiter As Char() = _
                             delimStr.ToCharArray()

                            Dim strAddressParts() As String = _
                             strAddress.Split(delimiter)
                            Dim strAddressPart As String

                            For Each strAddressPart In _
                             strAddressParts
                                .WriteElementString("P", _
                                 strAddressPart)
                            Next
                            ' Region isn't always entered.
                            '  When it is, want a commma between
                            '  City and Region
                            Dim strRegion As String = _
                             dr("Region") & ""
                            If strRegion.Length > 0 Then
                                .WriteElementString("P", _
                                 dr("City") & ", " & dr("Region"))
                            Else
                                .WriteElementString("P", _
                                 dr("City"))
                            End If
                            .WriteElementString("P", dr("Phone"))

                            .WriteEndElement()  ' Heading
                        End With
                    End While
                Else
                    myXMLwriter.WriteElementString("P", _
                     "No matches found")
                End If
            End If

            ' Link for Country form
            With myXMLwriter
                .WriteElementString("HorizontalRule", "")
                .WriteStartElement("NewQuery")
                .WriteAttributeString("query", _
                 "Search by country")
                .WriteElementString("Text", _
                 "Search by country...")
                .WriteEndElement()

                .WriteEndElement() ' Content

                .WriteEndElement() ' Results
                .WriteEndElement() ' Range

                .WriteElementString("Status", "SUCCESS")

                .WriteEndElement() ' Response
                .WriteEndElement() ' ResponsePacket
                .WriteEndDocument()
            End With
            myXMLwriter.Flush()
            ioMemStream.Flush()
            ioMemStream.Position = 0
            iostReader = New IO.StreamReader(ioMemStream)

            strResult = iostReader.ReadToEnd.ToString
            Query = strResult
        Catch ex As Exception
            Console.Write(ex.ToString)

        Finally
            xWrite = Nothing
            ioMemStream = Nothing
            iostReader = Nothing
            odCommand = Nothing
            dr = Nothing
            odConnection = Nothing
        End Try
    End Function
End Class

Listing 3: The Research task pane sends a query packet like this when the user searches for the name Elizabeth from Word 2003

<QueryPacket xmlns="urn:Microsoft.Search.Query" 
revision="1" build="(11.0.5329)">
<Query domain="{302E491F-6221-4977-B179-F33F6678BC44}">
<QueryId>
               {38D73421-D647-422C-BD60-4CA0EF5C51F9}
            </QueryId>
<OriginatorId>
               {F6FF7BE0-F39C-4ddc-A7D0-09A4C6C647A5}
            </OriginatorId>
<SupportedFormats>
<Format revision="1">
                   urn:Microsoft.Search.Response.Document:Document
                  </Format>
<Format revision="1">
                   urn:Microsoft.Search.Response.Content:Content
                  </Format>
<Format revision="1">
                   urn:Microsoft.Search.Response.Form:Form
                  </Format>
</SupportedFormats>
<Context>
<QueryText type="STRING" language="en-us">
                    Elizabeth
                  </QueryText>
<LanguagePreference>en-us</LanguagePreference>
<Requery/>
</Context>
<Range id="result"/>
<OfficeContext        
             xmlns="urn:Microsoft.Search.Query.Office.Context" 
             revision="1">
<UserPreferences>
<ParentalControl>false</ParentalControl>
</UserPreferences>
<ServiceData/>
<ApplicationContext>
<Name>Microsoft Office Word</Name>
<Version>(11.0.5329)</Version>
<SystemInformation>
<SkuLanguage>en-us</SkuLanguage>
<LanguagePack>en-us</LanguagePack>
<InterfaceLanguage>
                               en-us
                              </InterfaceLanguage>
<Location>US</Location>
</SystemInformation>
</ApplicationContext>
<QueryLanguage>en-us</QueryLanguage>
<KeyboardLanguage>en-us</KeyboardLanguage>
</OfficeContext>
<Keywords
             xmlns="urn:Microsoft.Search.Query.Office.Keywords" 
             revision="1">
<QueryText>Elizabeth</QueryText>
<Keyword>
<Word>Elizabeth</Word>
</Keyword>
</Keywords>
</Query>
</QueryPacket>

Listing 4: The sample research service returns the XML shown here as a query result

<?xml version="1.0"?>
<ResponsePacket revision="1" xmlns="urn:Microsoft.Search.Response">
<Response domain="{302E491F-6221-4977-B179-F33F6678BC44}">
<QueryID>
             {690EF6D8-575D-4897-8F30-293E175C1B99}
            </QueryID>
<Range>
<Results>
<Content xmlns=
                         "urn:Microsoft.Search.Response.Content">
<Heading>
<Text>Elizabeth Lincoln</Text>
<P>23 Tsawassen Blvd.</P>
<P>Tsawassen</P>
</Heading>
<Heading>
<Text>Elizabeth Brown</Text>
<P>Berkeley Gardens
12  Brewery </P>
<P>London</P>
</Heading>
<HorizontalRule />
<NewQuery query="Search by country">
<Text>
Search by country...
</Text>
</NewQuery>
</Content>
</Results>
</Range>
<Status>SUCCESS</Status>
</Response>
</ResponsePacket>