A recent Sitecore implementation required the retrieval and display of knowledge base articles from a 3rd party vendor. In another word: (pause for dramatic effect) Integration.
An innocuous word in and of itself, but with seemingly infinite definitions and a complexity belying it's singularity. Fortunately, there are a number of avenues available to Sitecore developers when determining the best approach for weaving external data into the fabric of a website. Depending on requirements, you may find yourself using data providers, custom item editors, wildcard items, data importing, magic or some other technique.
Can I get a wildcard please?
As I'm sure you inferred from the title of this post, we ultimately decided upon wildcard items for the aforementioned Sitecore implementation. Our decision was based on the following factors:
- Content authors didn't need to manage or edit external data within Sitecore.
- External data didn't need workflow or versioning.
- External data needed to appear as though it were part of the site from a design, URL, SEO and internal search perspective.
- Web services were available from the knowledge base vendor to search for and retrieve articles.
- Budget and time constraints.
If you've never heard of or worked with wildcard items, fret not, they are just one of the "undocumented" features of Sitecore. Essentially, a wildcard item is an item that exists at some level in your content tree that will handle a request for any item not found on the same level as the wildcard item. A wildcard item can be based on any template you choose and is created like any other item, except that it needs to be named * (asterisk).
Based on the content tree in the image above, a request for /Help/Some-Page.aspx would be handled by the 'Some-Page' item. However, a request for /Help/1234.aspx or /Help/ArticleXYZ.aspx would be handled by the wildcard item and subsequently the presentation components defined for that item.
Examples of wildcard item usage found out in the ether typically demonstrate using a single presentation component to handle processing and rendering of a wildcard item. This presentation component typically parses the request URL for some sort of identifier and renders data based on the identifier value. While this approach to processing and rendering wildcard items is useful, we quickly ran into an obstacle: our wildcard item is rendered using multiple, distinct presentation components that all need access to a common external data source.
At Aware, we employ a component-based approach to constructing item templates and rendering item data to the page. For instance, metadata, breadcrumbs and content titles (H1 elements) are typically all rendered by separate components. An item utilizing each of these components therefore has an associated field in it's template to manipulate the rendering of each component, e.g. a Breadcrumb Title field, a Metadata Title field and a Content Title field.
For "normal" items, this component-based approach works extremely well and allows us to clearly separate code and reduce dependencies. However, for a wildcard item containing multiple components, each of the title fields shown in the image above need to be rendered using data from an external data source, i.e. the title of whichever knowledge base article we're retrieving.
Naturally, the quick and obvious solution would be to abstract the data retrieval process to a helper that can be leveraged within each of the presentation components. However, each component is already constructed to examine the Sitecore context item for the values it needs to render. Therefore, the helper solution would require us to add further conditional logic to our components to specifically handle wildcard items. Alternatively, we could create a value provider for each component that would allow us to "plug in" component data sources as needed. Unfortunately, both of these solutions require changes, ranging from minimal to extensive, to the code within each component. Ideally, our component code wouldn't need to change and we could instead manipulate the Sitecore context item before it reaches the presentation components.
At this point you might be thinking "it can't be done- you can't simply change the values in a Sitecore item, much less the context item, without permanently committing those changes to the database". I admit, that was my first thought, but it turns out I was wrong.
Typically, making changes to the field values for an item requires you to put the item into editing mode, make your field value changes, then exit editing mode. Upon exiting editing mode, via a call to the Sitecore.Data.Items.ItemEditing.EndEdit() method, Sitecore will in turn immediately save the changes you made using the default Sitecore ItemProvider. However, prior to saving, the ItemProvider makes one final check against the RuntimeSettings.Temporary property of the item being saved. If the RuntimeSettings.Temporary property is true, the ItemProvider will not save any changes you've made to the item while in editing mode. This hidden little secret is exactly what we needed.
Putting it all together
With our newfound friend the RuntimeSettings.Temporary property, the next step was creating a custom processor for the HttpRequestBegin pipeline that would be executed immediately after the existing ItemResolver processor. The custom processor walks through the following steps:
- Determine whether or not the resolved context item is based on our special knowledge base article template, which indicates (based on our content tree structure) that the context item is a wildcard item.
- Clone the context item.
- Set the cloned item's RuntimeSettings.Temporary property to true.
- Retrieve data from an external data source for use in the cloned item.
- Enter editing mode for the cloned item.
- Change field values in the cloned item to values retrieved from the external data source.
- Exit editing mode for the cloned item.
- Set the Sitecore.Context.Item property to the cloned item.
- Add retrieved external data to the current HttpContext for use by renderings or other controls.
At the end of the pipeline, our context item contains those data necessary for our components to render properly - with no changes to our component code necessary and only minimal magic employed.
As I'm sure you can imagine, the practice of temporarily modifying an item's field values can be used for other purposes. In a future post I'm planning to highlight another usage for the techniques outlined in this post (hint: Sitecore OMS is involved). Until then, happy integrating!
For more information on wildcard items and their potential uses:
Below is sample code for a custom processor that clones and temporarily modifies the cloned item's field values:
KnowledgeBaseItemResolver : HttpRequestProcessor
//gets the last part of the request url without the file extension,
//e.g. /Help/1234.aspx returns 1234
pageName = Sitecore.Web.WebUtil.GetUrlName(0);
Item currentItem = Sitecore.Context.Item;
currentItem.TemplateID != [Knowledge Base Article Template ID])
//retrieve knowledge base article
//assumes data retrieval library is responsible for caching
kb = SomeOtherLibrary.GetKbArticleById(KBID);
Item clone = currentItem.Clone(currentItem);
//IMPORTANT: be sure to set the RuntimeSettings.Temporary property to
//true for the cloned item.
//If it's false, Sitecore will attempt to save the cloned item after
//ending an edit operation (item.Editing.EndEdit())
clone[BreadcrumbTitleFieldName] = kb.Title;
clone[MetadataTitleFieldName] = kb.Title;
clone[ContentTitleFieldName] = kb.Title;
//Setting the 'silent' argument to true disables events on save.
//NOTE: the RuntimeSettings.Temporary property precludes the need
//to set silent to true, but it doesn't hurt to be defensive.
//set the context item to the clone
Sitecore.Context.Item = clone;
//add retrieved data to the HttpContext, for use by other renderings