Extending the Coveo REST service in Coveo for Sitecore

Posted 08/07/2014 by Matt Schultz

Coveo has a powerful Javascript framework for developing client side web search user interfaces. Coveo for Sitecore uses this framework exclusively on the front end and provides a rich search experience out of the box. However, there are certain instances where you might want or even need to look up additional Sitecore items or perform other server side logic.

There's really no efficient way to run server side code directly from the Coveo Javascript framework because it would require an additional round trip. Aside from the performance issues, the coding would be fairly convoluted and require additional framework to implement and maintain.

I ran into this issue on a recent project. We wanted to leverage all the functionality that Coveo for Sitecore provided but we absolutely had to do some server side processing as well. After some thought, I realized that what I really needed was the ability to append to or transform the Coveo JSON before it was sent to the browser. That way I could add any data needed for the given request in the same round trip and seamlessly integrate with the Coveo for Sitecore model.

The natural choice to extend the REST service was a Sitecore pipeline. This allowed me to add or remove processing steps as needed and made the processing steps themselves resistant to change. If Coveo changed how it implemented the REST service itself, the process around initializing the pipeline could be updated leaving the pipeline steps unchanged. This actually happened during our upgrade from Coveo minor versions, proving this theory.

Here's how I did it.

First, Coveo's rest service in a Coveo for Sitecore install is located at:


<WebRoot>/Coveo/rest/Default.aspx

The first thing I did was to decompile their existing version and create my own _default class that extended CoveoRest.SitecoreRestService_Search.


public partial class _default : CoveoRest.SitecoreRestService_Search
{
...

I then referenced it in the Default.aspx page Inherits property.


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="Example.Coveo.rest._default" %>

The entry point into the rest service is the OnInit Method which looks something like this:


protected override void OnInit(EventArgs p_Args)
{
	SitecoreRestConfiguration.EnsureConfigIsChanged();
	base.BeforeProviderCreation += new EventHandler<BeforeProviderCreationEventArgs>(this.SitecoreRestService_Search_BeforeProviderCreation);

	RestSearchService service = this.CreateRestSearchService();
	RestQueryParameters rqp = this.LoadQueryParametersFromRequest();
	RestResponse restResponse = service.performQueryAndReturnJson(rqp);

	this.SendRestResponse(restResponse, rqp);
}

What I wanted to do was annotate the rest response in a highly extensible way. For that, I lifted the SendRestResponse method from the base class and moved it into my version of _default. Then I retrieved the response data, ran it through my pipeline method and sent it on to whoever was requesting it.


protected void SendRestResponse(RestResponse restResponse, RestQueryParameters rqp)
{
	WebUtilities.EnableHttpCompressionIfPossible();
	base.Response.StatusCode = restResponse.statusCode();

	var memoryStream = new MemoryStream();
	var netStream = new DotNetStreamAsJavaOutputStream(memoryStream);
	restResponse.bodyStreamer().apply(netStream);

	string body = RunPipeline(Encoding.UTF8.GetString(memoryStream.ToArray()), rqp);

	string str = base.Request.QueryString["callback"];
	if (!string.IsNullOrEmpty(str))
	{
		base.Response.ContentType = "application/x-javascript";
		base.Response.Write(str);
		base.Response.Write("(");
		base.Response.Write(body);
		base.Response.Write(")");
	}
	else
	{
		base.Response.ContentType = "application/json";
		base.Response.Write(body);
	}
	base.Response.End();
}
		

I actually ended up creating two separate pipelines, one for the entire response and one for each result. Below is my implementation of RunPipeline. I've left out some of the helper methods but I think they're all pretty self explanatory.


private string RunPipeline(string responseBody, RestQueryParameters rqp)
{

	JObject json = JObject.Parse(responseBody);

	JArray results = (JArray)json["results"];

	if (results.Count > 0)
	{

		string lang = GetLanguage(rqp.getCq());

		CoveoUri curi = ExampleCoveoUtility.ParseUri(results[0]["Uri"].ToString());

		CoveoRestResponsePipelineArgs facetPipelineArgs = new CoveoRestResponsePipelineArgs
		{
			FacetResults = (JArray)json["groupByResults"],
			Language = lang,
			ContextDB = Factory.GetDatabase(curi.Database)
		};

		CorePipeline.Run("coveoRestResponse", facetPipelineArgs);

		if (results != null)
		{
			foreach (JObject result in results)
			{
				result["clickUri"] = ReplaceHostWithCurrent( result["clickUri"].ToString());
				CorePipeline.Run("coveoRestResult", new CoveoRestResultPipelineArgs { Result = result, Language = lang });
			}
		}
	}
	
	string response = json.ToString();

	return response;
}

Here's what the config might look like:


<coveoRestResult>        
	<processor type="Example.Coveo.CoveoRestResult.PopulateItem, Example" />
</coveoRestResult>

<coveoRestResponse>
	<processor type="Example.Coveo.CoveoRestResponse.TranslateFacetsValues, Example" />
</coveoRestResponse>
  

So, if you're using Coveo for Sitecore but you need data or processing that isn't in the index this might be a solution to your problem.

Share:

Archive

Syndication