-
June 23rd, 2011SitecoreThe aim of this post is to highlight some pitfalls I have run into in the past when working with the Sitecore API. Hopefully some ideas demonstrated here will help people avoid some common mistakes when programming with Sitecore.
Over time I’d like this list to grow so if anyone has any feedback or suggestions for more items, please let me know.
For each item I will highlight some examples of where I have seen the mistakes and how they can best be avoided.
- Direct database access
- Expensive content queries
1. Direct database access
There are several ways to get a reference to a Sitecore database. Note, these are defined within the config (<databases>). In the following example, the first 2 items get a specific database, the last the context database.Sitecore.Data.Database master = Sitecore.Configuration.Factory.GetDatabase("master"); Sitecore.Data.Database web = Sitecore.Configuration.Factory.GetDatabase("web"); Sitecore.Data.Database contextDatabase = Sitecore.Context.Database;Why is this bad?
In larger Sitecore builds one of the common tasks is to run through the ideas stored in the following guides on the sdn: security hardening and configuring production environments.One of key steps in these documents is that the content delivery site only has a reference to the web database. Typically you would have an independent content authoring environment which has references to core, master and web. In this setup, if your code has a direct reference to master, you will get an exception since master doesnt exist.
When might you want to do this?
It may be that in certain circumstances you do want to target a specific database. Consider an import routine. In this example you would want to ensure that new items are only added to master – note you would need to ensure the import routine is run from an environment which can access the master database.2. Expensive content queries
Sitecore.Data.Items.ItemAxes exposes a set of methods which can be used to drill up and down through the content tree. Some examples of this are:namespace Sitecore.Data.Items { // Summary: // Provides access to other items relative to a source item. // // Remarks: // Implements the Sitecore.Data.Items.Item.Axes property. // You should never instantiate this class. public class ItemAxes { ... // Summary: // Gets an ancestor item. // // Parameters: // itemID: // The item ID. // // includeSelf: // if set to true this instance is include self. // // Returns: // The ancestor. public Item GetAncestor(ID itemID, bool includeSelf); // Summary: // Gets all ancestors. // // Returns: // The ancestors. public Item[] GetAncestors(); // // Summary: // Gets a child item. ... // // Summary: // Gets a list of items that is in descendant axis of the source item. // // Returns: // The descendants. public Item[] GetDescendants(); } }Within your sublayouts you could then call eg:
Sitecore.Data.Items.Item[] descendants = Sitecore.Context.Item.Axes.GetDescendants();
Why is this bad?
If your content tree contains thousands of content items and you call GetDescendants from the root node – you will effectively be loading every single item in the tree – I can guarantee the bigger the tree, the slower this will go!When might you want to do this?
If you are comfortable that the result of a descendants call will expose a controlled set of nodes then you may find them more useful than querying direct children. An example of this is if folders are used in the structure you are querying.Where might you make this mistake?
A typical place I have seen this implemented is building footer navigation. Consider the following: ‘A developer understands your template structure and sees there is a common base template for each page so adds a new checkbox ‘Show in footer navigation’. In the footer control they then start at the home node calling GetDescendants, checking each item for the new checkbox.‘What can I do instead?
In the footer example, try to consider alternative solutions for defining which items should be shown in the footer. How about a configuration area of the content tree where the footer navigation is defined as its own node (and children if needed). Your descendants call could then target these specific items.Other alternatives are using shallower axes for your queries for example: direct children or siblings.
Tags: common mistakes, sitecore -
June 17th, 2011SitecoreThis post aims to demonstrate how to add new versions for all languages if they dont exist in the cms.
When you click buttons within the Sitecore client, typically Sitecore commands are used to map these actions to c# code. This link is defined in /App_Config/Commands.config. Some sample entries here are:
<command name="item:addversion" type="Sitecore.Shell.Framework.Commands.AddVersion,Sitecore.Kernel" /> <command name="item:archive" type="Sitecore.Shell.Framework.Commands.Archive,Sitecore.Kernel" />
If you want to add your own commands you can either edit this file or setup a patch file in /App_Config/Include. Note the patch file is the preferable option.
The code used for this example is:
using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Text; using Sitecore; using Sitecore.Data.Items; using Sitecore.Data.Managers; using Sitecore.Diagnostics; using Sitecore.Globalization; using Sitecore.Shell.Framework.Commands; using Sitecore.Web.UI.Sheer; namespace ###.Domain.Cms.Specialization.Shell.Framework.Commands { /// <summary> /// Create a version of the current item in all languages /// </summary> public class CreateVersionInAllLanguages : Command { public override void Execute(CommandContext context) { Assert.ArgumentNotNull(context, "context"); if (context.Items.Length == 1) { NameValueCollection parameters = new NameValueCollection(); parameters["items"] = base.SerializeItems(context.Items); Context.ClientPage.Start(this, "Run", parameters); } } protected void Run(ClientPipelineArgs args) { Item item = base.DeserializeItems(args.Parameters["items"])[0]; if (item == null) { return; } IEnumerable<Language> languages = LanguageManager.GetLanguages(item.Database).Where(a => a != item.Language); if (SheerResponse.CheckModified()) { //prompt user they want to add versions if (args.IsPostBack) { if (args.Result == "yes") { foreach (Language language in languages) { Item localizedItem = item.Database.GetItem(item.ID, language); //if Versions.Count == 0 then no entries exist in the given language if (localizedItem.Versions.Count == 0) { localizedItem.Editing.BeginEdit(); localizedItem.Versions.AddVersion(); localizedItem.Editing.EndEdit(); } Sitecore.Web.UI.HtmlControls.DataContext contentEditorDataContext = Sitecore.Context.ClientPage.FindControl("ContentEditorDataContext") as Sitecore.Web.UI.HtmlControls.DataContext; contentEditorDataContext.SetFolder(item.Uri); } } } else { StringBuilder builder = new StringBuilder(); builder.Append("Create version for each language?"); SheerResponse.Confirm(builder.ToString()); args.WaitForPostBack(); } } } } }This then needs to be added into the commands section of the config with the following patch file:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <commands> <command name="item:addversiontoalllanguages" type="###.Domain.Cms.Specialization.Shell.Framework.Commands.CreateVersionInAllLanguages,###.Domain.Cms" /> </commands> </sitecore> </configuration>If you want this functionality available from a cms button, you need to wire up the button to the command. To do this, switch to the core database.
In this example we will add to the Language chunk of the ribbon (/sitecore/content/Applications/Content Editor/Ribbons/Chunks/Language). You need to create a new button and then setup the data section paying close attention to the Command field. This wants to be the same value as set in the patch file (item:addversiontoalllanguages).
Tags: command, language, sitecore -
June 16th, 2011SitecoreA quick tip for checking data flowing through the Sitecore pipelines is to setup empty processors and then move them sequentially through the required pipeline.
An example for the
pipeline: using Sitecore.Pipelines.HttpRequest; namespace ###.Domain.Cms.Specialization.Pipelines.HttpRequest { public class PipelineDebugger : HttpRequestProcessor { public override void Process(HttpRequestArgs args) { } } }Then add your debug point and dig into the args.
Some example usage for checking which site has been resolved:
<processor type="Sitecore.Pipelines.HttpRequest.SiteResolver, Sitecore.Kernel" /> <processor type="###.Domain.Cms.Specialization.Pipelines.HttpRequest.PipelineDebugger, ###.Domain.Cms" /> <processor type="Sitecore.Pipelines.HttpRequest.UserResolver, Sitecore.Kernel" />
Tags: pipelines, sitecore -
June 16th, 2011SitecoreSitecore gutters are a great way of seeing quick summaries of content within the tree. Some existing gutter options include Locked Items, Workflow State, Missing Versions and more. These can be toggled by right clicking in the left column of the content editor.
Its easy to build custom gutters – in the example above we have a new item available – ‘Custom Presentation’. When this is active on an item it shows:
Behind the scenes there is very little code to achieve this:using System; using System.Linq; using ###.DataSource.Cms; using Sitecore; using Sitecore.Data.Fields; using Sitecore.Data.Items; using Sitecore.Diagnostics; using Sitecore.Globalization; using Sitecore.Shell.Applications.ContentEditor.Gutters; namespace ###.Domain.Cms.Specialization.Shell.Applications.ContentEditor.Gutters { /// <summary> /// Shows icon if layout field on item doesnt use presentation value set on __StandardValues /// </summary> public class PresentationGutter : GutterRenderer { protected override GutterIconDescriptor GetIconDescriptor(Item item) { Assert.ArgumentNotNull(item, "item"); Field layoutField = item.Fields[FieldIDs.LayoutField]; //ItemKeys.Home is a hard coded Guid elswhere in the app if (layoutField == null || !item.Axes.GetAncestors().Any(a => a.ID.Guid == ItemKeys.Home)) { return null; } if (!String.IsNullOrEmpty(layoutField.Value) && !layoutField.ContainsStandardValue) { GutterIconDescriptor descriptor = new GutterIconDescriptor(); descriptor.Icon = "applications/16x16/document_into.png"; descriptor.Tooltip = Translate.Text("Item doesn't have same presentation as standard values"); return descriptor; } return null; } } }Gutters then need to be added to the core database at ‘/sitecore/content/Applications/Content Editor/Gutters‘
Tags: gutters, presentation, sitecore -
June 16th, 2011SitecoreOne of Sitecore’s most useful features is the plug-ability of functionality via the configuration factory. Its very easy to add or update custom implementations where necessary.
A typical programming model used throughout this is the Provider model – Membership, Roles, Item, Proxy… the list is endless. Unfortunately one provider thats not exposed in the config factory is the MediaProvider.
No problem, thanks to some help from support they suggested a way to get around this, you can tap into the initialise pipeline. Here is the patch file to enable this:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <pipelines> <initialize> <processor patch:after="processor[@type='Sitecore.Pipelines.Loader.EnsureAnonymousUsers, Sitecore.Kernel']" type="###.Domain.Cms.Specialization.Pipelines.Loader.InitializeMediaProvider, ###.Domain.Cms" /> </initialize> </pipelines> </sitecore> </configuration>This pipeline runs as the application intializes. Next you need the implementation of the pipeline processor:
using Sitecore.Diagnostics; using Sitecore.Pipelines; using Sitecore.Resources.Media; namespace ###.Domain.Cms.Specialization.Pipelines.Loader { /// <summary> /// Sets the Provider for Sitecore's MediaManager to a custom version. /// </summary> public class InitializeMediaProvider { public void Process(PipelineArgs args) { Assert.IsNotNull(args, "args"); MediaManager.Provider = new ###.Domain.Cms.Specialization.Resources.Media.MediaProvider(); } } }And finally the custom implementation:
namespace ###.Domain.Cms.Specialization.Resources.Media { public class MediaProvider : Sitecore.Resources.Media.MediaProvider { public override string GetMediaUrl(MediaItem item, MediaUrlOptions options) { ... } } }Our customization allowed us to push certain media extensions to known file types.
Tags: MediaProvider, sitecore -
June 15th, 2011SitecoreThere are several options available when setting up which data feeds into your sublayouts or renderings within Sitecore. Typically we work with sublayouts since they fit the programming style used in-house.
When you add a presentation component to a page you can configure data in a manner of ways:
In this example we have setup rendering parameters for ‘Section Image Header’ (the items that feed this dropdown are content items elsewhere in the content tree). Alternatively you can explicitly set the ‘Data Source’.
When coding your sublayouts, its simple to extract information from either the rendering parameters or the ‘Data Source’ field using the following examples.
//this is your ascx code behind Sublayout sublayout = this.Parent as Sublayout; if (sublayout != null) { //the ascx path string sublayoutPath = sublayout.Path; Placeholder placeholder = sublayout.Parent as Placeholder; //the sitecore placeholder key string placeHolderKey = ""; if (placeholder != null) { placeHolderKey = placeholder.ContextKey; } //the data source item Item item = Sitecore.Context.Database.GetItem(sublayout.DataSource); if (item == null) { //handle appropriately } else { //use the item } //the sublayout parameters NameValueCollection parameters = WebUtil.ParseUrlParameters(sublayout.Parameters); }To then access the ‘Section Image Header’ rendering parameter you could query:
string sectionImageHeader = parameters["Section Image Header"];
One disadvantage with using parameters to setup the data into sublayouts is that the links database doesnt honour the relationship. See where is shared content used for help with this.
Rather than needing the code shown above in every sublayout you can create a base classes to expose the data source and parameters as properties and methods:
public class SUserControl : UserControl { public Item ItemDataSource { get; set; } public string GetParameter(string key); public string PlaceholderKey(); public string SublayoutAscx();Another tip to consider, you can setup your sublayouts with an attribute to automatically use the rendering parameters as the sublayouts data:
[SRenderingParameter("Section Image Header")] public partial class SectionImageHeader : SUserControlIn this example the sublayout will know to take its data from the item related in the ‘Section Image Header’ rendering parameter.
Tags: sublayouts -

When building web controls, a common scenario is how to cascade control parameters to the control from the markup (or code-behind). Within Sitecore controls this is typically the Field you want to render.
<tc:SText runat="server" Field="Title" HideIfEmpty="true" />
This approach to programming controls works very well until the set of parameters becomes very long or the control doesnt need to know about each parameter. Consider embedding a flash movie, the number of parameters to code could be huge.
In the flash example, often the parameters aren’t needed by your c# code, instead they just to be rendered to the markup.
<pf:SFlash runat="server" Field="Flash Item" Width="384" Height="380"> <ParametersTemplate> <param name="quality" value="high" /> <param name="bgcolor" value="#006699" /> ... <param name="salign" value="" /> <param name="allowScriptAccess" value="sameDomain" /> <param name="flashVars" value='dataURL=<%= FeedUrl() %>' /> </ParametersTemplate> </pf:SFlash>One useful tip is that you can get information from your code behind into the template markup eg
<%= FeedUrl() %>
To build the control you need to add the following attribute:
[ParseChildren(true)] public class SFlash : Control
And then the template you want to use:
[Browsable(false), DefaultValue(null), PersistenceMode(PersistenceMode.InnerProperty)] public virtual ITemplate ParametersTemplate { get { return _parametersTemplate; } set { _parametersTemplate = value; } } private ITemplate _parametersTemplate;The content of the template can be extracted as a string with the following:
PlaceHolder placeholder = new PlaceHolder(); if (ParametersTemplate != null) { ParametersTemplate.InstantiateIn(placeholder); } StringWriter stringWriter = new StringWriter(); HtmlTextWriter writer = new HtmlTextWriter(stringWriter); placeholder.RenderControl(writer);Note, based on the chosen implementation, you may not need the content of the template as a string. Instead you could simply instantiate to the control’s child controls.
Tags: sitecore -
June 10th, 2011SitecoreA common query about the sitecore tree is how to track down related content. For link fields this is easy to check – the links flyout shows you. Another approach is to use rendering parameters to setup which content item(s) feeds sublayouts, when this is the case the links flyout doesnt display the relationship.
Using the advanced system reporter it is easy to extend the functionality to perform this check. Note, this has been built and tested against Sitecore 6.4.
- Create new filter and custom implementation
- Create new parameter for filter
- If the marketing centre is being used, create new scanner
- Setup the new report
1. Create new filter and custom implementation
Filters can be found at /sitecore/system/Modules/ASR/Configuration/Filters. The class and assembly need setting to match your applications assembly name and class name. Attributes: Value={Value}
using System; using ASR.Interface; using Sitecore.Data; using Sitecore.Data.Fields; using Sitecore.Data.Items; namespace ###.CMS.Specialization.ASR.Filters { public class RenderingsContainsValue : BaseFilter { public override bool Filter(object element) { Item item = null; string fullName = element.GetType().FullName; item = element as Item; if (item != null) { //check renderings field if (CheckField(item, item.Fields["__renderings"])) { return true; } //check marketing centre data source field if (CheckField(item, item.Fields["Data Source"])) { return true; } } return false; } private bool CheckField(Item item, Field field) { if (field != null) { string fieldValue = field.Value.ToLower(); if (fieldValue.Contains(this.Value.Replace("{", "").Replace("}", "").ToLower())) { return true; } Item searchItem = item.Database.GetItem(new ID(new Guid(Value))); if (searchItem != null && fieldValue.Contains(searchItem.Paths.FullPath.ToLower())) { return true; } } return false; } public string Value { get; set; } } }2. Create new parameter for filter
In our code snippet above, we use the parameter Value to query the content items. Create this in /sitecore/system/Modules/ASR/Configuration/Parameters. Note – this was setup to be of type Item Selector.
3. If the marketing centre is being used, create new scanner
Scanners allow for querying the sitecore tree – if you need to check through the marketing centre, setup a new scanner in /sitecore/system/Modules/ASR/Configuration/Scanners.
- Assembly: ASR.Reports
- Class: ASR.Reports.Items.QueryScanner
- Attributes: query=fast:/sitecore/system/Marketing Center/descendant-or-self::*
4. Setup the new report
The last step is to setup the report to use all the filters, scanners and parameters as above. Reports can be found in /sitecore/system/Modules/ASR/Reports.
- Scanners: All items & scanner created in step 3
- Viewers: Item viewer
- Filters: New filter from step 1
-


