A common requirement when creating components in Sitecore is for the component to rely on data being supplied via the DataSource. Whilst you can try your best to ensure that a DataSource is associated with a component, there will inevitably be occasions when that DataSource gets deleted or moved and ultimately isn’t provided to the component at the time of rendering.

What can work well in these occasions is to provide the content editor with some feedback that the component won’t function correctly, so that they know they shouldn’t publish the page in its current state.

In Sitecore Controller Renderings, this could be done through some code at the top of each rendering action, checking for a DataSource and acting accordingly if one wasn’t found. However, rather than write repetitve code in each rendering, a better method is to make use of a reusable ActionFilter.

When the ActionFilter is executed, it attempts to locate a DataSource within the current RenderingContext. If no DataSource is found, then a PartialView is returned and the action will not continue to execute.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class RequiresDataSource : FilterAttribute, IActionFilter
{
private const string DefaultNoDataSourceView = "_NoDataSource";
protected Item DataSourceItem { get; set; }
public virtual void OnActionExecuting(ActionExecutingContext filterContext)
{
DataSourceItem = GetDataSourceItem();
if (DataSourceItem == null)
{
filterContext.Result = new PartialViewResult
{
ViewName = NoDataSourceView ?? DefaultNoDataSourceView
};
}
}
private Item GetDataSourceItem()
{
if (RenderingContext.Current == null || RenderingContext.Current.Rendering == null ||
string.IsNullOrEmpty(RenderingContext.Current.Rendering.DataSource))
{
return null;
}
// A DataSource has at least been set. Now to find out if it is an actual item.
return Sitecore.Context.Database.GetItem(RenderingContext.Current.Rendering.DataSource);
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// Not required.
}
public string NoDataSourceView { get; set; }
}

With this attribute created, you can assign it to an action:

1
2
3
4
5
6
7
[RequiresDataSource]
public ActionResult HomepageCarousel()
{
// Controller rendering code goes here...
return PartialView();
}

You can also assign it to a controller if you want it to execute for every action within that controller.

If you don’t specify a view, the default will be used. However, if you need to specify a particular view for a given rendering, you can:

1
[RequiresDataSource(NoDataSourceView="_NoDataSourceCustom")]

What do you put in the view? That is completly up to what is necessary in your solution - however a good strategy is to provide a simple error message to a user using the PageEditor, but actually render nothing if the component still makes it to the published page without a DataSource:

1
2
3
4
5
6
7
8
@if (PageMode.IsPageEditor)
{
<div class="error">
<h1>Heads up!</h1>
<p>This component requires a DataSource but one has not been specified.
If this page is published in its current state, this component will be empty.</p>
</div>
}

Now, if the component is viewed within the Page Editor, and no DataSource has been set, the user will see an informative message:

As well as provided the user with some feedback, it has removed the need for the null-checking that would have otherwise been required in the action to prevent exceptions, we’re now guaranteed that the code will only be executed if a DataSource has been set.

Extending the attribute

To provide the user with some more specific feedback, and also to ensure that our code only executes if the correct type of DataSource has been applied, we can extend the attribute to create another.

The RequiresDataSourceOfTemplate attribute shown below adds an additional check that occurs if the DataSource has been set, whereupon it additionally confirms that the DataSource is of the right template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class RequiresDataSourceOfTemplate : RequiresDataSource
{
private const string DefaultInvalidDataSourceView = "_InvalidDataSource";
private readonly Guid _templateId;
public RequiresDataSourceOfTemplate(Guid templateId)
{
_templateId = templateId;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Only continue if we have a datasource, i.e. the base action didn't result in anything.
if (DataSourceItem != null && !DataSourceItem.DerivesFromTemplate(_templateId))
{
var correctTemplateItem = Sitecore.Context.Database.GetItem(_templateId.ToID());
filterContext.Result = new PartialViewResult
{
ViewData = {
{"RequiredTemplateName", correctTemplateItem.Name },
{"FoundTemplateName", DataSourceItem.TemplateName}
},
ViewName = InvalidDataSourceView ?? DefaultInvalidDataSourceView
};
}
}
public string InvalidDataSourceView { get; set; }
}

This time, as well as returning an alternate view, some additional metadata is pushed into the ViewData. That allows us to provde create a view that provides the user with a bit of information about what went wrong:

Once you have these attributes, you can decorate all of your actions that rely on DataSources with them. This will eliminate a lot of common code needing to be rewritten across these actions, so instead your actions can focus on just delivering the right content with the correct DataSources in place.