Skip to content

Instantly share code, notes, and snippets.

@jstemerdink
Last active January 5, 2023 14:07
Show Gist options
  • Save jstemerdink/208cba7eb16b4589930f740804e2c24b to your computer and use it in GitHub Desktop.
Save jstemerdink/208cba7eb16b4589930f740804e2c24b to your computer and use it in GitHub Desktop.

Create Find facets based on attributes

Read my blog here

/// <summary>
/// Class FacetAttribute. This class cannot be inherited.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class FacetAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="FacetAttribute"/> class.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="amount">The amount.</param>
/// <param name="facetType">Type of the facet.</param>
public FacetAttribute(int index, int amount, FacetType facetType)
{
Index = index;
Amount = amount;
FacetType = facetType;
}
/// <summary>
/// Initializes a new instance of the <see cref="FacetAttribute"/> class.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="facetType">Type of the facet.</param>
public FacetAttribute(int index, FacetType facetType)
{
Index = index;
Amount = 10;
FacetType = facetType;
}
/// <summary>
/// Initializes a new instance of the <see cref="FacetAttribute"/> class.
/// </summary>
/// <param name="index">The index.</param>
public FacetAttribute(int index)
{
Index = index;
Amount = 10;
FacetType = FacetType.TermsFacetFor;
}
/// <summary>
/// Initializes a new instance of the <see cref="FacetAttribute"/> class.
/// </summary>
public FacetAttribute()
{
Index = 0;
Amount = 10;
FacetType = FacetType.TermsFacetFor;
}
/// <summary>
/// Gets the index.
/// </summary>
/// <value>The index.</value>
public int Index { get; }
/// <summary>
/// Gets the amount of facets to return.
/// </summary>
/// <value>The amount.</value>
public int Amount { get; }
/// <summary>
/// Gets the type of the facet.
/// </summary>
/// <value>The type of the facet.</value>
public FacetType FacetType { get; }
/// <summary>
/// Gets a value indicating whether the property is used as a facet in EPiServer Find.
/// </summary>
/// <value><c>true</c> if [used as a facet in EPiServer Find]; otherwise, <c>false</c>.</value>
public static bool Facet
{
get
{
return true;
}
}
}
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
using EPiServer.Find;
using EPiServer.Find.Api.Facets;
public static class FacetExtensions
{
/// <summary>
/// Adds facets to a Find query base on an attribute.
/// </summary>
/// <typeparam name="T">The content type to add the facets for.</typeparam>
/// <param name="query">The query.</param>
/// <returns>The ITypeSearch{T} with facets.</returns>
public static ITypeSearch<T> AddFacets<T>(this ITypeSearch<T> query)
where T : IContent, new()
{
IEnumerable<PropertyInfo> propertyInfoList = GetFacetedPropertiesSortedByIndex<T>();
foreach (PropertyInfo propertyInfo in propertyInfoList)
{
try
{
FacetAttribute facetAttribute =
Attribute.GetCustomAttribute(element: propertyInfo, typeof(FacetAttribute)) as FacetAttribute;
if (facetAttribute == null)
{
continue;
}
ParameterExpression expParam = Expression.Parameter(typeof(T), "x");
MemberExpression expProp = Expression.Property(expression: expParam, property: propertyInfo);
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
dynamic expression = Expression.Lambda(delegateType: delegateType, body: expProp, expParam);
switch (facetAttribute.FacetType)
{
case FacetType.TermsFacetFor:
query = TypeSearchExtensions.TermsFacetFor(
query,
expression,
FacetRequestActionForField(propertyInfo: propertyInfo, size: facetAttribute.Amount));
break;
case FacetType.TermsFacetForWordsIn:
query = TypeSearchExtensions.TermsFacetForWordsIn(query, expression, facetAttribute.Amount);
break;
default:
query = TypeSearchExtensions.TermsFacetFor(
query,
expression,
FacetRequestActionForField(propertyInfo: propertyInfo, size: facetAttribute.Amount));
break;
}
}
catch (Exception)
{
}
}
return query;
}
/// <summary>
/// Gets the facet values.
/// </summary>
/// <typeparam name="T">The content type to retrieve the facet values for.</typeparam>
/// <param name="contentResult">The content result.</param>
/// <returns>A Dictionary{System.String, IEnumerable{TermCount}}.</returns>
public static Dictionary<string, IEnumerable<TermCount>> GetFacetValues<T>(
this IHasFacetResults<T> contentResult)
where T : IContent, new()
{
ConcurrentDictionary<string, IEnumerable<TermCount>> facetResults = new();
IEnumerable<PropertyInfo> propertyInfoList = GetFacetedPropertiesSortedByIndex<T>();
foreach (PropertyInfo propertyInfo in propertyInfoList)
{
try
{
FacetAttribute facetAttribute =
Attribute.GetCustomAttribute(element: propertyInfo, typeof(FacetAttribute)) as FacetAttribute;
if (facetAttribute == null)
{
continue;
}
ParameterExpression expParam = Expression.Parameter(typeof(T), "x");
MemberExpression expProp = Expression.Property(expression: expParam, property: propertyInfo);
Expression conversion = Expression.Convert(expression: expProp, typeof(object));
Expression<Func<T, object>> expression = Expression.Lambda<Func<T, object>>(body: conversion, expParam);
IEnumerable<TermCount> termCounts = contentResult.TermsFacetFor(fieldSelector: expression);
facetResults.AddOrUpdate(
key: propertyInfo.Name,
addValue: termCounts,
(s, oldValue) => oldValue.Concat(second: termCounts));
}
catch (Exception)
{
}
}
Dictionary<string, IEnumerable<TermCount>> facetResultsDictionary = facetResults.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value,
comparer: facetResults.Comparer);
return facetResultsDictionary;
}
/// <summary>
/// Facets the request action for field.
/// </summary>
/// <param name="fieldName">Name of the field.</param>
/// <param name="size">The size.</param>
/// <returns>The Action{TermsFacetRequest{&gt;}.</returns>
private static Action<TermsFacetRequest> FacetRequestActionForField(string fieldName, int size)
{
return x =>
{
x.Field = fieldName;
x.Size = size;
};
}
/// <summary>
/// Facets the request action for field.
/// </summary>
/// <param name="propertyInfo">The property information.</param>
/// <param name="size">The size.</param>
/// <returns>The Action{TermsFacetRequest{&gt;}.</returns>
private static Action<TermsFacetRequest> FacetRequestActionForField(PropertyInfo propertyInfo, int size)
{
return FacetRequestActionForField(fieldName: propertyInfo.Name, size: size);
}
/// <summary>
/// Gets the properties that has the FacetAttribute sorted by the index.
/// </summary>
/// <typeparam name="T">The type to get the properties for.</typeparam>
/// <returns>An IEnumerable{PropertyInfo}.</returns>
private static IEnumerable<PropertyInfo> GetFacetedPropertiesSortedByIndex<T>()
where T : IContent
{
return GetFacetedPropertiesSortedByIndex(typeof(T));
}
/// <summary>
/// Gets the properties that has the FacetAttribute sorted by the index.
/// </summary>
/// <param name="type">The type to get the properties for.</param>
/// <returns>An IEnumerable{PropertyInfo}.</returns>
private static IEnumerable<PropertyInfo> GetFacetedPropertiesSortedByIndex(Type type)
{
PropertyInfo[] allProperties = type.GetProperties().Where(predicate: HasAttribute<FacetAttribute>)
.Select(
x => new
{
Property = x,
Attribute = (FacetAttribute)Attribute.GetCustomAttribute(
element: x,
typeof(FacetAttribute),
true)
}).OrderBy(x => x.Attribute?.Index ?? -1).Select(x => x.Property).ToArray();
return allProperties;
}
/// <summary>
/// Determines whether the specified property has the specified attribute.
/// </summary>
/// <typeparam name="T">The attribute type.</typeparam>
/// <param name="propertyInfo">The propertyInfo.</param>
/// <returns><c>true</c> if the specified self has attribute; otherwise, <c>false</c>.</returns>
private static bool HasAttribute<T>(PropertyInfo propertyInfo)
where T : Attribute
{
T attr = default;
try
{
attr = (T)Attribute.GetCustomAttribute(element: propertyInfo, typeof(T));
}
catch (Exception)
{
}
return attr != null;
}
}
public enum FacetType
{
TermsFacetForWordsIn,
TermsFacetFor
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment