In today's Sitecore Tip of the Day, I will show you how to use Reflection to extend the BaseSublayout
so that it will automatically cast your DataSource
to its CustomItem
type.
Before I begin, if you have not done so already, I highly suggest that you read Mark Ursino's blog post entitled "Using the DataSource Field with Sitecore Sublayouts", as this post will build on the BaseSublayout
class that he defined (which I have modified and included, below). In his post, Mark provides detailed explanations of how to apply a DataSource to a control in an item's Presentation Details, as well as how to statically bind a DataSource to a Sublayout in Code.
Examining the BaseSublayout
Class
Let's start by taking a look at the BaseSublayout
class defined by Mark Ursino:
using Sitecore.Data.Items;
using Sitecore.Web.UI.WebControls;
namespace Some.Namespace
{
public class BaseSublayout : System.Web.UI.UserControl
{
private Item _dataSource;
public Item DataSource
{
get
{
return (_dataSource = _dataSource ??
(Parent is Sublayout
? Sitecore.Context.Database.GetItem(((Sublayout)Parent).DataSource) ?? Sitecore.Context.Item //if still null, fall back to the context item
: Sitecore.Context.Item));
}
}
public BaseSublayout() : base() { }
}
}
First off, note that this class inherits the System.Web.UI.UserControl
class and should be the base
class of every Sublayout on your site. This is one of the most powerful classes, and will enable you to add custom properties, methods and error handling to be shared across all of your Sublayouts.
Now take a look at the DataSource
property of BaseSublayout
. Note how it the getter first checks to see if a DataSource
has been set (statically or in Sitecore), and then falls back to the Context Item if one was not set. The addition of the fall-back is a modification of Mark Ursino's BaseSublayout
which enables us to consider the DataSource
property to be the most logical source of data for the Sublayout.
For clarity, consider a situation in which a DataSource
is applied to a Sublayout. Are you more likely to now grab data from the context item or from the DataSource
? if you feel that you would still like to keep your DataSource
property separate from the Context Item, by the end of this article you should have enough to modify the solution to fit your needs. If you do not, add a comment and I will do what I can to help you.
Extending the BaseSublayout
Class
Now that we have seen and understand the BaseSublayout
class, let's extend it to enable the automatic casting of our DataSource
to its CustomItem
class. Have a look at the following extension of the BaseSublayout
class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sitecore.Data.Items;
namespace Some.Namespace
{
public class BaseSublayout<T> : BaseSublayout where T : CustomItem
{
private T _model;
public T Model
{
get
{
return _model ?? (_model = (T)Activator.CreateInstance(typeof(T), DataSource));
}
}
}
}
What we are doing here is defining a new generic property, Model
, in a generic extension of the BaseSublayout
class. This property will be of type T
, where T
is a CustomItem
.
The getter of our new Model
property is where the real magic happens. The first thing that we do is check to see if the Model
has already been set, and if not we use reflection to initialize it with the correct type.
Tying it all together, we know that BaseSublayout
will try to determine the value of the DataSource
property by checking to see if one was statically bound or added in Sitecore. If an item is found it will be used as the DataSource
, otherwise Sitecore.Context.Item
is used. If the BaseSublayout
was type-cast when it was inherited, then the Model
property will become available and will hold the item determined to be the DataSource
cast to the specified CustomItem
type.
Note that this is an extension to the BaseSublayout
class, and that as such you will still be able to use the BaseSublayout
class without the type-casting, when desirable.
Lastly, note that the Model
property is also public
, so it may be accessed from the front-end using bee-stings/server tags, if necessary.
Examples of BaseSublayout
and BaseSublayout<T>
The first two examples, below, are of the ordinary use of the BaseSublayout
class in a sublayout with a bound DataSource
and without one, respectively:
Example 1: BaseSublayout
with a bound DataSource
:
public partial class FeaturedCarousel : BaseSublayout
{
protected FeaturedArticlesFolderItem FeaturedArticlesFolder { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
FeaturedArticlesFolder = DataSource;
...
}
protected void BindArticles()
{
var articles = FeaturedArticlesFolder.GetArticles();
...
}
}
Example 2: BaseSublayout
without a bound DataSource
:
public partial class NewsArticlePageContent : BaseSublayout
{
protected NewsArticlePageItem PageItem { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
PageItem = DataSource; //or Sitecore.Context.Item if defending against datasources that should not have been set
...
}
protected void BindContent()
{
txtAuthor.Text = PageItem.Author.Rendered;
...
}
}
The next two examples, below, demonstrate the use of the type-cast BaseSublayout<T>
class, in a Sublayout with a bound DataSource
and without one, respectively:
Example 3: BaseSublayout<T>
with a bound DataSource
:
public partial class FeaturedCarousel : BaseSublayout<FeaturedArticlesFolderItem>
{
protected void Page_Load(object sender, EventArgs e)
{
...
}
protected void BindArticles()
{
var articles = Model.GetArticles();
...
}
}
Example 4: BaseSublayout<T>
without a bound DataSource
:
public partial class NewsArticlePageContent : BaseSublayout<NewsArticlePageItem>
{
protected void Page_Load(object sender, EventArgs e)
{
...
}
protected void BindContent()
{
txtAuthor.Text = Model.Author.Rendered;
...
}
}
Summary
While the gains observed in these examples may seem fairly limited, consider a solution with dozens of Sublayouts (as most have), and now imagine the cumulative amount of time spent creating those properties on each page. Would it not be easier to have that done for you, with you only needing to specify the generated CustomItem
type that is appropriate?
This technique can be used to save countless hours of development time when implemented across multiple projects, without inhibiting the ability to code without using it. Use it on every Sublayout or just use it on one - the choice is yours.
Good luck and happy coding! :)