Adding search facets on computed fields in Sitecore 9 with SOLR

Adding facets to help content authors in searching content is always a great idea, especially when you have rather large buckets with loads of items.

One thing I noticed while trying to set this up, was that I needed untokenized fields in the index to make this work as expected, while text fields by default are tokenized in solr.

To understand this better, lets take an example of a bucket of products – say jewelry. I have a field called Jewelry Type on my products. If I wanted to give the content author an option to find jewelry by this field, I could add a facet on the existing field.

I would expect to see facet values like these:


But instead, I would see separate facets for each word, like ‘Engagement’, ‘Rings’, ‘Right’, ‘Hand’ etc. That’s certainly not what we need.

So to do that – instead of changing your solr settings to treat all fields of the certain type as untokenized (which would open a different can of worms!), we created a computed field to populate the Jewelry Type values into an untokenized field. This would treat the whole field value as a complete string and not break it down.

This is a relatively straightforward computed field:

using Foundation.Search.Solr.Helpers;
using Sitecore.ContentSearch;
using Sitecore.ContentSearch.ComputedFields;

namespace Foundation.Search.Solr.ComputedFields
	public class JewelryType : IComputedIndexField
		public object ComputeFieldValue(IIndexable indexable)
			SitecoreIndexableItem sitecoreIndexable = indexable as SitecoreIndexableItem;

			if (sitecoreIndexable == null) return null;

			if (sitecoreIndexable.Item.TemplateID.ToString() == Constants.ProductTemplateId)
				return sitecoreIndexable.Item["JewelryType"];

			return null;

		public string FieldName { get; set; }
		public string ReturnType { get; set; }

Now we set up the facet with this new computed field created: /sitecore/system/Settings/Buckets/Facets

Using the template: /sitecore/templates/System/Item Buckets/Facet

Where the field name – matches the name of the computed field you created.


You could choose to mark this facet as global or local.
If your facet is very specific to a particular bucketed item template, you could as well mark it as local, and then add it on the particular bucket item only.




Using Custom Routes in Sitecore MVC

Many a time, we come across the need to customize our urls in sitecore.
While doing this would definitely need overriding the item resolver in sitecore, additionally we use MVC routes to be able to define the route structure we need.

In our project, we found this especially useful for bucketable items and also certain special / listing pages – where we used the JS History API to account for paging / sorting filtering and the likes.

Consider the scenario of bucketable product items:


Now, we would definitely not want the product url correspond with the sitecore structure as shown above. It would be more likely that we would want the product url to perhaps embed a super category name, a category name, and may be a sku along with the product name itself. In this case, we will implement custom routes and also override the sitecore item resolver and link provide to make the circle complete.
This kind of implementation will also let you have separate presentations on every item if you wanted to, as opposed to the approach of using sitecore wildcard nodes where the presentation couldn’t be updated by the content author for individual items.

To achieve the url we want, the route would need to be defined:

public class RouteConfig
    public static void RegisterRoutes(RouteCollection routes)
         routes.MapRoute("Products", "products/{supercategory}/{category}/{productname}/{sku}");

Point to note: If you have multiple routes which are similar – in the sense that they start with the same sequence, you will want to add most specific to most generic route to your route table.

And in your global.asax.cs, you need to register the route(s) you just created:

public override void Application_Start()

Now you will need to add to the httpRequestBegin sitecore pipeline – a new itemresolver pipeline where you need to add the logic to identify the item from the route you just created above.
Basically, for any product, if you were to get the identifiers that were declared ({supercategory},{category},{productname},{sku} in this case), the code which fetches the corresponding product item from sitecore based on these identifiers, needs to be in the itemresolver code.

In this case, we will assume that the sku is a unique identifier for a product.
Note: If you are using an item name as an identifier, you will want to ensure that the item name is unique for the template you are searching for (in the location you are searching, if it is not the entire tree). It would be best to ensure that the item names are unique in this case, in the scope you will define for the item search. You could use this post, to ensure this and add a constraint to this effect: Unique item name constraint using index search in Sitecore.

namespace myassemblyname
    public class CustomItemResolver : HttpRequestProcessor
        public override void Process(HttpRequestArgs args)
            Assert.ArgumentNotNull(args, "args");
            if (Sitecore.Context.Item != null || Sitecore.Context.Database == null || args.Url.ItemPath.Length == 0) return;

            RouteBase route = RouteTable.Routes["Products"];

            if (route == null) return;

            RouteData routeData = route.GetRouteData(new HttpContextWrapper(HttpContext.Current));

            if (routeData == null) return;

            string productSku = (string)routeData.Values["sku"];

            if (string.IsNullOrWhiteSpace(productSku)) return;

            // Logic to fetch the product from sitecore / index based on the sku fetched from the url
            // you could optionally also use the other identifiers like the category / super category etc.
            Item product = ...

            if (product == null) return;

            Sitecore.Context.Item = product;

The config update here to add the above processor:

        <processor type="myassemblyname.CustomItemResolver, myassemblyname" />

In addition to this, to complete the circle here, you will also want to customize the your sitecore link provider here. This is so that the product urls when requested from the LinkManager are returned in the format you want it to, instead of the one corresponding to the sitecore folder structure (buckets in this case)

namespace myassemblyname
    public class CustomLinkProvider : LinkProvider
        public override string GetItemUrl(Item item, UrlOptions urlOptions)
            var holdUrlOptions = urlOptions;
                urlOptions.SiteResolving = Settings.Rendering.SiteResolving;

                string customUrl;

                // We want this to apply to only Products
                if (item.TemplateID.ToString() == "<Product template guid>")
                    // Code to fetch the sku / category / super category of the product given that we have the product item itself in 'item'
                    string category = ...
                    string superCategory = ...

                    if (!string.IsNullOrWhiteSpace(category) && !string.IsNullOrWhiteSpace(superCategory))
                        // NOTE, if this is being called from an asynchronous tasks, context will be null
                        // in this case, we will need to set the url by replacing the identifiers with the corresponding values in the routes
                        if (HttpContext.Current != null)
                            var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext);
                            customUrl = urlHelper.RouteUrl("Products", new
                    urlOptions.SiteResolving = Settings.Rendering.SiteResolving;
                    return base.GetItemUrl(item, holdUrlOptions);

The linkprovider override will also need to be applied in the config:

<linkManager defaultProvider="custom">
        <add name="custom" type="myassemblyname.CustomLinkProvider, myassemblyname" addAspxExtension="false" alwaysIncludeServerUrl="false" encodeNames="true" languageEmbedding="never" languageLocation="filePath" lowercaseUrls="true" shortenUrls="true" useDisplayName="false" />

So this should allow you to use product urls like the one below throughout your site.
Note: you are defining the custom route in a single place (in the RegisterRoutes method above), so this is a clean approach as well!

In addition to the above, you might also come across random scenarios where implementing custom routes may be a great idea. If you decide to use the history api to manage interactions like sorting / paging etc, to support maintaining listing states on page refresh, you will need custom routes implemented.

An example of this scenario, would be a product listing, accessed using
Say you were on page 2, had sorted by descending order of product name.
And the sameple resulting url would be:

If you didnt have custom routes implemented, and refreshed the page, you would ideally land on a 404, since the route till ‘products’ corresponds to the sitecore tree, but the remaining don’t.

To get around this, you could use a route definition like:

     routes.MapRoute("ProductListing", "products/params/{*args}");

Having a separator string like ‘params’ would be advised here so that the routes don’t interfere with each other.
Also, {*args} will correspond to a variable number of arguments.
You could also fetch the entire string of these variable arguments in your controller action using the method signature:

public ActionResult CustomIndex(string args = null)
     // args will contain the string after params, in this example '2/ztoa'
     // this string could be split and used to return the page to its intended state if required!

Happy routing!

Open items from bucket search in main content tree in Sitecore

Sitecore now provides us with this very useful ways of searching for items which are organized within item buckets.
We ran into a requirement where we didn’t want multiple tabs to open as and when one clicked on a search result, and we wanted the search result item to open in the main content tree instead.
This would enable the content authors to locate items in the item tree and create subitems / manipulate them. Even though the bucket search results page does allow creation of subitems (provided insert options are set), this provides a simpler means of browsing through subitems in a more effective manner.

To be able to achieve this, we need a config update and corresponding code update.


using Sitecore;
using Sitecore.Buckets.Pipelines.UI.LaunchResultPipeline;
using Sitecore.Data;
using Sitecore.Data.Items;
using System;

namespace MySite.SitecoreProcessors
    public class LaunchSitecoreResultMainWindow : LaunchSitecoreResult
        protected override void LaunchResult(LaunchResultArgs args)
            if (!string.IsNullOrEmpty(args.IndexableId))
                ID itemId = ID.Parse(args.IndexableId);
                Item myItem = Context.Database.GetItem(itemId);

                if (myItem != null)
                    Context.ClientPage.SendMessage(this, String.Concat(
                        new object[] {"item:load(id=", myItem.ID, ",language=", myItem.Language, ",version=", myItem.Version, ")"}));

Here’s the config update:

          <processor desc="sitecore">
            <patch:attribute name="type">MySite.SitecoreProcessors.LaunchSitecoreResultMainWindow, MySite</patch:attribute>

So now, when we search for an item within a bucket, and click on a search result, it will open in the main content editor.