The Maze
If you have worked with Sitecore MVC you have probably noticed a few (to say the least) .Item
properties in various contexts:
Model.
Item
(whereModel
is aRenderingModel
)Model.Page
Item
Model.Rendering.
Item
(orRenderingContext.Current.Rendering.
Item)Model.Rendering.Rendering
Item
RenderingContext.Current.Context
Item
PageContext.Current.
Item
@Html.Sitecore().Current
Item
And of course a good old Context.
Item
is always available. A maze indeed.
Intrigued? Need a map? Ok, pack your lunch and let’s go look around. If you’re patient we will make it out by sunset.
Not the Item you need
Let’s start by marking the dead ends and wrong turns and by pointing out the two Item
s you most likely don’t need
1. Rendering.RenderingItem
Your rendering is represented by Sitecore.Mvc.Presentation.Rendering
object. You can get to it via:
RenderingContext.Current.Renderingor via:
@Model.Renderingwhen in your razor view, provided that your model is an instance of the
RenderingModel
. TheRenderingItem
property points to the definition item of your component, the item under/sitecore/layout/Renderings
. It’s probably not the item you need.2. Context.Item
A good old friend… It’s hard, I know, but it’s time to move on. Depending on where you are in the Sitecore MVC land it may or may not be the item you need. It’s probably worth a blog post of its own but I would recommend you just don’t use
Context.Item
in your Sitecore MVC journey.The two items you need
You most likely need one of the two items – the current page item, and the current component’s data source item. Or let’s better call it the current component’s context item as your rendering might not be datasource-driven, for example.
1. Current Page Item
In context of a rendering (view and controller rendering alike), a page item is the item that’s being rendered. The one that carries your rendering in its layout definition.
var page = PageContext.Current.Item;And it is normally determined by running the
mvc.getPageItem
pipeline. We’re in a maze though, remember? It’s probably that item. It’s most likely that item. It can, however, be explicitly set on the currentPageContext
. And the latter could also be changed by pushing a new context object onto a stack usingContextService.Get().Push()
. That said, if no code in your solution is trying to outsmart Sitecore you can be pretty sure it’s the current page item.In context of a routed request it’s the request’s context item. You can read more about it here. It’s the same idea actually –
mvc.getPageItem
pipeline. The processors in it will take turns to try and figure out what should be the page item and the guys there know about route values and{*scItemPath}
.[su_note note_color=”#fafafa”]If you’re in context of a view rendering and your model is a (or a descendant of)
RenderingModel
(and why wouldn’t it be?) you can just use:@Model.PageItemto explicitly refer to the current page. We’ll see how you can, in some circumstances, get the same item implicitly via
Model.Item
but I am a big proponent of stating your intention as clear as possible when writing your code. I always favorSingleOrDefault()
andSingle()
overFirstOrDefault()
orFirst()
, for example, unless, of course, your intent is, in fact, to pick the first out of potentially many.
[/su_note]2. Current Context Item
In context of a routed request there’s only one context item and we just talked about it. This particular one only makes sense in context of a rendering.
Let’s start with an example. In your razor view:
@Html.Sitecore().Field("Title")will render a
Title
field of what item exactly?Is it the field of:
@Html.Sitecore().CurrentItemor
@Model.Itemor
@Model.Rendering.Itemor
RenderingContext.Current.Rendering.Item?
The answer is “Yes“.
The last three are all the same. The
CurrentItem
on the helper will almost always be equal to them as well. There’s one specific case when it won’t be and I will explain in a minute. TheItem
property of theRendering
object does the following:public virtual Item Item { get { return this.StaticItem ?? this.GetItemFromContext(); } set { this.StaticItem = value; } }which roughly (please read the code for the exact transcript) translates to the following sequence:
- Try to get the item from the rendering’s
ItemId
property. Return it if notnull
. I never used and I am not really sure what it is to be honest- Try to get the item from the rendering’s Data Source. Return it if not
null
- Try to get the
ContextItem
from the currentRenderingContext
if datasource nesting is allowed. This thing deserves special attention and I will give it its due in the next chapter. Don’t return just yet if notnull
.- If the
ContextItem
wasnull
(or datasource nesting disabled) then grab the current page item from the currentPageContext
. Don’t return it just yet if notnull
.- If nothing found then return
null
. Can’t see why you would have a rendering running outside of a page context (and thus without the current page item) but nothing is impossible with Sitecore- If we have either the
ContextItem
or the page item and the rendering’s Data Source property is null or empty then return that item we have- Otherwise try to evaluate the Data Source relative to that item we have
Still with me? Ok then, moving on.
Remember I told you the
CurrentItem
on the helper does it a little differently? It actually runs this exact sequence with one extra step at the very end. In case after all these attempts to get something back we got anull
(and how could we? do you see why we would get anull
after all the hassle we just went through to get something?), so in case we have nothing at the end it will one more time reach out and grab the page item. I guess the helper hold itself to a slightly higher standard.[su_note note_color=”#fafafa”]It’s that last step in the long sequence that may produce a
null
. If, for example, your rendering’s data source was an ID that was no longer in your Sitecore database or maybe it was there but it was not in the right database, it would returnnull
on the second step and would later try to get it again relative to theContextItem
or the page item. An ID is not a path so Sitecore would basically just do theGetItem()
by that ID again. It’s silly, I know, but it’s what it does and that’s how you can end up with anull
. I had it happen to me so that’s how I know[/su_note]The Hidden Gem Item
So what was that
ContextItem
on theRenderingContext
? Is it an implementation detail or can it come in handy?The
mvc.renderRendering
pipeline runsEnterRenderingContext
processor right before executing the Renderer. This processor is tasked with pushing the newRenderingContext
for the rendering that is about to render onto the stack of theContextService
. In case a rendering is nested within another rendering theContextItem
will be set to the outer rendering’sItem
– the result of running the sequence we have just looked into in the previous chapter – it’s called datasource nesting. Otherwise it will be set to be the current page item.There’s a setting that controls whether datasource nesting is allowed –
Mvc.AllowDataSourceNesting
. It defaults totrue
but if you set it tofalse
theContextItem
will not be used to resolve the current context item.This hidden gem item can come in very handy. Next time (or maybe the time after next as I already have another blog post in the making) I will tell you how you can use it to your advantage. Stay tuned!
Thanks for taking the time to go into this in detail – as you point out, the context item situation was getting a bit confusing.
One question. Is there a way to set the context item somehow without using custom item resolvers?
If you want to set it temporarily you can always use the
using (new ContextItemSwitcher(myItem))
{
// Sitecre.Context.Item now points to myItem
}
Thanks for the detail explanation.
Pingback: Resolving Context.Item in Sitecore MVC | jammykam