Sitecore 9: ContentSearch Solr query quirks with spaces and wildcards

Sitecore provides a Linq powered IQueryable mechanism with which you can build powerful search queries. Your query will be translated into a native query for your underlying search engine (eg. Solr). There are some odd quirks (bugs?) with this translation in Sitecore 9.0 and 9.0.1 when your search term includes a space. Let’s take a look.

In the below examples, context is an instance of IProviderSearchContext, which you’d typically wire up with dependency injection. In each case, we’re looking to query something from the index based the item’s path in the Sitecore tree.

Querying on exact matches:

context.GetQueryable().Where(x => x.Path == "Hello");
 Translates to: {_fullpath:(Hello)}

Ok! This makes sense.

context.GetQueryable().Where(x => x.Path == "Hello World");
 Translates to: {_fullpath:("Hello World")}

Notice that if your query term has a space, we need to wrap the term in quotes.

context.GetQueryable().Where(x => x.Path == "\\Hello");
 Translates to: {_fullpath:(\\Hello)}

Backslash? No problem.

context.GetQueryable().Where(x => x.Path == "/Hello");
 Translates to: {_fullpath:(\/Hello)}

Forwardslash? We need to escape that with a ‘\’

context.GetQueryable().Where(x => x.Path == "\\Hello World");
 Translates to: {_fullpath:("\\Hello World")}

Backslash with space? No problem, just add the quotes.

context.GetQueryable().Where(x => x.Path == "/Hello World");
 Translates to: {_fullpath:("\/Hello World")}

As above, we’re all good, the forwardslash is just escaped.

Querying on partial matches – where things get interesting:

context.GetQueryable().Where(x => x.Path.Contains("Hello"));
 Translates to: {_fullpath:(*Hello*)}

All good. Here, we wrap our search term in a wildcard, *

context.GetQueryable().Where(x => x.Path.Contains("Hello World"));
 Translates to: {_fullpath:("\*Hello\\ World\*")}

Uh oh! Something weird has happened. The quotes and wildcard seem to have got mixed up, and we’ve ended up with something which won’t return the results we want. Having read more about wildcard / space combinations here , we probably want to end up with something simpler, like {_fullpath:(*Hello\ World*)}

context.GetQueryable().Where(x => x.Path.Contains("\\Hello"));
 Translates to: {_fullpath:(*\\Hello*)}

No problem with this partial match, as we don’t have a space to deal with.

context.GetQueryable().Where(x => x.Path.Contains("/Hello"));
 Translates to: {_fullpath:(*\/Hello*)}

Again, fine.

context.GetQueryable().Where(x => x.Path.Contains("\\Hello World"));
 Translates to: {_fullpath:("\*\\Hello\\ World\*")}

The space completely breaks everything here

context.GetQueryable().Where(x => x.Path.Contains("/Hello World"));
 Translates to: {_fullpath:("\*\/Hello\\ World\*")}

and here..

Summary

I raised this with Sitecore and it has been raised as a bug. In the meantime – if you can get away with using StartsWith rather than Contains, you’ll find this works OK:

context.GetQueryable().Where(x => x.Path.StartsWith("Hello World"));
 Translates to: {_fullpath:(Hello\ World*)}

Which is just about perfect.

Digging into Unicorn configs in Sitecore Habitat

Sitecore Habitat ships with Unicorn as the serialization utility of choice. Unicorn works in the background during the development of a Sitecore implementation and writes out a YAML copy of templates, renderings and other Sitecore items to disk.
You can then add these YAML (.yml) files to your version control repository, merge changes with other developers and ensure they are picked up / deployed to target environments as part of your continuous integration (CI) pipeline.

In Habitat, we bundle a Unicorn-specific configuration file with each module – in line with the Helix principle that any assets relating to a module should be bundled and deployed with that module.

If we had a feature module called ‘Car’, we’d create a config patch in App_Config/Include/Feature called Feature.Car.Serialization.Config

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
 <sitecore>
  <unicorn>
   <configurations>
    <configuration name="Feature.Car" description="Feature Car" dependencies="Foundation.*" extends="Helix.Feature">
     <rolePredicate>
      <include domain="modules" pattern="^Feature Car .*$" />
     </rolePredicate>
    </configuration>
   </configurations>
  </unicorn>
 </sitecore>
</configuration>

The key line setting up this configuration file is:

<configuration name="Feature.Car" description="Feature Car" dependencies="Foundation.*" extends="Helix.Feature">

Aside from the obvious name and description, dependencies tells Unicorn to run other configurations before this one (in our case, run any other configuration named Foundation.* first. Extends allows us to inherit from a parent config, Helix.Feature. This is a great feature which removes unnecessary code duplication in Helix solutions (which can have hundreds of modules!)

With this simple config (which extends the Helix.Feature abstract configuration), Unicorn will sync any Sitecore definition items under any folder called /Feature/Car in Sitecore. Happily, the Helix.Feature abstract config will ensure that Unicorn puts its .yml files alongside the rest of your source code for the Car module. We can track down the configuration line which determines the serialization location in Unicorn.Helix.config:

<configuration name="Helix.Feature" abstract="true" extends="Helix.Base">
 <predicate>
   <include name="Templates" database="master" path="/sitecore/templates/$(layer)/$(module)" />
   <include name="Renderings" database="master" path="/sitecore/layout/renderings/$(layer)/$(module)" />
   <include name="Media" database="master" path="/sitecore/media library/$(layer)/$(module)" />
  </predicate>
</configuration>

 

Read more about Unicorn configuration at Kam’s blog.

Error: Could not find method: Process. Pipeline: /sitecore[database=”SqlServer” xmlns:patch=”http://www.sitecore.net/xmlconfig/”]/pipelines/mvc.resultExecuting[patch:source=”Sitecore.Mvc.config”]

When adding custom pipeline processors, you may come across the error:

Could not find method: Process. Pipeline: /sitecore[database="SqlServer" xmlns:patch="http://www.sitecore.net/xmlconfig/"]/pipelines/mvc.resultExecuting[patch:source="Sitecore.Mvc.config"]/processor[type="SitecoreCustom.Feature.Logging.LifecycleLogger.Pipelines.ActionExecuting.LogActionExecuting, SitecoreCustom.Feature.Logging.LifecycleLogger" patch:source="Feature.LifecycleLogger.config"]
Description: An unhandled exception occurred.

There are two usual culprits to check.

  1. Did you reference the correct class and assembly, in your pipeline configuration? A typo here will mean that the processor cannot be found.
  2. Are you sending in the correct typed arguments? Each pipeline requires a specific typed argument object, such as InitializeContainerArgs. If I expect the wrong args type for my chosen pipeline, Sitecore will consider the Process method invalid and will skip it.

 

These are both really easy mistakes to make, and hard to spot where you might be going wrong, as everything will compile fine.

Patching the Sitecore Commands.config file

A quick tip Mark Cassidy helped me with – patching the Commands.config is something you may need to do when adding custom Sitecore ribbon buttons.

The contents of Commands.config get wrapped into Web.config when Sitecore initializes, so actually, we can patch Commands.config just like we would Web.config. The element you’re aiming for is sitecore/commands/command.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <commands>
      <command name="item:clone">
        <patch:attribute name="type">ChrisPerks.Features.Ticker.Commands.CustomButton, ChrisPerks.Features.Ticker</patch:attribute>
      </command>
    </commands>
  </sitecore>
</configuration>

And et voila! Your Commands.config is patched.

Sitecore MVC Internals: IControllerActivator

When we previously looked into IControllerFactory implementations in both Sitecore MVC and standard ASP.NET MVC, we noticed that Microsoft’s default IControllerFactory implementation, DefaultControllerFactory, doesn’t actually handle the instantiation of new IController instances. To enable dependency injection at the point of object creation, DefaultControllerFactory hands off instation of IController objects to another player – an object which implements IControllerActivator.

How we call an IControllerActivator implementation

Let’s look back at the code for the GetControllerInstance method of Microsoft’s DefaultControllerFactory class (see previous post for a more in-depth discussion)

protected internal virtual IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
    //...
    return this.ControllerActivator.Create(requestContext, controllerType);
}

This method crucially takes in the Type of a controller, which we have determined by looking carefully at the requested URL and matching it to a known route. We see that DefaultControllerFactory already has an implementation of IControllerActivator to work with, referenced by this.ControllerActivator.

What is Microsoft’s default implementation?

In standard ASP.NET MVC sites, this implementation is Microsoft’s DefaultControllerActivator. Let’s look at the DefaultControllerActivator.Create() method in full:

public IController Create(RequestContext requestContext, Type controllerType)
{
    try{
            return (IController) (this._resolverThunk().GetService(controllerType) ?? Activator.CreateInstance(controllerType));
    }
    catch (Exception ex)
    {
            throw new InvalidOperationException(string.Format((IFormatProvider) CultureInfo.CurrentCulture, MvcResources.DefaultControllerFactory_ErrorCreatingController, new object[1]{(object) controllerType}), ex);
    }
}

Ignoring boilerplate, the line we’re left with is:

(IController) (this._resolverThunk().GetService(controllerType) ?? Activator.CreateInstance(controllerType));

Don’t be discouraged by the odd looking _resolverThunk(). This is a reference to the chosen Dependency Resolver for your project. Remembering that ASP.NET MVC was built for extendability at every point, a Dependency Resolver lets us have full control over how any object is created – typically using a Dependency Injection framework. We’ll look at Dependency Resolvers in a future post.

Back to the line above. If you have a Dependency Resolver in place, the GetService() method will use this resolver to return an instance of the IController you need to fulfil the request. If not, we fall back to good old Activator.CreateInstance(), which is the .NET frameworks vanilla way of creating new objects at runtime.

Does Sitecore implement IControllerActivator?

No, Sitecore doesn’t utilist IControllerActivator. There’s a simple reason – once Sitecore has determined the type of Controller you would like to create, it hands off the job of creating the controller to the mvc.createController Pipeline:

IController controller = PipelineService.Get().RunPipeline<CreateControllerArgs, IController>(“mvc.createController”, 
        new CreateControllerArgs(requestContext, controllerName), 
        (Func<CreateControllerArgs, IController>) (args => args.Result));

That’s it for IControllerActivator, happy hacking!

Sitecore MVC internals: SitecoreControllerFactory

In this post, the first in a series of posts looking into the internals of Sitecore MVC, we’ll be looking at the SitecoreControllerFactory class in detail.

What does SitecoreControllerFactory do?

This class, provided by Sitecore in the Sitecore.Mvc.Controllers namespace, is responsible for creating an instance of an MVC controller, used by Sitecore for turning a HTTP request (for say, /products/12345) into a HTML response, rendered by the browser.

The concept of a Controller Factory is not specific to Sitecore, and is one of the mechanics of the wider ASP.NET MVC framework. Let’s look at the signature for this class.

public class SitecoreControllerFactory : IControllerFactory

Here we’re seeing a Sitecore class, SitecoreControllerFactory, implement an interface, IControllerFactory, which belongs to the wider ASP.NET MVC framework. IControllerFactory defines one method we’re interested in here:

public interface IControllerFactory
{
    IController CreateController(RequestContext requestContext, string controllerName);
    //..others
}

This means that SitecoreControllerFactory must implement a method called CreateController. Similarly, any non-Sitecore sites you build using ASP.NET MVC will likely use the built-in DefaultControllerFactory class that Microsoft provide for you. It’s happening behind the scenes, and unless you needed to do some heavy customisation to your project, you may not have noticed. Before we get back to Sitecore, let’s look at Microsoft’s implementation of CreateController, in their vanilla IControllerFactory implementation, DefaultControllerFactory:

public class DefaultControllerFactory : IControllerFactory
{
    //.. snipped
    public virtual IController CreateController(RequestContext requestContext, string controllerName)
    {
      //.. snipped
      Type controllerType = this.GetControllerType(requestContext, controllerName);
      return this.GetControllerInstance(requestContext, controllerType); 
    }
}

So, Microsoft’s default CreateController implementation returns a Controller object. How does this method know which Controller you want? Well, we break it up into two steps. First, we pass a controller name (such as ‘Products’) to the method. This name is taken from the route data extracted from our request to /products/12345. This controller name is passed to the GetControllerType method, which finds the appropriate Controller Type.

Secondly, we pass this Type to another method, GetControllerInstance, which handles the actual instantiation of the new Controller object, which we’ll eventually return. GetControllerInstance makes use of another MVC feature called Activators, which we’ll handle in another blog post.

So why do we need all of these steps, just to instantiate a Controller object?

ASP.NET MVC was built with a few design goals in mind – mainly testability and extensibility. One way of achieving these goals was to include wide support for dependency injection. In short – each of these steps can be replaced with our implementation, should we need to. And handily, that brings us back to Sitecore.

Back to Sitecore

Now we know that ASP.NET MVC makes it easy for us to replace any of the default behaviour for our own, bespoke implementations, we can look at one instance of where Sitecore have done exactly that. Sitecore’s SitecoreControllerFactory class implements IControllerFactory, and is used in place of Microsoft’s DefaultControllerFactory.

Let’s look at SitecoreControllerFactory’s CreateController method:

public virtual IController CreateController(RequestContext requestContext, string controllerName)
{
    //snipped
    IController controllerInstance = this.CreateControllerInstance(requestContext, controllerName);
    this.PrepareController(controllerInstance, controllerName);
    return controllerInstance;
}

This method doesn’t do much more than call two other methods – CreateControllerInstance and PrepareController. The main work we’re interested in here is in CreateControllerInstance, so let’s follow along that thread:

protected virtual IController CreateControllerInstance(RequestContext requestContext, string controllerName)
{
    if (controllerName.EqualsText(this.SitecoreControllerName))
    return this.CreateSitecoreController(requestContext, controllerName);

    if (TypeHelper.LooksLikeTypeName(controllerName))
    {
        Type type = TypeHelper.GetType(controllerName);
        if (type != (Type)null)
            return TypeHelper.CreateObject(type);
        }

    return this.InnerFactory.CreateController(requestContext, controllerName);
}

So, there are three ways in which Sitecore can choose to instantiate a Controller! This seems a little more involved than Microsoft’s default implementation, but this is Sitecore, so that seems reasonable. Let’s follow down the path of CreateSitecoreController.

protected virtual IController CreateSitecoreController(RequestContext requestContext, string controllerName)
{
    IController controller = PipelineService.Get().RunPipeline("mvc.createController", 
        new CreateControllerArgs(requestContext, controllerName), 
        (Func)(args => args.Result));
}

Ok! We’re now back firmly in Sitecore territory. Here’s where we branch from the way vanilla ASP.NET MVC does things, and how Sitecore handles things. Just like in standard ASP.NET MVC, we have a request context and a Controller name. However, instead of handing it off to GetControllerInstance as we did in DefaultControllerFactory, we call upon a Sitecore Pipeline, called mvc.createController.

This pipeline handles the instantiation of Controller objects by pulling some relevant details from Sitecore:

string controllerName = ((BaseItem) item).get_Item("__Controller");

string actionName = ((BaseItem) item).get_Item("__Controller Action");

Finally, we know which Controller should be used for the Sitecore item being requested (remember? /Products/12345). To handle the actual instantiation of the item, Sitecore gives us two ways to do this, both called from the mvc.createController pipeline:

IController CreateControllerUsingFactory()

IController CreateControllerUsingReflection()

The difference? Well, we can actually invoke another IControllerFactory instance to do the creation work for us. Think of this as Sitecore playing nicely – the SitecoreControllerFactory does only what it needs to, retrieving the details of which controller we need; it then relinquishes control of the actual instantiation of that controller to any other IControllerFactory you want to use. Sitecore wraps this detail up in a ControllerSpecification, however we’ll leave it there for now. This is something we’ll address in another post.

If no ControllerSpecification is given, then we can fall back to good old Reflection to create our instance.

I hope you’ve enjoyed this peek into the workings of Sitecore MVC. There’s plenty more to be discovered, so happy digging!