Overriding the Sitecore Item Resolver? Watch out for the mvc.getPageItem pipeline!
OSomething that has been a bit annoying to me when developing custom ItemResolver's for Sitecore MVC is that you cannot simply add logic into the httpRequestBegin pipeline to add your custom logic. No, that would be too simple right? If you are using Sitecore MVC than not only must you do your Item Resolution logic within the httpRequestBegin, you must also override or rearrange the mvc.getPageItem pipeline so that the MVC pipelines do not override or cannot find your Sitecore.Context.Item.
Sitecore MVC Background
To accommodate MVC many years ago, Sitecore added several processors to detect when a route or a layout needed MVC and if so it would transfer processing to MVC or ignore the request entirely. After Sitecore detects that the request needs MVC it will run the mvc.getPageItem which does similar processing to what the ItemResolver does within the httpRequestBegin pipeline except that it doesn't assume that an Item was already found. Seems odd that we would have the same logic run twice... In any case, if we look at the important processors in the mvc.getPageItem pipeline we find the following (in this order):
- SetLanguage - Sets the Sitecore.Context.Language
- GetFromRouteValue - Sets the Sitecore.Context.Item by replacing values within the URL with route data
- GetFromRouteUrl - Sets the Sitecore.Context.Item by querying Sitecore with the URL path in relation to the Sitecore.Context.Site's StartItem
- GetFromOldContext - Sets the Sitecore.Context.Item by using the original Sitecore.Context.Item found within the httpRequestBegin
If we back up for a moment and dig a little deeper into the source code we find that the real flaw in logic comes within mvc.requestBegin and creating the _PageContex_t. The PageContext contains a method, GetItem, that essential calls the mvc.getPageItem pipeline regardless of whether there is already a Sitecore.Context.Item or Sitecore.Context.Language found. Oh Sitecore... If you already did the work to find the Language and the Context.Item, the mvc.getPageItem pipeline should probably not run.
How to resolve
The really simple solution to resolve this issue is to reorder the processors within the mvc.getPageItem pipeline so that GetFromOldContext always comes after the SetLanguage processor.
<sitecore>
<pipelines>
<mvc.getPageItem>
<processor type="Sitecore.Mvc.Pipelines.Response.GetPageItem.GetFromOldContext, Sitecore.Mvc">
<patch:delete />
</processor>
<processor patch:after="processor\[@type='Sitecore.Mvc.Pipelines.Response.GetPageItem.SetLanguage, Sitecore.Mvc'\]" type="Sitecore.Mvc.Pipelines.Response.GetPageItem.GetFromOldContext, Sitecore.Mvc"/>
</mvc.getPageItem>
</pipelines>
</sitecore>
A little more involved solution is to override the PageContext to fix the issue; to do that we need to override the SetupPageContext processor to add our custom PageContext (fair warning, I did not test this :( ):
namespace Example
{
using System.Web.Routing;
using Sitecore.Data.Items;
using Sitecore.Mvc.Pipelines.Request.RequestBegin;
using Sitecore.Mvc.Presentation;
public class SetupPageContext : Sitecore.Mvc.Pipelines.Request.RequestBegin.SetupPageContext
{
protected override PageContext CreateInstance(RequestContext requestContext, RequestBeginArgs args)
{
return new PageContextFixed
{
RequestContext = requestContext
};
}
}
public class PageContextFixed : PageContext
{
protected override Item GetItem()
{
// Assumption: If you have a Context.Item you have to have a Context.Language
return Sitecore.Context.Item ?? base.GetItem();
}
}
}
And the configuration:
<sitecore>
<pipelines>
<mvc.requestBegin>
<processor patch:instead="processor\[@type='Sitecore.Mvc.Pipelines.Request.RequestBegin.SetupPageContext, Sitecore.Mvc'\]" type="Example.SetupPageContext,Example" />
</mvc.requestBegin>
</pipelines>
</sitecore>
There we go, two simple solutions to fix Sitecore MVC issues with using Sitecore MVC and custom ItemResolvers.