Protect Your Sitecore Renderings From Bad Datasources

We’ve all been there before, you get the urgent email off hours, or the call while you’re sleeping. “The site is down!  Our homepage is not coming up!” You go to check and sure enough, the error handler page is displaying when you hit the homepage.  Checking the logs, you see your old friend, “Object reference not set to an instance of an object.”

There are many reasons why a rendering can get published with an invalid or missing datasource.  The datasource could still be stuck in workflow, but the page it was added to was approved and published. A content editor may have set a publishing restriction on the datasource, setting up content in advance to be published at a certain date dictated by the business.  Perhaps the datasource was created in English, but the site is multilingual and no version in Spanish exists.

There are strategies to handle each of these scenarios, but content editors are always finding ways to do unexpected things.  A Sitecore developer needs to write code that can handle these situations gracefully, without bringing down the page or emitting malformed HTML.  However, checking in every rendering for null or invalid datasources can become tedious.  This is ideally handled in a base class that your renderings inherit from.

There are a couple paradigms in play when developing Sitecore solutions.  This post assumes you’re using one of the Sitecore ORM layers, either Custom Item Generator or Glass Mapper.  If you’re starting a new build, you’re probably using MVC. For older Sitecore sites, you’re probably using WebForms.

WebForms and CIG

I’ll start with the scenario that prompted this post, a client that has an existing Sitecore build.  Their site is using WebForms, and authors are adding renderings to a page with datasources that are in a draft status.  When that page gets picked up by the automated publish job, the change to the renderings field goes out (since this is not shared) but the datasource does not.  This causes null reference exceptions in the sublayout code, as many of them are assuming the datasource exists.

Here’s an example. Say we have a sidebar component on our page that displays a promotional item.  The promotion template has a few text fields, maybe a promotional image.  Content authors create a few of these promotion datasources to support a marketing campaign launching next week, and set publishing restrictions accordingly.  They then go through key pages, including the home page, and add this promotional sublayout with the new datasources, preview their work, then go home for the weekend.  That night the auto-publish job kicks off and the rendering changes get pushed out without their datasources.  If we didn’t account for this in our sublayout code, we’re getting a call at midnight that the homepage is down.

A sublayout base class to address this will do two things. First, it will verify that the datasource does in fact exist. Second, it will ensure the datasource is of the expected template, or inherits from it, so that we can reference and use field values with confidence.  Additionally, we can pass a type parameter to this class and cast our datasource to the expected Custom Item type, removing that step as well.  This class itself inherits from the shared source sublayout base discussed in John West’s book Professional Sitecore Development. The code for this class would look like this.

public abstract class CustomItemSublayoutBase<T> : SublayoutBase where T : SitecoreItem
{
	protected override void OnInit(EventArgs e)
	{
		base.OnInit(e);

		if (DataSourceItem == null)
		{
			SetErrorState();
			return;
		}

		// Invoke the custom item constructor
		var ctor = typeof (T).GetConstructor(new[] {typeof (Item)});
		Model = ctor.Invoke(new[] {DataSourceItem}) as T;

		// Verify the appropriate DataSource
		if (Model == null
			|| !DataSourceItem.IsOfTemplate(Model.GetExpectedTemplateId(),2))
		{
			SetErrorState();
			return;
		}

	}

	protected T Model { get; private set; }
	private bool DataSourceError { get; set; }

	private void SetErrorState()
	{
		Visible = Sitecore.Context.PageMode.IsPageEditorEditing;
		DataSourceError = true;
	}

	protected override void Render(HtmlTextWriter writer)
	{

		if (DataSourceError)
		{
			WriteDataSourceError(writer);
		}
		else
		{
			base.Render(writer);
		}
	}

	/// <summary>
	/// Provides a simple error text block rendered in a div block element, with the
	/// style class set to "dataSourceError". Override this method to provide error
	/// HTML specific to the rendering.
	/// </summary>
	/// <param name="writer"></param>
	protected virtual void WriteDataSourceError(HtmlTextWriter writer)
	{
		writer.AddAttribute("class", "dataSourceError");
		writer.RenderBeginTag("div");
		writer.Write("The datasource for this control is not set or of the wrong type.");
		writer.RenderEndTag();
	}
}

We can have our sublayouts inherit from this class and use it in the following manner.

<%@ Control Language="C#" AutoEventWireup="true" Inherits="CustomItemSublayoutBase<PromotionItem>" %>

<div class="promoModule">
	<%= Model.Image.Rendered %>
	<p>
		<span>
			<%= Model.Title.Rendered %>
		</span>
		<%=Model.Caption.Rendered %>
	</p>
</div>

So what happens when the PromotionItem datasource is not published?  In the OnInit method of our base class, the validity of the datasource is checked.  If the datasource is not published, the control sets its visible property to false.  This prevents any other lifecycle events from running, and none of your sublayout code will execute.

The same is done if the datasource is invalid, meaning it does not inherit from the expected datasource template.  If the site is in Edit mode, instead a block of error text will be rendered informing the content editor something is wrong. In this way, you can pro-actively alert content editors that they’ve done something unexpected with the sublayout, and that they should correct it.

This class is meant to be used on a WebForms project using Custom Item Generator. What about a project built with MVC and using Glass.Mapper?

MVC and Glass

The Glass Mapper package provides you with the base class GlassView, which you can use for our View Renderings.  This class gives you a lot of the functionality described above out of the box.  Your datasource is automatically mapped to the type specified in the type parameter, this is a key feature of Glass.  Additionally, we don’t need to worry about the datasource being null; Glass will simply return a POCO object with default values for all your fields.

This can still be problematic, however, in that we may be rendering HTML in your view with no field values, empty divs or spans that can throw off your presentation.  Consider this view, for a Promotion rendering using the same datasource template:

@inherits GlassView<Promotion>

<div class="promoModule">
	@RenderImage(m => m.Image)
	<p>
		<span>
			@Model.Title
		</span>
		@Model.Caption
	</p>
</div>

Here, if the model is not valid, we won’t get exceptions thanks to Glass, but we will be rendering a bunch of empty HTML tags. In most cases, if there is no datasource, it’s better to render nothing at all.  We can check for empty field values in all of our views, or we can handle this in our base class similarly to the method we used for WebForms and CIG.

public class BaseGlassView<TModel> : GlassView<TModel> where TModel : IGlassBase
{
	public override void ExecutePageHierarchy()
	{
		if (!CheckValidDataSource())
		{
			if (IsInEditingMode)
			{
				WriteDataSourceError();
			}
			return;
		}

		base.ExecutePageHierarchy();
	}

	private const string _dataSourceErrorMessage = "The datasource for this rendering is invalid or not set.";
	protected virtual string DataSourceErrorMessage { get { return _dataSourceErrorMessage; } }

	protected virtual void WriteDataSourceError()
	{
		Output.Write("<div class=\"datasource-error\">");
		Output.Write(DataSourceErrorMessage);
		Output.Write("</div>");
	}

	private bool CheckValidDataSource()
	{
		Item dataSource = GetDataSource();
		if (dataSource == null)
		{
			return false;
		}

		Guid templateId;
		if (!Guid.TryParse(GetExpectedTemplateId(typeof(TModel)), out templateId))
		{
			return false;
		}

		// We can't compare the mapped Model.TemplateId to the Expected template ID because
		// it does not account for inheritance.
		return dataSource.IsOfTemplate(new ID(templateId).ToString(), true);
	}

	private string GetExpectedTemplateId(Type type)
	{
		var attrs = Attribute.GetCustomAttributes(type);
		foreach (var attr in attrs)
		{
			if (attr is SitecoreTypeAttribute)
			{
				SitecoreTypeAttribute st = (SitecoreTypeAttribute)attr;
				if (!string.IsNullOrEmpty(st.TemplateId))
				{
					return new ID(st.TemplateId).ToString();
				}
			}
		}
		return null;
	}

	private Item GetDataSource()
	{
		if (Sitecore.Context.Database == null
			|| RenderingContext.CurrentOrNull == null
			|| RenderingContext.Current.Rendering == null)
		{
			return null;
		}

		if (!string.IsNullOrEmpty(RenderingContext.Current.Rendering.DataSource))
		{
			return Sitecore.Context.Database.GetItem(RenderingContext.Current.Rendering.DataSource);
		}

		return Sitecore.Context.Item;
	}
}

The idea here is the same, we check the validity of the datasource and if it is invalid, we render nothing at all, aborting the view rendering all together.  In Edit mode, we’ll render an error message, alerting the author that something needs correcting.

Benefits

Using the patterns I described above had two benefits.  First, it proved to be a significant development accelerator for our dev team.  Checking for null and template inheritance on datasources may be a trivial thing, but doing it a hundred times over a hundred different sublayouts or views is tedious.  Who wants to write the same code over and over again?  And that one sublayout where it was forgotten, or cut and paste wrong, hopefully QA catches that before it goes to the client.

Second, it provides a quality of life benefit for content authors. We do our best to make sure our end user experience is top notch, but as Sitecore developers we need to remember content authors are also one of our target audiences. If we can do something as simple as alert them that their rendering needs some help with its datasource, why not do that? It keeps them happy, makes you look good, and keeps the bug tickets out of your queue.

Disclaimer

If you try to use the base classes above verbatim you will likely encounter build errors.  The first is with the IsOfTemplate extension method. This method does exactly what it sounds like, returns true if the item is based on the given template. You can find this in the Sitecore.Commons shared source library.
https://github.com/Velir/Sitecore-Commons

The BaseGlassView class references IGlassBase. This is a base interface all our models implement. We use TDS Code Generation to create our models, and the code generation template includes an IGlassBase interface that contains common Sitecore fields such as ID, Name, and so on.

  • Faiyazul haque Noor

    public class PewGlassView : GlassView where TModel : IGlassBase

    Hi writing above lines i am getting error on IGlassBase, to create new class, can you tell me which directive to include and which dll to reference

    • csulham

      IGlassBase is our base interface in our generated models. We use TDS code generation to create our models, and all our models implement IGlassBase.

      I’ll update the post to make that clearer. Thanks!

    • csulham

      IGlassBase is our base interface in our generated models. We use TDS code generation to create our models, and all our models implement IGlassBase.

      I’ll update the post to make that clearer. Thanks!

  • Jason Espin

    I’m getting a number of errors using this code. Firstly, the class definition has to be as follows:
    public class BaseGlassView : GlassView where TModel : class, IBase

    Secondly, I am having issues with the following error:

    Abstract inherited member ‘void System.Web.WebPages.WebPageExecutingBase.Execute()’ is not implemented. What did you do with the Execute method?

  • Jason Espin

    I’m also having issue with “RenderingContext.Current.Rendering.DataSource” where I get the error ‘Cannot resolve symbol – Current’

    • csulham

      Hi Jason,

      This code was written against an older version of Sitecore and GlassMapper, and I haven’t tested it with the latest releases. What versions are you running into difficulty with?