GSA (Google Search Appliance) Suggest using C# and jQuery

The Google Search Appliance (GSA) is a search box (a server) that you buy which contains basically the complete search engine from Google. Using this search box allows you to apply the magical Google search results on your intranet, extranet or internet.

The GSA comes with a fully customizable frontend so it will act as a stand-alone machine. But in one of my recent projects we integrated the search results into the customer internet site using a ListView that reads search results in XML format from the GSA and formats it to nice HTML.

Furthermore we implemented the Google Query Suggestion Service on the search box, allowing us to display search suggestions:

GSA Suggestions

GSA Suggestions

Here is what you need. First of all you need to use jQuery. Then you need to download the Ajax Autocomplete for jQuery that implements the autocomplete feature for any input box. The Autocomplete Javascript reads JSON from an .ashx page. So our task is to create an .ashx page that reads suggestions from the GSA and returns them as JSON.

The HttpHandler looks like this (code is sample code. You should apply your own error handling and comments):

namespace GoogleSearchAppliance
{
  public class Suggest : IHttpHandler
  {
    public bool IsReusable
    {
      get { return true; }
    }

    public void ProcessRequest(HttpContext context)
    {
      if (string.IsNullOrEmpty(context.Request.QueryString[_QUERY_PARAM]))
        throw new Exception(string.Format("Could not find parameter '{0}'", _QUERY_PARAM));
     
      // Get the suggestion word from the parameter
      string suggestiveWord = context.Request.QueryString[_QUERY_PARAM];
      // Create an URL to the GSA
      UrlString suggestionUrl = SuggestionUrl(suggestiveWord);
      // Call the GSA and get the GSA result as a string
      string page = GetPageAsString(suggestionUrl);
      // Convert the GSA result to Json
      string jSonResult = ConvertToJson(page);
      // Return the JSON
      context.Response.Write(jSonResult);
      context.Response.End();
    }

    private string SuggestionUrl(string suggestiveWord)
    {
       // You should modify this line to connect to your
       // own GSA, using the correct collection and frontend
       return "http://myGSAurl/suggest?site=default_collection&client=default_frontend&access=p&format=rich&q" + suggestiveWord;
    }

    private string GetPageAsString(string address)
    {
      // Add your own error handling here
      HttpWebRequest request = WebRequest.Create(address) as HttpWebRequest;
      using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
      {
        StreamReader reader = new StreamReader(response.GetResponseStream());
        return reader.ReadToEnd();
      }
    }

    private string ConvertToJson(string gsaSuggestResult)
    {
      bool isFirst = true;
      StringBuilder sb = new StringBuilder();
      sb.Append("{ query:");
      foreach (string token in ParseGsaInput(gsaSuggestResult))
      {
        if (isFirst)
        {
          sb.AppendFormat("'{0}', suggestions:[", token.Trim());
          isFirst = false;
        }
        else
        {
          sb.AppendFormat("'{0}',", token.Trim());
        }
      }
      sb.Remove(sb.Length-1, 1);
      sb.Append(@"]}");
      return sb.ToString();
    }
   
    private IEnumerable<string> ParseGsaInput(string gsaSuggestResult)
    {
      gsaSuggestResult = gsaSuggestResult.Replace("[", "").Replace("]", "").Replace("\"", "");
      return gsaSuggestResult.Split(',');
    }

    private const string _QUERY_PARAM = "query";
  }
}

With the HttpHandler in place it is really (really) easy to hook up the code on the input box. Remember to include jQuery and the jquery.autocomplete.js on your page. This is an example code where I assume that the HttpHandler I created is called Suggest.ashx and is placed in the root of my project:

<script language="javascript" type="text/javascript">
  var options, a;
  $(function() {
    options = { serviceUrl: '/Suggest.ashx'};
    a = $jQuery('.metaInput').autocomplete(options);
  });
</script>
<asp:TextBox ID="gssQuery" runat="server" size="31" CssClass="metaInput" Text="Google Site Search"/>

The Autocomplete has some CSS that is applied:
.autocomplete-w1 { position:absolute; top:0px; left:0px; margin:6px 0 0 6px; /* IE6 fix: */ _background:none; _margin:1px 0 0 0; }
.autocomplete { border:1px solid #999; background:#FFF; cursor:default; text-align:left; max-height:350px; overflow:auto; margin:-8px 6px 6px -9px; /* IE6 specific: */ _height:350px;  _margin:0; _overflow-x:hidden; }
.autocomplete .selected { background:#F0F0F0; }
.autocomplete div { padding:2px 5px; white-space:nowrap; overflow:hidden; }
.autocomplete strong { font-weight:normal; color:#CC3333; }

The .ashx extension – Writing your own HttpHandler

Have you noticed how Sitecore serves images using an .ashx extension? A .ashx file is a HttpHandler. A HttpHandler is kind of a lightweight aspx page, as the HttpHandler only deals with the HttpContext - for example there is no page information. This makes it a great tool for implementing providers for images, xml, rss feeds or other stuff that can be generated using parameters only.

The .ashx file implements the System.Web.IHttpHandler (or the System.Web.IHttpAcynchandler for async calls – I’ll only show the first one), which contains only 1 property (IsReusable) and one function (ProcessRequest()). The ProcessRequest() function have one parameter only, the HttpContext to read and write data to.

My Visual Studio have no default creation of .ashx extensions, so I’ll have to make one manually. I create a new file with the .ashx extension and adds the following line:

<% @ WebHandler language="C#" class="MyNamespace.MyClass" codebehind="mycodebehind.cs" %>

Then I have to create a mycodebehind.cs file and add a class called MyNamespace.MyClass. This class should implement the IHttpHandler interface:

using System.Web;

namespace MyNamespace
{
  public class MyClass : IHttpHandler
  {
    public bool IsReusable
    {
      get { return true; }
    }

    public void ProcessRequest(HttpContext context)
    {
      // Here goes all of my code
    }
  }
}

Now my ProcessRequest can serve anything I like. I can reteieve paramaters from the Request and write to the Response:

public void ProcessRequest(HttpContext context)
{
  string parameter = context.Request.Params["myparameter"];
  context.Response.Write(parameter);
}

Or I can output a file (like Sitecore does):

public void ProcessRequest(HttpContext context)
{
  System.IO.MemoryStream ms = new System.IO.MemoryStream();
  Image image = SomeFunctionGeneratingAnImageForme();
  image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
  context.Response.ContentType = "image/jpeg";
  context.Response.OutputStream.Write(ms.GetBuffer(), 0, Convert.ToInt32(ms.Length));
}

I’ve noticed how Sitecore registers theit HttpHandlers in the web.config. You really don’t have to do that (even if Microsoft says so). You can call them directly. But if you wish to do so, you should read this article on how to register a HttpHandler.

Follow

Get every new post delivered to your Inbox.

Join 92 other followers