Creating a Research Service for Word 2007 in C# with Visual Studio 2008

This post examines how to build research services for Word and other Office applications. Word has a task pane called the Research task pane that enables you to enter a search term and search various sources for that search term. To display the Research task pane, click the Research button in the Review tab of Word’s ribbon.

image

Office enables developers to write a special Web service called a research service that implements two Web methods defined by Office: Registration and Query. Both Web methods take a string and return a string. A research service can be registered with Office and used in Office’s Research task pane. For example, you might write a research service that searches for the search term in a corporate database.

Although the signatures of the two Web methods you must declare are simple, the actual implementation of these methods is somewhat complex because Word has four separate XML schemas that must be used for the request passed to Registration, the response returned by Registration, the request passed to Query, and the response returned by Query.

To build a research service, launch Visual Studio 2008 and choose New Project from the File menu. Select Web from the Visual C# Projects in the Project types window and click the ASP.NET Web Service Application in the Templates window, as shown here.

image

When you click OK, a web service project is created for you. Within the project is a file called Service1.asmx.cs. Service1.asmx.cs contains a class called Service1 that contains one web method called HelloWorld. We are going to delete the HelloWorld web method and replace it with an implementation of the two web methods required by Research services: Registration and QueryResponse.

Edit Service1.asmx.cs to produce the result shown below. If the user searches for the string “Eric” the service will send back information about me :)

 using System;
 using System.ComponentModel;
 using System.Web;
 using System.Web.Services;
 using System.Web.Services.Protocols;
 using System.Xml;
 using System.IO;
 using System.Web.Util;
  
 namespace ResearchService
 {
   [WebService(Namespace = "urn:Microsoft.Search", Description = "My First C# Research Service")]
   [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
   [ToolboxItem(false)]
   public class Service1 : System.Web.Services.WebService
   {
     [WebMethod]
     public string Registration(string registrationXml)
     {
       MemoryStream stream = new MemoryStream();
       XmlTextWriter writer = new XmlTextWriter(
         stream, System.Text.Encoding.UTF8);
  
       writer.Formatting = Formatting.Indented;    
       writer.WriteStartDocument();
  
       // Registration Response Packet
       writer.WriteStartElement("ProviderUpdate", 
         "urn:Microsoft.Search.Registration.Response");
       writer.WriteElementString("Status", "SUCCESS");
  
       // Provider
       writer.WriteStartElement("Providers");
       writer.WriteStartElement("Provider");
       writer.WriteElementString("Message", "This is an example research library written in Visual Studio 2008 with C#.");
       
       writer.WriteElementString("Id", 
         "{C37EE888-D74E-47e5-B113-BA613D87F0B2}");
       writer.WriteElementString("Name", 
         "My First Research Service");
       writer.WriteElementString("QueryPath", 
         @"https://" + HttpContext.Current.Request.Url.Host + ":" +
         HttpContext.Current.Request.Url.Port.ToString() +
         HttpContext.Current.Request.Url.AbsolutePath);
  
       writer.WriteElementString("RegistrationPath",
         @"https://" + HttpContext.Current.Request.Url.Host + ":" +
         HttpContext.Current.Request.Url.Port.ToString() +
         HttpContext.Current.Request.Url.AbsolutePath);
  
       writer.WriteElementString("Type", "SOAP");
  
       // Services
       writer.WriteStartElement("Services");
       writer.WriteStartElement("Service");
       writer.WriteElementString("Id", 
         "{8DD063CA-94FC-4514-8D83-3B36B12432BE}");
  
       writer.WriteElementString("Name", 
         "My First Research Service in C#");
  
       writer.WriteElementString("Description", 
         "My First Research Service, created in C# and Visual Studio 2008.");
  
       writer.WriteElementString("Copyright", "(C) 2008");
       writer.WriteElementString("Display", "On");
       writer.WriteElementString("Category", "RESEARCH_GENERAL");
       writer.WriteEndElement(); // Service
       writer.WriteEndElement(); // Services
       writer.WriteEndElement(); // Provider
       writer.WriteEndElement(); // Providers
       writer.WriteEndElement(); // ProviderUpdate
       writer.WriteEndDocument();
  
       writer.Flush();
  
       stream.Position = 0;
       StreamReader reader = new StreamReader(stream);
       string result = reader.ReadToEnd();
       return result;
     }
  
     [WebMethod]
     public string Query(string queryXml)
     {
       XmlDocument xmlQuery = new XmlDocument();
       xmlQuery.LoadXml(queryXml);
  
       XmlNamespaceManager nm1 = 
         new XmlNamespaceManager(xmlQuery.NameTable);
       nm1.AddNamespace("ns", "urn:Microsoft.Search.Query");
       nm1.AddNamespace("oc", 
         "urn:Microsoft.Search.Query.Office.Context");
       string queryString = xmlQuery.SelectSingleNode(
         "//ns:QueryText", nm1).InnerText;
  
       XmlNamespaceManager nm2 = new XmlNamespaceManager(
         xmlQuery.NameTable);
       nm2.AddNamespace("msq", "urn:Microsoft.Search.Query");
       string domain = xmlQuery.SelectSingleNode(
         "/msq:QueryPacket/msq:Query", 
         nm2).Attributes.GetNamedItem("domain").Value;
       string queryId = xmlQuery.SelectSingleNode(
         "/msq:QueryPacket/msq:Query/msq:QueryId", 
         nm2).InnerText;
  
       MemoryStream stream = new MemoryStream();
       XmlTextWriter writer = new XmlTextWriter(stream, null);
       writer.Formatting = Formatting.Indented;
  
       // Compose the Query Response packet.
       writer.WriteStartDocument();
       writer.WriteStartElement("ResponsePacket", 
         "urn:Microsoft.Search.Response");
       // The providerRevision attribute can be used
       // to update the service.
       writer.WriteAttributeString("providerRevision", "1");
       writer.WriteStartElement("Response");
       // The domain attribute identifies the service 
       // that executed the query.
       writer.WriteAttributeString("domain", domain);
       writer.WriteElementString("QueryID", queryId);
  
       if (String.Compare("Eric", queryString, true) == 0)
       {
         writer.WriteStartElement("Range");
         writer.WriteStartElement("Results");
  
         // Begin Document element
         writer.WriteStartElement("Document", 
           "urn:Microsoft.Search.Response.Document");
         writer.WriteElementString("Title", "Eric Carter's Blog");
         writer.WriteStartElement("Action");
         writer.WriteStartElement("LinkUrl");
         writer.WriteAttributeString("fileExt", "htm");
         writer.WriteString("https://blogs.msdn.com/eric_carter");
         writer.WriteEndElement(); //LinkUrl
         writer.WriteEndElement(); //Action
         writer.WriteElementString("DisplayUrl", 
           "https://blogs.msdn.com/eric_carter");
         writer.WriteElementString("Description", 
           ".NET for Office, the blog of Eric Carter.");
         // Include an image
         writer.WriteStartElement("Media");
         writer.WriteAttributeString("type", "IMAGE");
         writer.WriteElementString("SrcUrl", 
           "https://ericca.members.winisp.net/eric.jpg");
         writer.WriteElementString("AltText", "Eric Carter");
         writer.WriteEndElement(); //Media
         writer.WriteEndElement(); //Document
  
         // Include additional text
         // End Document element
         // Begin Content element
         writer.WriteStartElement("Content", 
           "urn:Microsoft.Search.Response.Content");
         writer.WriteStartElement("HorizontalRule");
         writer.WriteEndElement(); //Horizontal rule
         writer.WriteElementString("P", 
           ".NET for Office Highlights");
         writer.WriteStartElement("HorizontalRule");
         writer.WriteEndElement(); //Horizontal rule
         writer.WriteStartElement("Heading");
         writer.WriteElementString("Text", "Top Articles");
         writer.WriteEndElement(); //Heading
         writer.WriteElementString("P", "Excel and UDFs");
         writer.WriteElementString("P", "VSTO 3.0 Features");
         writer.WriteStartElement("P");
         writer.WriteString("Using ");
         writer.WriteStartElement("Char");
         writer.WriteAttributeString("bold", "true");
         writer.WriteString(".NET");
         writer.WriteEndElement(); //Char
         writer.WriteString(" in Office.");
         writer.WriteEndElement(); //P
         writer.WriteElementString("P", "");
         writer.WriteEndElement(); //Content
  
         // Finish up.
         writer.WriteEndElement(); //Results
         writer.WriteEndElement(); //Range
       }
       writer.WriteElementString("Status", "SUCCESS");
       writer.WriteEndElement(); //Response
       writer.WriteEndElement(); //ResponsePacket
       writer.WriteEndDocument();
  
       writer.Flush();
  
       // Move the results into a string.
       stream.Position = 0;
       StreamReader reader = new StreamReader(stream);
       string result = reader.ReadToEnd();
       return result;
     }
   }
 }

After building the project, press Ctrl+F5 to run the project. A web browser window will appear with a page that you can use to invoke the web service. Copy the address from the address bar in the web browser (the address will be something like https://localhost:2139/Service1.asmx) as you will need this address to register the web service with Word.

Launch Word. Then, bring up Word’s Research task pane by choosing Research from the Review tab in the Proofing group. At the very bottom of the Research task pane is some text that says Research options. Click that text to get to the Research options dialog. Then click the Add Services button. The dialog shown below appears. In this dialog, paste the address to the Web service .asmx file. Then click the Add button.

image

When you click the Add button, Word calls the research service and invokes the Registration web method. The implementation of this method returns the block of XML shown here:

 <?xml version="1.0" encoding="utf-8" ?>
 <ProviderUpdate xmlns="urn:Microsoft.Search.Registration.Response">
   <Status>SUCCESS</Status>
   <Providers>
     <Provider>
       <Message>This is an example research library written in 
         Visual Studio 2008 with C#.</Message>
       <Id>{C37EE888-D74E-47e5-B113-BA613D87F0B2}</Id>
       <Name>My First Research Service</Name>
       <QueryPath>https://localhost:2139/Service1.asmx/
         Registration</QueryPath>
       <RegistrationPath>https://localhost:2139/Service1.asmx/
         Registration</RegistrationPath>
       <Type>SOAP</Type>
       <Services>
         <Service>
           <Id>{8DD063CA-94FC-4514-8D83-3B36B12432BE}</Id>
           <Name>My First Research Service in C#</Name>
           <Description>My First Research Service, created in C# 
             and Visual Studio 2008.</Description>
           <Copyright>(C) 2008</Copyright>
           <Display>On</Display>
           <Category>RESEARCH_GENERAL</Category>
         </Service>
       </Services>
     </Provider>
   </Providers>
 </ProviderUpdate>

Word then displays a dialog announcing the provider of the research service, as shown here.

image

Clicking Continue brings up a dialog showing details about the research service (as determined from the return value of Registration) shown below. Click Install to install the research service.

image

Clicking Install returns to the Research Options dialog shown below, which now has the newly added research service installed in the Research Sites category (since we returned RESEARCH_GENERAL in the Category element). Click OK to continue.

image

Now, you can type the text Eric in the Research task pane search box and drop down the list of sites to search to select My First Research Service in C#. Click the green arrow button to search. An alternative way to search for text is to type it in the document, select it, and then click it while holding down the Alt key. The research service is contacted, and the Query web method is called. The Query web method is passed XML from Word which is shown here.

 <QueryPacket xmlns="urn:Microsoft.Search.Query" revision="1" build="(12.0.6213)">
   <Query domain="{8DD063CA-94FC-4514-8D83-3B36B12432BE}">
     <QueryId>{98E522B1-5680-4371-8804-72F9FE283807}</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">eric</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>(12.0.6213)</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>eric</QueryText>
       <Keyword>
         <Word>eric</Word>
         <StemWord>eric</StemWord>
       </Keyword>
     </Keywords>
   </Query>
 </QueryPacket>

The implementation of the web method Query reads the input XML above and extracts from it the query text—the text searched for by the user. It also extracts the query id and domain, which are returned to Office in the response to the web method call. If the query text matches “Eric” then the Query method returns the response shown below. If the query text does not match “Eric” then the Query method returns the response shown below but omits everything within the Range element. The response uses a schema defined by Office for returning results—this schema is documented in the Research Service SDK, but you can get an idea for the types of results Office can render by looking at the picture below which shows what Word renders in the research pane for the XML shown below.

 <?xml version="1.0" ?>
 <ResponsePacket providerRevision="1" xmlns="urn:Microsoft.Search.Response">
   <Response domain="{8DD063CA-94FC-4514-8D83-3B36B12432BE}">
     <QueryID>{98E522B1-5680-4371-8804-72F9FE283807}</QueryID>
     <Range>
       <Results>
         <Document xmlns="urn:Microsoft.Search.Response.Document">
           <Title>Eric Carter's Blog</Title>
           <Action>
             <LinkUrl fileExt="htm">https://blogs.msdn.com/
               eric_carter</LinkUrl>
           </Action>
           <DisplayUrl>https://blogs.msdn.com/
               eric_carter</DisplayUrl>
           <Description>.NET for Office, the blog of Eric
                Carter.</Description>
           <Media type="IMAGE">
             <SrcUrl>https://ericca.members.winisp.net/
               eric.jpg</SrcUrl>
             <AltText>Eric Carter</AltText>
           </Media>
         </Document>
         <Content xmlns="urn:Microsoft.Search.Response.Content">
           <HorizontalRule />
           <P>.NET for Office Highlights</P>
           <HorizontalRule />
           <Heading>
             <Text>Top Articles</Text>
           </Heading>
           <P>Excel and UDFs</P>
           <P>VSTO 3.0 Features</P>
           <P>
             Using
             <Char bold="true">.NET</Char>
             in Office.
           </P>
           <P />
         </Content>
       </Results>
     </Range>
     <Status>SUCCESS</Status>
   </Response>
 </ResponsePacket>

image

This has been a brief introduction to how to get started creating research service in C# using Visual Studio. For more information about creating research services, search https://msdn.microsoft.com for the phrase “research services.” You will find an SDK (listed under Office 2003) which documents the schemas and provides additional sample research services.

Excerpted from the book Visual Studio Tools for Office 2007.