Read my blog here
Instantly share code, notes, and snippets.
Created
June 25, 2020 11:58
-
Star
(1)
1
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save jstemerdink/1206f28b8f34abb353cb50e2f65a08f1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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