Binh Nguyen Thi
Jul 1, 2024
visibility 3270
star star star star star
(0 votes)

Optimizely Search and Navigation - Part 2 - Filter Tips

Introduction

Continuing from Part 1 – Search Tips, today I will share the next part – filter tips.

The platform versions used for this article are Optimizely CMS 12.27.x, Optimizely Customized Commerce 14.21.x, and EpiServer.Find 16.1.x.

How to Add a New Custom Filter

In the built-in Optimizely Search & Navigation, the default filters are as follows:

  • And Filter
  • Bool Filter
  • Exists Filter
  • Geo Distance Filter
  • Geo Distance Range Filter
  • Geo Polygon Filter
  • Has Child Filter
  • Ids Filter
  • Kilometers Filter
  • Nested Filter
  • Not Filter
  • Or Filter
  • Prefix Filter
  • Query Filter
  • Range Filter
  • Script Filter
  • Term Filter

These filters generate corresponding body text when sending requests to Elasticsearch through the Search & Navigation framework using JSON converters.

What if a filter exists in Elasticsearch but not in Search & Navigation, or an existing filter lacks required parameters? Can you create a new filter?

Yes, you can add new custom filters in Search & Navigation. Here's how to build a new Regular Expression filter.

Step 1: Create a New DTO Based on Filter Class

[JsonConverter(typeof(RegularExpressionFilterConverter))]
public class RegularExpressionFilter : Filter
{
    [JsonIgnore]
    public string Field { get; set; }

    [JsonProperty("value")]
    public string Value { get; set; }

    public RegularExpressionFilter(string field, string value)
    { 
        Field = field;
        Value = value;
    }
}

Step 2: Create a New JSON Converter for the New Filter

internal class RegularExpressionFilterConverter : CustomWriteConverterBase<RegularExpressionFilter>
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value is not RegularExpressionFilter)
        {
            writer.WriteNull();
            return;
        }
        var regularExpressionFilter = (RegularExpressionFilter)value;
        writer.WriteStartObject();
        writer.WritePropertyName("regexp");
        writer.WriteStartObject();
        writer.WritePropertyName(regularExpressionFilter.Field);
        serializer.Serialize(writer, regularExpressionFilter.Value);
        writer.WriteEndObject();
        writer.WriteEndObject();
    }
}

Step 3: Create a New Filter Method for Your Search

public static DelegateFilterBuilder MatchRegularExpression(this string value, string input)
{ 
   return new DelegateFilterBuilder((string field) => new RegularExpressionFilter(field, input));
}

Step 4: Apply the New Filter to Your Search

if (!string.IsNullOrEmpty(filterOptions.Q)){
    query = query.Filter(x => x.Name.MatchRegularExpression(filterOptions.Q));
}

The keyword for search in this example could be text within regular expression syntax. You can find syntax for ElasticSearch here.

How to Apply AND/OR for a Group of Sub Filters

Consider the following example:

You have a product content type with these properties:

[Display(Name = "On sale", GroupName = SystemTabNames.Content, Order = 50)]
public virtual bool OnSale { get; set; }

[Display(Name = "New arrival", GroupName = SystemTabNames.Content, Order = 55)]
public virtual bool NewArrival { get; set; }

[Display(Name = "Best seller", GroupName = SystemTabNames.Content, Order = 58)]
public virtual bool BestSeller { get; set; }

To find products that are on sale and/or new arrivals, you can:

Use AND/OR Operator in FilterExpression:

  • AND operator
query = query.Filter(x => x.OnSale.Match(true) & x.NewArrival.Match(true));
  • OR operator
query = query.Filter(x => x.OnSale.Match(true) | x.NewArrival.Match(true));

Use AND/OR Filter:

  • AND filter
var andFilter = new AndFilter();
andFilter.Filters.Add(new TermFilter(searchClient.GetFullFieldName("OnSale", typeof(bool)), true));
andFilter.Filters.Add(new TermFilter(searchClient.GetFullFieldName("NewArrival", typeof(bool)), true));
query = query.Filter(andFilter);
  • OR Filter
var orFilter = new OrFilter();
orFilter.Filters.Add(new TermFilter(searchClient.GetFullFieldName("OnSale", typeof(bool)), true));
orFilter.Filters.Add(new TermFilter(searchClient.GetFullFieldName("NewArrival", typeof(bool)), true));
query = query.Filter(orFilter);

Note that the field name understood by Elasticsearch is not the same as the field name in the Optimizely Content Type Model. Use the following method to get the indexed field name:

public static string GetFullFieldName(this IClient searchClient, string fieldName, Type type)
{
    if (type != null)
        return fieldName + searchClient.Conventions.FieldNameConvention.GetFieldName(Expression.Variable(type, fieldName));
    return fieldName;
}

Pros and Cons:

  • Using AND/OR Operator: Short and easy to use but not suitable for dynamically building filters.
  • Using AND/OR Filter: Longer code but allows for flexible filter building based on field names and input values.

How to Sort Search Results Based on a Set of Conditions

Search & Navigation allows sorting based on one or more fields:

query = query.OrderBy(x => x.Name).ThenByDescending(x => x.StartPublish);

Example Requirement: Display best seller products at the top, followed by new arrivals, and then on sale products.

You can achieve this by using boost matching:

query = query.BoostMatching(x => (x as GenericProduct).NewArrival.Match(true), 2);
query = query.BoostMatching(x => (x as GenericProduct).OnSale.Match(true), 3);
query = query.BoostMatching(x => (x as GenericProduct).BestSeller.Match(true), 4);

Issue: If a product is both on sale and a new arrival, it appears at the top even if it is not a best seller. The score of this product (5) is higher than that of a best seller (4).

Solution: Ensure best sellers are always on top by setting Boost value as following rule: Next boost value = total of all previous boost values + 1:

query = query.BoostMatching(x => (x as GenericProduct).NewArrival.Match(true), 2);
query = query.BoostMatching(x => (x as GenericProduct).OnSale.Match(true), 3);
query = query.BoostMatching(x => (x as GenericProduct).BestSeller.Match(true), 6);

Conclusion

These tips are based on my experience with Search & Navigation. I hope they help you implement similar features.

Enjoy coding!

Jul 01, 2024

Comments

error Please login to comment.
Latest blogs
Drag-and-Drop Reordering for Commerce Media Collection in Optimizely Commerce Connect

Optimizely Commerce Connect ships a polished asset editor for the CommerceMediaCollection  property on catalog entries. It lets editors add, remove...

Linh Doan Cuu | Aug 5, 2026

Order tabs with drag and drop V2

I earlier did a very simple Blazor version to be able to sort tabs with drag and drop. I wanted to update it a bit and also display how the tabs ar...

Per Nergård (MVP) | Aug 4, 2026

Optimizely : Missing Language Manager Gadget Fix in CMS 12 After Upgrading to .NET 10

Recently, while upgrading an Optimizely CMS solution to .NET 10, we came across an issue where the Language Manager gadget completely disappeared...

Madhu | Aug 2, 2026 |

Flat or Nested? Weighing Your Content Modeling Options in Optimizely SaaS CMS

When building a headless site on Optimizely SaaS CMS, one of the earliest and most critical design milestones your team will face is defining your...

Vipin Banka | Jul 31, 2026

From SDK to Core: Modernizing Optimizely Configured Commerce for .NET 8 and .NET 10

The .NET Framework 4.8 clock is running out for Configured Commerce customizations. Handled well, this deadline is not a chore it is the cleanest...

Vaibhav | Jul 29, 2026

Parallel Development in Optimizely CMS SaaS: Shifting to a Schema Migration Mindset

  Part 3 of the Parallel Development series. The branch-scoped push script from Part 1 works. Run it on a feature branch where you've touched a...

Vipin Banka | Jul 25, 2026