Skip to content

Instantly share code, notes, and snippets.

@jstemerdink
Created June 25, 2020 11:58
Show Gist options
  • Save jstemerdink/1206f28b8f34abb353cb50e2f65a08f1 to your computer and use it in GitHub Desktop.
Save jstemerdink/1206f28b8f34abb353cb50e2f65a08f1 to your computer and use it in GitHub Desktop.

"Filter" your commerce navigation

Read my blog here

Powered by ReSharper image

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using EPiServer;
using EPiServer.Commerce.Catalog.ContentTypes;
using EPiServer.Core;
using EPiServer.Find;
using EPiServer.Find.ClientConventions;
using EPiServer.Find.Cms;
using EPiServer.Find.Commerce;
using EPiServer.Logging;
using EPiServer.ServiceLocation;
public static class FindNavigationExtensions
{
private static readonly Lazy<IContentLoader> ContentLoader =
new Lazy<IContentLoader>(() => ServiceLocator.Current.GetInstance<IContentLoader>());
private static readonly Lazy<IClient> FindClient =
new Lazy<IClient>(() => ServiceLocator.Current.GetInstance<IClient>());
private static readonly ILogger Log = LogManager.GetLogger();
/// <summary>
/// Adds the segment conventions.
/// </summary>
/// <param name="conventionBuilder">The convention builder.</param>>
/// <exception cref="T:System.MemberAccessException">The <see cref="T:System.Lazy`1" /> instance is initialized to use the default constructor of the type that is being lazily initialized, and permissions to access the constructor are missing.</exception>
/// <exception cref="T:System.MissingMemberException">The <see cref="T:System.Lazy`1" /> instance is initialized to use the default constructor of the type that is being lazily initialized, and that type does not have a public, parameterless constructor.</exception>
/// <exception cref="T:System.InvalidOperationException">The initialization function tries to access <see cref="P:System.Lazy`1.Value" /> on this instance.</exception>
public static void AddSegmentConventions(this TypeConventionBuilder<ProductContent> conventionBuilder)
{
conventionBuilder
.IncludeField(x => x.MainCategoryRouteSegments())
.IncludeField(p => p.SubCategoryRouteSegments());
}
/// <summary>
/// Gets the catalog main nodes.
/// </summary>
/// <param name="catalogContent">The catalog.</param>
/// <returns>A List of nodes.</returns>
/// <exception cref="T:System.MemberAccessException">The <see cref="T:System.Lazy`1" /> instance is initialized to use the default constructor of the type that is being lazily initialized, and permissions to access the constructor are missing.</exception>
/// <exception cref="T:System.MissingMemberException">The <see cref="T:System.Lazy`1" /> instance is initialized to use the default constructor of the type that is being lazily initialized, and that type does not have a public, parameterless constructor.</exception>
/// <exception cref="T:System.InvalidOperationException">The initialization function tries to access <see cref="P:System.Lazy`1.Value" /> on this instance.</exception>
public static List<NodeContent> GetCatalogMainNodes(this CatalogContent catalogContent)
{
if (catalogContent == null)
{
return new List<NodeContent>();
}
List<NodeContent> mainNodes =
ContentLoader.Value.GetChildren<NodeContent>(contentLink: catalogContent.ContentLink).ToList();
try
{
List<string> segments = catalogContent.ContentLink.GetMainSegments();
return mainNodes.Where(n => segments.Contains(item: n.RouteSegment)).OrderBy(n => n.DisplayName).ToList();
}
catch (Exception exception)
{
Log.Error(message: exception.Message, exception: exception);
return mainNodes.ToList();
}
}
/// <summary>
/// Gets the catalog sub nodes.
/// </summary>
/// <param name="node">The catalog node.</param>
/// <returns>A List of nodes.</returns>
/// <exception cref="T:System.MemberAccessException">The <see cref="T:System.Lazy`1" /> instance is initialized to use the default constructor of the type that is being lazily initialized, and permissions to access the constructor are missing.</exception>
/// <exception cref="T:System.MissingMemberException">The <see cref="T:System.Lazy`1" /> instance is initialized to use the default constructor of the type that is being lazily initialized, and that type does not have a public, parameterless constructor.</exception>
/// <exception cref="T:System.InvalidOperationException">The initialization function tries to access <see cref="P:System.Lazy`1.Value" /> on this instance.</exception>
public static List<NodeContent> GetCatalogSubNodes(NodeContent node)
{
if (node == null)
{
return new List<NodeContent>();
}
List<NodeContent> subNodes =
ContentLoader.Value.GetChildren<NodeContent>(contentLink: node.ContentLink).ToList();
try
{
List<string> segments = node.ContentLink.GetSubSegments();
return subNodes.Where(n => segments.Contains(item: n.RouteSegment)).ToList();
}
catch (Exception exception)
{
Log.Error(message: exception.Message, exception: exception);
return subNodes.ToList();
}
}
/// <summary>
/// Gets the main segments.
/// </summary>
/// <param name="contentReference">The content reference.</param>
/// <returns>A List of main route segments.</returns>
/// <exception cref="T:System.MemberAccessException">The <see cref="T:System.Lazy`1" /> instance is initialized to use the default constructor of the type that is being lazily initialized, and permissions to access the constructor are missing.</exception>
/// <exception cref="T:System.MissingMemberException">The <see cref="T:System.Lazy`1" /> instance is initialized to use the default constructor of the type that is being lazily initialized, and that type does not have a public, parameterless constructor.</exception>
/// <exception cref="T:System.InvalidOperationException">The initialization function tries to access <see cref="P:System.Lazy`1.Value" /> on this instance.</exception>
public static List<string> GetMainSegments(this ContentReference contentReference)
{
ITypeSearch<ProductContent> query = FindClient.Value.Search<ProductContent>()
.Filter(x => x.Ancestors().Match(contentReference.ToString()))
.FilterOnCurrentMarket()
.FilterPriceAvailableForCurrentUser()
.FilterOnCurrentSite()
.FilterForVisitor();
return query.GetMainSegmentsForQuery();
}
/// <summary>
/// Gets the main segments.
/// </summary>
/// <typeparam name="T">The type to search for.</typeparam>
/// <param name="query">The query.</param>
/// <returns>A List of main route segments.</returns>
public static List<string> GetMainSegmentsForQuery<T>(this ITypeSearch<T> query) where T : ProductContent
{
query = query
.TermsFacetFor(x => x.MainCategoryRouteSegments(), facet => facet.Size = 25)
.Take(0)
.StaticallyCacheFor(new TimeSpan(0, 1, 0, 0));
IContentResult<T> results = query.GetContentResult(3600);
List<string> segments = results.TermsFacetFor(x => x.MainCategoryRouteSegments()).Terms
.Where(tc => tc.Count > 0).Select(tc => tc.Term).ToList();
return segments;
}
/// <summary>
/// Gets the sub segments.
/// </summary>
/// <param name="contentReference">The content reference.</param>
/// <returns>A List of sub route segments.</returns> /// <exception cref="T:System.MemberAccessException">The <see cref="T:System.Lazy`1" /> instance is initialized to use the default constructor of the type that is being lazily initialized, and permissions to access the constructor are missing.</exception>
/// <exception cref="T:System.MissingMemberException">The <see cref="T:System.Lazy`1" /> instance is initialized to use the default constructor of the type that is being lazily initialized, and that type does not have a public, parameterless constructor.</exception>
/// <exception cref="T:System.InvalidOperationException">The initialization function tries to access <see cref="P:System.Lazy`1.Value" /> on this instance.</exception>
public static List<string> GetSubSegments(this ContentReference contentReference)
{
ITypeSearch<ProductContent> query = FindClient.Value.Search<ProductContent>()
.Filter(x => x.Ancestors().Match(contentReference.ToString()))
.FilterOnCurrentMarket()
.FilterPriceAvailableForCurrentUser()
.FilterOnCurrentSite()
.FilterForVisitor();
return query.GetSubSegmentsForQuery();
}
/// <summary>
/// Gets the sub segments.
/// </summary>
/// <typeparam name="T">The type to search for.</typeparam>
/// <param name="query">The query.</param>
/// <returns>A List of sub route segments.</returns>
public static List<string> GetSubSegmentsForQuery<T>(this ITypeSearch<T> query) where T : ProductContent
{
query = query
.TermsFacetFor(x => x.SubCategoryRouteSegments(), facet => facet.Size = 25)
.Take(0)
.StaticallyCacheFor(new TimeSpan(0, 1, 0, 0));
IContentResult<T> results = query.GetContentResult(3600);
List<string> segments = results.TermsFacetFor(x => x.SubCategoryRouteSegments()).Terms
.Where(tc => tc.Count > 0).Select(tc => tc.Term).ToList();
return segments;
}
/// <summary>
/// Gets the main category route segments.
/// </summary>
/// <param name="product">The product.</param>
/// <returns>A list of the main category route segments.</returns>
/// <exception cref="T:System.MemberAccessException">The <see cref="T:System.Lazy`1" /> instance is initialized to use the default constructor of the type that is being lazily initialized, and permissions to access the constructor are missing.</exception>
/// <exception cref="T:System.MissingMemberException">The <see cref="T:System.Lazy`1" /> instance is initialized to use the default constructor of the type that is being lazily initialized, and that type does not have a public, parameterless constructor.</exception>
/// <exception cref="T:System.InvalidOperationException">The initialization function tries to access <see cref="P:System.Lazy`1.Value" /> on this instance.</exception>
public static List<string> MainCategoryRouteSegments(this ProductContent product)
{
List<string> categorieList = new List<string>();
IEnumerable<ContentReference> categories = product.GetCategories();
foreach (ContentReference categorySubNode in categories)
{
IEnumerable<NodeContent> mainCategories = ContentLoader.Value.GetAncestors(contentLink: categorySubNode)
.OfType<NodeContent>();
categorieList.AddRange(mainCategories.Select(c => c.RouteSegment).ToList());
}
return categorieList.Distinct().ToList();
}
/// <summary>
/// Gets the sub category route segments.
/// </summary>
/// <param name="product">The product.</param>
/// <returns>A list of the sub category route segments.</returns>
/// <exception cref="T:System.MemberAccessException">The <see cref="T:System.Lazy`1" /> instance is initialized to use the default constructor of the type that is being lazily initialized, and permissions to access the constructor are missing.</exception>
/// <exception cref="T:System.MissingMemberException">The <see cref="T:System.Lazy`1" /> instance is initialized to use the default constructor of the type that is being lazily initialized, and that type does not have a public, parameterless constructor.</exception>
/// <exception cref="T:System.InvalidOperationException">The initialization function tries to access <see cref="P:System.Lazy`1.Value" /> on this instance.</exception>
public static List<string> SubCategoryRouteSegments(this ProductContent product)
{
ReadOnlyCollection<NodeContent> categories = product.GetCategories().ToContent<NodeContent>();
return categories.Select(c => c.RouteSegment).Distinct().ToList();
}
/// <summary>
/// Convert a ContentReference collection to a list of IContent objects.
/// </summary>
/// <typeparam name="T">The type of the content items.</typeparam>
/// <param name="contentReferenceCollection">The link item collection.</param>
/// <returns>A List of IContent objects of type {T}</returns>
/// <exception cref="T:System.MemberAccessException">The <see cref="T:System.Lazy`1" /> instance is initialized to use the default constructor of the type that is being lazily initialized, and permissions to access the constructor are missing.</exception>
/// <exception cref="T:System.MissingMemberException">The <see cref="T:System.Lazy`1" /> instance is initialized to use the default constructor of the type that is being lazily initialized, and that type does not have a public, parameterless constructor.</exception>
/// <exception cref="T:System.InvalidOperationException">The initialization function tries to access <see cref="P:System.Lazy`1.Value" /> on this instance.</exception>
public static ReadOnlyCollection<T> ToContent<T>(this IEnumerable<ContentReference> contentReferenceCollection)
where T : IContent
{
List<T> contentList = new List<T>();
if (contentReferenceCollection == null)
{
return new ReadOnlyCollection<T>(list: contentList);
}
foreach (ContentReference contentReference in contentReferenceCollection)
{
T item;
if (ContentLoader.Value.TryGet(contentLink: contentReference, content: out item))
{
contentList.Add(item: item);
}
}
return new ReadOnlyCollection<T>(list: contentList);
}
/// <summary>
/// Gets the segments.
/// </summary>
/// <typeparam name="T">The type to search for.</typeparam>
/// <param name="contentReference">The content reference.</param>
/// <param name="segmentSelector">The segment selector.</param>
/// <returns>A List of route segments.</returns>
private static List<string> GetSegments<T>(this ContentReference contentReference, Expression<Func<T, IEnumerable<string>>> segmentSelector) where T : ProductContent
{
ITypeSearch<T> query = FindClient.Value.Search<T>()
.Filter(x => x.Ancestors().Match(contentReference.ToString()))
.FilterOnCurrentMarket()
.FilterPriceAvailableForCurrentUser()
.FilterOnCurrentSite()
.FilterForVisitor();
return query.GetSegmentsForQuery(segmentSelector);
}
/// <summary>
/// Gets the segments.
/// </summary>
/// <typeparam name="T">The type to search for.</typeparam>
/// <param name="query">The query.</param>
/// <param name="segmentSelector">The segment selector.</param>
/// <returns>A List of route segments.</returns>
private static List<string> GetSegmentsForQuery<T>(this ITypeSearch<T> query, Expression<Func<T, IEnumerable<string>>> segmentSelector) where T : ProductContent
{
Expression<Func<T, object>> segmentFilter = segmentSelector.ToUntyped();
query = query
.TermsFacetFor(fieldSelector: segmentSelector, facetRequestAction: facet => facet.Size = 25)
.Take(0)
.StaticallyCacheFor(new TimeSpan(0, 1, 0, 0));
IContentResult<T> results = query.GetContentResult(3600);
List<string> segments = results.TermsFacetFor(fieldSelector: segmentFilter).Terms
.Where(tc => tc.Count > 0)
.Select(tc => tc.Term).ToList();
return segments;
}
/// <summary>
/// Convert to an untyped expression.
/// </summary>
/// <typeparam name="TInput">The type of the input.</typeparam>
/// <typeparam name="TOutput">The type of the output.</typeparam>
/// <param name="expression">The expression.</param>
/// <returns>An Expression{Func{TInput, System.Object}}.</returns>
private static Expression<Func<TInput, object>> ToUntyped<TInput, TOutput>(this Expression<Func<TInput, TOutput>> expression)
{
// Add the boxing operation, but get a weakly typed expression
Expression converted = Expression.Convert(expression.Body, typeof(object));
// Use Expression.Lambda to get back to strong typing
return Expression.Lambda<Func<TInput, object>>(converted, expression.Parameters);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment