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{>}.</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{>}.</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; } }