blog
Automatically Casting the DataSource in Sublayouts when Using CIG
Using Reflection and the BaseSublayout to Automatically Cast to a Sitecore Item's CustomItem Type without having to create a new property in each Sublayout

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! :)

0 Comments
Post a Comment