A generic TermsFacetFor solution

Those of you who know me a bit better know that I hate doing the same thing over and over again.
When you do a TypedSearch in EPiServer Find you will have to add your facets to the query. If you have multiple types you might end up with a switch, separate queries for different types, … So I wanted a solution where I can add an attribute to a property and add a facet to the query for each property that has the attribute. This way I can write a generic TypedSearch query.

So here’s how you could do that:

/// <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>ITypeSearch&lt;T&gt;.</returns>
        public static ITypeSearch<T> AddFacets<T>(this ITypeSearch<T> query) where T : IContent
        {
            foreach (PropertyInfo propertyInfo in GetPropertiesSortedByIndex<T>())
            {
                FacetAttribute facetAttribute = Attribute.GetCustomAttribute(propertyInfo, typeof(FacetAttribute)) as FacetAttribute;

                if (facetAttribute == null)
                {
                    continue;
                }

                ParameterExpression expParam = Expression.Parameter(typeof(T), "x");
                MemberExpression expProp = Expression.Property(expParam, propertyInfo);
                Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
                dynamic expression = Expression.Lambda(delegateType, expProp, expParam);

                try
                {
                    switch (facetAttribute.FacetType)
                    {
                        case FacetType.TermsFacetFor:
                            query = TypeSearchExtensions.TermsFacetFor(query, expression);
                            break;

                        case FacetType.TermsFacetForWordsIn:
                            query = TypeSearchExtensions.TermsFacetForWordsIn(query, expression);
                            break;

                        default:
                            query = TypeSearchExtensions.TermsFacetFor(query, expression);
                            break;
                    }

                }
                catch (Exception)
                {
                }
            }

            return query;
        }

This extension gets the properties that has the FacetAttribute added to it and orders them by the Index set on it.

It then creates a dynamic Lambda expression based on Property Info and adds a TermsFacet to the query, the type can be set on the attribute as well.

So you end up with

IClient searchManager = Client.CreateFromConfig();
ITypeSearch<StandardPage> queryTest = searchManager.Search<StandardPage>().For("test");
queryTest = queryTest.AddFacets();
IContentResult<StandardPage> results = queryTest.GetContentResult();

which is a bit shorter then some of the queries I created.

The complete code can be found here

9 thoughts on “A generic TermsFacetFor solution

  1. “Those of you who know me a bit better know that I hate doing the same thing over and over again.” -> Love the approach! 🙂

    Like

      1. Hi, thank you for very useful nice post! I was also looking for a way to get around the repeating switch approach… I extended the solution with a method to retrieve the facets from the results and I also added a size constructor to the attribute to get more than the default 10. Maybe could be of interest. /Nino

        Like

      1. Imagine that you have:
        [ContentType(DisplayName = “DummyContent”)]
        public class DummyContent : PageData
        {
        public IEnumerable IndustryList()
        {
        IList industryTags = IndustryTags.SplitByComma().ToList();
        foreach (ContentAreaItem contentAreaItem in HowItWorksData.CaseStoryAssets.FilteredItems)
        {
        MarketplaceAssetBaseBlock assetBlock = contentAreaItem.GetContent() as MarketplaceAssetBaseBlock;
        if (assetBlock == null)
        {
        continue;
        }

        industryTags.Add(assetBlock.Industry);
        }

        return industryTags.Distinct();
        }
        }

        then:
        clientConventions.ForInstancesOf().IncludeField(x => x.IndustryList());

        and finally this:
        .TermsFacetFor(facets => facets.IndustryList(), facet => facet.Size = facetSize)

        We could potentially expend AddFacets to look for methods as well as properties?

        Best
        Miroslav

        Like

      2. I am not a big fan of having logic like this in your model, you can easily turn it into an extension method and have Find index it that way. This method could also be rewritten as a readonly property though, so you would not need to extend AddFacets . That being said, I guess you can always look for methods with the attribute. Haven’t tried it though

        Like

Leave a comment