My recent work with Sitecore has involved getting to spend some time customizing the search results for editors. One task I wanted to accomplish was to improve the standard DateRange facet that is supplied with Sitecore. I had two issues I wanted to solve; I wanted a facet that was:

  • More granular. The existing facet has values such as This Week, This Month, This Year. I wanted editors to have more options here, for example a facet to show items “3-6 months old”.
  • A better display. The standard facet displays its values in a rather user-unfriendly manner, e.g. “thisweek”. I would prefer “This week”. The facets used in Search Results are tied to items underneath /System/Settings/Buckets/Facets. I created a new item here, called “Friendly Date Range”, that I would use in place of “Date Range”. I associated this with a field I would create, to be called friendlydaterange.

Now, the interesting thing about the existing daterange field is that it is not tied to an existing field in the index, it uses a Virtual Field. You can see how daterange is defined in the default index configuration here:

1
2
3
4
<virtualFieldProcessors hint="raw:AddVirtualFieldProcessor">
<virtualFieldProcessor fieldName="daterange" type="Sitecore.ContentSearch.VirtualFields.DateRangeFieldProcessor, Sitecore.ContentSearch" />
... other fields ...
</virtualFieldProcessors>

I had a good look around this class to determine how these fields are created, and thought I would summarise the interfaces that you will need to implement if you want to implement your own virtual field.

Virtual Fields

A virtual field is a field in the search index that doesn’t actually exist in the physical index. Instead, it is computed at search run-time, and will typically use one or more backing fields that do exist within the index to create an entirely new field. Note that this is different to ComputedIndexFields which are computed when creating the index and are stored in the index.

Why is a virtual field useful? Well it allows you to have a value of the field is dependent on when the search is run, rather than when the index was made; this is great for field values that are date and time dependent.

In the case of daterange - the actual index stores the absolute creation date of an item under __smallcreateddate. This value is set and doesn’t change. However, the daterange virtual field is used to turn this absolute value of, say, 20150803 into a relative one of thismonth. This new value obviously cannot be stored directly in the index itself, as it will quickly become out of date!

The virtual field works by implementing several methods that are called during the execution of the search. These are used to read and alter the fields of a document that come back with a search, as well as altering the outgoing query when a search needs to be made using the virtual field. There is also full support for faceting on virtual fields.

Let’s look at how the interfaces are implemented. The DateRangeFieldProcessor shown above implements two interfaces, IFieldQueryTranslator and IVirtualFieldProcessor. Here is what each implementation of these is required to do for a virtual field to work. The following was applied to Sitecore 7.2, but should be good for versions following that too.

IFieldQueryTranslator

The IFieldQueryTranslator has two methods and a property. Here’s what they do:

1
string FieldName { get; }

Simply the field name your virtual field will use.

1
TranslatedFieldQuery TranslateFieldQuery(string fieldName, object fieldValue, ComparisonType comparison, FieldNameTranslator fieldNameTranslator);

Takes the query from the search, which will include your virtual field being queried on, e.g. daterange:thismonth. Here you convert that query into a query on the real backing field(s), e.g. A range query on __smallcreateddate for values that fall in this current month.

This is only called if the fieldName passed in, matches the FieldName property, so it will only get called if your virtual field has been searched on.

1
IDictionary<string, object> TranslateFieldResult(IDictionary<string, object> fields, FieldNameTranslator fieldNameTranslator);

When the results of the query come back from the index, you will want to inject the value for your virtual field into the document. Typically you would inspect the real backing field(s), determine what the value of your virtual field would be, and add it to the fields dictionary to return.

e.g. The document contains a value that falls within the current month in __smallcreateddate, so “thismonth” is added to daterange in the fields dictionary.

IVirtualFieldProcessor

The IVirtualFieldProcessor has two methods to add faceting support. Here’s what they do:

1
GetFacetsArgs TranslateFacetQuery(GetFacetsArgs args);

When a faceted search is made, you can inject a FacetQuery into the search for the real backing field(s) your virtual field uses. This is so that when the results come back from the index, you’ll have that facet data to use to create the facet data for your virtual field.

1
IDictionary<string, ICollection<KeyValuePair<string, int>>> TranslateFacetResult(ProcessFacetsArgs args);

When the results come back from the index, you’ll be given a collection of all of the facets and their values. At this point you can take the facet results for the backing field(s) you are using, and process them to create your own facet results.

For example, if with TranslateFacetQuery you added a FacetQuery for __smallcreateddate, you can iterate through this facet result and sum up all of the values that fall within the current month to create your own facet result for “thismonth”.

The virtual facet result is added to the collection, and the backing field results are removed as they are no longer needed.

Summary

That’s all you need to do to implement a virtual field. Please leave a comment if there is something I’ve missed or if you have any questions.