This post describes how it's possible to customise how best bets behave in Episerver Find. It uses an example of matching best bets for specific Episerver locales.

By default Episerver only allows editors to define best bets for site(s) and/or language(s) defined for your site. This works well in most cases, however in scenarios where a site is set up to serve different countries using localisation of the same language it may not work as expected.

For example in the following scenario Episerver localisation can be used to define different country sites:

  • site.com/en-GB/ - Great Britain
  • site.com/en-AU/ - Australia
  • site.com/en-US/ - United States

The works fine but from Find's perspective this is all the same site (site.com) and the same Find language (en) so it's not possible to define a best bet for example for Australia only or the UK only as it can only be defined for site.com in English.

This post describes a possible way to define best bets for specific locales in Episerver Find. Best bet matching in Find is handled by the implementation of IPhraseCriterion. As of Find v12.5.2 it's possible to override or intercept the implementation of IPhraseCriterion.

For this post I am going to implement a convention as follows: If the best bet ends with #[locale] then only apply it for the described locale if the user is browsing in that locale. For example if a user defines the following best bet: Snag #en-AU then it will match the term "Snag" only browsing in the en-AU locale. While Sausage #en-GB would match "Sausage" if browsing in the en-GB locale. 

To achieve this the default IPhraseCriterion implementation needs to be replaced with a custom LocaleAwarePhraseCriterion that implements IPhraseCriterion:

using EPiServer.ServiceLocation;
using EPiServer.Framework.Initialization;
using EPiServer.Find.Framework.BestBets;
using EPiServer.Framework;
namespace BestBetExtension.Business.CustomPhraseCriterion
{
[InitializableModule]
[ModuleDependency(typeof(ServiceContainerInitialization))]
[ModuleDependency(typeof(EPiServer.Find.Framework.FrameworkInitializationModule))]
public class ReplacePhraseCriterionInit : IConfigurableModule
{
public void ConfigureContainer(ServiceConfigurationContext context)
{
context.StructureMap().Configure(x => { x.For<IPhraseCriterion>().Use<LocaleAwarePhraseCriterion>(); });
}
public void Initialize(InitializationEngine context) { }
public void Uninitialize(InitializationEngine context) { }
}
}

The new LocaleAwarePhraseCriterion implementation checks the current locale and ensures the locale matches the defined best bet phrase:

using EPiServer.Find.Framework.BestBets;
using EPiServer.Globalization;
using System;
using System.Globalization;
using System.Linq;
namespace BestBetExtension.Business.CustomPhraseCriterion
{
public class LocaleAwarePhraseCriterion : DefaultPhraseCriterion, IPhraseCriterion
{
private const string localeIdenifier = "#";
public override bool Match(string searchPhrase)
{
if (string.IsNullOrWhiteSpace(searchPhrase) || (Phrase == null))
{
return false;
}
if (Phrase.Contains(localeIdenifier))
{
// Get current Episerver locale
CultureInfo currentCulture = ContentLanguage.PreferredCulture;
string languageBranch = currentCulture.Name;
// Get matches for the current culture
string[] bestBetPhrases = ParsePhrasesForLocale(Phrase, languageBranch);
string[] searchPhrases = ParsePhrases(searchPhrase);
return bestBetPhrases.Any<string>(p => searchPhrases.Contains<string>(p, StringComparer.OrdinalIgnoreCase));
}
return base.Match(searchPhrase);
}
internal static string[] ParsePhrases(string value)
{
if (!string.IsNullOrWhiteSpace(value))
{
string[] separator = new string[] { "," };
return (from e in value.Split(separator, StringSplitOptions.RemoveEmptyEntries)
where !string.IsNullOrWhiteSpace(e)
select e.Trim()).ToArray<string>();
}
return new string[0];
}
internal static string[] ParsePhrasesForLocale(string value, string locale)
{
string localeHash = localeIdenifier + locale;
// Parse out locales that do not match
var phrases = ParsePhrases(value);
if (phrases.Length > 0)
{
return (from e in phrases
where e.EndsWith(localeIdenifier + locale, StringComparison.OrdinalIgnoreCase)
select e.Trim().Replace(localeHash, string.Empty).Trim()).ToArray();
}
return phrases;
}
}
}

This allows Find administrators to hashtag their best bets when setting them up (click for full size):

Conclusion

Using this approach its possible to extend how best bets are matched using this approach and gives developers more control over the Find implementation which allows for bespoke / specific best bet matches if required.  

This is a very simple/quick implementation that allows users to implement best bets for individual locales but could be extended to work any you wished. The code was written as a proof of concept and can be tailored to specific needs so please don’t treat it as production ready!


Comments

Recommendations for you