Implementing a Virtual Field in Sitecore
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
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:
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.
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,
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 has two methods and a property. Here’s what they do:
Simply the field name your virtual field will use.
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.
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 has two methods to add faceting support. Here’s what they do:
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.
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
__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.
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.