Or: How Sitecore DMS and Personalization killed the Sitecore.Context.Item. This post is about how to properly ensure that you get data from the correct item when you develop websites that can be personalized.
The Sitecore.Context.Item returns the current item, i.e. the item that corresponds with the URL that the user has entered. Old Sitecore developers like me has been used to adding the fields of a page to the current item, and then accessing these fields directly using the sc:FieldRenderer:
<%@ Register TagPrefix="sc" Namespace="Sitecore.Web.UI.WebControls" Assembly="Sitecore.Kernel" %> <sc:FieldRenderer ID="MyField" runat="server" FieldName="NameOfMyField" />
This approach is no longer a good approach, as the the customer have been given access to DMS and personalization. Now you no longer know if the content that the usercontrol need, is part of the current item, or another item.
The customer can now acces any component he likes, and use personalization to alter the data source (or even the component itself). This breaks the pattern of assuming that content is part of the current item.
So what can we do?
Before we grab any field, we need to go through these steps:
- Check the datasource of the current sublayout
- If the datasource is set, use the item from the datasource
- If not, use the current item
These 2 extension methods ease the process:
namespace SitecoreExtensions { public static class SublayoutExtensions { public static Item GetDataSourceItem(this Sublayout sublayout) { string dataSource = sublayout.DataSource; if (string.IsNullOrEmpty(dataSource)) return (Item) null; return Context.Database.GetItem(dataSource); } } public static class UserControlExtensions { public static Item GetDataSourceItem(this UserControl control) { Sublayout sublayout = control.Parent as Sublayout; return sublayout == null ? (Item) null : SublayoutExtensions.GetDataSourceItem(sublayout); } public static Item GetDataSourceOrContextItem(this UserControl control) { return UserControlExtensions.GetDataSourceItem(control) ?? Context.Item; } } }
The SublayoutExtensions extend the Sitecore Sublayout. The GetDataSourceItem returns the item that the datasource points at, or null if the datasource is not set.
The UserControlExtensions extend the .NET UserControl (that really is the base of a Sitecore Sublayout). The GetDataSourceOrContextItem returns the datasource item, with a fallback to Sitecore.Context.Item.
To use the method, you must add the function to the item property of the sc:FieldRenderer:
<sc:FieldRenderer ID="MyField" runat="server" FieldName="NameOfMyField" Item="<%# this.GetDataSourceOrContextItem() %>" />
And you must remember to call DataBind().
This pattern is good because you never end up with a NULL item, as sc:FieldRenderer will crash if you feed it with a NULL value in the item property. There is no reason to crash the entire page just because one component is not configured correct.
From code-behind you simply replace Sitecore.Context.Item with this.GetDataSourceOrContextItem:
var text = Sitecore.Context.Item["NameOfMyField"]; // old way var text = this.GetDataSourceOrContextItem()["NameOfMyField"]; // new way
I would not be surprised if Sitecore in the future would build these functions into sc:FieldRenderer, but untill then we have to do it ourselves.
In Sitecore 7 update1 you can use: this.Attributes[“sc_datasource”];
LikeLike
Yes, Sitecore is getting there. Next thing they need to do is to extend Sitecore.Context with Sitecore.Context.DataSourceItem and Sitecore.Context.DataSourceOrCurrentItem. Then they need a new attribute in sc:FieldRenderer that checks the datasource with fallback to the current item.
LikeLike
FYI, the Sublayout Parameter Helper has actually been out for years now and it handles this too.
http://marketplace.sitecore.net/en/Modules/Sub_Layout_Parameter_Helper.aspx
LikeLike
Really interesting recommendation. But does it make sense to have a Context property give different values based on which rendering you are in? That breaks the metaphor of all the other Context values, which are at a request level.
LikeLike
I agree that Sitecore.Context.Item should return the request level item, but I do not agree that sc:FieldRenderer by default should use this item as context. The fieldrenderer should primarily look at the datasource, and fall back to the Sitecore.Context.Item. And Sitecore should provide another property, say Sitecore.Context.DataSource or Sitecore.Context.DataSourceOrConextItem which is then the way to get your values from Sitecore.
LikeLike
Pingback: How to make a customized control for the Page Editor | Visions In Code
Pingback: Make your own Droplink control for the Sitecore Page Editor | Visions In Code
Pingback: Extend the Sitecore FieldRenderer | Brian Pedersen's Sitecore and .NET Blog
Pingback: Which of my old Sitecore posts are still valid in Sitecore 9? | Brian Pedersen's Sitecore and .NET Blog