About the coveoPostItemProcessingPipeline Pipeline

The coveoPostItemProcessingPipeline can be used with items indexed by the Sitecore Search Provider to alter or duplicate them before they’re sent to the Coveo index. Adding a processor in the coveoPostItemProcessingPipeline pipeline is often an alternative to creating a computed field for an item (see Create Computed Fields).

This article explains how you can add a processor to the pipeline, followed by a code example.

Adding a Processor

You need to create a class that implements the IProcessor<CoveoPostItemProcessingPipelineArgs> interface and its Process method. This method receives a CoveoPostItemProcessingPipelineArgs object.

CoveoPostItemProcessingPipelineArgs objects encapsulate a Sitecore item and the corresponding Coveo index representation of the item (i.e., a CoveoIndexableItem). The CoveoPostItemProcessingPipelineArgs class lets you change the values of fields. You can also use the CoveoPostItemProcessingPipelineArgs class to index many items for a single Sitecore item, which can be required when using wildcard items in Sitecore (see Wildcards and Data Driven URLs).

If you want to add fields to the CoveoItem in the coveoPostItemProcessingPipeline pipeline, make sure to add them in the fieldMap section of Coveo.SearchProvider.Custom.config. If you don’t already have one, create it under defaultIndexConfiguration. It should follow the same syntax as the fieldMap section of the Coveo.SearchProvider.config file. If you fail to do this, your fields won’t be created in the index. Never modify the Coveo.SearchProvider.config directly, as doing so may cause unforeseen upgrading issues.

To know how to add fields to the fieldMap, see About the Coveo Search Provider Configuration File.

Finally, to have your code executed, you need to add a <processor> node that references your class in the <coveoPostItemProcessingPipeline> element of the Coveo.SearchProvider.Custom.config file.

CoveoPostItemProcessingPipelineArgs Properties

The CoveoPostItemProcessingPipelineArgs class has the following properties:

Name Type Description
Item IIndexable The original Sitecore item. It's typically used to retrieve field values.
CoveoItem CoveoIndexableItem The Coveo item currently processed by the pipeline.
IndexConfiguration CoveoIndexConfiguration The configuration of the index which defines how Coveo accesses its platform, targets its sources and indexes items. You shouldn't have to modify this property.
OutputCoveoItems List<CoveoIndexableItem> The list of items that will be indexed by Coveo, as Coveo for Sitecore only considers this list, and not the CoveoItem itself, after the pipeline is done. The list is initialized with CoveoItem. An empty list means that no items will be indexed, but this won't delete an existing item from the index.

CoveoIndexableItem Properties

The following properties can be edited on the CoveoIndexableItem type.

Name Type Description

BinaryData

Byte[]

The item body, represented in binary.

BinaryDataMimeType

string

The MIME type of the item binary data.

ClickableUri

string

The ClickableUri used to open a specific location when a search result is clicked.

FileName

string

The item filename, if the item is representing a file.

HasSubItems

bool

Whether the item has sub items or not.

Id

string

The item ID (string representation).

Metadata

Dictionary<string, object>

A dictionary containing all the item metadata, e.g. all the fields values.

ModifiedDate

DateTime

The item last modified date. You shouldn’t edit this.

Parent

CoveoIndexableItem

The item parent, if available.

ParentId

string

The item parent ID.

Path

string

The item path, e.g. /sitecore/content/Home.

Permissions

AccessRulesHierarchy

Data structure containing the security of the item. You should only edit this if you know what you’re doing.

PrintablePath

string

The PrintablePath used to show a friendly URL in a search result UI.

Title

string

The item title (also known as display name).

UniqueId

string

The item unique ID, when available.

Uri

string

The item Uri.

EXAMPLE

You want to create a custom processor to duplicate a CoveoItem. To do so, you would add a new CoveoIndexableItem to the OutputCoveoItems list, using a code snippet such as the following:

CoveoIndexableItem duplicateItem = new CoveoIndexableItem();
duplicateItem.Uri = p_Args.CoveoItem.Uri + "/copy";
duplicateItem.Permissions = p_Args.CoveoItem.Permissions;
duplicateItem.Title = String.Format("Duplicate of {0}", p_Args.CoveoItem.Title);
p_Args.OutputCoveoItems.Add(duplicateItem);

Upstream processors may have created copies of the original Sitecore item already. Prior to adding a code snippet like the one above, use the Re-Index Tree option on a target item in Sitecore and subsequently browse your indexed items by Indexed date to verify if copies of the targeted item are already being created in an upstream processor.

A Sample Processor

This example lives in the context of the Jetstream Sitecore demo, a travel aggregation website offering flights. Several flight items are available in the database and were indexed by Coveo for Sitecore.

The default output in a basic search results list looks like this:

FlightSearchResults

For each result, you can see the name of the item in Sitecore, which is the flight number followed by the cabin class (e.g., first class, business class, or economy class).

Out of context, the item title doesn’t tell much. However, by leveraging Flight item field values, you can easily create a custom processor that displays a better item title.

Data Structure

The Flight items shown above are all issued from the Flight template. The template contains a few fields such as:

  • flight number: Integer

  • class: Single-Line-Text

  • departure airport: Droplist with source on Airport Template

  • arrival airport: Droplist with source on Airport Template

The Airport template contains Airport items with this single field:

  • City: Single-Line-Text

Both the Flight and Airport template names are unique. These templates could have more fields, but for this example we’ll only use those listed above.

Setting Up the Visual Studio Project

Before you can implement the custom processor itself, you need a suitable project to add it to. You will need a C# project that targets .NET framework 4. Once you have this project, add the following references to it:

  • Coveo.AbstractLayer.dll

  • Coveo.Framework.dll

  • Coveo.SearchProviderBase.dll

  • Sitecore.ContentSearch.dll

  • Sitecore.Kernel.dll

Sitecore 7 and 8 These assemblies are located in the <SITECORE_INSTANCE_ROOT>\Website\bin\ folder.

Sitecore 9 and 10 These assemblies are located in the <SITECORE_INSTANCE_ROOT>\bin folder.

Implementing the Processor Class

Now that your Visual Studio project is ready, you can move on to implement the custom processor.

Create a new class that implements the IProcessor<CoveoPostItemProcessingPipelineArgs> interface as in the code sample below.

Skeleton of the Custom Processor Class

using System;
using Coveo.Framework.Processor;
using Coveo.SearchProvider.Pipelines;
using Sitecore;
using Sitecore.ContentSearch;
using Sitecore.Data;
using Sitecore.Data.Items;
namespace Samples.Pipelines
{
    public class FlightTitleProcessor : IProcessor<CoveoPostItemProcessingPipelineArgs>
    {
        public void Process(CoveoPostItemProcessingPipelineArgs p_Args)
        {
            throw new NotImplementedException();
        }
    }
}

Implementing the Process Method

The hardest task is to implement the Process method. Here, we’ll alter the item title to include the flight number, the departure city, and the arrival city.

Therefore, the flight with the title 9981 - Business would become 9981 - From Paris to Los Angeles.

Here is the complete implementation of the class.

public class FlightTitleProcessor : IProcessor<CoveoPostItemProcessingPipelineArgs>
{
    public void Process(CoveoPostItemProcessingPipelineArgs p_Args)
    {
        if (p_Args.Item is SitecoreIndexableItem) {
            Item item = ((SitecoreIndexableItem) p_Args.Item).Item;
            // This processing only makes sense for instances of the "Flight" template.
            if (item != null && item.TemplateName.Equals("Flight", StringComparison.OrdinalIgnoreCase)) {
                // Get the flight number
                string flightNumber = item["flight number"];
                // Get the linked airport items
                string departureAirportId = item["departure airport"];
                string arrivalAirportId = item["arrival airport"];
                string departureCity = GetAirportCity(item.Database, departureAirportId);
                string arrivalCity = GetAirportCity(item.Database, arrivalAirportId);
                if (!String.IsNullOrEmpty(flightNumber) &&
                        !String.IsNullOrEmpty(departureCity) &&
                        !String.IsNullOrEmpty(arrivalCity)) {
                    // Set the new title on the Coveo item
                    p_Args.CoveoItem.Title = String.Format("{0} - From {1} to {2}", flightNumber, departureCity, arrivalCity);
                }
            }
        }
    }
    private string GetAirportCity(Database p_Database, string p_AirportId)
    {
        string city = "";
        Item airport = p_Database.GetItem(p_AirportId);
        if (airport != null) {
            city = airport["city"];
        }
        return city;
    }
}

First, the Sitecore.Data.Items.Item instance is retrieved, as the item needs to be an instance of the Flight template and the Item class allows you to access the TemplateName property.

Then, the flight number and the airports are retrieved. For each airport, the GetAirportCity method returns the city name for the given airport. The Item.Database property is used to ensure that you get the airport from the same database as the flight item itself.

Finally, the .NET Framework System.String class Format method is used to modify the Title property of the CoveoItem object.

p_Args.CoveoItem.Title = String.Format("{0} - From {1} to {2}", flightNumber, departureCity, arrivalCity);

The Title of certain search result items might not be altered using the example above, for example, if the Sitecore item has a populated and indexed Title field (see About the Search Result Title Selection Sequence).

Enabling the Processor in Sitecore

The custom processor is ready to be used by Sitecore. Here is how you can add it to the configuration file.

  1. Copy the assembly containing the custom processor into the bin folder of the Sitecore website. Default location is <SITECORE_INSTANCE_ROOT>\website\bin.

  2. Open the Coveo.SearchProvider.Custom.config file in a text editor. The default file location is <SITECORE_INSTANCE_ROOT>\website\App_Config\Include\Coveo for Sitecore 7 and 8 instances, and <SITECORE_INSTANCE_ROOT>\App_Config\Include\Coveo for Sitecore 9 and 10 instances.

  3. Find the coveoPostItemProcessingPipeline element.

  4. Add a processor element that maps the implementation of the custom processor in the pipeline. For example, if you have created a Samples.dll file, add the following node in the coveoPostItemProcessingPipeline element.

    <processor type="Samples.Pipelines.FlightTitleProcessor, Samples" />
  5. Save the file and reindex Sitecore items.

The New Search Result Item Titles

Now, the results page shows the customized titles. Since you changed the title on the indexed items, a search with the paris keyword returns the flight items with paris in their title.

FinalFlightSearchResults

Conclusion

In this example, you learned how to alter the item title for a specific item type in Sitecore. It’s also possible to alter item field values, URLs, and the Quick View, though the latter requires much effort. Almost any field/property on the item can be customized.

You can also add as many custom processors as you want, but remember that the more you add, the slower the indexing gets.

Another Code Sample

The following code sample shows you how to index entirely virtual items (i.e. items that don’t really exist in Sitecore) based on existing items. You will need to adapt this code sample to make it work in your own environment.

using System.Collections.Generic;
using Coveo.AbstractLayer.RepositoryItem;
using Coveo.Framework.Processor;
using Coveo.SearchProvider.Pipelines;
using Sitecore.ContentSearch;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
namespace Coveo.Demos.Pipelines
{
    public class TranslatedItemPostProcessor : IProcessor<CoveoPostItemProcessingPipelineArgs>
    {
        public void Process(CoveoPostItemProcessingPipelineArgs p_Args)
        {
            // In this example, our goal is to manually translate a specific item from English to French, and index its translated version as a separate item.
            List<CoveoIndexableItem> itemsToAdd = new List<CoveoIndexableItem>();
            // We operate on the indexables in p_Args.CoveoOutputItems in case previous processors added new items. A reference to the original item is already in this list
            foreach (CoveoIndexableItem coveoIndexableItem in p_Args.OutputCoveoItems) {
                SitecoreIndexableItem indexableItem = p_Args.Item as SitecoreIndexableItem;
                IIndexableBuiltinFields builtInFields = indexableItem;
                if (indexableItem != null) {
                    Item item = indexableItem.Item;
                    // If the item being currently processed is of the expected type (template = Sample Item), we process it.
                    if (builtInFields.TemplateId.ToString().Equals("{76036F5E-CBCE-46D1-AF0A-4143F9B557AA}")) {
                        // If the 'Text' field has the expected value, it means that the current item is indeed the one that we are interested in.
                        Field textField = item.Fields["Text"];
                        if (textField.Value == "<p>This is a test!</p>") {
                            // The value of the original item's UniqueId property has the following format: sitecore://{database}/{item_id}?lang={language}&ver={version}.
                            // But our new item must have a different UniqueId value to be indexed by Coveo, so we change the values of its language and version parameters.
                            string newUniqueId = coveoIndexableItem.UniqueId.Replace("lang=en", "lang=fr").Replace("ver=1", "ver=2");
                            // To create a new item, we clone the original one and we change the properties and values that we are interested in.
                            CoveoIndexableItem newItem = new CoveoIndexableItem {
                                BinaryData = coveoIndexableItem.BinaryData,
                                BinaryDataMimeType = coveoIndexableItem.BinaryDataMimeType,
                                BinaryDataPath = coveoIndexableItem.BinaryDataPath,
                                ClickableUri = coveoIndexableItem.ClickableUri,
                                FileName = coveoIndexableItem.FileName,
                                HasSubItems = coveoIndexableItem.HasSubItems,
                                Id = coveoIndexableItem.Id,
                                IsDeletedItem = coveoIndexableItem.IsDeletedItem,
                                ModifiedDate = coveoIndexableItem.ModifiedDate,
                                Parent = coveoIndexableItem.Parent,
                                ParentId = coveoIndexableItem.ParentId,
                                Path = coveoIndexableItem.Path,
                                Permissions = coveoIndexableItem.Permissions,
                                PrintablePath = coveoIndexableItem.PrintablePath,
                                Title = "Item Exemple",
                                UniqueId = newUniqueId,
                                Metadata = new Dictionary<string, object>(coveoIndexableItem.Metadata)
                            };
                            // Here, we:
                            // -Manually translate the original title to French.
                            // -Change the value of a few metadata fields to reflect the differences in our translated item.
                            newItem.SetMetadata("Text", "Ceci est un test!");
                            newItem.SetMetadata("Language", "fr");
                            newItem.SetMetadata("Languages", "fr");
                            newItem.SetMetadata("Version", "2");
                            // Since we are using a post-processing pipeline, computed fields such as ParsedLanguage have already been processed.
                            // So here we change its value to 'french'.
                            newItem.SetMetadata("parsedlanguage", "french");
                            // OutputCoveoItems always contains the original item as its first item. It's actually the same instance of CoveoIndexableItem as in p_Args.CoveoItem, so if you want to
                            // modify the original item, you can do it either through p_Args.CoveoItem or p_Args.OutputCoveoItems. We add the new item to the output items.
                            itemsToAdd.Add(newItem);
                        }
                    }
                }
            }
            p_Args.OutputCoveoItems.AddRange(itemsToAdd);
        }
    }
}
What's Next for Me?