Mark Gibbons
Published on

Sitecore SXA — How to override the Search Controller to fix the Cache-Control header

Authors

This article explains how to swap out the built-in SXA SearchController which handles the API for the Search Results component. I needed to do this as the response headers for this was resulting cache-control: no-cache instead of cache-control: no-store as I need the latter to prevent caching by CDN’s such as Azure Front Door.

Response showing cache-control: no-store

The following is for SXA in Sitecore 9. Caching for this particular case is now configurable in Sitecore 10 (there is a new cachingHeaders configuration), but this might be a useful technique if you need to replace built-in Sitecore controllers.

Set up the cache attribute

Create a new class:

[AttributeUsage(AttributeTargets.Method)]
public class CacheWebApiAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(HttpActionExecutedContext filterContext)
    {
        filterContext.Response.Headers.CacheControl = new CacheControlHeaderValue
        {
            NoStore = true
        };
    }
}

Create the Controller override:

public class XASearchOverrideController : Sitecore.XA.Feature.Search.Controllers.SearchController
{
    [RegisterSearchEvent]
    [CacheWebApi]
    public ResultSet GetResultsOverride([ModelBinder(BinderType = typeof(QueryModelBinder))] QueryModel model)
    {
        return base.GetResults(model);
    }
 
    [CacheWebApi]
    public FacetSet GetFacetsOverride([ModelBinder(BinderType = typeof(FacetsModelBinder))] FacetsModel model)
    {
        return base.GetFacets(model);
    }
 
    [CacheWebApi]
    public SuggestionsSet GetSuggestionsOverride([ModelBinder(BinderType = typeof(QueryModelBinder))] QueryModel model)
    {
        return base.GetSuggestions(model);
    }
}

Remove the old one from DI and add our new one

You need a IServicesConfigurator to remove the default:

public void Configure(IServiceCollection serviceCollection)
        {
            serviceCollection.Remove(serviceCollection.Single(x => x.ServiceType == typeof(Sitecore.XA.Feature.Search.Controllers.SearchController)));
            serviceCollection.AddTransient<My.Controllers.XASearchOverrideController>();
        }

Register the route handler

public class RegisterRoutes
{
    public void Process(PipelineArgs args)
    {
        foreach (string virtualdir in ServiceLocator.ServiceProvider.GetService<ISiteInfoResolver>()
            .Sites.Select<SiteInfo, string>((Func<SiteInfo, string>)(s => s.VirtualFolder.Trim('/'))).Distinct<string>())
        {
            string sitepath = virtualdir.Length > 0 ? virtualdir + "/" : virtualdir;
            //RouteTable.Routes.MapHttpRoute(sitepath + "sxa", sitepath + "sxa/{controller}/{action}").RouteHandler = new SessionHttpControllerRouteHandler();
            RouteTable.Routes.MapHttpRoute(
                name: sitepath + "sxaresults",
                routeTemplate: sitepath + "sxa/search/results",
                defaults: new { controller = "XASearchOverride", action = "GetResultsOverride" }
            ).RouteHandler = new SessionHttpControllerRouteHandler();
            RouteTable.Routes.MapHttpRoute(
                name: sitepath + "sxafacets",
                routeTemplate: sitepath + "sxa/search/facets",
                defaults: new { controller = "XASearchOverride", action = "GetFacetsOverride" }
            ).RouteHandler = new SessionHttpControllerRouteHandler();
            RouteTable.Routes.MapHttpRoute(
                name: sitepath + "sxasuggestions",
                routeTemplate: sitepath + "sxa/search/suggestions",
                defaults: new { controller = "XASearchOverride", action = "GetSuggestionsOverride" }
            ).RouteHandler = new SessionHttpControllerRouteHandler();
        }
    }
}

Patch out the built-in route handler and patch ours in:

<sitecore>
    <pipelines>
      <initialize>
        <processor patch:instead="processor[@type='Sitecore.XA.Feature.Search.Pipelines.Initialize.InitializeRouting, Sitecore.XA.Feature.Search']"
           type="My.Pipelines.RegisterRoutes, ALS.Feature.XaComponents" />
      </initialize>
    </pipelines>
  </sitecore>

That’s all — it is fairly involved but at least it is possible.